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