• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5  **********************************************************************
6  * Copyright (c) 2004-2016, International Business Machines
7  * Corporation and others.  All Rights Reserved.
8  **********************************************************************
9  * Author: Alan Liu
10  * Created: April 20, 2004
11  * Since: ICU 3.0
12  **********************************************************************
13  */
14 package ohos.global.icu.text;
15 
16 import java.io.Externalizable;
17 import java.io.IOException;
18 import java.io.InvalidObjectException;
19 import java.io.ObjectInput;
20 import java.io.ObjectOutput;
21 import java.io.ObjectStreamException;
22 import java.math.RoundingMode;
23 import java.text.FieldPosition;
24 import java.text.ParsePosition;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.MissingResourceException;
31 import java.util.concurrent.ConcurrentHashMap;
32 
33 import ohos.global.icu.impl.DontCareFieldPosition;
34 import ohos.global.icu.impl.FormattedStringBuilder;
35 import ohos.global.icu.impl.FormattedValueStringBuilderImpl;
36 import ohos.global.icu.impl.ICUData;
37 import ohos.global.icu.impl.ICUResourceBundle;
38 import ohos.global.icu.impl.SimpleCache;
39 import ohos.global.icu.impl.SimpleFormatterImpl;
40 import ohos.global.icu.impl.Utility;
41 import ohos.global.icu.impl.number.DecimalQuantity;
42 import ohos.global.icu.impl.number.DecimalQuantity_DualStorageBCD;
43 import ohos.global.icu.impl.number.LongNameHandler;
44 import ohos.global.icu.impl.number.RoundingUtils;
45 import ohos.global.icu.number.IntegerWidth;
46 import ohos.global.icu.number.LocalizedNumberFormatter;
47 import ohos.global.icu.number.NumberFormatter;
48 import ohos.global.icu.number.NumberFormatter.UnitWidth;
49 import ohos.global.icu.number.Precision;
50 import ohos.global.icu.text.ListFormatter.FormattedListBuilder;
51 import ohos.global.icu.util.Currency;
52 import ohos.global.icu.util.ICUUncheckedIOException;
53 import ohos.global.icu.util.Measure;
54 import ohos.global.icu.util.MeasureUnit;
55 import ohos.global.icu.util.ULocale;
56 import ohos.global.icu.util.ULocale.Category;
57 import ohos.global.icu.util.UResourceBundle;
58 
59 // If you update the examples in the doc, don't forget to update MesaureUnitTest.TestExamplesInDocs too.
60 /**
61  * A formatter for Measure objects.
62  *
63  * <p>
64  * <strong>IMPORTANT:</strong> New users are strongly encouraged to see if
65  * {@link NumberFormatter} fits their use case.  Although not deprecated, this
66  * class, MeasureFormat, is provided for backwards compatibility only.
67  * <hr>
68  *
69  * <p>
70  * To format a Measure object, first create a formatter object using a MeasureFormat factory method. Then
71  * use that object's format or formatMeasures methods.
72  *
73  * Here is sample code:
74  *
75  * <pre>
76  * MeasureFormat fmtFr = MeasureFormat.getInstance(ULocale.FRENCH, FormatWidth.SHORT);
77  * Measure measure = new Measure(23, MeasureUnit.CELSIUS);
78  *
79  * // Output: 23 °C
80  * System.out.println(fmtFr.format(measure));
81  *
82  * Measure measureF = new Measure(70, MeasureUnit.FAHRENHEIT);
83  *
84  * // Output: 70 °F
85  * System.out.println(fmtFr.format(measureF));
86  *
87  * MeasureFormat fmtFrFull = MeasureFormat.getInstance(ULocale.FRENCH, FormatWidth.WIDE);
88  * // Output: 70 pieds et 5,3 pouces
89  * System.out.println(fmtFrFull.formatMeasures(new Measure(70, MeasureUnit.FOOT),
90  *         new Measure(5.3, MeasureUnit.INCH)));
91  *
92  * // Output: 1 pied et 1 pouce
93  * System.out.println(
94  *         fmtFrFull.formatMeasures(new Measure(1, MeasureUnit.FOOT), new Measure(1, MeasureUnit.INCH)));
95  *
96  * MeasureFormat fmtFrNarrow = MeasureFormat.getInstance(ULocale.FRENCH, FormatWidth.NARROW);
97  * // Output: 1′ 1″
98  * System.out.println(fmtFrNarrow.formatMeasures(new Measure(1, MeasureUnit.FOOT),
99  *         new Measure(1, MeasureUnit.INCH)));
100  *
101  * MeasureFormat fmtEn = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE);
102  *
103  * // Output: 1 inch, 2 feet
104  * fmtEn.formatMeasures(new Measure(1, MeasureUnit.INCH), new Measure(2, MeasureUnit.FOOT));
105  * </pre>
106  * <p>
107  * This class does not do conversions from one unit to another. It simply formats whatever units it is
108  * given
109  * <p>
110  * This class is immutable and thread-safe so long as its deprecated subclass, TimeUnitFormat, is never
111  * used. TimeUnitFormat is not thread-safe, and is mutable. Although this class has existing subclasses,
112  * this class does not support new sub-classes.
113  *
114  * @see ohos.global.icu.text.UFormat
115  * @author Alan Liu
116  */
117 public class MeasureFormat extends UFormat {
118 
119     // Generated by serialver from JDK 1.4.1_01
120     static final long serialVersionUID = -7182021401701778240L;
121 
122     private final transient FormatWidth formatWidth;
123 
124     // PluralRules is documented as being immutable which implies thread-safety.
125     private final transient PluralRules rules;
126 
127     private final transient NumericFormatters numericFormatters;
128 
129     private final transient NumberFormat numberFormat;
130 
131     private final transient LocalizedNumberFormatter numberFormatter;
132 
133     private static final SimpleCache<ULocale, NumericFormatters> localeToNumericDurationFormatters = new SimpleCache<>();
134 
135     private static final Map<MeasureUnit, Integer> hmsTo012 = new HashMap<>();
136 
137     static {
hmsTo012.put(MeasureUnit.HOUR, 0)138         hmsTo012.put(MeasureUnit.HOUR, 0);
hmsTo012.put(MeasureUnit.MINUTE, 1)139         hmsTo012.put(MeasureUnit.MINUTE, 1);
hmsTo012.put(MeasureUnit.SECOND, 2)140         hmsTo012.put(MeasureUnit.SECOND, 2);
141     }
142 
143     // For serialization: sub-class types.
144     private static final int MEASURE_FORMAT = 0;
145     private static final int TIME_UNIT_FORMAT = 1;
146     private static final int CURRENCY_FORMAT = 2;
147 
148     /**
149      * Formatting width enum.
150      */
151     // Be sure to update MeasureUnitTest.TestSerialFormatWidthEnum
152     // when adding an enum value.
153     public enum FormatWidth {
154 
155         /**
156          * Spell out everything.
157          */
158         WIDE(ListFormatter.Style.UNIT, UnitWidth.FULL_NAME, UnitWidth.FULL_NAME),
159 
160         /**
161          * Abbreviate when possible.
162          */
163         SHORT(ListFormatter.Style.UNIT_SHORT, UnitWidth.SHORT, UnitWidth.ISO_CODE),
164 
165         /**
166          * Brief. Use only a symbol for the unit when possible.
167          */
168         NARROW(ListFormatter.Style.UNIT_NARROW, UnitWidth.NARROW, UnitWidth.SHORT),
169 
170         /**
171          * Identical to NARROW except when formatMeasures is called with an hour and minute; minute and
172          * second; or hour, minute, and second Measures. In these cases formatMeasures formats as 5:37:23
173          * instead of 5h, 37m, 23s.
174          */
175         NUMERIC(ListFormatter.Style.UNIT_NARROW, UnitWidth.NARROW, UnitWidth.SHORT),
176 
177         /**
178          * The default format width for getCurrencyFormat(), which is to show the symbol for currency
179          * (UnitWidth.SHORT) but wide for other units.
180          *
181          * @deprecated ICU 61 This API is ICU internal only.
182          * @hide draft / provisional / internal are hidden on OHOS
183          */
184         @Deprecated
185         DEFAULT_CURRENCY(ListFormatter.Style.UNIT, UnitWidth.FULL_NAME, UnitWidth.SHORT);
186 
187         private final ListFormatter.Style listFormatterStyle;
188 
189         /**
190          * The {@link UnitWidth} (used for newer NumberFormatter API) that corresponds to this
191          * FormatWidth (used for the older APIs) for all units except currencies.
192          */
193         final UnitWidth unitWidth;
194 
195         /**
196          * The {@link UnitWidth} (used for newer NumberFormatter API) that corresponds to this
197          * FormatWidth (used for the older APIs) for currencies.
198          */
199         final UnitWidth currencyWidth;
200 
FormatWidth(ListFormatter.Style style, UnitWidth unitWidth, UnitWidth currencyWidth)201         private FormatWidth(ListFormatter.Style style, UnitWidth unitWidth, UnitWidth currencyWidth) {
202             this.listFormatterStyle = style;
203             this.unitWidth = unitWidth;
204             this.currencyWidth = currencyWidth;
205         }
206 
getListFormatterStyle()207         ListFormatter.Style getListFormatterStyle() {
208             return listFormatterStyle;
209         }
210     }
211 
212     /**
213      * Create a format from the locale, formatWidth, and format.
214      *
215      * @param locale
216      *            the locale.
217      * @param formatWidth
218      *            hints how long formatted strings should be.
219      * @return The new MeasureFormat object.
220      */
getInstance(ULocale locale, FormatWidth formatWidth)221     public static MeasureFormat getInstance(ULocale locale, FormatWidth formatWidth) {
222         return getInstance(locale, formatWidth, NumberFormat.getInstance(locale));
223     }
224 
225     /**
226      * Create a format from the {@link java.util.Locale} and formatWidth.
227      *
228      * @param locale
229      *            the {@link java.util.Locale}.
230      * @param formatWidth
231      *            hints how long formatted strings should be.
232      * @return The new MeasureFormat object.
233      */
getInstance(Locale locale, FormatWidth formatWidth)234     public static MeasureFormat getInstance(Locale locale, FormatWidth formatWidth) {
235         return getInstance(ULocale.forLocale(locale), formatWidth);
236     }
237 
238     /**
239      * Create a format from the locale, formatWidth, and format.
240      *
241      * @param locale
242      *            the locale.
243      * @param formatWidth
244      *            hints how long formatted strings should be.
245      * @param format
246      *            This is defensively copied.
247      * @return The new MeasureFormat object.
248      */
getInstance( ULocale locale, FormatWidth formatWidth, NumberFormat format)249     public static MeasureFormat getInstance(
250             ULocale locale,
251             FormatWidth formatWidth,
252             NumberFormat format) {
253         return new MeasureFormat(locale, formatWidth, format, null, null);
254     }
255 
256     /**
257      * Create a format from the {@link java.util.Locale}, formatWidth, and format.
258      *
259      * @param locale
260      *            the {@link java.util.Locale}.
261      * @param formatWidth
262      *            hints how long formatted strings should be.
263      * @param format
264      *            This is defensively copied.
265      * @return The new MeasureFormat object.
266      */
getInstance( Locale locale, FormatWidth formatWidth, NumberFormat format)267     public static MeasureFormat getInstance(
268             Locale locale,
269             FormatWidth formatWidth,
270             NumberFormat format) {
271         return getInstance(ULocale.forLocale(locale), formatWidth, format);
272     }
273 
274     /**
275      * Able to format Collection&lt;? extends Measure&gt;, Measure[], and Measure by delegating to
276      * formatMeasures. If the pos argument identifies a NumberFormat field, then its indices are set to
277      * the beginning and end of the first such field encountered. MeasureFormat itself does not supply
278      * any fields.
279      *
280      * Calling a <code>formatMeasures</code> method is preferred over calling this method as they give
281      * better performance.
282      *
283      * @param obj
284      *            must be a Collection&lt;? extends Measure&gt;, Measure[], or Measure object.
285      * @param toAppendTo
286      *            Formatted string appended here.
287      * @param fpos
288      *            Identifies a field in the formatted text.
289      * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
290      */
291     @Override
format(Object obj, StringBuffer toAppendTo, FieldPosition fpos)292     public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition fpos) {
293         int prevLength = toAppendTo.length();
294         fpos.setBeginIndex(0);
295         fpos.setEndIndex(0);
296         if (obj instanceof Collection) {
297             Collection<?> coll = (Collection<?>) obj;
298             Measure[] measures = new Measure[coll.size()];
299             int idx = 0;
300             for (Object o : coll) {
301                 if (!(o instanceof Measure)) {
302                     throw new IllegalArgumentException(obj.toString());
303                 }
304                 measures[idx++] = (Measure) o;
305             }
306             formatMeasuresInternal(toAppendTo, fpos, measures);
307         } else if (obj instanceof Measure[]) {
308             formatMeasuresInternal(toAppendTo, fpos, (Measure[]) obj);
309         } else if (obj instanceof Measure) {
310             FormattedStringBuilder result = formatMeasure((Measure) obj);
311             // No offset: toAppendTo.length() is considered below
312             FormattedValueStringBuilderImpl.nextFieldPosition(result, fpos);
313             Utility.appendTo(result, toAppendTo);
314         } else {
315             throw new IllegalArgumentException(obj.toString());
316         }
317         if (prevLength > 0 && fpos.getEndIndex() != 0) {
318             fpos.setBeginIndex(fpos.getBeginIndex() + prevLength);
319             fpos.setEndIndex(fpos.getEndIndex() + prevLength);
320         }
321         return toAppendTo;
322     }
323 
324     /**
325      * Parses text from a string to produce a <code>Measure</code>.
326      *
327      * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
328      * @throws UnsupportedOperationException
329      *             Not supported.
330      * @hide draft / provisional / internal are hidden on OHOS
331      */
332     @Override
parseObject(String source, ParsePosition pos)333     public Measure parseObject(String source, ParsePosition pos) {
334         throw new UnsupportedOperationException();
335     }
336 
337     /**
338      * Format a sequence of measures. Uses the ListFormatter unit lists. So, for example, one could
339      * format “3 feet, 2 inches”. Zero values are formatted (eg, “3 feet, 0 inches”). It is the caller’s
340      * responsibility to have the appropriate values in appropriate order, and using the appropriate
341      * Number values. Typically the units should be in descending order, with all but the last Measure
342      * having integer values (eg, not “3.2 feet, 2 inches”).
343      *
344      * @param measures
345      *            a sequence of one or more measures.
346      * @return the formatted string.
347      */
formatMeasures(Measure... measures)348     public final String formatMeasures(Measure... measures) {
349         return formatMeasures(new StringBuilder(), DontCareFieldPosition.INSTANCE, measures).toString();
350     }
351 
352     // NOTE: For formatMeasureRange(), see http://bugs.icu-project.org/trac/ticket/12454
353 
354     /**
355      * Formats a single measure per unit.
356      *
357      * An example of such a formatted string is "3.5 meters per second."
358      *
359      * @param measure
360      *            the measure object. In above example, 3.5 meters.
361      * @param perUnit
362      *            the per unit. In above example, it is MeasureUnit.SECOND
363      * @param appendTo
364      *            formatted string appended here.
365      * @param pos
366      *            The field position.
367      * @return appendTo.
368      */
formatMeasurePerUnit( Measure measure, MeasureUnit perUnit, StringBuilder appendTo, FieldPosition pos)369     public StringBuilder formatMeasurePerUnit(
370             Measure measure,
371             MeasureUnit perUnit,
372             StringBuilder appendTo,
373             FieldPosition pos) {
374         DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(measure.getNumber());
375         FormattedStringBuilder string = new FormattedStringBuilder();
376         getUnitFormatterFromCache(
377             NUMBER_FORMATTER_STANDARD, measure.getUnit(), perUnit
378         ).formatImpl(dq, string);
379         DecimalFormat.fieldPositionHelper(dq, string, pos, appendTo.length());
380         Utility.appendTo(string, appendTo);
381         return appendTo;
382     }
383 
384     /**
385      * Formats a sequence of measures.
386      *
387      * If the fieldPosition argument identifies a NumberFormat field, then its indices are set to the
388      * beginning and end of the first such field encountered. MeasureFormat itself does not supply any
389      * fields.
390      *
391      * @param appendTo
392      *            the formatted string appended here.
393      * @param fpos
394      *            Identifies a field in the formatted text.
395      * @param measures
396      *            the measures to format.
397      * @return appendTo.
398      * @see MeasureFormat#formatMeasures(Measure...)
399      */
formatMeasures( StringBuilder appendTo, FieldPosition fpos, Measure... measures)400     public StringBuilder formatMeasures(
401             StringBuilder appendTo,
402             FieldPosition fpos,
403             Measure... measures) {
404         int prevLength = appendTo.length();
405         formatMeasuresInternal(appendTo, fpos, measures);
406         if (prevLength > 0 && fpos.getEndIndex() > 0) {
407             fpos.setBeginIndex(fpos.getBeginIndex() + prevLength);
408             fpos.setEndIndex(fpos.getEndIndex() + prevLength);
409         }
410         return appendTo;
411     }
412 
formatMeasuresInternal( Appendable appendTo, FieldPosition fieldPosition, Measure... measures)413     private void formatMeasuresInternal(
414             Appendable appendTo,
415             FieldPosition fieldPosition,
416             Measure... measures) {
417         // fast track for trivial cases
418         if (measures.length == 0) {
419             return;
420         }
421         if (measures.length == 1) {
422             FormattedStringBuilder result = formatMeasure(measures[0]);
423             FormattedValueStringBuilderImpl.nextFieldPosition(result, fieldPosition);
424             Utility.appendTo(result, appendTo);
425             return;
426         }
427 
428         if (formatWidth == FormatWidth.NUMERIC) {
429             // If we have just hour, minute, or second follow the numeric
430             // track.
431             Number[] hms = toHMS(measures);
432             if (hms != null) {
433                 formatNumeric(hms, appendTo);
434                 return;
435             }
436         }
437 
438         ListFormatter listFormatter = ListFormatter.getInstance(getLocale(),
439                 formatWidth.getListFormatterStyle());
440         if (fieldPosition != DontCareFieldPosition.INSTANCE) {
441             formatMeasuresSlowTrack(listFormatter, appendTo, fieldPosition, measures);
442             return;
443         }
444         // Fast track: No field position.
445         String[] results = new String[measures.length];
446         for (int i = 0; i < measures.length; i++) {
447             if (i == measures.length - 1) {
448                 results[i] = formatMeasure(measures[i]).toString();
449             } else {
450                 results[i] = formatMeasureInteger(measures[i]).toString();
451             }
452         }
453         FormattedListBuilder builder = listFormatter.formatImpl(Arrays.asList(results), false);
454         builder.appendTo(appendTo);
455     }
456 
457     /**
458      * Gets the display name of the specified {@link MeasureUnit} corresponding to the current locale and
459      * format width.
460      *
461      * @param unit
462      *            The unit for which to get a display name.
463      * @return The display name in the locale and width specified in {@link MeasureFormat#getInstance},
464      *         or null if there is no display name available for the specified unit.
465      */
getUnitDisplayName(MeasureUnit unit)466     public String getUnitDisplayName(MeasureUnit unit) {
467         return LongNameHandler.getUnitDisplayName(getLocale(), unit, formatWidth.unitWidth);
468     }
469 
470     /**
471      * Two MeasureFormats, a and b, are equal if and only if they have the same formatWidth, locale, and
472      * equal number formats.
473      */
474     @Override
equals(Object other)475     public final boolean equals(Object other) {
476         if (this == other) {
477             return true;
478         }
479         if (!(other instanceof MeasureFormat)) {
480             return false;
481         }
482         MeasureFormat rhs = (MeasureFormat) other;
483         // A very slow but safe implementation.
484         return getWidth() == rhs.getWidth()
485                 && getLocale().equals(rhs.getLocale())
486                 && getNumberFormatInternal().equals(rhs.getNumberFormatInternal());
487     }
488 
489     /**
490      * {@inheritDoc}
491      */
492     @Override
hashCode()493     public final int hashCode() {
494         // A very slow but safe implementation.
495         return (getLocale().hashCode() * 31 + getNumberFormatInternal().hashCode()) * 31 + getWidth().hashCode();
496     }
497 
498     /**
499      * Get the format width this instance is using.
500      */
getWidth()501     public MeasureFormat.FormatWidth getWidth() {
502         if (formatWidth == MeasureFormat.FormatWidth.DEFAULT_CURRENCY) {
503             return MeasureFormat.FormatWidth.WIDE;
504         }
505         return formatWidth;
506     }
507 
508     /**
509      * Get the locale of this instance.
510      */
getLocale()511     public final ULocale getLocale() {
512         return getLocale(ULocale.VALID_LOCALE);
513     }
514 
515     /**
516      * Get a copy of the number format.
517      */
getNumberFormat()518     public NumberFormat getNumberFormat() {
519         return (NumberFormat) numberFormat.clone();
520     }
521 
522     /**
523      * Get a copy of the number format without cloning. Internal method.
524      */
getNumberFormatInternal()525     NumberFormat getNumberFormatInternal() {
526         return numberFormat;
527     }
528 
529     /**
530      * Return a formatter for CurrencyAmount objects in the given locale.
531      *
532      * @param locale
533      *            desired locale
534      * @return a formatter object
535      */
getCurrencyFormat(ULocale locale)536     public static MeasureFormat getCurrencyFormat(ULocale locale) {
537         return new CurrencyFormat(locale);
538     }
539 
540     /**
541      * Return a formatter for CurrencyAmount objects in the given {@link java.util.Locale}.
542      *
543      * @param locale
544      *            desired {@link java.util.Locale}
545      * @return a formatter object
546      */
getCurrencyFormat(Locale locale)547     public static MeasureFormat getCurrencyFormat(Locale locale) {
548         return getCurrencyFormat(ULocale.forLocale(locale));
549     }
550 
551     /**
552      * Return a formatter for CurrencyAmount objects in the default <code>FORMAT</code> locale.
553      *
554      * @return a formatter object
555      * @see Category#FORMAT
556      */
getCurrencyFormat()557     public static MeasureFormat getCurrencyFormat() {
558         return getCurrencyFormat(ULocale.getDefault(Category.FORMAT));
559     }
560 
561     // This method changes the NumberFormat object as well to match the new locale.
withLocale(ULocale locale)562     MeasureFormat withLocale(ULocale locale) {
563         return MeasureFormat.getInstance(locale, getWidth());
564     }
565 
withNumberFormat(NumberFormat format)566     MeasureFormat withNumberFormat(NumberFormat format) {
567         return new MeasureFormat(getLocale(),
568                 this.formatWidth,
569                 format,
570                 this.rules,
571                 this.numericFormatters);
572     }
573 
MeasureFormat(ULocale locale, FormatWidth formatWidth)574     MeasureFormat(ULocale locale, FormatWidth formatWidth) {
575         this(locale, formatWidth, null, null, null);
576     }
577 
MeasureFormat( ULocale locale, FormatWidth formatWidth, NumberFormat numberFormat, PluralRules rules, NumericFormatters formatters)578     private MeasureFormat(
579             ULocale locale,
580             FormatWidth formatWidth,
581             NumberFormat numberFormat,
582             PluralRules rules,
583             NumericFormatters formatters) {
584         // Needed for getLocale(ULocale.VALID_LOCALE).
585         setLocale(locale, locale);
586         this.formatWidth = formatWidth;
587 
588         if (rules == null) {
589             rules = PluralRules.forLocale(locale);
590         }
591         this.rules = rules;
592 
593         if (numberFormat == null) {
594             numberFormat = NumberFormat.getInstance(locale);
595         } else {
596             numberFormat = (NumberFormat) numberFormat.clone();
597         }
598         this.numberFormat = numberFormat;
599 
600         if (formatters == null && formatWidth == FormatWidth.NUMERIC) {
601             formatters = localeToNumericDurationFormatters.get(locale);
602             if (formatters == null) {
603                 formatters = loadNumericFormatters(locale);
604                 localeToNumericDurationFormatters.put(locale, formatters);
605             }
606         }
607         this.numericFormatters = formatters;
608 
609         if (!(numberFormat instanceof DecimalFormat)) {
610             throw new IllegalArgumentException();
611         }
612         numberFormatter = ((DecimalFormat) numberFormat).toNumberFormatter()
613                 .unitWidth(formatWidth.unitWidth);
614     }
615 
MeasureFormat( ULocale locale, FormatWidth formatWidth, NumberFormat numberFormat, PluralRules rules)616     MeasureFormat(
617             ULocale locale,
618             FormatWidth formatWidth,
619             NumberFormat numberFormat,
620             PluralRules rules) {
621         this(locale, formatWidth, numberFormat, rules, null);
622         if (formatWidth == FormatWidth.NUMERIC) {
623             throw new IllegalArgumentException(
624                     "The format width 'numeric' is not allowed by this constructor");
625         }
626     }
627 
628     static class NumericFormatters {
629         private String hourMinute;
630         private String minuteSecond;
631         private String hourMinuteSecond;
632 
NumericFormatters( String hourMinute, String minuteSecond, String hourMinuteSecond)633         public NumericFormatters(
634                 String hourMinute,
635                 String minuteSecond,
636                 String hourMinuteSecond) {
637             this.hourMinute = hourMinute;
638             this.minuteSecond = minuteSecond;
639             this.hourMinuteSecond = hourMinuteSecond;
640         }
641 
getHourMinute()642         public String getHourMinute() {
643             return hourMinute;
644         }
645 
getMinuteSecond()646         public String getMinuteSecond() {
647             return minuteSecond;
648         }
649 
getHourMinuteSecond()650         public String getHourMinuteSecond() {
651             return hourMinuteSecond;
652         }
653     }
654 
loadNumericFormatters(ULocale locale)655     private static NumericFormatters loadNumericFormatters(ULocale locale) {
656         ICUResourceBundle r = (ICUResourceBundle) UResourceBundle
657                 .getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
658         return new NumericFormatters(loadNumericDurationFormat(r, "hm"),
659                 loadNumericDurationFormat(r, "ms"),
660                 loadNumericDurationFormat(r, "hms"));
661     }
662 
663     /// BEGIN NUMBER FORMATTER CACHING MACHINERY ///
664 
665     static final int NUMBER_FORMATTER_STANDARD = 1;
666     static final int NUMBER_FORMATTER_CURRENCY = 2;
667     static final int NUMBER_FORMATTER_INTEGER = 3;
668 
669     static class NumberFormatterCacheEntry {
670         int type;
671         MeasureUnit unit;
672         MeasureUnit perUnit;
673         LocalizedNumberFormatter formatter;
674     }
675 
676     // formatter1 is most recently used.
677     private transient NumberFormatterCacheEntry formatter1 = null;
678     private transient NumberFormatterCacheEntry formatter2 = null;
679     private transient NumberFormatterCacheEntry formatter3 = null;
680 
getUnitFormatterFromCache( int type, MeasureUnit unit, MeasureUnit perUnit)681     private synchronized LocalizedNumberFormatter getUnitFormatterFromCache(
682             int type,
683             MeasureUnit unit,
684             MeasureUnit perUnit) {
685         if (formatter1 != null) {
686             if (formatter1.type == type && formatter1.unit == unit && formatter1.perUnit == perUnit) {
687                 return formatter1.formatter;
688             }
689             if (formatter2 != null) {
690                 if (formatter2.type == type
691                         && formatter2.unit == unit
692                         && formatter2.perUnit == perUnit) {
693                     return formatter2.formatter;
694                 }
695                 if (formatter3 != null) {
696                     if (formatter3.type == type
697                             && formatter3.unit == unit
698                             && formatter3.perUnit == perUnit) {
699                         return formatter3.formatter;
700                     }
701                 }
702             }
703         }
704 
705         // No hit; create a new formatter.
706         LocalizedNumberFormatter formatter;
707         if (type == NUMBER_FORMATTER_STANDARD) {
708             formatter = getNumberFormatter().unit(unit).perUnit(perUnit)
709                     .unitWidth(formatWidth.unitWidth);
710         } else if (type == NUMBER_FORMATTER_CURRENCY) {
711             formatter = NumberFormatter.withLocale(getLocale()).unit(unit).perUnit(perUnit)
712                     .unitWidth(formatWidth.currencyWidth);
713         } else {
714             assert type == NUMBER_FORMATTER_INTEGER;
715             formatter = getNumberFormatter().unit(unit).perUnit(perUnit).unitWidth(formatWidth.unitWidth)
716                     .precision(Precision.integer().withMode(
717                             RoundingUtils.mathContextUnlimited(RoundingMode.DOWN)));
718         }
719         formatter3 = formatter2;
720         formatter2 = formatter1;
721         formatter1 = new NumberFormatterCacheEntry();
722         formatter1.type = type;
723         formatter1.unit = unit;
724         formatter1.perUnit = perUnit;
725         formatter1.formatter = formatter;
726         return formatter;
727     }
728 
clearCache()729     synchronized void clearCache() {
730         formatter1 = null;
731         formatter2 = null;
732         formatter3 = null;
733     }
734 
735     // Can be overridden by subclasses:
getNumberFormatter()736     LocalizedNumberFormatter getNumberFormatter() {
737         return numberFormatter;
738     }
739 
740     /// END NUMBER FORMATTER CACHING MACHINERY ///
741 
formatMeasure(Measure measure)742     private FormattedStringBuilder formatMeasure(Measure measure) {
743         MeasureUnit unit = measure.getUnit();
744         DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(measure.getNumber());
745         FormattedStringBuilder string = new FormattedStringBuilder();
746         if (unit instanceof Currency) {
747             getUnitFormatterFromCache(NUMBER_FORMATTER_CURRENCY, unit, null)
748                     .formatImpl(dq, string);
749         } else {
750             getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD, unit, null)
751                     .formatImpl(dq, string);
752         }
753         return string;
754     }
755 
formatMeasureInteger(Measure measure)756     private FormattedStringBuilder formatMeasureInteger(Measure measure) {
757         DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(measure.getNumber());
758         FormattedStringBuilder string = new FormattedStringBuilder();
759         getUnitFormatterFromCache(NUMBER_FORMATTER_INTEGER, measure.getUnit(), null)
760                 .formatImpl(dq, string);
761         return string;
762     }
763 
formatMeasuresSlowTrack( ListFormatter listFormatter, Appendable appendTo, FieldPosition fieldPosition, Measure... measures)764     private void formatMeasuresSlowTrack(
765             ListFormatter listFormatter,
766             Appendable appendTo,
767             FieldPosition fieldPosition,
768             Measure... measures) {
769         String[] results = new String[measures.length];
770 
771         // Zero out our field position so that we can tell when we find our field.
772         FieldPosition fpos = new FieldPosition(fieldPosition.getFieldAttribute(),
773                 fieldPosition.getField());
774 
775         int fieldPositionFoundIndex = -1;
776         for (int i = 0; i < measures.length; ++i) {
777             FormattedStringBuilder result;
778             if (i == measures.length - 1) {
779                 result = formatMeasure(measures[i]);
780             } else {
781                 result = formatMeasureInteger(measures[i]);
782             }
783             if (fieldPositionFoundIndex == -1) {
784                 FormattedValueStringBuilderImpl.nextFieldPosition(result, fpos);
785                 if (fpos.getEndIndex() != 0) {
786                     fieldPositionFoundIndex = i;
787                 }
788             }
789             results[i] = result.toString();
790         }
791         ListFormatter.FormattedListBuilder builder = listFormatter.formatImpl(Arrays.asList(results), true);
792 
793         // Fix up FieldPosition indexes if our field is found.
794         int offset = builder.getOffset(fieldPositionFoundIndex);
795         if (offset != -1) {
796             fieldPosition.setBeginIndex(fpos.getBeginIndex() + offset);
797             fieldPosition.setEndIndex(fpos.getEndIndex() + offset);
798         }
799         builder.appendTo(appendTo);
800     }
801 
802     // type is one of "hm", "ms" or "hms"
loadNumericDurationFormat(ICUResourceBundle r, String type)803     private static String loadNumericDurationFormat(ICUResourceBundle r, String type) {
804         r = r.getWithFallback(String.format("durationUnits/%s", type));
805         // We replace 'h' with 'H' because 'h' does not make sense in the context of durations.
806         return r.getString().replace("h", "H");
807     }
808 
809     // Returns hours in [0]; minutes in [1]; seconds in [2] out of measures array. If
810     // unsuccessful, e.g measures has other measurements besides hours, minutes, seconds;
811     // hours, minutes, seconds are out of order; or have negative values, returns null.
812     // If hours, minutes, or seconds is missing from measures the corresponding element in
813     // returned array will be null.
toHMS(Measure[] measures)814     private static Number[] toHMS(Measure[] measures) {
815         Number[] result = new Number[3];
816         int lastIdx = -1;
817         for (Measure m : measures) {
818             if (m.getNumber().doubleValue() < 0.0) {
819                 return null;
820             }
821             Integer idxObj = hmsTo012.get(m.getUnit());
822             if (idxObj == null) {
823                 return null;
824             }
825             int idx = idxObj.intValue();
826             if (idx <= lastIdx) {
827                 // hour before minute before second
828                 return null;
829             }
830             lastIdx = idx;
831             result[idx] = m.getNumber();
832         }
833         return result;
834     }
835 
836     // Formats numeric time duration as 5:00:47 or 3:54. In the process, it replaces any null
837     // values in hms with 0.
formatNumeric(Number[] hms, Appendable appendable)838     private void formatNumeric(Number[] hms, Appendable appendable) {
839         String pattern;
840 
841         // All possible combinations: "h", "m", "s", "hm", "hs", "ms", "hms"
842         if (hms[0] != null && hms[2] != null) { // "hms" & "hs" (we add minutes if "hs")
843             pattern = numericFormatters.getHourMinuteSecond();
844             if (hms[1] == null)
845                 hms[1] = 0;
846             hms[1] = Math.floor(hms[1].doubleValue());
847             hms[0] = Math.floor(hms[0].doubleValue());
848         } else if (hms[0] != null && hms[1] != null) { // "hm"
849             pattern = numericFormatters.getHourMinute();
850             hms[0] = Math.floor(hms[0].doubleValue());
851         } else if (hms[1] != null && hms[2] != null) { // "ms"
852             pattern = numericFormatters.getMinuteSecond();
853             hms[1] = Math.floor(hms[1].doubleValue());
854         } else { // h m s, handled outside formatNumeric. No value is also an error.
855             throw new IllegalStateException();
856         }
857 
858         // We can create it on demand, but all of the patterns (right now) have mm and ss.
859         // So unless it is hours only we will need a 0-padded 2 digits formatter.
860         LocalizedNumberFormatter numberFormatter2 = numberFormatter.integerWidth(IntegerWidth.zeroFillTo(2));
861         FormattedStringBuilder fsb = new FormattedStringBuilder();
862 
863         boolean protect = false;
864         for (int i = 0; i < pattern.length(); i++) {
865             char c = pattern.charAt(i);
866 
867             // Also set the proper field in this switch
868             // We don't use DateFormat.Field because this is not a date / time, is a duration.
869             Number value = 0;
870             switch (c) {
871                 case 'H': value = hms[0]; break;
872                 case 'm': value = hms[1]; break;
873                 case 's': value = hms[2]; break;
874             }
875 
876             // There is not enough info to add Field(s) for the unit because all we have are plain
877             // text patterns. For example in "21:51" there is no text for something like "hour",
878             // while in something like "21h51" there is ("h"). But we can't really tell...
879             switch (c) {
880                 case 'H':
881                 case 'm':
882                 case 's':
883                     if (protect) {
884                         fsb.appendChar16(c, null);
885                     } else {
886                         if ((i + 1 < pattern.length()) && pattern.charAt(i + 1) == c) { // doubled
887                             fsb.append(numberFormatter2.format(value), null); // TODO: Use proper Field
888                             i++;
889                         } else {
890                             fsb.append(numberFormatter.format(value), null); // TODO: Use proper Field
891                         }
892                     }
893                     break;
894                 case '\'':
895                     // '' is escaped apostrophe
896                     if ((i + 1 < pattern.length()) && pattern.charAt(i + 1) == c) {
897                         fsb.appendChar16(c, null);
898                         i++;
899                     } else {
900                         protect = !protect;
901                     }
902                     break;
903                 default:
904                     fsb.appendChar16(c, null);
905             }
906         }
907 
908         try {
909             appendable.append(fsb);
910         } catch (IOException e) {
911             throw new ICUUncheckedIOException(e);
912         }
913     }
914 
toTimeUnitProxy()915     Object toTimeUnitProxy() {
916         return new MeasureProxy(getLocale(), formatWidth, getNumberFormatInternal(), TIME_UNIT_FORMAT);
917     }
918 
toCurrencyProxy()919     Object toCurrencyProxy() {
920         return new MeasureProxy(getLocale(), formatWidth, getNumberFormatInternal(), CURRENCY_FORMAT);
921     }
922 
writeReplace()923     private Object writeReplace() throws ObjectStreamException {
924         return new MeasureProxy(getLocale(), formatWidth, getNumberFormatInternal(), MEASURE_FORMAT);
925     }
926 
927     static class MeasureProxy implements Externalizable {
928         private static final long serialVersionUID = -6033308329886716770L;
929 
930         private ULocale locale;
931         private FormatWidth formatWidth;
932         private NumberFormat numberFormat;
933         private int subClass;
934         private HashMap<Object, Object> keyValues;
935 
MeasureProxy(ULocale locale, FormatWidth width, NumberFormat numberFormat, int subClass)936         public MeasureProxy(ULocale locale, FormatWidth width, NumberFormat numberFormat, int subClass) {
937             this.locale = locale;
938             this.formatWidth = width;
939             this.numberFormat = numberFormat;
940             this.subClass = subClass;
941             this.keyValues = new HashMap<>();
942         }
943 
944         // Must have public constructor, to enable Externalizable
MeasureProxy()945         public MeasureProxy() {
946         }
947 
948         @Override
writeExternal(ObjectOutput out)949         public void writeExternal(ObjectOutput out) throws IOException {
950             out.writeByte(0); // version
951             out.writeUTF(locale.toLanguageTag());
952             out.writeByte(formatWidth.ordinal());
953             out.writeObject(numberFormat);
954             out.writeByte(subClass);
955             out.writeObject(keyValues);
956         }
957 
958         @Override
959         @SuppressWarnings("unchecked")
readExternal(ObjectInput in)960         public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
961             in.readByte(); // version.
962             locale = ULocale.forLanguageTag(in.readUTF());
963             formatWidth = fromFormatWidthOrdinal(in.readByte() & 0xFF);
964             numberFormat = (NumberFormat) in.readObject();
965             if (numberFormat == null) {
966                 throw new InvalidObjectException("Missing number format.");
967             }
968             subClass = in.readByte() & 0xFF;
969 
970             // This cast is safe because the serialized form of hashtable can have
971             // any object as the key and any object as the value.
972             keyValues = (HashMap<Object, Object>) in.readObject();
973             if (keyValues == null) {
974                 throw new InvalidObjectException("Missing optional values map.");
975             }
976         }
977 
createTimeUnitFormat()978         private TimeUnitFormat createTimeUnitFormat() throws InvalidObjectException {
979             int style;
980             if (formatWidth == FormatWidth.WIDE) {
981                 style = TimeUnitFormat.FULL_NAME;
982             } else if (formatWidth == FormatWidth.SHORT) {
983                 style = TimeUnitFormat.ABBREVIATED_NAME;
984             } else {
985                 throw new InvalidObjectException("Bad width: " + formatWidth);
986             }
987             TimeUnitFormat result = new TimeUnitFormat(locale, style);
988             result.setNumberFormat(numberFormat);
989             return result;
990         }
991 
readResolve()992         private Object readResolve() throws ObjectStreamException {
993             switch (subClass) {
994             case MEASURE_FORMAT:
995                 return MeasureFormat.getInstance(locale, formatWidth, numberFormat);
996             case TIME_UNIT_FORMAT:
997                 return createTimeUnitFormat();
998             case CURRENCY_FORMAT:
999                 return MeasureFormat.getCurrencyFormat(locale);
1000             default:
1001                 throw new InvalidObjectException("Unknown subclass: " + subClass);
1002             }
1003         }
1004     }
1005 
fromFormatWidthOrdinal(int ordinal)1006     private static FormatWidth fromFormatWidthOrdinal(int ordinal) {
1007         FormatWidth[] values = FormatWidth.values();
1008         if (ordinal < 0 || ordinal >= values.length) {
1009             return FormatWidth.SHORT;
1010         }
1011         return values[ordinal];
1012     }
1013 
1014     private static final Map<ULocale, String> localeIdToRangeFormat = new ConcurrentHashMap<>();
1015 
1016     /**
1017      * Return a formatter (compiled SimpleFormatter pattern) for a range, such as "{0}–{1}".
1018      *
1019      * @param forLocale
1020      *            locale to get the format for
1021      * @param width
1022      *            the format width
1023      * @return range formatter, such as "{0}–{1}"
1024      * @deprecated This API is ICU internal only.
1025      * @hide deprecated on icu4j-org
1026      * @hide draft / provisional / internal are hidden on OHOS
1027      */
1028     @Deprecated
getRangeFormat(ULocale forLocale, FormatWidth width)1029     public static String getRangeFormat(ULocale forLocale, FormatWidth width) {
1030         // TODO fix Hack for French
1031         if (forLocale.getLanguage().equals("fr")) {
1032             return getRangeFormat(ULocale.ROOT, width);
1033         }
1034         String result = localeIdToRangeFormat.get(forLocale);
1035         if (result == null) {
1036             ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle
1037                     .getBundleInstance(ICUData.ICU_BASE_NAME, forLocale);
1038             ULocale realLocale = rb.getULocale();
1039             if (!forLocale.equals(realLocale)) { // if the child would inherit, then add a cache entry
1040                                                  // for it.
1041                 result = localeIdToRangeFormat.get(forLocale);
1042                 if (result != null) {
1043                     localeIdToRangeFormat.put(forLocale, result);
1044                     return result;
1045                 }
1046             }
1047             // At this point, both the forLocale and the realLocale don't have an item
1048             // So we have to make one.
1049             NumberingSystem ns = NumberingSystem.getInstance(forLocale);
1050 
1051             String resultString = null;
1052             try {
1053                 resultString = rb
1054                         .getStringWithFallback("NumberElements/" + ns.getName() + "/miscPatterns/range");
1055             } catch (MissingResourceException ex) {
1056                 resultString = rb.getStringWithFallback("NumberElements/latn/patterns/range");
1057             }
1058             result = SimpleFormatterImpl
1059                     .compileToStringMinMaxArguments(resultString, new StringBuilder(), 2, 2);
1060             localeIdToRangeFormat.put(forLocale, result);
1061             if (!forLocale.equals(realLocale)) {
1062                 localeIdToRangeFormat.put(realLocale, result);
1063             }
1064         }
1065         return result;
1066     }
1067 }
1068