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<? extends Measure>, 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<? extends Measure>, 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