1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package libcore.icu; 18 19 import java.math.BigDecimal; 20 import java.math.BigInteger; 21 import java.math.RoundingMode; 22 import java.text.AttributedCharacterIterator; 23 import java.text.AttributedString; 24 import java.text.DecimalFormatSymbols; 25 import java.text.FieldPosition; 26 import java.text.Format; 27 import java.text.NumberFormat; 28 import java.text.ParsePosition; 29 import java.util.Currency; 30 import java.util.NoSuchElementException; 31 32 public final class NativeDecimalFormat implements Cloneable { 33 /** 34 * Constants corresponding to the native type UNumberFormatSymbol, for setSymbol. 35 */ 36 private static final int UNUM_DECIMAL_SEPARATOR_SYMBOL = 0; 37 private static final int UNUM_GROUPING_SEPARATOR_SYMBOL = 1; 38 private static final int UNUM_PATTERN_SEPARATOR_SYMBOL = 2; 39 private static final int UNUM_PERCENT_SYMBOL = 3; 40 private static final int UNUM_ZERO_DIGIT_SYMBOL = 4; 41 private static final int UNUM_DIGIT_SYMBOL = 5; 42 private static final int UNUM_MINUS_SIGN_SYMBOL = 6; 43 private static final int UNUM_PLUS_SIGN_SYMBOL = 7; 44 private static final int UNUM_CURRENCY_SYMBOL = 8; 45 private static final int UNUM_INTL_CURRENCY_SYMBOL = 9; 46 private static final int UNUM_MONETARY_SEPARATOR_SYMBOL = 10; 47 private static final int UNUM_EXPONENTIAL_SYMBOL = 11; 48 private static final int UNUM_PERMILL_SYMBOL = 12; 49 private static final int UNUM_PAD_ESCAPE_SYMBOL = 13; 50 private static final int UNUM_INFINITY_SYMBOL = 14; 51 private static final int UNUM_NAN_SYMBOL = 15; 52 private static final int UNUM_SIGNIFICANT_DIGIT_SYMBOL = 16; 53 private static final int UNUM_MONETARY_GROUPING_SEPARATOR_SYMBOL = 17; 54 private static final int UNUM_FORMAT_SYMBOL_COUNT = 18; 55 56 /** 57 * Constants corresponding to the native type UNumberFormatAttribute, for 58 * getAttribute/setAttribute. 59 */ 60 private static final int UNUM_PARSE_INT_ONLY = 0; 61 private static final int UNUM_GROUPING_USED = 1; 62 private static final int UNUM_DECIMAL_ALWAYS_SHOWN = 2; 63 private static final int UNUM_MAX_INTEGER_DIGITS = 3; 64 private static final int UNUM_MIN_INTEGER_DIGITS = 4; 65 private static final int UNUM_INTEGER_DIGITS = 5; 66 private static final int UNUM_MAX_FRACTION_DIGITS = 6; 67 private static final int UNUM_MIN_FRACTION_DIGITS = 7; 68 private static final int UNUM_FRACTION_DIGITS = 8; 69 private static final int UNUM_MULTIPLIER = 9; 70 private static final int UNUM_GROUPING_SIZE = 10; 71 private static final int UNUM_ROUNDING_MODE = 11; 72 private static final int UNUM_ROUNDING_INCREMENT = 12; 73 private static final int UNUM_FORMAT_WIDTH = 13; 74 private static final int UNUM_PADDING_POSITION = 14; 75 private static final int UNUM_SECONDARY_GROUPING_SIZE = 15; 76 private static final int UNUM_SIGNIFICANT_DIGITS_USED = 16; 77 private static final int UNUM_MIN_SIGNIFICANT_DIGITS = 17; 78 private static final int UNUM_MAX_SIGNIFICANT_DIGITS = 18; 79 private static final int UNUM_LENIENT_PARSE = 19; 80 81 /** 82 * Constants corresponding to the native type UNumberFormatTextAttribute, for 83 * getTextAttribute/setTextAttribute. 84 */ 85 private static final int UNUM_POSITIVE_PREFIX = 0; 86 private static final int UNUM_POSITIVE_SUFFIX = 1; 87 private static final int UNUM_NEGATIVE_PREFIX = 2; 88 private static final int UNUM_NEGATIVE_SUFFIX = 3; 89 private static final int UNUM_PADDING_CHARACTER = 4; 90 private static final int UNUM_CURRENCY_CODE = 5; 91 private static final int UNUM_DEFAULT_RULESET = 6; 92 private static final int UNUM_PUBLIC_RULESETS = 7; 93 94 /** 95 * The address of the ICU DecimalFormat* on the native heap. 96 */ 97 private int address; 98 99 /** 100 * The last pattern we gave to ICU, so we can make repeated applications cheap. 101 * This helps in cases like String.format("%.2f,%.2f\n", x, y) where the DecimalFormat is 102 * reused. 103 */ 104 private String lastPattern; 105 106 // TODO: store all these in DecimalFormat instead! 107 private boolean negPrefNull; 108 private boolean negSuffNull; 109 private boolean posPrefNull; 110 private boolean posSuffNull; 111 112 private transient boolean parseBigDecimal; 113 114 /** 115 * Cache the BigDecimal form of the multiplier. This is null until we've 116 * formatted a BigDecimal (with a multiplier that is not 1), or the user has 117 * explicitly called {@link #setMultiplier(int)} with any multiplier. 118 */ 119 private BigDecimal multiplierBigDecimal = null; 120 121 public NativeDecimalFormat(String pattern, DecimalFormatSymbols dfs) { 122 try { 123 this.address = open(pattern, dfs.getCurrencySymbol(), 124 dfs.getDecimalSeparator(), dfs.getDigit(), dfs.getExponentSeparator(), 125 dfs.getGroupingSeparator(), dfs.getInfinity(), 126 dfs.getInternationalCurrencySymbol(), dfs.getMinusSign(), 127 dfs.getMonetaryDecimalSeparator(), dfs.getNaN(), dfs.getPatternSeparator(), 128 dfs.getPercent(), dfs.getPerMill(), dfs.getZeroDigit()); 129 this.lastPattern = pattern; 130 } catch (NullPointerException npe) { 131 throw npe; 132 } catch (RuntimeException re) { 133 throw new IllegalArgumentException("syntax error: " + re.getMessage() + ": " + pattern); 134 } 135 } 136 137 // Used so java.util.Formatter doesn't need to allocate DecimalFormatSymbols instances. 138 public NativeDecimalFormat(String pattern, LocaleData data) { 139 this.address = open(pattern, data.currencySymbol, 140 data.decimalSeparator, '#', data.exponentSeparator, data.groupingSeparator, 141 data.infinity, data.internationalCurrencySymbol, data.minusSign, 142 data.monetarySeparator, data.NaN, data.patternSeparator, 143 data.percent, data.perMill, data.zeroDigit); 144 this.lastPattern = pattern; 145 } 146 147 public synchronized void close() { 148 if (address != 0) { 149 close(address); 150 address = 0; 151 } 152 } 153 154 @Override protected void finalize() throws Throwable { 155 try { 156 close(); 157 } finally { 158 super.finalize(); 159 } 160 } 161 162 @Override public Object clone() { 163 try { 164 NativeDecimalFormat clone = (NativeDecimalFormat) super.clone(); 165 clone.address = cloneImpl(address); 166 clone.lastPattern = lastPattern; 167 clone.negPrefNull = negPrefNull; 168 clone.negSuffNull = negSuffNull; 169 clone.posPrefNull = posPrefNull; 170 clone.posSuffNull = posSuffNull; 171 return clone; 172 } catch (CloneNotSupportedException unexpected) { 173 throw new AssertionError(unexpected); 174 } 175 } 176 177 /** 178 * Note: this doesn't check that the underlying native DecimalFormat objects' configured 179 * native DecimalFormatSymbols objects are equal. It is assumed that the 180 * caller (DecimalFormat) will check the DecimalFormatSymbols objects 181 * instead, for performance. 182 * 183 * This is also unreasonably expensive, calling down to JNI multiple times. 184 * 185 * TODO: remove this and just have DecimalFormat.equals do the right thing itself. 186 */ 187 @Override 188 public boolean equals(Object object) { 189 if (object == this) { 190 return true; 191 } 192 if (!(object instanceof NativeDecimalFormat)) { 193 return false; 194 } 195 NativeDecimalFormat obj = (NativeDecimalFormat) object; 196 if (obj.address == this.address) { 197 return true; 198 } 199 return obj.toPattern().equals(this.toPattern()) && 200 obj.isDecimalSeparatorAlwaysShown() == this.isDecimalSeparatorAlwaysShown() && 201 obj.getGroupingSize() == this.getGroupingSize() && 202 obj.getMultiplier() == this.getMultiplier() && 203 obj.getNegativePrefix().equals(this.getNegativePrefix()) && 204 obj.getNegativeSuffix().equals(this.getNegativeSuffix()) && 205 obj.getPositivePrefix().equals(this.getPositivePrefix()) && 206 obj.getPositiveSuffix().equals(this.getPositiveSuffix()) && 207 obj.getMaximumIntegerDigits() == this.getMaximumIntegerDigits() && 208 obj.getMaximumFractionDigits() == this.getMaximumFractionDigits() && 209 obj.getMinimumIntegerDigits() == this.getMinimumIntegerDigits() && 210 obj.getMinimumFractionDigits() == this.getMinimumFractionDigits() && 211 obj.isGroupingUsed() == this.isGroupingUsed(); 212 } 213 214 /** 215 * Copies the DecimalFormatSymbols settings into our native peer in bulk. 216 */ 217 public void setDecimalFormatSymbols(final DecimalFormatSymbols dfs) { 218 setDecimalFormatSymbols(this.address, dfs.getCurrencySymbol(), dfs.getDecimalSeparator(), 219 dfs.getDigit(), dfs.getExponentSeparator(), dfs.getGroupingSeparator(), 220 dfs.getInfinity(), dfs.getInternationalCurrencySymbol(), dfs.getMinusSign(), 221 dfs.getMonetaryDecimalSeparator(), dfs.getNaN(), dfs.getPatternSeparator(), 222 dfs.getPercent(), dfs.getPerMill(), dfs.getZeroDigit()); 223 } 224 225 public void setDecimalFormatSymbols(final LocaleData localeData) { 226 setDecimalFormatSymbols(this.address, localeData.currencySymbol, localeData.decimalSeparator, 227 '#', localeData.exponentSeparator, localeData.groupingSeparator, 228 localeData.infinity, localeData.internationalCurrencySymbol, localeData.minusSign, 229 localeData.monetarySeparator, localeData.NaN, localeData.patternSeparator, 230 localeData.percent, localeData.perMill, localeData.zeroDigit); 231 } 232 233 public char[] formatBigDecimal(BigDecimal value, FieldPosition field) { 234 FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field); 235 char[] result = formatDigitList(this.address, value.toString(), fpi); 236 if (fpi != null) { 237 FieldPositionIterator.setFieldPosition(fpi, field); 238 } 239 return result; 240 } 241 242 public char[] formatBigInteger(BigInteger value, FieldPosition field) { 243 FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field); 244 char[] result = formatDigitList(this.address, value.toString(10), fpi); 245 if (fpi != null) { 246 FieldPositionIterator.setFieldPosition(fpi, field); 247 } 248 return result; 249 } 250 251 public char[] formatLong(long value, FieldPosition field) { 252 FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field); 253 char[] result = formatLong(this.address, value, fpi); 254 if (fpi != null) { 255 FieldPositionIterator.setFieldPosition(fpi, field); 256 } 257 return result; 258 } 259 260 public char[] formatDouble(double value, FieldPosition field) { 261 FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field); 262 char[] result = formatDouble(this.address, value, fpi); 263 if (fpi != null) { 264 FieldPositionIterator.setFieldPosition(fpi, field); 265 } 266 return result; 267 } 268 269 public void applyLocalizedPattern(String pattern) { 270 applyPattern(this.address, true, pattern); 271 lastPattern = null; 272 } 273 274 public void applyPattern(String pattern) { 275 if (lastPattern != null && pattern.equals(lastPattern)) { 276 return; 277 } 278 applyPattern(this.address, false, pattern); 279 lastPattern = pattern; 280 } 281 282 public AttributedCharacterIterator formatToCharacterIterator(Object object) { 283 if (!(object instanceof Number)) { 284 throw new IllegalArgumentException(); 285 } 286 Number number = (Number) object; 287 FieldPositionIterator fpIter = new FieldPositionIterator(); 288 String text; 289 if (number instanceof BigInteger || number instanceof BigDecimal) { 290 text = new String(formatDigitList(this.address, number.toString(), fpIter)); 291 } else if (number instanceof Double || number instanceof Float) { 292 double dv = number.doubleValue(); 293 text = new String(formatDouble(this.address, dv, fpIter)); 294 } else { 295 long lv = number.longValue(); 296 text = new String(formatLong(this.address, lv, fpIter)); 297 } 298 299 AttributedString as = new AttributedString(text); 300 301 while (fpIter.next()) { 302 Format.Field field = fpIter.field(); 303 as.addAttribute(field, field, fpIter.start(), fpIter.limit()); 304 } 305 306 // return the CharacterIterator from AttributedString 307 return as.getIterator(); 308 } 309 310 private int makeScalePositive(int scale, StringBuilder val) { 311 if (scale < 0) { 312 scale = -scale; 313 for (int i = scale; i > 0; i--) { 314 val.append('0'); 315 } 316 scale = 0; 317 } 318 return scale; 319 } 320 321 public String toLocalizedPattern() { 322 return toPatternImpl(this.address, true); 323 } 324 325 public String toPattern() { 326 return toPatternImpl(this.address, false); 327 } 328 329 public Number parse(String string, ParsePosition position) { 330 return parse(address, string, position, parseBigDecimal); 331 } 332 333 // start getter and setter 334 335 public int getMaximumFractionDigits() { 336 return getAttribute(this.address, UNUM_MAX_FRACTION_DIGITS); 337 } 338 339 public int getMaximumIntegerDigits() { 340 return getAttribute(this.address, UNUM_MAX_INTEGER_DIGITS); 341 } 342 343 public int getMinimumFractionDigits() { 344 return getAttribute(this.address, UNUM_MIN_FRACTION_DIGITS); 345 } 346 347 public int getMinimumIntegerDigits() { 348 return getAttribute(this.address, UNUM_MIN_INTEGER_DIGITS); 349 } 350 351 public int getGroupingSize() { 352 return getAttribute(this.address, UNUM_GROUPING_SIZE); 353 } 354 355 public int getMultiplier() { 356 return getAttribute(this.address, UNUM_MULTIPLIER); 357 } 358 359 public String getNegativePrefix() { 360 if (negPrefNull) { 361 return null; 362 } 363 return getTextAttribute(this.address, UNUM_NEGATIVE_PREFIX); 364 } 365 366 public String getNegativeSuffix() { 367 if (negSuffNull) { 368 return null; 369 } 370 return getTextAttribute(this.address, UNUM_NEGATIVE_SUFFIX); 371 } 372 373 public String getPositivePrefix() { 374 if (posPrefNull) { 375 return null; 376 } 377 return getTextAttribute(this.address, UNUM_POSITIVE_PREFIX); 378 } 379 380 public String getPositiveSuffix() { 381 if (posSuffNull) { 382 return null; 383 } 384 return getTextAttribute(this.address, UNUM_POSITIVE_SUFFIX); 385 } 386 387 public boolean isDecimalSeparatorAlwaysShown() { 388 return getAttribute(this.address, UNUM_DECIMAL_ALWAYS_SHOWN) != 0; 389 } 390 391 public boolean isParseBigDecimal() { 392 return parseBigDecimal; 393 } 394 395 public boolean isParseIntegerOnly() { 396 return getAttribute(this.address, UNUM_PARSE_INT_ONLY) != 0; 397 } 398 399 public boolean isGroupingUsed() { 400 return getAttribute(this.address, UNUM_GROUPING_USED) != 0; 401 } 402 403 public void setDecimalSeparatorAlwaysShown(boolean value) { 404 int i = value ? -1 : 0; 405 setAttribute(this.address, UNUM_DECIMAL_ALWAYS_SHOWN, i); 406 } 407 408 public void setCurrency(Currency currency) { 409 setSymbol(this.address, UNUM_CURRENCY_SYMBOL, currency.getSymbol()); 410 setSymbol(this.address, UNUM_INTL_CURRENCY_SYMBOL, currency.getCurrencyCode()); 411 } 412 413 public void setGroupingSize(int value) { 414 setAttribute(this.address, UNUM_GROUPING_SIZE, value); 415 } 416 417 public void setGroupingUsed(boolean value) { 418 int i = value ? -1 : 0; 419 setAttribute(this.address, UNUM_GROUPING_USED, i); 420 } 421 422 public void setMaximumFractionDigits(int value) { 423 setAttribute(this.address, UNUM_MAX_FRACTION_DIGITS, value); 424 } 425 426 public void setMaximumIntegerDigits(int value) { 427 setAttribute(this.address, UNUM_MAX_INTEGER_DIGITS, value); 428 } 429 430 public void setMinimumFractionDigits(int value) { 431 setAttribute(this.address, UNUM_MIN_FRACTION_DIGITS, value); 432 } 433 434 public void setMinimumIntegerDigits(int value) { 435 setAttribute(this.address, UNUM_MIN_INTEGER_DIGITS, value); 436 } 437 438 public void setMultiplier(int value) { 439 setAttribute(this.address, UNUM_MULTIPLIER, value); 440 // Update the cached BigDecimal for multiplier. 441 multiplierBigDecimal = BigDecimal.valueOf(value); 442 } 443 444 public void setNegativePrefix(String value) { 445 negPrefNull = value == null; 446 if (!negPrefNull) { 447 setTextAttribute(this.address, UNUM_NEGATIVE_PREFIX, value); 448 } 449 } 450 451 public void setNegativeSuffix(String value) { 452 negSuffNull = value == null; 453 if (!negSuffNull) { 454 setTextAttribute(this.address, UNUM_NEGATIVE_SUFFIX, value); 455 } 456 } 457 458 public void setPositivePrefix(String value) { 459 posPrefNull = value == null; 460 if (!posPrefNull) { 461 setTextAttribute(this.address, UNUM_POSITIVE_PREFIX, value); 462 } 463 } 464 465 public void setPositiveSuffix(String value) { 466 posSuffNull = value == null; 467 if (!posSuffNull) { 468 setTextAttribute(this.address, UNUM_POSITIVE_SUFFIX, value); 469 } 470 } 471 472 public void setParseBigDecimal(boolean value) { 473 parseBigDecimal = value; 474 } 475 476 public void setParseIntegerOnly(boolean value) { 477 int i = value ? -1 : 0; 478 setAttribute(this.address, UNUM_PARSE_INT_ONLY, i); 479 } 480 481 private static void applyPattern(int addr, boolean localized, String pattern) { 482 try { 483 applyPatternImpl(addr, localized, pattern); 484 } catch (NullPointerException npe) { 485 throw npe; 486 } catch (RuntimeException re) { 487 throw new IllegalArgumentException("syntax error: " + re.getMessage() + ": " + pattern); 488 } 489 } 490 491 public void setRoundingMode(RoundingMode roundingMode, double roundingIncrement) { 492 final int nativeRoundingMode; 493 switch (roundingMode) { 494 case CEILING: nativeRoundingMode = 0; break; 495 case FLOOR: nativeRoundingMode = 1; break; 496 case DOWN: nativeRoundingMode = 2; break; 497 case UP: nativeRoundingMode = 3; break; 498 case HALF_EVEN: nativeRoundingMode = 4; break; 499 case HALF_DOWN: nativeRoundingMode = 5; break; 500 case HALF_UP: nativeRoundingMode = 6; break; 501 default: throw new AssertionError(); 502 } 503 setRoundingMode(address, nativeRoundingMode, roundingIncrement); 504 } 505 506 // Utility to get information about field positions from native (ICU) code. 507 private static class FieldPositionIterator { 508 private int[] data; 509 private int pos = -3; // so first call to next() leaves pos at 0 510 511 private FieldPositionIterator() { 512 } 513 514 public static FieldPositionIterator forFieldPosition(FieldPosition fp) { 515 if (fp != null && fp.getField() != -1) { 516 return new FieldPositionIterator(); 517 } 518 return null; 519 } 520 521 private static int getNativeFieldPositionId(FieldPosition fp) { 522 // NOTE: -1, 0, and 1 were the only valid original java field values 523 // for NumberFormat. They take precedence. This assumes any other 524 // value is a mistake and the actual value is in the attribute. 525 // Clients can construct FieldPosition combining any attribute with any field 526 // value, which is just wrong, but there you go. 527 528 int id = fp.getField(); 529 if (id < -1 || id > 1) { 530 id = -1; 531 } 532 if (id == -1) { 533 Format.Field attr = fp.getFieldAttribute(); 534 if (attr != null) { 535 for (int i = 0; i < fields.length; ++i) { 536 if (fields[i].equals(attr)) { 537 id = i; 538 break; 539 } 540 } 541 } 542 } 543 return id; 544 } 545 546 private static void setFieldPosition(FieldPositionIterator fpi, FieldPosition fp) { 547 if (fpi != null && fp != null) { 548 int field = getNativeFieldPositionId(fp); 549 if (field != -1) { 550 while (fpi.next()) { 551 if (fpi.fieldId() == field) { 552 fp.setBeginIndex(fpi.start()); 553 fp.setEndIndex(fpi.limit()); 554 break; 555 } 556 } 557 } 558 } 559 } 560 561 public boolean next() { 562 // if pos == data.length, we've already returned false once 563 if (data == null || pos == data.length) { 564 throw new NoSuchElementException(); 565 } 566 pos += 3; 567 return pos < data.length; 568 } 569 570 private void checkValid() { 571 if (data == null || pos < 0 || pos == data.length) { 572 throw new NoSuchElementException(); 573 } 574 } 575 576 public int fieldId() { 577 return data[pos]; 578 } 579 580 public Format.Field field() { 581 checkValid(); 582 return fields[data[pos]]; 583 } 584 585 public int start() { 586 checkValid(); 587 return data[pos + 1]; 588 } 589 590 public int limit() { 591 checkValid(); 592 return data[pos + 2]; 593 } 594 595 private static Format.Field fields[] = { 596 // The old java field values were 0 for integer and 1 for fraction. 597 // The new java field attributes are all objects. ICU assigns the values 598 // starting from 0 in the following order; note that integer and 599 // fraction positions match the old field values. 600 NumberFormat.Field.INTEGER, 601 NumberFormat.Field.FRACTION, 602 NumberFormat.Field.DECIMAL_SEPARATOR, 603 NumberFormat.Field.EXPONENT_SYMBOL, 604 NumberFormat.Field.EXPONENT_SIGN, 605 NumberFormat.Field.EXPONENT, 606 NumberFormat.Field.GROUPING_SEPARATOR, 607 NumberFormat.Field.CURRENCY, 608 NumberFormat.Field.PERCENT, 609 NumberFormat.Field.PERMILLE, 610 NumberFormat.Field.SIGN, 611 }; 612 613 // called by native 614 private void setData(int[] data) { 615 this.data = data; 616 this.pos = -3; 617 } 618 } 619 620 private static native void applyPatternImpl(int addr, boolean localized, String pattern); 621 private static native int cloneImpl(int addr); 622 private static native void close(int addr); 623 private static native char[] formatLong(int addr, long value, FieldPositionIterator iter); 624 private static native char[] formatDouble(int addr, double value, FieldPositionIterator iter); 625 private static native char[] formatDigitList(int addr, String value, FieldPositionIterator iter); 626 private static native int getAttribute(int addr, int symbol); 627 private static native String getTextAttribute(int addr, int symbol); 628 private static native int open(String pattern, String currencySymbol, 629 char decimalSeparator, char digit, String exponentSeparator, char groupingSeparator, 630 String infinity, String internationalCurrencySymbol, char minusSign, 631 char monetaryDecimalSeparator, String nan, char patternSeparator, char percent, 632 char perMill, char zeroDigit); 633 private static native Number parse(int addr, String string, ParsePosition position, boolean parseBigDecimal); 634 private static native void setDecimalFormatSymbols(int addr, String currencySymbol, 635 char decimalSeparator, char digit, String exponentSeparator, char groupingSeparator, 636 String infinity, String internationalCurrencySymbol, char minusSign, 637 char monetaryDecimalSeparator, String nan, char patternSeparator, char percent, 638 char perMill, char zeroDigit); 639 private static native void setSymbol(int addr, int symbol, String str); 640 private static native void setAttribute(int addr, int symbol, int i); 641 private static native void setRoundingMode(int addr, int roundingMode, double roundingIncrement); 642 private static native void setTextAttribute(int addr, int symbol, String str); 643 private static native String toPatternImpl(int addr, boolean localized); 644 } 645