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 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