17935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
27935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert **********************************************************************
3f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert * Copyright (c) 2004-2015, International Business Machines
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Corporation and others.  All Rights Reserved.
57935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert **********************************************************************
67935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Author: Alan Liu
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Created: April 20, 2004
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Since: ICU 3.0
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert **********************************************************************
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.text;
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.Externalizable;
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.IOException;
157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.InvalidObjectException;
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.ObjectInput;
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.ObjectOutput;
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.ObjectStreamException;
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.FieldPosition;
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.ParsePosition;
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Arrays;
227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Collection;
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Date;
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.EnumMap;
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.HashMap;
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Locale;
277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Map;
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.MissingResourceException;
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.concurrent.ConcurrentHashMap;
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.impl.DontCareFieldPosition;
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.impl.ICUData;
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.impl.ICUResourceBundle;
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.impl.SimpleCache;
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.impl.SimplePatternFormatter;
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.math.BigDecimal;
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.Factory;
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.StandardPluralCategories;
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.Currency;
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.CurrencyAmount;
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.Measure;
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.MeasureUnit;
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.TimeZone;
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.ULocale;
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.ULocale.Category;
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.UResourceBundle;
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert// If you update the examples in the doc, don't forget to update MesaureUnitTest.TestExamplesInDocs too.
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/**
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * A formatter for Measure objects.
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>To format a Measure object, first create a formatter
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * object using a MeasureFormat factory method.  Then use that
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * object's format or formatMeasures methods.
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Here is sample code:
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <pre>
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      MeasureFormat fmtFr = MeasureFormat.getInstance(
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              ULocale.FRENCH, FormatWidth.SHORT);
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      Measure measure = new Measure(23, MeasureUnit.CELSIUS);
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      // Output: 23 °C
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      System.out.println(fmtFr.format(measure));
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      Measure measureF = new Measure(70, MeasureUnit.FAHRENHEIT);
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      // Output: 70 °F
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      System.out.println(fmtFr.format(measureF));
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      MeasureFormat fmtFrFull = MeasureFormat.getInstance(
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              ULocale.FRENCH, FormatWidth.WIDE);
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      // Output: 70 pieds et 5,3 pouces
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      System.out.println(fmtFrFull.formatMeasures(
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              new Measure(70, MeasureUnit.FOOT),
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              new Measure(5.3, MeasureUnit.INCH)));
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      // Output: 1 pied et 1 pouce
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      System.out.println(fmtFrFull.formatMeasures(
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              new Measure(1, MeasureUnit.FOOT),
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              new Measure(1, MeasureUnit.INCH)));
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      MeasureFormat fmtFrNarrow = MeasureFormat.getInstance(
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ULocale.FRENCH, FormatWidth.NARROW);
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      // Output: 1′ 1″
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      System.out.println(fmtFrNarrow.formatMeasures(
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              new Measure(1, MeasureUnit.FOOT),
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              new Measure(1, MeasureUnit.INCH)));
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      MeasureFormat fmtEn = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE);
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      // Output: 1 inch, 2 feet
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *      fmtEn.formatMeasures(
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              new Measure(1, MeasureUnit.INCH),
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *              new Measure(2, MeasureUnit.FOOT));
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </pre>
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * This class does not do conversions from one unit to another. It simply formats
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * whatever units it is given
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * This class is immutable and thread-safe so long as its deprecated subclass,
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * TimeUnitFormat, is never used. TimeUnitFormat is not thread-safe, and is
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * mutable. Although this class has existing subclasses, this class does not support new
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * sub-classes.
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @see com.ibm.icu.text.UFormat
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @author Alan Liu
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @stable ICU 3.0
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic class MeasureFormat extends UFormat {
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Generated by serialver from JDK 1.4.1_01
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static final long serialVersionUID = -7182021401701778240L;
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final transient ImmutableNumberFormat numberFormat;
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final transient FormatWidth formatWidth;
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // PluralRules is documented as being immutable which implies thread-safety.
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final transient PluralRules rules;
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Measure unit -> format width -> plural form -> pattern ("{0} meters")
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final transient Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat;
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final transient NumericFormatters numericFormatters;
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final transient ImmutableNumberFormat currencyFormat;
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final transient ImmutableNumberFormat integerFormat;
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final transient Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern;
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final transient EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern;
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final SimpleCache<ULocale, MeasureFormatData> localeMeasureFormatData
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    = new SimpleCache<ULocale, MeasureFormatData>();
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final SimpleCache<ULocale, NumericFormatters> localeToNumericDurationFormatters
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    = new SimpleCache<ULocale,NumericFormatters>();
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final Map<MeasureUnit, Integer> hmsTo012 =
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            new HashMap<MeasureUnit, Integer>();
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static {
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        hmsTo012.put(MeasureUnit.HOUR, 0);
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        hmsTo012.put(MeasureUnit.MINUTE, 1);
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        hmsTo012.put(MeasureUnit.SECOND, 2);
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // For serialization: sub-class types.
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int MEASURE_FORMAT = 0;
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int TIME_UNIT_FORMAT = 1;
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int CURRENCY_FORMAT = 2;
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formatting width enum.
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
159f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Be sure to update MeasureUnitTest.TestSerialFormatWidthEnum
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // when adding an enum value.
1637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public enum FormatWidth {
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
1667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Spell out everything.
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
168f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         * @stable ICU 53
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        WIDE("units", ListFormatter.Style.DURATION, NumberFormat.PLURALCURRENCYSTYLE),
1717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
1737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Abbreviate when possible.
1747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
175f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         * @stable ICU 53
1767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
1777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        SHORT("unitsShort", ListFormatter.Style.DURATION_SHORT, NumberFormat.ISOCURRENCYSTYLE),
1787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
1807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Brief. Use only a symbol for the unit when possible.
1817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
182f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         * @stable ICU 53
1837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
1847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        NARROW("unitsNarrow", ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE),
1857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
1877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Identical to NARROW except when formatMeasures is called with
1887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * an hour and minute; minute and second; or hour, minute, and second Measures.
1897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * In these cases formatMeasures formats as 5:37:23 instead of 5h, 37m, 23s.
1907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
191f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         * @stable ICU 53
1927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
1937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        NUMERIC("unitsNarrow", ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE);
1947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Be sure to update the toFormatWidth and fromFormatWidth() functions
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // when adding an enum value.
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final String resourceKey;
1997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private final ListFormatter.Style listFormatterStyle;
2007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private final int currencyStyle;
2017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private FormatWidth(String resourceKey, ListFormatter.Style style, int currencyStyle) {
2037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.resourceKey = resourceKey;
2047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.listFormatterStyle = style;
2057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.currencyStyle = currencyStyle;
2067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ListFormatter.Style getListFormatterStyle() {
2097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return listFormatterStyle;
2107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int getCurrencyStyle() {
2137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return currencyStyle;
2147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Create a format from the locale, formatWidth, and format.
2197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
2207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the locale.
2217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param formatWidth hints how long formatted strings should be.
2227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The new MeasureFormat object.
223f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
2247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static MeasureFormat getInstance(ULocale locale, FormatWidth formatWidth) {
2267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getInstance(locale, formatWidth, NumberFormat.getInstance(locale));
2277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Create a format from the JDK locale, formatWidth, and format.
2317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
2327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the JDK locale.
2337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param formatWidth hints how long formatted strings should be.
2347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The new MeasureFormat object.
2357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 54
2367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
2377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static MeasureFormat getInstance(Locale locale, FormatWidth formatWidth) {
2397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getInstance(ULocale.forLocale(locale), formatWidth);
2407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Create a format from the locale, formatWidth, and format.
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the locale.
2467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param formatWidth hints how long formatted strings should be.
2477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param format This is defensively copied.
2487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The new MeasureFormat object.
249f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
2507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static MeasureFormat getInstance(ULocale locale, FormatWidth formatWidth, NumberFormat format) {
2527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules rules = PluralRules.forLocale(locale);
2537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        NumericFormatters formatters = null;
2547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MeasureFormatData data = localeMeasureFormatData.get(locale);
2557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (data == null) {
2567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            data = loadLocaleData(locale);
2577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            localeMeasureFormatData.put(locale, data);
2587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (formatWidth == FormatWidth.NUMERIC) {
2607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            formatters = localeToNumericDurationFormatters.get(locale);
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (formatters == null) {
2627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                formatters = loadNumericFormatters(locale);
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                localeToNumericDurationFormatters.put(locale, formatters);
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        NumberFormat intFormat = NumberFormat.getInstance(locale);
2677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        intFormat.setMaximumFractionDigits(0);
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        intFormat.setMinimumFractionDigits(0);
2697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        intFormat.setRoundingMode(BigDecimal.ROUND_DOWN);
2707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new MeasureFormat(
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                locale,
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                formatWidth,
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new ImmutableNumberFormat(format),
2747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                rules,
2757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                data.unitToStyleToCountToFormat,
2767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                formatters,
2777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new ImmutableNumberFormat(NumberFormat.getInstance(locale, formatWidth.getCurrencyStyle())),
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new ImmutableNumberFormat(intFormat),
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                data.unitToStyleToPerUnitPattern,
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                data.styleToPerPattern);
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Create a format from the JDK locale, formatWidth, and format.
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the JDK locale.
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param formatWidth hints how long formatted strings should be.
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param format This is defensively copied.
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The new MeasureFormat object.
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 54
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
2927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static MeasureFormat getInstance(Locale locale, FormatWidth formatWidth, NumberFormat format) {
2947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getInstance(ULocale.forLocale(locale), formatWidth, format);
2957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Able to format Collection&lt;? extends Measure&gt;, Measure[], and Measure
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * by delegating to formatMeasures.
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If the pos argument identifies a NumberFormat field,
3017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * then its indices are set to the beginning and end of the first such field
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * encountered. MeasureFormat itself does not supply any fields.
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Calling a
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>formatMeasures</code> method is preferred over calling
3067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * this method as they give better performance.
3077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param obj must be a Collection<? extends Measure>, Measure[], or Measure object.
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param toAppendTo Formatted string appended here.
3107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pos Identifies a field in the formatted text.
3117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
3127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
313f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU53
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
3167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int prevLength = toAppendTo.length();
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        FieldPosition fpos =
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new FieldPosition(pos.getFieldAttribute(), pos.getField());
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (obj instanceof Collection) {
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Collection<?> coll = (Collection<?>) obj;
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Measure[] measures = new Measure[coll.size()];
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int idx = 0;
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (Object o : coll) {
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (!(o instanceof Measure)) {
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    throw new IllegalArgumentException(obj.toString());
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                measures[idx++] = (Measure) o;
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            toAppendTo.append(formatMeasures(new StringBuilder(), fpos, measures));
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else if (obj instanceof Measure[]) {
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            toAppendTo.append(formatMeasures(new StringBuilder(), fpos, (Measure[]) obj));
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else if (obj instanceof Measure){
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            toAppendTo.append(formatMeasure((Measure) obj, numberFormat, new StringBuilder(), fpos));
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
3367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException(obj.toString());
3377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
3397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            pos.setBeginIndex(fpos.getBeginIndex() + prevLength);
3407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            pos.setEndIndex(fpos.getEndIndex() + prevLength);
3417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return toAppendTo;
3437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Parses text from a string to produce a <code>Measure</code>.
3477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
3487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws UnsupportedOperationException Not supported.
349f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @draft ICU 53 (Retain)
3507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
3517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
3537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Measure parseObject(String source, ParsePosition pos) {
3547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        throw new UnsupportedOperationException();
3557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Format a sequence of measures. Uses the ListFormatter unit lists.
3597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * So, for example, one could format “3 feet, 2 inches”.
3607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Zero values are formatted (eg, “3 feet, 0 inches”). It is the caller’s
3617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * responsibility to have the appropriate values in appropriate order,
3627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and using the appropriate Number values. Typically the units should be
3637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * in descending order, with all but the last Measure having integer values
3647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * (eg, not “3.2 feet, 2 inches”).
3657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param measures a sequence of one or more measures.
3677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the formatted string.
368f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
3697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public final String formatMeasures(Measure... measures) {
3717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return formatMeasures(
3727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new StringBuilder(),
3737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                DontCareFieldPosition.INSTANCE,
3747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                measures).toString();
3757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Format a range of measures, such as "3.4-5.1 meters". It is the caller’s
3797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * responsibility to have the appropriate values in appropriate order,
3807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and using the appropriate Number values.
3817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <br>Note: If the format doesn’t have enough decimals, or lowValue ≥ highValue,
3827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the result will be a degenerate range, like “5-5 meters”.
3837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <br>Currency Units are not yet supported.
3847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param lowValue low value in range
3867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param highValue high value in range
3877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the formatted string.
3887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @internal
3897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @deprecated This API is ICU internal only.
3907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Deprecated
3927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public final String formatMeasureRange(Measure lowValue, Measure highValue) {
3937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MeasureUnit unit = lowValue.getUnit();
3947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!unit.equals(highValue.getUnit())) {
3957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException("Units must match: " + unit + " ≠ " + highValue.getUnit());
3967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Number lowNumber = lowValue.getNumber();
3987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Number highNumber = highValue.getNumber();
3997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final boolean isCurrency = unit instanceof Currency;
4007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        UFieldPosition lowFpos = new UFieldPosition();
4027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        UFieldPosition highFpos = new UFieldPosition();
4037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuffer lowFormatted = null;
4047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuffer highFormatted = null;
4057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (isCurrency) {
4077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Currency currency = (Currency) unit;
4087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int fracDigits = currency.getDefaultFractionDigits();
4097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int maxFrac = numberFormat.nf.getMaximumFractionDigits();
4107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int minFrac = numberFormat.nf.getMinimumFractionDigits();
4117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (fracDigits != maxFrac || fracDigits != minFrac) {
4127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                DecimalFormat currentNumberFormat = (DecimalFormat) numberFormat.get();
4137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                currentNumberFormat.setMaximumFractionDigits(fracDigits);
4147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                currentNumberFormat.setMinimumFractionDigits(fracDigits);
4157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                lowFormatted = currentNumberFormat.format(lowNumber, new StringBuffer(), lowFpos);
4167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                highFormatted = currentNumberFormat.format(highNumber, new StringBuffer(), highFpos);
4177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (lowFormatted == null) {
4207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            lowFormatted = numberFormat.format(lowNumber, new StringBuffer(), lowFpos);
4217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            highFormatted = numberFormat.format(highNumber, new StringBuffer(), highFpos);
4227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final double lowDouble = lowNumber.doubleValue();
4257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String keywordLow = rules.select(new PluralRules.FixedDecimal(lowDouble,
4267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                lowFpos.getCountVisibleFractionDigits(), lowFpos.getFractionDigits()));
4277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final double highDouble = highNumber.doubleValue();
4297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String keywordHigh = rules.select(new PluralRules.FixedDecimal(highDouble,
4307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                highFpos.getCountVisibleFractionDigits(), highFpos.getFractionDigits()));
4317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final PluralRanges pluralRanges = Factory.getDefaultFactory().getPluralRanges(getLocale());
4337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StandardPluralCategories resolvedCategory = pluralRanges.get(
4347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                StandardPluralCategories.valueOf(keywordLow), StandardPluralCategories.valueOf(keywordHigh));
4357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        SimplePatternFormatter rangeFormatter = getRangeFormat(getLocale(), formatWidth);
4377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String formattedNumber = rangeFormatter.format(lowFormatted, highFormatted);
4387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (isCurrency) {
4407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Nasty hack
4417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            currencyFormat.format(1d); // have to call this for the side effect
4427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Currency currencyUnit = (Currency) unit;
4447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringBuilder result = new StringBuilder();
4457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendReplacingCurrency(currencyFormat.getPrefix(lowDouble >= 0), currencyUnit, resolvedCategory, result);
4467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result.append(formattedNumber);
4477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendReplacingCurrency(currencyFormat.getSuffix(highDouble >= 0), currencyUnit, resolvedCategory, result);
4487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return result.toString();
4497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            //            StringBuffer buffer = new StringBuffer();
4507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            //            CurrencyAmount currencyLow = (CurrencyAmount) lowValue;
4517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            //            CurrencyAmount currencyHigh = (CurrencyAmount) highValue;
4527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            //            FieldPosition pos = new FieldPosition(NumberFormat.INTEGER_FIELD);
4537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            //            currencyFormat.format(currencyLow, buffer, pos);
4547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            //            int startOfInteger = pos.getBeginIndex();
4557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            //            StringBuffer buffer2 = new StringBuffer();
4567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            //            FieldPosition pos2 = new FieldPosition(0);
4577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            //            currencyFormat.format(currencyHigh, buffer2, pos2);
4587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
4597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(lowValue.getUnit());
4607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
4617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            SimplePatternFormatter formatter = countToFormat.getByVariant(resolvedCategory.toString());
4627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return formatter.format(formattedNumber);
4637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void appendReplacingCurrency(String affix, Currency unit, StandardPluralCategories resolvedCategory, StringBuilder result) {
4677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String replacement = "¤";
4687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int pos = affix.indexOf(replacement);
4697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (pos < 0) {
4707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            replacement = "XXX";
4717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            pos = affix.indexOf(replacement);
4727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (pos < 0) {
4747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result.append(affix);
4757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
4767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // for now, just assume single
4777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result.append(affix.substring(0,pos));
4787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // we have a mismatch between the number style and the currency style, so remap
4797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int currentStyle = formatWidth.getCurrencyStyle();
4807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (currentStyle == NumberFormat.ISOCURRENCYSTYLE) {
4817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result.append(unit.getCurrencyCode());
4827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
4837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result.append(unit.getName(currencyFormat.nf.getLocale(ULocale.ACTUAL_LOCALE),
4847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        currentStyle == NumberFormat.CURRENCYSTYLE ? Currency.SYMBOL_NAME :  Currency.PLURAL_LONG_NAME,
4857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                resolvedCategory.toString(), null));
4867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result.append(affix.substring(pos+replacement.length()));
4887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
492f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Formats a single measure per unit.
4937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
494f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * An example of such a formatted string is "3.5 meters per second."
495f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *
496f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param measure  the measure object. In above example, 3.5 meters.
497f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param perUnit  the per unit. In above example, it is MeasureUnit.SECOND
498f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param appendTo formatted string appended here.
499f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param pos      The field position.
5007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return appendTo.
501f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @draft ICU 55
502f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @provisional This API might change or be removed in a future release.
5037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
504f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public StringBuilder formatMeasurePerUnit(
505f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            Measure measure,
506f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            MeasureUnit perUnit,
507f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            StringBuilder appendTo,
508f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            FieldPosition pos) {
509f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        MeasureUnit resolvedUnit = MeasureUnit.resolveUnitPerUnit(
510f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                measure.getUnit(), perUnit);
511f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (resolvedUnit != null) {
512f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            Measure newMeasure = new Measure(measure.getNumber(), resolvedUnit);
513f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return formatMeasure(newMeasure, numberFormat, appendTo, pos);
514f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
5157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        FieldPosition fpos = new FieldPosition(
516f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                pos.getFieldAttribute(), pos.getField());
517f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        int offset = withPerUnitAndAppend(
518f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                formatMeasure(measure, numberFormat, new StringBuilder(), fpos),
5197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                perUnit,
5207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                appendTo);
5217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
522f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            pos.setBeginIndex(fpos.getBeginIndex() + offset);
523f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            pos.setEndIndex(fpos.getEndIndex() + offset);
5247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return appendTo;
5267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats a sequence of measures.
5307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
5317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If the fieldPosition argument identifies a NumberFormat field,
5327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * then its indices are set to the beginning and end of the first such field
5337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * encountered. MeasureFormat itself does not supply any fields.
5347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
5357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param appendTo the formatted string appended here.
5367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param fieldPosition Identifies a field in the formatted text.
5377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param measures the measures to format.
5387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return appendTo.
5397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see MeasureFormat#formatMeasures(Measure...)
540f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
5417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public StringBuilder formatMeasures(
5437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringBuilder appendTo, FieldPosition fieldPosition, Measure... measures) {
5447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // fast track for trivial cases
5457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (measures.length == 0) {
5467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return appendTo;
5477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (measures.length == 1) {
5497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return formatMeasure(measures[0], numberFormat, appendTo, fieldPosition);
5507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (formatWidth == FormatWidth.NUMERIC) {
5537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // If we have just hour, minute, or second follow the numeric
5547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // track.
5557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Number[] hms = toHMS(measures);
5567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (hms != null) {
5577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return formatNumeric(hms, appendTo);
5587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ListFormatter listFormatter = ListFormatter.getInstance(
5627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                getLocale(), formatWidth.getListFormatterStyle());
5637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fieldPosition != DontCareFieldPosition.INSTANCE) {
5647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return formatMeasuresSlowTrack(listFormatter, appendTo, fieldPosition, measures);
5657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Fast track: No field position.
5677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String[] results = new String[measures.length];
5687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < measures.length; i++) {
5697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            results[i] = formatMeasure(
5707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    measures[i],
5717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    i == measures.length - 1 ? numberFormat : integerFormat);
5727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return appendTo.append(listFormatter.format((Object[]) results));
5747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Two MeasureFormats, a and b, are equal if and only if they have the same formatWidth,
5797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * locale, and equal number formats.
580f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
5817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
5837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public final boolean equals(Object other) {
5847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (this == other) {
5857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return true;
5867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!(other instanceof MeasureFormat)) {
5887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return false;
5897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MeasureFormat rhs = (MeasureFormat) other;
5917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // A very slow but safe implementation.
5927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getWidth() == rhs.getWidth()
5937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                && getLocale().equals(rhs.getLocale())
5947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                && getNumberFormat().equals(rhs.getNumberFormat());
5957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
599f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
6007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
6027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public final int hashCode() {
6037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // A very slow but safe implementation.
6047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return (getLocale().hashCode() * 31
6057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                + getNumberFormat().hashCode()) * 31 + getWidth().hashCode();
6067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Get the format width this instance is using.
610f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
6117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public MeasureFormat.FormatWidth getWidth() {
6137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return formatWidth;
6147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Get the locale of this instance.
618f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
6197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public final ULocale getLocale() {
6217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getLocale(ULocale.VALID_LOCALE);
6227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Get a copy of the number format.
626f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @stable ICU 53
6277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public NumberFormat getNumberFormat() {
6297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return numberFormat.get();
6307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return a formatter for CurrencyAmount objects in the given
6347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * locale.
6357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale desired locale
6367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return a formatter object
6377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
6387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static MeasureFormat getCurrencyFormat(ULocale locale) {
6407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new CurrencyFormat(locale);
6417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return a formatter for CurrencyAmount objects in the given
6457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * JDK locale.
6467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale desired JDK locale
6477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return a formatter object
6487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 54
6497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
6507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static MeasureFormat getCurrencyFormat(Locale locale) {
6527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getCurrencyFormat(ULocale.forLocale(locale));
6537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return a formatter for CurrencyAmount objects in the default
6577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>FORMAT</code> locale.
6587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return a formatter object
6597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see Category#FORMAT
6607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
6617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static MeasureFormat getCurrencyFormat() {
6637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getCurrencyFormat(ULocale.getDefault(Category.FORMAT));
6647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // This method changes the NumberFormat object as well to match the new locale.
6677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    MeasureFormat withLocale(ULocale locale) {
6687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return MeasureFormat.getInstance(locale, getWidth());
6697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    MeasureFormat withNumberFormat(NumberFormat format) {
6727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new MeasureFormat(
6737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                getLocale(),
6747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                this.formatWidth,
6757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new ImmutableNumberFormat(format),
6767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                this.rules,
6777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                this.unitToStyleToCountToFormat,
6787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                this.numericFormatters,
6797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                this.currencyFormat,
6807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                this.integerFormat,
6817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                this.unitToStyleToPerUnitPattern,
6827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                this.styleToPerPattern);
6837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private MeasureFormat(
6867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ULocale locale,
6877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            FormatWidth formatWidth,
6887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ImmutableNumberFormat format,
6897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules rules,
6907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat,
6917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            NumericFormatters formatters,
6927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ImmutableNumberFormat currencyFormat,
6937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ImmutableNumberFormat integerFormat,
6947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern,
6957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern) {
6967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        setLocale(locale, locale);
6977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.formatWidth = formatWidth;
6987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.numberFormat = format;
6997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.rules = rules;
7007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.unitToStyleToCountToFormat = unitToStyleToCountToFormat;
7017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.numericFormatters = formatters;
7027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.currencyFormat = currencyFormat;
7037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.integerFormat = integerFormat;
7047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.unitToStyleToPerUnitPattern = unitToStyleToPerUnitPattern;
7057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.styleToPerPattern = styleToPerPattern;
7067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    MeasureFormat() {
7097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Make compiler happy by setting final fields to null.
7107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.formatWidth = null;
7117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.numberFormat = null;
7127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.rules = null;
7137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.unitToStyleToCountToFormat = null;
7147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.numericFormatters = null;
7157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.currencyFormat = null;
7167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.integerFormat = null;
7177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.unitToStyleToPerUnitPattern = null;
7187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.styleToPerPattern = null;
7197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static class NumericFormatters {
7227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private DateFormat hourMinute;
7237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private DateFormat minuteSecond;
7247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private DateFormat hourMinuteSecond;
7257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public NumericFormatters(
7277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                DateFormat hourMinute,
7287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                DateFormat minuteSecond,
7297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                DateFormat hourMinuteSecond) {
7307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.hourMinute = hourMinute;
7317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.minuteSecond = minuteSecond;
7327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.hourMinuteSecond = hourMinuteSecond;
7337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public DateFormat getHourMinute() { return hourMinute; }
7367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public DateFormat getMinuteSecond() { return minuteSecond; }
7377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public DateFormat getHourMinuteSecond() { return hourMinuteSecond; }
7387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static NumericFormatters loadNumericFormatters(
7417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ULocale locale) {
7427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
743f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
7447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new NumericFormatters(
7457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                loadNumericDurationFormat(r, "hm"),
7467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                loadNumericDurationFormat(r, "ms"),
7477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                loadNumericDurationFormat(r, "hms"));
7487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns formatting data for all MeasureUnits except for currency ones.
7527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
7537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static MeasureFormatData loadLocaleData(
7547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ULocale locale) {
7557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        QuantityFormatter.Builder builder = new QuantityFormatter.Builder();
7567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat
7577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        = new HashMap<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>();
7587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern
7597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        = new HashMap<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>>();
7607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
7617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern = new EnumMap<FormatWidth, SimplePatternFormatter>(FormatWidth.class);
7627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (FormatWidth styleItem : FormatWidth.values()) {
7637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
7647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ICUResourceBundle unitTypeRes = resource.getWithFallback(styleItem.resourceKey);
7657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ICUResourceBundle compoundRes = unitTypeRes.getWithFallback("compound");
7667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ICUResourceBundle perRes = compoundRes.getWithFallback("per");
7677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                styleToPerPattern.put(styleItem, SimplePatternFormatter.compile(perRes.getString()));
7687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch (MissingResourceException e) {
7697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // may not have compound/per for every width.
7707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                continue;
7717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        fillInStyleMap(styleToPerPattern);
7747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (MeasureUnit unit : MeasureUnit.getAvailable()) {
7757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Currency data cannot be found here. Skip.
7767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (unit instanceof Currency) {
7777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                continue;
7787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            EnumMap<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
7807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (styleToCountToFormat == null) {
7817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                unitToStyleToCountToFormat.put(unit, styleToCountToFormat = new EnumMap<FormatWidth, QuantityFormatter>(FormatWidth.class));
7827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            EnumMap<FormatWidth, SimplePatternFormatter> styleToPerUnitPattern = new EnumMap<FormatWidth, SimplePatternFormatter>(FormatWidth.class);
7847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            unitToStyleToPerUnitPattern.put(unit, styleToPerUnitPattern);
7857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (FormatWidth styleItem : FormatWidth.values()) {
7867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                try {
7877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    ICUResourceBundle unitTypeRes = resource.getWithFallback(styleItem.resourceKey);
7887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    ICUResourceBundle unitsRes = unitTypeRes.getWithFallback(unit.getType());
7897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(unit.getSubtype());
7907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    builder.reset();
7917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    boolean havePluralItem = false;
7927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    int len = oneUnitRes.getSize();
7937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    for (int i = 0; i < len; i++) {
7947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        UResourceBundle countBundle;
7957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        try {
7967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            countBundle = oneUnitRes.get(i);
7977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        } catch (MissingResourceException e) {
7987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            continue;
7997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
8007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        String resKey = countBundle.getKey();
8017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (resKey.equals("dnam")) {
8027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            continue; // skip display name & per pattern (new in CLDR 26 / ICU 54) for now, not part of plurals
8037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
8047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (resKey.equals("per")) {
8057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            styleToPerUnitPattern.put(
8067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                    styleItem, SimplePatternFormatter.compile(countBundle.getString()));
8077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            continue;
8087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
8097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        havePluralItem = true;
8107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        builder.add(resKey, countBundle.getString());
8117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
8127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (havePluralItem) {
8137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // might not have any plural items if countBundle only has "dnam" display name, for instance,
8147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // as with fr unitsNarrow/light/lux in CLDR 26
8157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        styleToCountToFormat.put(styleItem, builder.build());
8167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
8177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } catch (MissingResourceException e) {
8187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    continue;
8197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
8207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
8217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // TODO: if no fallback available, get from root.
8227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fillInStyleMap(styleToCountToFormat);
8237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fillInStyleMap(styleToPerUnitPattern);
8247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new MeasureFormatData(unitToStyleToCountToFormat, unitToStyleToPerUnitPattern, styleToPerPattern);
8267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static <T> boolean fillInStyleMap(Map<FormatWidth, T> styleMap) {
8297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (styleMap.size() == FormatWidth.values().length) {
8307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return true;
8317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        T fallback = styleMap.get(FormatWidth.SHORT);
8337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fallback == null) {
8347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fallback = styleMap.get(FormatWidth.WIDE);
8357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fallback == null) {
8377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return false;
8387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (FormatWidth styleItem : FormatWidth.values()) {
8407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            T item = styleMap.get(styleItem);
8417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (item == null) {
8427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                styleMap.put(styleItem, fallback);
8437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
8447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return true;
8467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
848f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    private int withPerUnitAndAppend(
849f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            CharSequence formatted, MeasureUnit perUnit, StringBuilder appendTo) {
8507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int[] offsets = new int[1];
8517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Map<FormatWidth, SimplePatternFormatter> styleToPerUnitPattern =
8527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                unitToStyleToPerUnitPattern.get(perUnit);
8537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        SimplePatternFormatter perUnitPattern = styleToPerUnitPattern.get(formatWidth);
8547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (perUnitPattern != null) {
855f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            perUnitPattern.formatAndAppend(appendTo, offsets, formatted);
8567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return offsets[0];
8577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        SimplePatternFormatter perPattern = styleToPerPattern.get(formatWidth);
8597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(perUnit);
8607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
8617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String perUnitString = countToFormat.getByVariant("one").getPatternWithNoPlaceholders().trim();
862f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        perPattern.formatAndAppend(appendTo, offsets, formatted, perUnitString);
8637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return offsets[0];
8647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String formatMeasure(Measure measure, ImmutableNumberFormat nf) {
8677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return formatMeasure(
8687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                measure, nf, new StringBuilder(),
8697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                DontCareFieldPosition.INSTANCE).toString();
8707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private StringBuilder formatMeasure(
8737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Measure measure,
8747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ImmutableNumberFormat nf,
8757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringBuilder appendTo,
8767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            FieldPosition fieldPosition) {
8777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (measure.getUnit() instanceof Currency) {
8787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return appendTo.append(
8797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    currencyFormat.format(
8807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            new CurrencyAmount(measure.getNumber(), (Currency) measure.getUnit()),
8817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            new StringBuffer(),
8827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            fieldPosition));
8837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Number n = measure.getNumber();
8867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MeasureUnit unit = measure.getUnit();
8877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        UFieldPosition fpos = new UFieldPosition(fieldPosition.getFieldAttribute(), fieldPosition.getField());
8887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuffer formattedNumber = nf.format(n, new StringBuffer(), fpos);
8897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String keyword = rules.select(new PluralRules.FixedDecimal(n.doubleValue(), fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()));
8907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
8927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
8937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        SimplePatternFormatter formatter = countToFormat.getByVariant(keyword);
8947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int[] offsets = new int[1];
895f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        formatter.formatAndAppend(appendTo, offsets, formattedNumber);
8967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (offsets[0] != -1) { // there is a number (may not happen with, say, Arabic dual)
8977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Fix field position
8987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
8997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fieldPosition.setBeginIndex(fpos.getBeginIndex() + offsets[0]);
9007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fieldPosition.setEndIndex(fpos.getEndIndex() + offsets[0]);
9017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
9027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return appendTo;
9047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final class MeasureFormatData {
9077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MeasureFormatData(
9087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat,
9097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern,
9107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern) {
9117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.unitToStyleToCountToFormat = unitToStyleToCountToFormat;
9127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.unitToStyleToPerUnitPattern = unitToStyleToPerUnitPattern;
9137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.styleToPerPattern = styleToPerPattern;
9147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat;
9167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern;
9177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern;
9187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Wrapper around NumberFormat that provides immutability and thread-safety.
9217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final class ImmutableNumberFormat {
9227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private NumberFormat nf;
9237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public ImmutableNumberFormat(NumberFormat nf) {
9257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.nf = (NumberFormat) nf.clone();
9267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public synchronized NumberFormat get() {
9297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return (NumberFormat) nf.clone();
9307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public synchronized StringBuffer format(
9337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Number n, StringBuffer buffer, FieldPosition pos) {
9347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return nf.format(n, buffer, pos);
9357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public synchronized StringBuffer format(
9387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                CurrencyAmount n, StringBuffer buffer, FieldPosition pos) {
9397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return nf.format(n, buffer, pos);
9407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        @SuppressWarnings("unused")
9437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public synchronized String format(Number number) {
9447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return nf.format(number);
9457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public String getPrefix(boolean positive) {
9487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return positive ? ((DecimalFormat)nf).getPositivePrefix() : ((DecimalFormat)nf).getNegativePrefix();
9497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public String getSuffix(boolean positive) {
9517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return positive ? ((DecimalFormat)nf).getPositiveSuffix() : ((DecimalFormat)nf).getPositiveSuffix();
9527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static final class PatternData {
9567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final String prefix;
9577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final String suffix;
9587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public PatternData(String pattern) {
9597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int pos = pattern.indexOf("{0}");
9607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (pos < 0) {
9617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                prefix = pattern;
9627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                suffix = null;
9637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
9647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                prefix = pattern.substring(0,pos);
9657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                suffix = pattern.substring(pos+3);
9667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
9677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public String toString() {
9697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return prefix + "; " + suffix;
9707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    Object toTimeUnitProxy() {
9757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), TIME_UNIT_FORMAT);
9767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    Object toCurrencyProxy() {
9797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), CURRENCY_FORMAT);
9807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private StringBuilder formatMeasuresSlowTrack(
9837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ListFormatter listFormatter,
9847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringBuilder appendTo,
9857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            FieldPosition fieldPosition,
9867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Measure... measures) {
9877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String[] results = new String[measures.length];
9887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Zero out our field position so that we can tell when we find our field.
9907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        FieldPosition fpos = new FieldPosition(
9917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fieldPosition.getFieldAttribute(), fieldPosition.getField());
9927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int fieldPositionFoundIndex = -1;
9947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < measures.length; ++i) {
9957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ImmutableNumberFormat nf = (i == measures.length - 1 ? numberFormat : integerFormat);
9967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (fieldPositionFoundIndex == -1) {
9977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                results[i] = formatMeasure(measures[i], nf, new StringBuilder(), fpos).toString();
9987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
9997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    fieldPositionFoundIndex = i;
10007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
10017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
10027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                results[i] = formatMeasure(measures[i], nf);
10037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
10047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ListFormatter.FormattedListBuilder builder =
10067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                listFormatter.format(Arrays.asList(results), fieldPositionFoundIndex);
10077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Fix up FieldPosition indexes if our field is found.
10097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (builder.getOffset() != -1) {
10107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fieldPosition.setBeginIndex(fpos.getBeginIndex() + builder.getOffset() + appendTo.length());
10117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fieldPosition.setEndIndex(fpos.getEndIndex() + builder.getOffset() + appendTo.length());
10127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return appendTo.append(builder.toString());
10147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // type is one of "hm", "ms" or "hms"
10177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static DateFormat loadNumericDurationFormat(
10187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ICUResourceBundle r, String type) {
10197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        r = r.getWithFallback(String.format("durationUnits/%s", type));
10207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // We replace 'h' with 'H' because 'h' does not make sense in the context of durations.
10217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        DateFormat result = new SimpleDateFormat(r.getString().replace("h", "H"));
10227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        result.setTimeZone(TimeZone.GMT_ZONE);
10237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
10247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Returns hours in [0]; minutes in [1]; seconds in [2] out of measures array. If
10277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // unsuccessful, e.g measures has other measurements besides hours, minutes, seconds;
10287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // hours, minutes, seconds are out of order; or have negative values, returns null.
10297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // If hours, minutes, or seconds is missing from measures the corresponding element in
10307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // returned array will be null.
10317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static Number[] toHMS(Measure[] measures) {
10327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Number[] result = new Number[3];
10337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int lastIdx = -1;
10347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (Measure m : measures) {
10357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (m.getNumber().doubleValue() < 0.0) {
10367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return null;
10377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
10387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Integer idxObj = hmsTo012.get(m.getUnit());
10397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (idxObj == null) {
10407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return null;
10417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
10427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int idx = idxObj.intValue();
10437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (idx <= lastIdx) {
10447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // hour before minute before second
10457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return null;
10467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
10477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            lastIdx = idx;
10487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result[idx] = m.getNumber();
10497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
10517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Formats numeric time duration as 5:00:47 or 3:54. In the process, it replaces any null
10547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // values in hms with 0.
10557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private StringBuilder formatNumeric(Number[] hms, StringBuilder appendable) {
10567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // find the start and end of non-nil values in hms array. We have to know if we
10587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // have hour-minute; minute-second; or hour-minute-second.
10597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int startIndex = -1;
10607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int endIndex = -1;
10617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < hms.length; i++) {
10627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (hms[i] != null) {
10637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                endIndex = i;
10647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (startIndex == -1) {
10657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    startIndex = endIndex;
10667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
10677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
10687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Replace nil value with 0.
10697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                hms[i] = Integer.valueOf(0);
10707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
10717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // convert hours, minutes, seconds into milliseconds.
10737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        long millis = (long) (((Math.floor(hms[0].doubleValue()) * 60.0
10747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                + Math.floor(hms[1].doubleValue())) * 60.0
10757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                + Math.floor(hms[2].doubleValue())) * 1000.0);
10767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Date d = new Date(millis);
10777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // if hour-minute-second
10787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (startIndex == 0 && endIndex == 2) {
10797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return formatNumeric(
10807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    d,
10817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    numericFormatters.getHourMinuteSecond(),
10827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    DateFormat.Field.SECOND,
10837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    hms[endIndex],
10847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    appendable);
10857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // if minute-second
10877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (startIndex == 1 && endIndex == 2) {
10887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return formatNumeric(
10897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    d,
10907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    numericFormatters.getMinuteSecond(),
10917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    DateFormat.Field.SECOND,
10927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    hms[endIndex],
10937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    appendable);
10947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // if hour-minute
10967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (startIndex == 0 && endIndex == 1) {
10977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return formatNumeric(
10987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    d,
10997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    numericFormatters.getHourMinute(),
11007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    DateFormat.Field.MINUTE,
11017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    hms[endIndex],
11027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    appendable);
11037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        throw new IllegalStateException();
11057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
11067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Formats a duration as 5:00:37 or 23:59.
11087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // duration is a particular duration after epoch.
11097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // formatter is a hour-minute-second, hour-minute, or minute-second formatter.
11107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // smallestField denotes what the smallest field is in duration: either
11117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // hour, minute, or second.
11127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // smallestAmount is the value of that smallest field. for 5:00:37.3,
11137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // smallestAmount is 37.3. This smallest field is formatted with this object's
11147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // NumberFormat instead of formatter.
11157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // appendTo is where the formatted string is appended.
11167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private StringBuilder formatNumeric(
11177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Date duration,
11187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            DateFormat formatter,
11197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            DateFormat.Field smallestField,
11207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Number smallestAmount,
11217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringBuilder appendTo) {
11227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Format the smallest amount ahead of time.
11237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String smallestAmountFormatted;
11247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Format the smallest amount using this object's number format, but keep track
11267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // of the integer portion of this formatted amount. We have to replace just the
11277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // integer part with the corresponding value from formatting the date. Otherwise
11287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // when formatting 0 minutes 9 seconds, we may get "00:9" instead of "00:09"
11297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        FieldPosition intFieldPosition = new FieldPosition(NumberFormat.INTEGER_FIELD);
11307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        smallestAmountFormatted = numberFormat.format(
11317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                smallestAmount, new StringBuffer(), intFieldPosition).toString();
11327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Give up if there is no integer field.
11337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (intFieldPosition.getBeginIndex() == 0 && intFieldPosition.getEndIndex() == 0) {
11347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalStateException();
11357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Format our duration as a date, but keep track of where the smallest field is
11377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // so that we can use it to replace the integer portion of the smallest value.
11387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        FieldPosition smallestFieldPosition = new FieldPosition(smallestField);
11397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String draft = formatter.format(
11407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                duration, new StringBuffer(), smallestFieldPosition).toString();
11417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // If we find the smallest field
11437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (smallestFieldPosition.getBeginIndex() != 0
11447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                || smallestFieldPosition.getEndIndex() != 0) {
11457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // add everything up to the start of the smallest field in duration.
11467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(draft, 0, smallestFieldPosition.getBeginIndex());
11477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // add everything in the smallest field up to the integer portion
11497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(smallestAmountFormatted, 0, intFieldPosition.getBeginIndex());
11507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Add the smallest field in formatted duration in lieu of the integer portion
11527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // of smallest field
11537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(
11547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    draft,
11557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    smallestFieldPosition.getBeginIndex(),
11567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    smallestFieldPosition.getEndIndex());
11577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Add the rest of the smallest field
11597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(
11607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    smallestAmountFormatted,
11617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    intFieldPosition.getEndIndex(),
11627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    smallestAmountFormatted.length());
11637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(draft, smallestFieldPosition.getEndIndex(), draft.length());
11647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
11657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // As fallback, just use the formatted duration.
11667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(draft);
11677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return appendTo;
11697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
11707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private Object writeReplace() throws ObjectStreamException {
11727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new MeasureProxy(
11737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                getLocale(), formatWidth, numberFormat.get(), MEASURE_FORMAT);
11747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
11757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static class MeasureProxy implements Externalizable {
11777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private static final long serialVersionUID = -6033308329886716770L;
11787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private ULocale locale;
11807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private FormatWidth formatWidth;
11817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private NumberFormat numberFormat;
11827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int subClass;
11837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private HashMap<Object, Object> keyValues;
11847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public MeasureProxy(
11867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ULocale locale,
11877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                FormatWidth width,
11887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                NumberFormat numberFormat,
11897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int subClass) {
11907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.locale = locale;
11917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.formatWidth = width;
11927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.numberFormat = numberFormat;
11937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.subClass = subClass;
11947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.keyValues = new HashMap<Object, Object>();
11957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Must have public constructor, to enable Externalizable
11987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public MeasureProxy() {
11997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
12007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void writeExternal(ObjectOutput out) throws IOException {
12027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            out.writeByte(0); // version
12037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            out.writeUTF(locale.toLanguageTag());
12047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            out.writeByte(formatWidth.ordinal());
12057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            out.writeObject(numberFormat);
12067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            out.writeByte(subClass);
12077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            out.writeObject(keyValues);
12087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
12097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        @SuppressWarnings("unchecked")
12117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
12127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            in.readByte(); // version.
12137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            locale = ULocale.forLanguageTag(in.readUTF());
12147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            formatWidth = fromFormatWidthOrdinal(in.readByte() & 0xFF);
12157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            numberFormat = (NumberFormat) in.readObject();
12167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (numberFormat == null) {
12177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new InvalidObjectException("Missing number format.");
12187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
12197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            subClass = in.readByte() & 0xFF;
12207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // This cast is safe because the serialized form of hashtable can have
12227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // any object as the key and any object as the value.
12237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            keyValues = (HashMap<Object, Object>) in.readObject();
12247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (keyValues == null) {
12257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new InvalidObjectException("Missing optional values map.");
12267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
12277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
12287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private TimeUnitFormat createTimeUnitFormat() throws InvalidObjectException {
12307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int style;
12317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (formatWidth == FormatWidth.WIDE) {
12327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                style = TimeUnitFormat.FULL_NAME;
12337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if (formatWidth == FormatWidth.SHORT) {
12347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                style = TimeUnitFormat.ABBREVIATED_NAME;
12357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
12367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new InvalidObjectException("Bad width: " + formatWidth);
12377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
12387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            TimeUnitFormat result = new TimeUnitFormat(locale, style);
12397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result.setNumberFormat(numberFormat);
12407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return result;
12417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
12427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private Object readResolve() throws ObjectStreamException {
12447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            switch (subClass) {
12457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case MEASURE_FORMAT:
12467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return MeasureFormat.getInstance(locale, formatWidth, numberFormat);
12477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case TIME_UNIT_FORMAT:
12487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return createTimeUnitFormat();
12497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case CURRENCY_FORMAT:
12507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return new CurrencyFormat(locale);
12517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            default:
12527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new InvalidObjectException("Unknown subclass: " + subClass);
12537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
12547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
12557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
12567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static FormatWidth fromFormatWidthOrdinal(int ordinal) {
12587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        FormatWidth[] values = FormatWidth.values();
12597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (ordinal < 0 || ordinal >= values.length) {
12607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return FormatWidth.WIDE;
12617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
12627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return values[ordinal];
12637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
12647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static final Map<ULocale, SimplePatternFormatter> localeIdToRangeFormat
12667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    = new ConcurrentHashMap<ULocale, SimplePatternFormatter>();
12677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
12697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return a simple pattern formatter for a range, such as "{0}–{1}".
12707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param forLocale locale to get the format for
12717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param width the format width
12727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return range formatter, such as "{0}–{1}"
12737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @internal
12747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @deprecated This API is ICU internal only.
12757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
12767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Deprecated
12777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static SimplePatternFormatter getRangeFormat(ULocale forLocale, FormatWidth width) {
12797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // TODO fix Hack for French
12807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (forLocale.getLanguage().equals("fr")) {
12817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return getRangeFormat(ULocale.ROOT, width);
12827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
12837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        SimplePatternFormatter result = localeIdToRangeFormat.get(forLocale);
12847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (result == null) {
12857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.
12867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    getBundleInstance(ICUData.ICU_BASE_NAME, forLocale);
12877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ULocale realLocale = rb.getULocale();
12887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!forLocale.equals(realLocale)) { // if the child would inherit, then add a cache entry for it.
12897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result = localeIdToRangeFormat.get(forLocale);
12907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (result != null) {
12917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    localeIdToRangeFormat.put(forLocale, result);
12927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return result;
12937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
12947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
12957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // At this point, both the forLocale and the realLocale don't have an item
12967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // So we have to make one.
12977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            NumberingSystem ns = NumberingSystem.getInstance(forLocale);
12987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String resultString = null;
13007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
13017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                resultString = rb.getStringWithFallback("NumberElements/" + ns.getName() + "/miscPatterns/range");
13027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch ( MissingResourceException ex ) {
13037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                resultString = rb.getStringWithFallback("NumberElements/latn/patterns/range");
13047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
13057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result = SimplePatternFormatter.compile(resultString);
13067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            localeIdToRangeFormat.put(forLocale, result);
13077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!forLocale.equals(realLocale)) {
13087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                localeIdToRangeFormat.put(realLocale, result);
13097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
13107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
13117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
13127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
13137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
13147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
13157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return a simple pattern pattern for a range, such as "{0}–{1}" or "{0}~{1}".
13167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param forLocale locale to get the range pattern for
13177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param width the format width.
13187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return range pattern
13197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @internal
13207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @deprecated This API is ICU internal only.
13217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
13227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Deprecated
13237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static String getRangePattern(ULocale forLocale, FormatWidth width) {
13247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getRangeFormat(forLocale, width).toString();
13257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
13267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
1327