Home | History | Annotate | Download | only in text
      1 /* GENERATED SOURCE. DO NOT MODIFY. */
      2 //  2016 and later: Unicode, Inc. and others.
      3 // License & terms of use: http://www.unicode.org/copyright.html#License
      4 /*
      5  **********************************************************************
      6  * Copyright (c) 2004-2016, International Business Machines
      7  * Corporation and others.  All Rights Reserved.
      8  **********************************************************************
      9  * Author: Alan Liu
     10  * Created: April 20, 2004
     11  * Since: ICU 3.0
     12  **********************************************************************
     13  */
     14 package android.icu.text;
     15 
     16 import java.io.Externalizable;
     17 import java.io.IOException;
     18 import java.io.InvalidObjectException;
     19 import java.io.ObjectInput;
     20 import java.io.ObjectOutput;
     21 import java.io.ObjectStreamException;
     22 import java.text.FieldPosition;
     23 import java.text.ParsePosition;
     24 import java.util.Arrays;
     25 import java.util.Collection;
     26 import java.util.Date;
     27 import java.util.EnumMap;
     28 import java.util.HashMap;
     29 import java.util.Locale;
     30 import java.util.Map;
     31 import java.util.MissingResourceException;
     32 import java.util.concurrent.ConcurrentHashMap;
     33 
     34 import android.icu.impl.DontCareFieldPosition;
     35 import android.icu.impl.ICUData;
     36 import android.icu.impl.ICUResourceBundle;
     37 import android.icu.impl.SimpleCache;
     38 import android.icu.impl.SimpleFormatterImpl;
     39 import android.icu.impl.StandardPlural;
     40 import android.icu.impl.UResource;
     41 import android.icu.math.BigDecimal;
     42 import android.icu.text.PluralRules.Factory;
     43 import android.icu.util.Currency;
     44 import android.icu.util.CurrencyAmount;
     45 import android.icu.util.ICUException;
     46 import android.icu.util.Measure;
     47 import android.icu.util.MeasureUnit;
     48 import android.icu.util.TimeZone;
     49 import android.icu.util.ULocale;
     50 import android.icu.util.ULocale.Category;
     51 import android.icu.util.UResourceBundle;
     52 
     53 // If you update the examples in the doc, don't forget to update MesaureUnitTest.TestExamplesInDocs too.
     54 /**
     55  * A formatter for Measure objects.
     56  *
     57  * <p>To format a Measure object, first create a formatter
     58  * object using a MeasureFormat factory method.  Then use that
     59  * object's format or formatMeasures methods.
     60  *
     61  * Here is sample code:
     62  * <pre>
     63  *      MeasureFormat fmtFr = MeasureFormat.getInstance(
     64  *              ULocale.FRENCH, FormatWidth.SHORT);
     65  *      Measure measure = new Measure(23, MeasureUnit.CELSIUS);
     66  *
     67  *      // Output: 23 C
     68  *      System.out.println(fmtFr.format(measure));
     69  *
     70  *      Measure measureF = new Measure(70, MeasureUnit.FAHRENHEIT);
     71  *
     72  *      // Output: 70 F
     73  *      System.out.println(fmtFr.format(measureF));
     74  *
     75  *      MeasureFormat fmtFrFull = MeasureFormat.getInstance(
     76  *              ULocale.FRENCH, FormatWidth.WIDE);
     77  *      // Output: 70 pieds et 5,3 pouces
     78  *      System.out.println(fmtFrFull.formatMeasures(
     79  *              new Measure(70, MeasureUnit.FOOT),
     80  *              new Measure(5.3, MeasureUnit.INCH)));
     81  *
     82  *      // Output: 1 pied et 1 pouce
     83  *      System.out.println(fmtFrFull.formatMeasures(
     84  *              new Measure(1, MeasureUnit.FOOT),
     85  *              new Measure(1, MeasureUnit.INCH)));
     86  *
     87  *      MeasureFormat fmtFrNarrow = MeasureFormat.getInstance(
     88                 ULocale.FRENCH, FormatWidth.NARROW);
     89  *      // Output: 1 1
     90  *      System.out.println(fmtFrNarrow.formatMeasures(
     91  *              new Measure(1, MeasureUnit.FOOT),
     92  *              new Measure(1, MeasureUnit.INCH)));
     93  *
     94  *
     95  *      MeasureFormat fmtEn = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE);
     96  *
     97  *      // Output: 1 inch, 2 feet
     98  *      fmtEn.formatMeasures(
     99  *              new Measure(1, MeasureUnit.INCH),
    100  *              new Measure(2, MeasureUnit.FOOT));
    101  * </pre>
    102  * <p>
    103  * This class does not do conversions from one unit to another. It simply formats
    104  * whatever units it is given
    105  * <p>
    106  * This class is immutable and thread-safe so long as its deprecated subclass,
    107  * TimeUnitFormat, is never used. TimeUnitFormat is not thread-safe, and is
    108  * mutable. Although this class has existing subclasses, this class does not support new
    109  * sub-classes.
    110  *
    111  * @see android.icu.text.UFormat
    112  * @author Alan Liu
    113  */
    114 public class MeasureFormat extends UFormat {
    115 
    116 
    117     // Generated by serialver from JDK 1.4.1_01
    118     static final long serialVersionUID = -7182021401701778240L;
    119 
    120     private final transient MeasureFormatData cache;
    121 
    122     private final transient ImmutableNumberFormat numberFormat;
    123 
    124     private final transient FormatWidth formatWidth;
    125 
    126     // PluralRules is documented as being immutable which implies thread-safety.
    127     private final transient PluralRules rules;
    128 
    129     private final transient NumericFormatters numericFormatters;
    130 
    131     private final transient ImmutableNumberFormat currencyFormat;
    132 
    133     private final transient ImmutableNumberFormat integerFormat;
    134 
    135     private static final SimpleCache<ULocale, MeasureFormatData> localeMeasureFormatData
    136     = new SimpleCache<ULocale, MeasureFormatData>();
    137 
    138     private static final SimpleCache<ULocale, NumericFormatters> localeToNumericDurationFormatters
    139     = new SimpleCache<ULocale,NumericFormatters>();
    140 
    141     private static final Map<MeasureUnit, Integer> hmsTo012 =
    142             new HashMap<MeasureUnit, Integer>();
    143 
    144     static {
    145         hmsTo012.put(MeasureUnit.HOUR, 0);
    146         hmsTo012.put(MeasureUnit.MINUTE, 1);
    147         hmsTo012.put(MeasureUnit.SECOND, 2);
    148     }
    149 
    150     // For serialization: sub-class types.
    151     private static final int MEASURE_FORMAT = 0;
    152     private static final int TIME_UNIT_FORMAT = 1;
    153     private static final int CURRENCY_FORMAT = 2;
    154 
    155     /**
    156      * Formatting width enum.
    157      */
    158     // Be sure to update MeasureUnitTest.TestSerialFormatWidthEnum
    159     // when adding an enum value.
    160     public enum FormatWidth {
    161 
    162         /**
    163          * Spell out everything.
    164          */
    165         WIDE(ListFormatter.Style.DURATION, NumberFormat.PLURALCURRENCYSTYLE),
    166 
    167         /**
    168          * Abbreviate when possible.
    169          */
    170         SHORT(ListFormatter.Style.DURATION_SHORT, NumberFormat.ISOCURRENCYSTYLE),
    171 
    172         /**
    173          * Brief. Use only a symbol for the unit when possible.
    174          */
    175         NARROW(ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE),
    176 
    177         /**
    178          * Identical to NARROW except when formatMeasures is called with
    179          * an hour and minute; minute and second; or hour, minute, and second Measures.
    180          * In these cases formatMeasures formats as 5:37:23 instead of 5h, 37m, 23s.
    181          */
    182         NUMERIC(ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE);
    183 
    184         // Be sure to update the toFormatWidth and fromFormatWidth() functions
    185         // when adding an enum value.
    186         private static final int INDEX_COUNT = 3;  // NARROW.ordinal() + 1
    187 
    188         private final ListFormatter.Style listFormatterStyle;
    189         private final int currencyStyle;
    190 
    191         private FormatWidth(ListFormatter.Style style, int currencyStyle) {
    192             this.listFormatterStyle = style;
    193             this.currencyStyle = currencyStyle;
    194         }
    195 
    196         ListFormatter.Style getListFormatterStyle() {
    197             return listFormatterStyle;
    198         }
    199 
    200         int getCurrencyStyle() {
    201             return currencyStyle;
    202         }
    203     }
    204 
    205     /**
    206      * Create a format from the locale, formatWidth, and format.
    207      *
    208      * @param locale the locale.
    209      * @param formatWidth hints how long formatted strings should be.
    210      * @return The new MeasureFormat object.
    211      */
    212     public static MeasureFormat getInstance(ULocale locale, FormatWidth formatWidth) {
    213         return getInstance(locale, formatWidth, NumberFormat.getInstance(locale));
    214     }
    215 
    216     /**
    217      * Create a format from the {@link java.util.Locale} and formatWidth.
    218      *
    219      * @param locale the {@link java.util.Locale}.
    220      * @param formatWidth hints how long formatted strings should be.
    221      * @return The new MeasureFormat object.
    222      */
    223     public static MeasureFormat getInstance(Locale locale, FormatWidth formatWidth) {
    224         return getInstance(ULocale.forLocale(locale), formatWidth);
    225     }
    226 
    227     /**
    228      * Create a format from the locale, formatWidth, and format.
    229      *
    230      * @param locale the locale.
    231      * @param formatWidth hints how long formatted strings should be.
    232      * @param format This is defensively copied.
    233      * @return The new MeasureFormat object.
    234      */
    235     public static MeasureFormat getInstance(ULocale locale, FormatWidth formatWidth, NumberFormat format) {
    236         PluralRules rules = PluralRules.forLocale(locale);
    237         NumericFormatters formatters = null;
    238         MeasureFormatData data = localeMeasureFormatData.get(locale);
    239         if (data == null) {
    240             data = loadLocaleData(locale);
    241             localeMeasureFormatData.put(locale, data);
    242         }
    243         if (formatWidth == FormatWidth.NUMERIC) {
    244             formatters = localeToNumericDurationFormatters.get(locale);
    245             if (formatters == null) {
    246                 formatters = loadNumericFormatters(locale);
    247                 localeToNumericDurationFormatters.put(locale, formatters);
    248             }
    249         }
    250         NumberFormat intFormat = NumberFormat.getInstance(locale);
    251         intFormat.setMaximumFractionDigits(0);
    252         intFormat.setMinimumFractionDigits(0);
    253         intFormat.setRoundingMode(BigDecimal.ROUND_DOWN);
    254         return new MeasureFormat(
    255                 locale,
    256                 data,
    257                 formatWidth,
    258                 new ImmutableNumberFormat(format),
    259                 rules,
    260                 formatters,
    261                 new ImmutableNumberFormat(NumberFormat.getInstance(locale, formatWidth.getCurrencyStyle())),
    262                 new ImmutableNumberFormat(intFormat));
    263     }
    264 
    265     /**
    266      * Create a format from the {@link java.util.Locale}, formatWidth, and format.
    267      *
    268      * @param locale the {@link java.util.Locale}.
    269      * @param formatWidth hints how long formatted strings should be.
    270      * @param format This is defensively copied.
    271      * @return The new MeasureFormat object.
    272      */
    273     public static MeasureFormat getInstance(Locale locale, FormatWidth formatWidth, NumberFormat format) {
    274         return getInstance(ULocale.forLocale(locale), formatWidth, format);
    275     }
    276 
    277     /**
    278      * Able to format Collection&lt;? extends Measure&gt;, Measure[], and Measure
    279      * by delegating to formatMeasures.
    280      * If the pos argument identifies a NumberFormat field,
    281      * then its indices are set to the beginning and end of the first such field
    282      * encountered. MeasureFormat itself does not supply any fields.
    283      *
    284      * Calling a
    285      * <code>formatMeasures</code> method is preferred over calling
    286      * this method as they give better performance.
    287      *
    288      * @param obj must be a Collection&lt;? extends Measure&gt;, Measure[], or Measure object.
    289      * @param toAppendTo Formatted string appended here.
    290      * @param pos Identifies a field in the formatted text.
    291      * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
    292      */
    293     @Override
    294     public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
    295         int prevLength = toAppendTo.length();
    296         FieldPosition fpos =
    297                 new FieldPosition(pos.getFieldAttribute(), pos.getField());
    298         if (obj instanceof Collection) {
    299             Collection<?> coll = (Collection<?>) obj;
    300             Measure[] measures = new Measure[coll.size()];
    301             int idx = 0;
    302             for (Object o : coll) {
    303                 if (!(o instanceof Measure)) {
    304                     throw new IllegalArgumentException(obj.toString());
    305                 }
    306                 measures[idx++] = (Measure) o;
    307             }
    308             toAppendTo.append(formatMeasures(new StringBuilder(), fpos, measures));
    309         } else if (obj instanceof Measure[]) {
    310             toAppendTo.append(formatMeasures(new StringBuilder(), fpos, (Measure[]) obj));
    311         } else if (obj instanceof Measure){
    312             toAppendTo.append(formatMeasure((Measure) obj, numberFormat, new StringBuilder(), fpos));
    313         } else {
    314             throw new IllegalArgumentException(obj.toString());
    315         }
    316         if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
    317             pos.setBeginIndex(fpos.getBeginIndex() + prevLength);
    318             pos.setEndIndex(fpos.getEndIndex() + prevLength);
    319         }
    320         return toAppendTo;
    321     }
    322 
    323     /**
    324      * Parses text from a string to produce a <code>Measure</code>.
    325      * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
    326      * @throws UnsupportedOperationException Not supported.
    327      * @hide draft / provisional / internal are hidden on Android
    328      */
    329     @Override
    330     public Measure parseObject(String source, ParsePosition pos) {
    331         throw new UnsupportedOperationException();
    332     }
    333 
    334     /**
    335      * Format a sequence of measures. Uses the ListFormatter unit lists.
    336      * So, for example, one could format 3 feet, 2 inches.
    337      * Zero values are formatted (eg, 3 feet, 0 inches). It is the callers
    338      * responsibility to have the appropriate values in appropriate order,
    339      * and using the appropriate Number values. Typically the units should be
    340      * in descending order, with all but the last Measure having integer values
    341      * (eg, not 3.2 feet, 2 inches).
    342      *
    343      * @param measures a sequence of one or more measures.
    344      * @return the formatted string.
    345      */
    346     public final String formatMeasures(Measure... measures) {
    347         return formatMeasures(
    348                 new StringBuilder(),
    349                 DontCareFieldPosition.INSTANCE,
    350                 measures).toString();
    351     }
    352 
    353     /**
    354      * Format a range of measures, such as "3.4-5.1 meters". It is the callers
    355      * responsibility to have the appropriate values in appropriate order,
    356      * and using the appropriate Number values.
    357      * <br>Note: If the format doesnt have enough decimals, or lowValue  highValue,
    358      * the result will be a degenerate range, like 5-5 meters.
    359      * <br>Currency Units are not yet supported.
    360      *
    361      * @param lowValue low value in range
    362      * @param highValue high value in range
    363      * @return the formatted string.
    364      * @deprecated This API is ICU internal only.
    365      * @hide original deprecated declaration
    366      * @hide draft / provisional / internal are hidden on Android
    367      */
    368     @Deprecated
    369     public final String formatMeasureRange(Measure lowValue, Measure highValue) {
    370         MeasureUnit unit = lowValue.getUnit();
    371         if (!unit.equals(highValue.getUnit())) {
    372             throw new IllegalArgumentException("Units must match: " + unit + "  " + highValue.getUnit());
    373         }
    374         Number lowNumber = lowValue.getNumber();
    375         Number highNumber = highValue.getNumber();
    376         final boolean isCurrency = unit instanceof Currency;
    377 
    378         UFieldPosition lowFpos = new UFieldPosition();
    379         UFieldPosition highFpos = new UFieldPosition();
    380         StringBuffer lowFormatted = null;
    381         StringBuffer highFormatted = null;
    382 
    383         if (isCurrency) {
    384             Currency currency = (Currency) unit;
    385             int fracDigits = currency.getDefaultFractionDigits();
    386             int maxFrac = numberFormat.nf.getMaximumFractionDigits();
    387             int minFrac = numberFormat.nf.getMinimumFractionDigits();
    388             if (fracDigits != maxFrac || fracDigits != minFrac) {
    389                 DecimalFormat currentNumberFormat = (DecimalFormat) numberFormat.get();
    390                 currentNumberFormat.setMaximumFractionDigits(fracDigits);
    391                 currentNumberFormat.setMinimumFractionDigits(fracDigits);
    392                 lowFormatted = currentNumberFormat.format(lowNumber, new StringBuffer(), lowFpos);
    393                 highFormatted = currentNumberFormat.format(highNumber, new StringBuffer(), highFpos);
    394             }
    395         }
    396         if (lowFormatted == null) {
    397             lowFormatted = numberFormat.format(lowNumber, new StringBuffer(), lowFpos);
    398             highFormatted = numberFormat.format(highNumber, new StringBuffer(), highFpos);
    399         }
    400 
    401         final double lowDouble = lowNumber.doubleValue();
    402         String keywordLow = rules.select(new PluralRules.FixedDecimal(lowDouble,
    403                 lowFpos.getCountVisibleFractionDigits(), lowFpos.getFractionDigits()));
    404 
    405         final double highDouble = highNumber.doubleValue();
    406         String keywordHigh = rules.select(new PluralRules.FixedDecimal(highDouble,
    407                 highFpos.getCountVisibleFractionDigits(), highFpos.getFractionDigits()));
    408 
    409         final PluralRanges pluralRanges = Factory.getDefaultFactory().getPluralRanges(getLocale());
    410         StandardPlural resolvedPlural = pluralRanges.get(
    411                 StandardPlural.fromString(keywordLow),
    412                 StandardPlural.fromString(keywordHigh));
    413 
    414         String rangeFormatter = getRangeFormat(getLocale(), formatWidth);
    415         String formattedNumber = SimpleFormatterImpl.formatCompiledPattern(
    416                 rangeFormatter, lowFormatted, highFormatted);
    417 
    418         if (isCurrency) {
    419             // Nasty hack
    420             currencyFormat.format(1d); // have to call this for the side effect
    421 
    422             Currency currencyUnit = (Currency) unit;
    423             StringBuilder result = new StringBuilder();
    424             appendReplacingCurrency(currencyFormat.getPrefix(lowDouble >= 0), currencyUnit, resolvedPlural, result);
    425             result.append(formattedNumber);
    426             appendReplacingCurrency(currencyFormat.getSuffix(highDouble >= 0), currencyUnit, resolvedPlural, result);
    427             return result.toString();
    428             //            StringBuffer buffer = new StringBuffer();
    429             //            CurrencyAmount currencyLow = (CurrencyAmount) lowValue;
    430             //            CurrencyAmount currencyHigh = (CurrencyAmount) highValue;
    431             //            FieldPosition pos = new FieldPosition(NumberFormat.INTEGER_FIELD);
    432             //            currencyFormat.format(currencyLow, buffer, pos);
    433             //            int startOfInteger = pos.getBeginIndex();
    434             //            StringBuffer buffer2 = new StringBuffer();
    435             //            FieldPosition pos2 = new FieldPosition(0);
    436             //            currencyFormat.format(currencyHigh, buffer2, pos2);
    437         } else {
    438             String formatter =
    439                     getPluralFormatter(lowValue.getUnit(), formatWidth, resolvedPlural.ordinal());
    440             return SimpleFormatterImpl.formatCompiledPattern(formatter, formattedNumber);
    441         }
    442     }
    443 
    444     private void appendReplacingCurrency(String affix, Currency unit, StandardPlural resolvedPlural, StringBuilder result) {
    445         String replacement = "";
    446         int pos = affix.indexOf(replacement);
    447         if (pos < 0) {
    448             replacement = "XXX";
    449             pos = affix.indexOf(replacement);
    450         }
    451         if (pos < 0) {
    452             result.append(affix);
    453         } else {
    454             // for now, just assume single
    455             result.append(affix.substring(0,pos));
    456             // we have a mismatch between the number style and the currency style, so remap
    457             int currentStyle = formatWidth.getCurrencyStyle();
    458             if (currentStyle == NumberFormat.ISOCURRENCYSTYLE) {
    459                 result.append(unit.getCurrencyCode());
    460             } else {
    461                 result.append(unit.getName(currencyFormat.nf.getLocale(ULocale.ACTUAL_LOCALE),
    462                         currentStyle == NumberFormat.CURRENCYSTYLE ? Currency.SYMBOL_NAME :  Currency.PLURAL_LONG_NAME,
    463                                 resolvedPlural.getKeyword(), null));
    464             }
    465             result.append(affix.substring(pos+replacement.length()));
    466         }
    467     }
    468 
    469     /**
    470      * Formats a single measure per unit.
    471      *
    472      * An example of such a formatted string is "3.5 meters per second."
    473      *
    474      * @param measure  the measure object. In above example, 3.5 meters.
    475      * @param perUnit  the per unit. In above example, it is MeasureUnit.SECOND
    476      * @param appendTo formatted string appended here.
    477      * @param pos      The field position.
    478      * @return appendTo.
    479      */
    480     public StringBuilder formatMeasurePerUnit(
    481             Measure measure,
    482             MeasureUnit perUnit,
    483             StringBuilder appendTo,
    484             FieldPosition pos) {
    485         MeasureUnit resolvedUnit = MeasureUnit.resolveUnitPerUnit(
    486                 measure.getUnit(), perUnit);
    487         if (resolvedUnit != null) {
    488             Measure newMeasure = new Measure(measure.getNumber(), resolvedUnit);
    489             return formatMeasure(newMeasure, numberFormat, appendTo, pos);
    490         }
    491         FieldPosition fpos = new FieldPosition(
    492                 pos.getFieldAttribute(), pos.getField());
    493         int offset = withPerUnitAndAppend(
    494                 formatMeasure(measure, numberFormat, new StringBuilder(), fpos),
    495                 perUnit,
    496                 appendTo);
    497         if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
    498             pos.setBeginIndex(fpos.getBeginIndex() + offset);
    499             pos.setEndIndex(fpos.getEndIndex() + offset);
    500         }
    501         return appendTo;
    502     }
    503 
    504     /**
    505      * Formats a sequence of measures.
    506      *
    507      * If the fieldPosition argument identifies a NumberFormat field,
    508      * then its indices are set to the beginning and end of the first such field
    509      * encountered. MeasureFormat itself does not supply any fields.
    510      *
    511      * @param appendTo the formatted string appended here.
    512      * @param fieldPosition Identifies a field in the formatted text.
    513      * @param measures the measures to format.
    514      * @return appendTo.
    515      * @see MeasureFormat#formatMeasures(Measure...)
    516      */
    517     public StringBuilder formatMeasures(
    518             StringBuilder appendTo, FieldPosition fieldPosition, Measure... measures) {
    519         // fast track for trivial cases
    520         if (measures.length == 0) {
    521             return appendTo;
    522         }
    523         if (measures.length == 1) {
    524             return formatMeasure(measures[0], numberFormat, appendTo, fieldPosition);
    525         }
    526 
    527         if (formatWidth == FormatWidth.NUMERIC) {
    528             // If we have just hour, minute, or second follow the numeric
    529             // track.
    530             Number[] hms = toHMS(measures);
    531             if (hms != null) {
    532                 return formatNumeric(hms, appendTo);
    533             }
    534         }
    535 
    536         ListFormatter listFormatter = ListFormatter.getInstance(
    537                 getLocale(), formatWidth.getListFormatterStyle());
    538         if (fieldPosition != DontCareFieldPosition.INSTANCE) {
    539             return formatMeasuresSlowTrack(listFormatter, appendTo, fieldPosition, measures);
    540         }
    541         // Fast track: No field position.
    542         String[] results = new String[measures.length];
    543         for (int i = 0; i < measures.length; i++) {
    544             results[i] = formatMeasure(
    545                     measures[i],
    546                     i == measures.length - 1 ? numberFormat : integerFormat);
    547         }
    548         return appendTo.append(listFormatter.format((Object[]) results));
    549 
    550     }
    551 
    552     /**
    553      * Gets the display name of the specified {@link MeasureUnit} corresponding to the current
    554      * locale and format width.
    555      * @param unit  The unit for which to get a display name.
    556      * @return  The display name in the locale and width specified in
    557      *          {@link MeasureFormat#getInstance}, or null if there is no display name available
    558      *          for the specified unit.
    559      */
    560     public String getUnitDisplayName(MeasureUnit unit) {
    561         FormatWidth width = getRegularWidth(formatWidth);
    562         Map<FormatWidth, String> styleToDnam = cache.unitToStyleToDnam.get(unit);
    563         if (styleToDnam == null) {
    564             return null;
    565         }
    566 
    567         String dnam = styleToDnam.get(width);
    568         if (dnam != null) {
    569             return dnam;
    570         }
    571         FormatWidth fallbackWidth = cache.widthFallback[width.ordinal()];
    572         if (fallbackWidth != null) {
    573             dnam = styleToDnam.get(fallbackWidth);
    574         }
    575         return dnam;
    576     }
    577 
    578     /**
    579      * Two MeasureFormats, a and b, are equal if and only if they have the same formatWidth,
    580      * locale, and equal number formats.
    581      */
    582     @Override
    583     public final boolean equals(Object other) {
    584         if (this == other) {
    585             return true;
    586         }
    587         if (!(other instanceof MeasureFormat)) {
    588             return false;
    589         }
    590         MeasureFormat rhs = (MeasureFormat) other;
    591         // A very slow but safe implementation.
    592         return getWidth() == rhs.getWidth()
    593                 && getLocale().equals(rhs.getLocale())
    594                 && getNumberFormat().equals(rhs.getNumberFormat());
    595     }
    596 
    597     /**
    598      * {@inheritDoc}
    599      */
    600     @Override
    601     public final int hashCode() {
    602         // A very slow but safe implementation.
    603         return (getLocale().hashCode() * 31
    604                 + getNumberFormat().hashCode()) * 31 + getWidth().hashCode();
    605     }
    606 
    607     /**
    608      * Get the format width this instance is using.
    609      */
    610     public MeasureFormat.FormatWidth getWidth() {
    611         return formatWidth;
    612     }
    613 
    614     /**
    615      * Get the locale of this instance.
    616      */
    617     public final ULocale getLocale() {
    618         return getLocale(ULocale.VALID_LOCALE);
    619     }
    620 
    621     /**
    622      * Get a copy of the number format.
    623      */
    624     public NumberFormat getNumberFormat() {
    625         return numberFormat.get();
    626     }
    627 
    628     /**
    629      * Return a formatter for CurrencyAmount objects in the given
    630      * locale.
    631      * @param locale desired locale
    632      * @return a formatter object
    633      */
    634     public static MeasureFormat getCurrencyFormat(ULocale locale) {
    635         return new CurrencyFormat(locale);
    636     }
    637 
    638     /**
    639      * Return a formatter for CurrencyAmount objects in the given
    640      * {@link java.util.Locale}.
    641      * @param locale desired {@link java.util.Locale}
    642      * @return a formatter object
    643      */
    644     public static MeasureFormat getCurrencyFormat(Locale locale) {
    645         return getCurrencyFormat(ULocale.forLocale(locale));
    646     }
    647 
    648     /**
    649      * Return a formatter for CurrencyAmount objects in the default
    650      * <code>FORMAT</code> locale.
    651      * @return a formatter object
    652      * @see Category#FORMAT
    653      */
    654     public static MeasureFormat getCurrencyFormat() {
    655         return getCurrencyFormat(ULocale.getDefault(Category.FORMAT));
    656     }
    657 
    658     // This method changes the NumberFormat object as well to match the new locale.
    659     MeasureFormat withLocale(ULocale locale) {
    660         return MeasureFormat.getInstance(locale, getWidth());
    661     }
    662 
    663     MeasureFormat withNumberFormat(NumberFormat format) {
    664         return new MeasureFormat(
    665                 getLocale(),
    666                 this.cache,
    667                 this.formatWidth,
    668                 new ImmutableNumberFormat(format),
    669                 this.rules,
    670                 this.numericFormatters,
    671                 this.currencyFormat,
    672                 this.integerFormat);
    673     }
    674 
    675     private MeasureFormat(
    676             ULocale locale,
    677             MeasureFormatData data,
    678             FormatWidth formatWidth,
    679             ImmutableNumberFormat format,
    680             PluralRules rules,
    681             NumericFormatters formatters,
    682             ImmutableNumberFormat currencyFormat,
    683             ImmutableNumberFormat integerFormat) {
    684         setLocale(locale, locale);
    685         this.cache = data;
    686         this.formatWidth = formatWidth;
    687         this.numberFormat = format;
    688         this.rules = rules;
    689         this.numericFormatters = formatters;
    690         this.currencyFormat = currencyFormat;
    691         this.integerFormat = integerFormat;
    692     }
    693 
    694     MeasureFormat() {
    695         // Make compiler happy by setting final fields to null.
    696         this.cache = null;
    697         this.formatWidth = null;
    698         this.numberFormat = null;
    699         this.rules = null;
    700         this.numericFormatters = null;
    701         this.currencyFormat = null;
    702         this.integerFormat = null;
    703     }
    704 
    705     static class NumericFormatters {
    706         private DateFormat hourMinute;
    707         private DateFormat minuteSecond;
    708         private DateFormat hourMinuteSecond;
    709 
    710         public NumericFormatters(
    711                 DateFormat hourMinute,
    712                 DateFormat minuteSecond,
    713                 DateFormat hourMinuteSecond) {
    714             this.hourMinute = hourMinute;
    715             this.minuteSecond = minuteSecond;
    716             this.hourMinuteSecond = hourMinuteSecond;
    717         }
    718 
    719         public DateFormat getHourMinute() { return hourMinute; }
    720         public DateFormat getMinuteSecond() { return minuteSecond; }
    721         public DateFormat getHourMinuteSecond() { return hourMinuteSecond; }
    722     }
    723 
    724     private static NumericFormatters loadNumericFormatters(
    725             ULocale locale) {
    726         ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
    727                 getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
    728         return new NumericFormatters(
    729                 loadNumericDurationFormat(r, "hm"),
    730                 loadNumericDurationFormat(r, "ms"),
    731                 loadNumericDurationFormat(r, "hms"));
    732     }
    733 
    734     /**
    735      * Sink for enumerating all of the measurement unit display names.
    736      * Contains inner sink classes, each one corresponding to a type of resource table.
    737      * The outer sink handles the top-level units, unitsNarrow, and unitsShort tables.
    738      *
    739      * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
    740      * Only store a value if it is still missing, that is, it has not been overridden.
    741      *
    742      * C++: Each inner sink class has a reference to the main outer sink.
    743      * Java: Use non-static inner classes instead.
    744      */
    745     private static final class UnitDataSink extends UResource.Sink {
    746         void setFormatterIfAbsent(int index, UResource.Value value, int minPlaceholders) {
    747             if (patterns == null) {
    748                 EnumMap<FormatWidth, String[]> styleToPatterns =
    749                         cacheData.unitToStyleToPatterns.get(unit);
    750                 if (styleToPatterns == null) {
    751                     styleToPatterns =
    752                             new EnumMap<FormatWidth, String[]>(FormatWidth.class);
    753                     cacheData.unitToStyleToPatterns.put(unit, styleToPatterns);
    754                 } else {
    755                     patterns = styleToPatterns.get(width);
    756                 }
    757                 if (patterns == null) {
    758                     patterns = new String[MeasureFormatData.PATTERN_COUNT];
    759                     styleToPatterns.put(width, patterns);
    760                 }
    761             }
    762             if (patterns[index] == null) {
    763                 patterns[index] = SimpleFormatterImpl.compileToStringMinMaxArguments(
    764                         value.getString(), sb, minPlaceholders, 1);
    765             }
    766         }
    767 
    768         void setDnamIfAbsent(UResource.Value value) {
    769             EnumMap<FormatWidth, String> styleToDnam = cacheData.unitToStyleToDnam.get(unit);
    770             if (styleToDnam == null) {
    771                 styleToDnam = new EnumMap<FormatWidth, String>(FormatWidth.class);
    772                 cacheData.unitToStyleToDnam.put(unit, styleToDnam);
    773             }
    774             if (styleToDnam.get(width) == null) {
    775                 styleToDnam.put(width, value.getString());
    776             }
    777         }
    778 
    779         /**
    780          * Consume a display pattern. For example,
    781          * unitsShort/duration/hour contains other{"{0} hrs"}.
    782          */
    783         void consumePattern(UResource.Key key, UResource.Value value) {
    784             if (key.contentEquals("dnam")) {
    785                 // The display name for the unit in the current width.
    786                 setDnamIfAbsent(value);
    787             } else if (key.contentEquals("per")) {
    788                 // For example, "{0}/h".
    789                 setFormatterIfAbsent(MeasureFormatData.PER_UNIT_INDEX, value, 1);
    790             } else {
    791                 // The key must be one of the plural form strings. For example:
    792                 // one{"{0} hr"}
    793                 // other{"{0} hrs"}
    794                 setFormatterIfAbsent(StandardPlural.indexFromString(key), value, 0);
    795             }
    796         }
    797 
    798         /**
    799          * Consume a table of per-unit tables. For example,
    800          * unitsShort/duration contains tables for duration-unit subtypes day & hour.
    801          */
    802         void consumeSubtypeTable(UResource.Key key, UResource.Value value) {
    803             unit = MeasureUnit.internalGetInstance(type, key.toString());  // never null
    804             // Trigger a fresh lookup of the patterns for this unit+width.
    805             patterns = null;
    806 
    807             // We no longer handle units like "coordinate" here (which do not have plural variants)
    808             if (value.getType() == ICUResourceBundle.TABLE) {
    809                 // Units that have plural variants
    810                 UResource.Table patternTableTable = value.getTable();
    811                 for (int i = 0; patternTableTable.getKeyAndValue(i, key, value); i++) {
    812                     consumePattern(key, value);
    813                 }
    814             } else {
    815                 throw new ICUException("Data for unit '" + unit + "' is in an unknown format");
    816             }
    817         }
    818 
    819         /**
    820          * Consume compound x-per-y display pattern. For example,
    821          * unitsShort/compound/per may be "{0}/{1}".
    822          */
    823         void consumeCompoundPattern(UResource.Key key, UResource.Value value) {
    824             if (key.contentEquals("per")) {
    825                 cacheData.styleToPerPattern.put(width,
    826                         SimpleFormatterImpl.compileToStringMinMaxArguments(
    827                                 value.getString(), sb, 2, 2));
    828             }
    829         }
    830 
    831         /**
    832          * Consume a table of unit type tables. For example,
    833          * unitsShort contains tables for area & duration.
    834          * It also contains a table for the compound/per pattern.
    835          */
    836         void consumeUnitTypesTable(UResource.Key key, UResource.Value value) {
    837             if (key.contentEquals("currency")) {
    838                 // Skip.
    839             } else if (key.contentEquals("compound")) {
    840                 if (!cacheData.hasPerFormatter(width)) {
    841                     UResource.Table compoundTable = value.getTable();
    842                     for (int i = 0; compoundTable.getKeyAndValue(i, key, value); i++) {
    843                         consumeCompoundPattern(key, value);
    844                     }
    845                 }
    846             } else if (key.contentEquals("coordinate")) {
    847                 // special handling but we need to determine what that is
    848             } else {
    849                 type = key.toString();
    850                 UResource.Table subtypeTable = value.getTable();
    851                 for (int i = 0; subtypeTable.getKeyAndValue(i, key, value); i++) {
    852                     consumeSubtypeTable(key, value);
    853                 }
    854             }
    855         }
    856 
    857         UnitDataSink(MeasureFormatData outputData) {
    858             cacheData = outputData;
    859         }
    860 
    861         void consumeAlias(UResource.Key key, UResource.Value value) {
    862             // Handle aliases like
    863             // units:alias{"/LOCALE/unitsShort"}
    864             // which should only occur in the root bundle.
    865             FormatWidth sourceWidth = widthFromKey(key);
    866             if (sourceWidth == null) {
    867                 // Alias from something we don't care about.
    868                 return;
    869             }
    870             FormatWidth targetWidth = widthFromAlias(value);
    871             if (targetWidth == null) {
    872                 // We do not recognize what to fall back to.
    873                 throw new ICUException("Units data fallback from " + key +
    874                         " to unknown " + value.getAliasString());
    875             }
    876             // Check that we do not fall back to another fallback.
    877             if (cacheData.widthFallback[targetWidth.ordinal()] != null) {
    878                 throw new ICUException("Units data fallback from " + key +
    879                         " to " + value.getAliasString() + " which falls back to something else");
    880             }
    881             cacheData.widthFallback[sourceWidth.ordinal()] = targetWidth;
    882         }
    883 
    884         public void consumeTable(UResource.Key key, UResource.Value value) {
    885             if ((width = widthFromKey(key)) != null) {
    886                 UResource.Table unitTypesTable = value.getTable();
    887                 for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
    888                     consumeUnitTypesTable(key, value);
    889                 }
    890             }
    891         }
    892 
    893         static FormatWidth widthFromKey(UResource.Key key) {
    894             if (key.startsWith("units")) {
    895                 if (key.length() == 5) {
    896                     return FormatWidth.WIDE;
    897                 } else if (key.regionMatches(5, "Short")) {
    898                     return FormatWidth.SHORT;
    899                 } else if (key.regionMatches(5, "Narrow")) {
    900                     return FormatWidth.NARROW;
    901                 }
    902             }
    903             return null;
    904         }
    905 
    906         static FormatWidth widthFromAlias(UResource.Value value) {
    907             String s = value.getAliasString();
    908             // For example: "/LOCALE/unitsShort"
    909             if (s.startsWith("/LOCALE/units")) {
    910                 if (s.length() == 13) {
    911                     return FormatWidth.WIDE;
    912                 } else if (s.length() == 18 && s.endsWith("Short")) {
    913                     return FormatWidth.SHORT;
    914                 } else if (s.length() == 19 && s.endsWith("Narrow")) {
    915                     return FormatWidth.NARROW;
    916                 }
    917             }
    918             return null;
    919         }
    920 
    921         @Override
    922         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
    923             // Main entry point to sink
    924             UResource.Table widthsTable = value.getTable();
    925             for (int i = 0; widthsTable.getKeyAndValue(i, key, value); i++) {
    926                 if (value.getType() == ICUResourceBundle.ALIAS) {
    927                     consumeAlias(key, value);
    928                 } else {
    929                     consumeTable(key, value);
    930                 }
    931             }
    932         }
    933 
    934         // Output data.
    935         MeasureFormatData cacheData;
    936 
    937         // Path to current data.
    938         FormatWidth width;
    939         String type;
    940         MeasureUnit unit;
    941 
    942         // Temporary
    943         StringBuilder sb = new StringBuilder();
    944         String[] patterns;
    945     }
    946 
    947     /**
    948      * Returns formatting data for all MeasureUnits except for currency ones.
    949      */
    950     private static MeasureFormatData loadLocaleData(ULocale locale) {
    951         ICUResourceBundle resource =
    952                 (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
    953         MeasureFormatData cacheData = new MeasureFormatData();
    954         UnitDataSink sink = new UnitDataSink(cacheData);
    955         resource.getAllItemsWithFallback("", sink);
    956         return cacheData;
    957     }
    958 
    959     private static final FormatWidth getRegularWidth(FormatWidth width) {
    960         if (width == FormatWidth.NUMERIC) {
    961             return FormatWidth.NARROW;
    962         }
    963         return width;
    964     }
    965 
    966     private String getFormatterOrNull(MeasureUnit unit, FormatWidth width, int index) {
    967         width = getRegularWidth(width);
    968         Map<FormatWidth, String[]> styleToPatterns = cache.unitToStyleToPatterns.get(unit);
    969         String[] patterns = styleToPatterns.get(width);
    970         if (patterns != null && patterns[index] != null) {
    971             return patterns[index];
    972         }
    973         FormatWidth fallbackWidth = cache.widthFallback[width.ordinal()];
    974         if (fallbackWidth != null) {
    975             patterns = styleToPatterns.get(fallbackWidth);
    976             if (patterns != null && patterns[index] != null) {
    977                 return patterns[index];
    978             }
    979         }
    980         return null;
    981     }
    982 
    983     private String getFormatter(MeasureUnit unit, FormatWidth width, int index) {
    984         String pattern = getFormatterOrNull(unit, width, index);
    985         if (pattern == null) {
    986             throw new MissingResourceException(
    987                     "no formatting pattern for " + unit + ", width " + width + ", index " + index,
    988                     null, null);
    989         }
    990         return pattern;
    991     }
    992 
    993     /**
    994      * @deprecated This API is ICU internal only.
    995      * @hide draft / provisional / internal are hidden on Android
    996      */
    997     @Deprecated
    998     public String getPluralFormatter(MeasureUnit unit, FormatWidth width, int index) {
    999         if (index != StandardPlural.OTHER_INDEX) {
   1000             String pattern = getFormatterOrNull(unit, width, index);
   1001             if (pattern != null) {
   1002                 return pattern;
   1003             }
   1004         }
   1005         return getFormatter(unit, width, StandardPlural.OTHER_INDEX);
   1006     }
   1007 
   1008     private String getPerFormatter(FormatWidth width) {
   1009         width = getRegularWidth(width);
   1010         String perPattern = cache.styleToPerPattern.get(width);
   1011         if (perPattern != null) {
   1012             return perPattern;
   1013         }
   1014         FormatWidth fallbackWidth = cache.widthFallback[width.ordinal()];
   1015         if (fallbackWidth != null) {
   1016             perPattern = cache.styleToPerPattern.get(fallbackWidth);
   1017             if (perPattern != null) {
   1018                 return perPattern;
   1019             }
   1020         }
   1021         throw new MissingResourceException("no x-per-y pattern for width " + width, null, null);
   1022     }
   1023 
   1024     private int withPerUnitAndAppend(
   1025             CharSequence formatted, MeasureUnit perUnit, StringBuilder appendTo) {
   1026         int[] offsets = new int[1];
   1027         String perUnitPattern =
   1028                 getFormatterOrNull(perUnit, formatWidth, MeasureFormatData.PER_UNIT_INDEX);
   1029         if (perUnitPattern != null) {
   1030             SimpleFormatterImpl.formatAndAppend(perUnitPattern, appendTo, offsets, formatted);
   1031             return offsets[0];
   1032         }
   1033         String perPattern = getPerFormatter(formatWidth);
   1034         String pattern = getPluralFormatter(perUnit, formatWidth, StandardPlural.ONE.ordinal());
   1035         String perUnitString = SimpleFormatterImpl.getTextWithNoArguments(pattern).trim();
   1036         SimpleFormatterImpl.formatAndAppend(
   1037                 perPattern, appendTo, offsets, formatted, perUnitString);
   1038         return offsets[0];
   1039     }
   1040 
   1041     private String formatMeasure(Measure measure, ImmutableNumberFormat nf) {
   1042         return formatMeasure(
   1043                 measure, nf, new StringBuilder(),
   1044                 DontCareFieldPosition.INSTANCE).toString();
   1045     }
   1046 
   1047     private StringBuilder formatMeasure(
   1048             Measure measure,
   1049             ImmutableNumberFormat nf,
   1050             StringBuilder appendTo,
   1051             FieldPosition fieldPosition) {
   1052         Number n = measure.getNumber();
   1053         MeasureUnit unit = measure.getUnit();
   1054         if (unit instanceof Currency) {
   1055             return appendTo.append(
   1056                     currencyFormat.format(
   1057                             new CurrencyAmount(n, (Currency) unit),
   1058                             new StringBuffer(),
   1059                             fieldPosition));
   1060 
   1061         }
   1062         StringBuffer formattedNumber = new StringBuffer();
   1063         StandardPlural pluralForm = QuantityFormatter.selectPlural(
   1064                 n, nf.nf, rules, formattedNumber, fieldPosition);
   1065         String formatter = getPluralFormatter(unit, formatWidth, pluralForm.ordinal());
   1066         return QuantityFormatter.format(formatter, formattedNumber, appendTo, fieldPosition);
   1067     }
   1068 
   1069     /**
   1070      * Instances contain all MeasureFormat specific data for a particular locale.
   1071      * This data is cached. It is never copied, but is shared via shared pointers.
   1072      *
   1073      * Note: We might change the cache data to have
   1074      * an array[WIDTH_INDEX_COUNT] or EnumMap<FormatWidth, ...> of
   1075      * complete sets of unit & per patterns,
   1076      * to correspond to the resource data and its aliases.
   1077      */
   1078     private static final class MeasureFormatData {
   1079         static final int PER_UNIT_INDEX = StandardPlural.COUNT;
   1080         static final int PATTERN_COUNT = PER_UNIT_INDEX + 1;
   1081 
   1082         boolean hasPerFormatter(FormatWidth width) {
   1083             return styleToPerPattern.containsKey(width);
   1084         }
   1085 
   1086         /**
   1087          * Redirection data from root-bundle, top-level sideways aliases.
   1088          * - null: initial value, just fall back to root
   1089          * - FormatWidth.WIDE/SHORT/NARROW: sideways alias for missing data
   1090          */
   1091         final FormatWidth widthFallback[] = new FormatWidth[FormatWidth.INDEX_COUNT];
   1092         /** Measure unit -> format width -> array of patterns ("{0} meters") (plurals + PER_UNIT_INDEX) */
   1093         final Map<MeasureUnit, EnumMap<FormatWidth, String[]>> unitToStyleToPatterns =
   1094                 new HashMap<MeasureUnit, EnumMap<FormatWidth, String[]>>();
   1095         final Map<MeasureUnit, EnumMap<FormatWidth, String>> unitToStyleToDnam =
   1096                 new HashMap<MeasureUnit, EnumMap<FormatWidth, String>>();
   1097         final EnumMap<FormatWidth, String> styleToPerPattern =
   1098                 new EnumMap<FormatWidth, String>(FormatWidth.class);;
   1099     }
   1100 
   1101     // Wrapper around NumberFormat that provides immutability and thread-safety.
   1102     private static final class ImmutableNumberFormat {
   1103         private NumberFormat nf;
   1104 
   1105         public ImmutableNumberFormat(NumberFormat nf) {
   1106             this.nf = (NumberFormat) nf.clone();
   1107         }
   1108 
   1109         public synchronized NumberFormat get() {
   1110             return (NumberFormat) nf.clone();
   1111         }
   1112 
   1113         public synchronized StringBuffer format(
   1114                 Number n, StringBuffer buffer, FieldPosition pos) {
   1115             return nf.format(n, buffer, pos);
   1116         }
   1117 
   1118         public synchronized StringBuffer format(
   1119                 CurrencyAmount n, StringBuffer buffer, FieldPosition pos) {
   1120             return nf.format(n, buffer, pos);
   1121         }
   1122 
   1123         @SuppressWarnings("unused")
   1124         public synchronized String format(Number number) {
   1125             return nf.format(number);
   1126         }
   1127 
   1128         public String getPrefix(boolean positive) {
   1129             return positive ? ((DecimalFormat)nf).getPositivePrefix() : ((DecimalFormat)nf).getNegativePrefix();
   1130         }
   1131         public String getSuffix(boolean positive) {
   1132             return positive ? ((DecimalFormat)nf).getPositiveSuffix() : ((DecimalFormat)nf).getNegativeSuffix();
   1133         }
   1134     }
   1135 
   1136     static final class PatternData {
   1137         final String prefix;
   1138         final String suffix;
   1139         public PatternData(String pattern) {
   1140             int pos = pattern.indexOf("{0}");
   1141             if (pos < 0) {
   1142                 prefix = pattern;
   1143                 suffix = null;
   1144             } else {
   1145                 prefix = pattern.substring(0,pos);
   1146                 suffix = pattern.substring(pos+3);
   1147             }
   1148         }
   1149         @Override
   1150         public String toString() {
   1151             return prefix + "; " + suffix;
   1152         }
   1153 
   1154     }
   1155 
   1156     Object toTimeUnitProxy() {
   1157         return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), TIME_UNIT_FORMAT);
   1158     }
   1159 
   1160     Object toCurrencyProxy() {
   1161         return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), CURRENCY_FORMAT);
   1162     }
   1163 
   1164     private StringBuilder formatMeasuresSlowTrack(
   1165             ListFormatter listFormatter,
   1166             StringBuilder appendTo,
   1167             FieldPosition fieldPosition,
   1168             Measure... measures) {
   1169         String[] results = new String[measures.length];
   1170 
   1171         // Zero out our field position so that we can tell when we find our field.
   1172         FieldPosition fpos = new FieldPosition(
   1173                 fieldPosition.getFieldAttribute(), fieldPosition.getField());
   1174 
   1175         int fieldPositionFoundIndex = -1;
   1176         for (int i = 0; i < measures.length; ++i) {
   1177             ImmutableNumberFormat nf = (i == measures.length - 1 ? numberFormat : integerFormat);
   1178             if (fieldPositionFoundIndex == -1) {
   1179                 results[i] = formatMeasure(measures[i], nf, new StringBuilder(), fpos).toString();
   1180                 if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
   1181                     fieldPositionFoundIndex = i;
   1182                 }
   1183             } else {
   1184                 results[i] = formatMeasure(measures[i], nf);
   1185             }
   1186         }
   1187         ListFormatter.FormattedListBuilder builder =
   1188                 listFormatter.format(Arrays.asList(results), fieldPositionFoundIndex);
   1189 
   1190         // Fix up FieldPosition indexes if our field is found.
   1191         if (builder.getOffset() != -1) {
   1192             fieldPosition.setBeginIndex(fpos.getBeginIndex() + builder.getOffset() + appendTo.length());
   1193             fieldPosition.setEndIndex(fpos.getEndIndex() + builder.getOffset() + appendTo.length());
   1194         }
   1195         return appendTo.append(builder.toString());
   1196     }
   1197 
   1198     // type is one of "hm", "ms" or "hms"
   1199     private static DateFormat loadNumericDurationFormat(
   1200             ICUResourceBundle r, String type) {
   1201         r = r.getWithFallback(String.format("durationUnits/%s", type));
   1202         // We replace 'h' with 'H' because 'h' does not make sense in the context of durations.
   1203         DateFormat result = new SimpleDateFormat(r.getString().replace("h", "H"));
   1204         result.setTimeZone(TimeZone.GMT_ZONE);
   1205         return result;
   1206     }
   1207 
   1208     // Returns hours in [0]; minutes in [1]; seconds in [2] out of measures array. If
   1209     // unsuccessful, e.g measures has other measurements besides hours, minutes, seconds;
   1210     // hours, minutes, seconds are out of order; or have negative values, returns null.
   1211     // If hours, minutes, or seconds is missing from measures the corresponding element in
   1212     // returned array will be null.
   1213     private static Number[] toHMS(Measure[] measures) {
   1214         Number[] result = new Number[3];
   1215         int lastIdx = -1;
   1216         for (Measure m : measures) {
   1217             if (m.getNumber().doubleValue() < 0.0) {
   1218                 return null;
   1219             }
   1220             Integer idxObj = hmsTo012.get(m.getUnit());
   1221             if (idxObj == null) {
   1222                 return null;
   1223             }
   1224             int idx = idxObj.intValue();
   1225             if (idx <= lastIdx) {
   1226                 // hour before minute before second
   1227                 return null;
   1228             }
   1229             lastIdx = idx;
   1230             result[idx] = m.getNumber();
   1231         }
   1232         return result;
   1233     }
   1234 
   1235     // Formats numeric time duration as 5:00:47 or 3:54. In the process, it replaces any null
   1236     // values in hms with 0.
   1237     private StringBuilder formatNumeric(Number[] hms, StringBuilder appendable) {
   1238 
   1239         // find the start and end of non-nil values in hms array. We have to know if we
   1240         // have hour-minute; minute-second; or hour-minute-second.
   1241         int startIndex = -1;
   1242         int endIndex = -1;
   1243         for (int i = 0; i < hms.length; i++) {
   1244             if (hms[i] != null) {
   1245                 endIndex = i;
   1246                 if (startIndex == -1) {
   1247                     startIndex = endIndex;
   1248                 }
   1249             } else {
   1250                 // Replace nil value with 0.
   1251                 hms[i] = Integer.valueOf(0);
   1252             }
   1253         }
   1254         // convert hours, minutes, seconds into milliseconds.
   1255         long millis = (long) (((Math.floor(hms[0].doubleValue()) * 60.0
   1256                 + Math.floor(hms[1].doubleValue())) * 60.0
   1257                 + Math.floor(hms[2].doubleValue())) * 1000.0);
   1258         Date d = new Date(millis);
   1259         // if hour-minute-second
   1260         if (startIndex == 0 && endIndex == 2) {
   1261             return formatNumeric(
   1262                     d,
   1263                     numericFormatters.getHourMinuteSecond(),
   1264                     DateFormat.Field.SECOND,
   1265                     hms[endIndex],
   1266                     appendable);
   1267         }
   1268         // if minute-second
   1269         if (startIndex == 1 && endIndex == 2) {
   1270             return formatNumeric(
   1271                     d,
   1272                     numericFormatters.getMinuteSecond(),
   1273                     DateFormat.Field.SECOND,
   1274                     hms[endIndex],
   1275                     appendable);
   1276         }
   1277         // if hour-minute
   1278         if (startIndex == 0 && endIndex == 1) {
   1279             return formatNumeric(
   1280                     d,
   1281                     numericFormatters.getHourMinute(),
   1282                     DateFormat.Field.MINUTE,
   1283                     hms[endIndex],
   1284                     appendable);
   1285         }
   1286         throw new IllegalStateException();
   1287     }
   1288 
   1289     // Formats a duration as 5:00:37 or 23:59.
   1290     // duration is a particular duration after epoch.
   1291     // formatter is a hour-minute-second, hour-minute, or minute-second formatter.
   1292     // smallestField denotes what the smallest field is in duration: either
   1293     // hour, minute, or second.
   1294     // smallestAmount is the value of that smallest field. for 5:00:37.3,
   1295     // smallestAmount is 37.3. This smallest field is formatted with this object's
   1296     // NumberFormat instead of formatter.
   1297     // appendTo is where the formatted string is appended.
   1298     private StringBuilder formatNumeric(
   1299             Date duration,
   1300             DateFormat formatter,
   1301             DateFormat.Field smallestField,
   1302             Number smallestAmount,
   1303             StringBuilder appendTo) {
   1304         // Format the smallest amount ahead of time.
   1305         String smallestAmountFormatted;
   1306 
   1307         // Format the smallest amount using this object's number format, but keep track
   1308         // of the integer portion of this formatted amount. We have to replace just the
   1309         // integer part with the corresponding value from formatting the date. Otherwise
   1310         // when formatting 0 minutes 9 seconds, we may get "00:9" instead of "00:09"
   1311         FieldPosition intFieldPosition = new FieldPosition(NumberFormat.INTEGER_FIELD);
   1312         smallestAmountFormatted = numberFormat.format(
   1313                 smallestAmount, new StringBuffer(), intFieldPosition).toString();
   1314         // Give up if there is no integer field.
   1315         if (intFieldPosition.getBeginIndex() == 0 && intFieldPosition.getEndIndex() == 0) {
   1316             throw new IllegalStateException();
   1317         }
   1318         // Format our duration as a date, but keep track of where the smallest field is
   1319         // so that we can use it to replace the integer portion of the smallest value.
   1320         FieldPosition smallestFieldPosition = new FieldPosition(smallestField);
   1321         String draft = formatter.format(
   1322                 duration, new StringBuffer(), smallestFieldPosition).toString();
   1323 
   1324         // If we find the smallest field
   1325         if (smallestFieldPosition.getBeginIndex() != 0
   1326                 || smallestFieldPosition.getEndIndex() != 0) {
   1327             // add everything up to the start of the smallest field in duration.
   1328             appendTo.append(draft, 0, smallestFieldPosition.getBeginIndex());
   1329 
   1330             // add everything in the smallest field up to the integer portion
   1331             appendTo.append(smallestAmountFormatted, 0, intFieldPosition.getBeginIndex());
   1332 
   1333             // Add the smallest field in formatted duration in lieu of the integer portion
   1334             // of smallest field
   1335             appendTo.append(
   1336                     draft,
   1337                     smallestFieldPosition.getBeginIndex(),
   1338                     smallestFieldPosition.getEndIndex());
   1339 
   1340             // Add the rest of the smallest field
   1341             appendTo.append(
   1342                     smallestAmountFormatted,
   1343                     intFieldPosition.getEndIndex(),
   1344                     smallestAmountFormatted.length());
   1345             appendTo.append(draft, smallestFieldPosition.getEndIndex(), draft.length());
   1346         } else {
   1347             // As fallback, just use the formatted duration.
   1348             appendTo.append(draft);
   1349         }
   1350         return appendTo;
   1351     }
   1352 
   1353     private Object writeReplace() throws ObjectStreamException {
   1354         return new MeasureProxy(
   1355                 getLocale(), formatWidth, numberFormat.get(), MEASURE_FORMAT);
   1356     }
   1357 
   1358     static class MeasureProxy implements Externalizable {
   1359         private static final long serialVersionUID = -6033308329886716770L;
   1360 
   1361         private ULocale locale;
   1362         private FormatWidth formatWidth;
   1363         private NumberFormat numberFormat;
   1364         private int subClass;
   1365         private HashMap<Object, Object> keyValues;
   1366 
   1367         public MeasureProxy(
   1368                 ULocale locale,
   1369                 FormatWidth width,
   1370                 NumberFormat numberFormat,
   1371                 int subClass) {
   1372             this.locale = locale;
   1373             this.formatWidth = width;
   1374             this.numberFormat = numberFormat;
   1375             this.subClass = subClass;
   1376             this.keyValues = new HashMap<Object, Object>();
   1377         }
   1378 
   1379         // Must have public constructor, to enable Externalizable
   1380         public MeasureProxy() {
   1381         }
   1382 
   1383         @Override
   1384         public void writeExternal(ObjectOutput out) throws IOException {
   1385             out.writeByte(0); // version
   1386             out.writeUTF(locale.toLanguageTag());
   1387             out.writeByte(formatWidth.ordinal());
   1388             out.writeObject(numberFormat);
   1389             out.writeByte(subClass);
   1390             out.writeObject(keyValues);
   1391         }
   1392 
   1393         @Override
   1394         @SuppressWarnings("unchecked")
   1395         public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
   1396             in.readByte(); // version.
   1397             locale = ULocale.forLanguageTag(in.readUTF());
   1398             formatWidth = fromFormatWidthOrdinal(in.readByte() & 0xFF);
   1399             numberFormat = (NumberFormat) in.readObject();
   1400             if (numberFormat == null) {
   1401                 throw new InvalidObjectException("Missing number format.");
   1402             }
   1403             subClass = in.readByte() & 0xFF;
   1404 
   1405             // This cast is safe because the serialized form of hashtable can have
   1406             // any object as the key and any object as the value.
   1407             keyValues = (HashMap<Object, Object>) in.readObject();
   1408             if (keyValues == null) {
   1409                 throw new InvalidObjectException("Missing optional values map.");
   1410             }
   1411         }
   1412 
   1413         private TimeUnitFormat createTimeUnitFormat() throws InvalidObjectException {
   1414             int style;
   1415             if (formatWidth == FormatWidth.WIDE) {
   1416                 style = TimeUnitFormat.FULL_NAME;
   1417             } else if (formatWidth == FormatWidth.SHORT) {
   1418                 style = TimeUnitFormat.ABBREVIATED_NAME;
   1419             } else {
   1420                 throw new InvalidObjectException("Bad width: " + formatWidth);
   1421             }
   1422             TimeUnitFormat result = new TimeUnitFormat(locale, style);
   1423             result.setNumberFormat(numberFormat);
   1424             return result;
   1425         }
   1426 
   1427         private Object readResolve() throws ObjectStreamException {
   1428             switch (subClass) {
   1429             case MEASURE_FORMAT:
   1430                 return MeasureFormat.getInstance(locale, formatWidth, numberFormat);
   1431             case TIME_UNIT_FORMAT:
   1432                 return createTimeUnitFormat();
   1433             case CURRENCY_FORMAT:
   1434                 return new CurrencyFormat(locale);
   1435             default:
   1436                 throw new InvalidObjectException("Unknown subclass: " + subClass);
   1437             }
   1438         }
   1439     }
   1440 
   1441     private static FormatWidth fromFormatWidthOrdinal(int ordinal) {
   1442         FormatWidth[] values = FormatWidth.values();
   1443         if (ordinal < 0 || ordinal >= values.length) {
   1444             return FormatWidth.SHORT;
   1445         }
   1446         return values[ordinal];
   1447     }
   1448 
   1449     private static final Map<ULocale, String> localeIdToRangeFormat =
   1450             new ConcurrentHashMap<ULocale, String>();
   1451 
   1452     /**
   1453      * Return a formatter (compiled SimpleFormatter pattern) for a range, such as "{0}{1}".
   1454      * @param forLocale locale to get the format for
   1455      * @param width the format width
   1456      * @return range formatter, such as "{0}{1}"
   1457      * @deprecated This API is ICU internal only.
   1458      * @hide original deprecated declaration
   1459      * @hide draft / provisional / internal are hidden on Android
   1460      */
   1461     @Deprecated
   1462     public static String getRangeFormat(ULocale forLocale, FormatWidth width) {
   1463         // TODO fix Hack for French
   1464         if (forLocale.getLanguage().equals("fr")) {
   1465             return getRangeFormat(ULocale.ROOT, width);
   1466         }
   1467         String result = localeIdToRangeFormat.get(forLocale);
   1468         if (result == null) {
   1469             ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.
   1470                     getBundleInstance(ICUData.ICU_BASE_NAME, forLocale);
   1471             ULocale realLocale = rb.getULocale();
   1472             if (!forLocale.equals(realLocale)) { // if the child would inherit, then add a cache entry for it.
   1473                 result = localeIdToRangeFormat.get(forLocale);
   1474                 if (result != null) {
   1475                     localeIdToRangeFormat.put(forLocale, result);
   1476                     return result;
   1477                 }
   1478             }
   1479             // At this point, both the forLocale and the realLocale don't have an item
   1480             // So we have to make one.
   1481             NumberingSystem ns = NumberingSystem.getInstance(forLocale);
   1482 
   1483             String resultString = null;
   1484             try {
   1485                 resultString = rb.getStringWithFallback("NumberElements/" + ns.getName() + "/miscPatterns/range");
   1486             } catch ( MissingResourceException ex ) {
   1487                 resultString = rb.getStringWithFallback("NumberElements/latn/patterns/range");
   1488             }
   1489             result = SimpleFormatterImpl.compileToStringMinMaxArguments(
   1490                     resultString, new StringBuilder(), 2, 2);
   1491             localeIdToRangeFormat.put(forLocale, result);
   1492             if (!forLocale.equals(realLocale)) {
   1493                 localeIdToRangeFormat.put(realLocale, result);
   1494             }
   1495         }
   1496         return result;
   1497     }
   1498 }
   1499