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