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