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) 2008-2014, Google, International Business Machines 7 * Corporation and others. All Rights Reserved. 8 ************************************************************************** 9 */ 10 package android.icu.text; 11 12 import java.io.ObjectStreamException; 13 import java.text.FieldPosition; 14 import java.text.ParseException; 15 import java.text.ParsePosition; 16 import java.util.HashMap; 17 import java.util.Locale; 18 import java.util.Map; 19 import java.util.Map.Entry; 20 import java.util.MissingResourceException; 21 import java.util.Set; 22 import java.util.TreeMap; 23 24 import android.icu.impl.ICUData; 25 import android.icu.impl.ICUResourceBundle; 26 import android.icu.impl.UResource; 27 import android.icu.util.Measure; 28 import android.icu.util.TimeUnit; 29 import android.icu.util.TimeUnitAmount; 30 import android.icu.util.ULocale; 31 import android.icu.util.ULocale.Category; 32 import android.icu.util.UResourceBundle; 33 34 35 /** 36 * Format or parse a TimeUnitAmount, using plural rules for the units where available. 37 * 38 * <P> 39 * Code Sample: 40 * <pre> 41 * // create a time unit instance. 42 * // only SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, and YEAR are supported 43 * TimeUnit timeUnit = TimeUnit.SECOND; 44 * // create time unit amount instance - a combination of Number and time unit 45 * TimeUnitAmount source = new TimeUnitAmount(2, timeUnit); 46 * // create time unit format instance 47 * TimeUnitFormat format = new TimeUnitFormat(); 48 * // set the locale of time unit format 49 * format.setLocale(new ULocale("en")); 50 * // format a time unit amount 51 * String formatted = format.format(source); 52 * System.out.println(formatted); 53 * try { 54 * // parse a string into time unit amount 55 * TimeUnitAmount result = (TimeUnitAmount) format.parseObject(formatted); 56 * // result should equal to source 57 * } catch (ParseException e) { 58 * } 59 * </pre> 60 * 61 * <P> 62 * @see TimeUnitAmount 63 * @see MeasureFormat 64 * @author markdavis 65 * @deprecated ICU 53 use {@link MeasureFormat} instead. 66 * @hide Only a subset of ICU is exposed in Android 67 */ 68 @Deprecated 69 public class TimeUnitFormat extends MeasureFormat { 70 71 /** 72 * Constant for full name style format. 73 * For example, the full name for "hour" in English is "hour" or "hours". 74 * @deprecated ICU 53 see {@link MeasureFormat.FormatWidth} 75 */ 76 @Deprecated 77 public static final int FULL_NAME = 0; 78 /** 79 * Constant for abbreviated name style format. 80 * For example, the abbreviated name for "hour" in English is "hr" or "hrs". 81 * @deprecated ICU 53 see {@link MeasureFormat.FormatWidth} 82 */ 83 @Deprecated 84 public static final int ABBREVIATED_NAME = 1; 85 86 private static final int TOTAL_STYLES = 2; 87 88 private static final long serialVersionUID = -3707773153184971529L; 89 90 // These fields are supposed to be the same as the fields in mf. They 91 // are here for serialization backward compatibility and to support parsing. 92 private NumberFormat format; 93 private ULocale locale; 94 private int style; 95 96 // We use this field in lieu of the super class because the super class 97 // is immutable while this class is mutable. The contents of the super class 98 // is an empty shell. Every public method of the super class is overridden to 99 // delegate to this field. Each time this object mutates, it replaces this field with 100 // a new immutable instance. 101 private transient MeasureFormat mf; 102 103 private transient Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns; 104 private transient PluralRules pluralRules; 105 private transient boolean isReady; 106 107 private static final String DEFAULT_PATTERN_FOR_SECOND = "{0} s"; 108 private static final String DEFAULT_PATTERN_FOR_MINUTE = "{0} min"; 109 private static final String DEFAULT_PATTERN_FOR_HOUR = "{0} h"; 110 private static final String DEFAULT_PATTERN_FOR_DAY = "{0} d"; 111 private static final String DEFAULT_PATTERN_FOR_WEEK = "{0} w"; 112 private static final String DEFAULT_PATTERN_FOR_MONTH = "{0} m"; 113 private static final String DEFAULT_PATTERN_FOR_YEAR = "{0} y"; 114 115 /** 116 * Create empty format using full name style, for example, "hours". 117 * Use setLocale and/or setFormat to modify. 118 * @deprecated ICU 53 use {@link MeasureFormat} instead. 119 */ 120 @Deprecated 121 public TimeUnitFormat() { 122 mf = MeasureFormat.getInstance(ULocale.getDefault(), FormatWidth.WIDE); 123 isReady = false; 124 style = FULL_NAME; 125 } 126 127 /** 128 * Create TimeUnitFormat given a ULocale, and using full name style. 129 * @param locale locale of this time unit formatter. 130 * @deprecated ICU 53 use {@link MeasureFormat} instead. 131 */ 132 @Deprecated 133 public TimeUnitFormat(ULocale locale) { 134 this(locale, FULL_NAME); 135 } 136 137 /** 138 * Create TimeUnitFormat given a Locale, and using full name style. 139 * @param locale locale of this time unit formatter. 140 * @deprecated ICU 53 use {@link MeasureFormat} instead. 141 */ 142 @Deprecated 143 public TimeUnitFormat(Locale locale) { 144 this(locale, FULL_NAME); 145 } 146 147 /** 148 * Create TimeUnitFormat given a ULocale and a formatting style. 149 * @param locale locale of this time unit formatter. 150 * @param style format style, either FULL_NAME or ABBREVIATED_NAME style. 151 * @throws IllegalArgumentException if the style is not FULL_NAME or 152 * ABBREVIATED_NAME style. 153 * @deprecated ICU 53 use {@link MeasureFormat} instead. 154 */ 155 @Deprecated 156 public TimeUnitFormat(ULocale locale, int style) { 157 if (style < FULL_NAME || style >= TOTAL_STYLES) { 158 throw new IllegalArgumentException("style should be either FULL_NAME or ABBREVIATED_NAME style"); 159 } 160 mf = MeasureFormat.getInstance( 161 locale, style == FULL_NAME ? FormatWidth.WIDE : FormatWidth.SHORT); 162 this.style = style; 163 164 // Needed for getLocale(ULocale.VALID_LOCALE) 165 setLocale(locale, locale); 166 this.locale = locale; 167 isReady = false; 168 } 169 170 private TimeUnitFormat(ULocale locale, int style, NumberFormat numberFormat) { 171 this(locale, style); 172 if (numberFormat != null) { 173 setNumberFormat((NumberFormat) numberFormat.clone()); 174 } 175 } 176 177 /** 178 * Create TimeUnitFormat given a Locale and a formatting style. 179 * @deprecated ICU 53 use {@link MeasureFormat} instead. 180 */ 181 @Deprecated 182 public TimeUnitFormat(Locale locale, int style) { 183 this(ULocale.forLocale(locale), style); 184 } 185 186 /** 187 * Set the locale used for formatting or parsing. 188 * @param locale locale of this time unit formatter. 189 * @return this, for chaining. 190 * @deprecated ICU 53 see {@link MeasureFormat}. 191 */ 192 @Deprecated 193 public TimeUnitFormat setLocale(ULocale locale) { 194 if (locale != this.locale){ 195 mf = mf.withLocale(locale); 196 197 // Needed for getLocale(ULocale.VALID_LOCALE) 198 setLocale(locale, locale); 199 this.locale = locale; 200 isReady = false; 201 } 202 return this; 203 } 204 205 /** 206 * Set the locale used for formatting or parsing. 207 * @param locale locale of this time unit formatter. 208 * @return this, for chaining. 209 * @deprecated ICU 53 see {@link MeasureFormat}. 210 */ 211 @Deprecated 212 public TimeUnitFormat setLocale(Locale locale) { 213 return setLocale(ULocale.forLocale(locale)); 214 } 215 216 /** 217 * Set the format used for formatting or parsing. Passing null is equivalent to passing 218 * {@link NumberFormat#getNumberInstance(ULocale)}. 219 * @param format the number formatter. 220 * @return this, for chaining. 221 * @deprecated ICU 53 see {@link MeasureFormat}. 222 */ 223 @Deprecated 224 public TimeUnitFormat setNumberFormat(NumberFormat format) { 225 if (format == this.format) { 226 return this; 227 } 228 if (format == null) { 229 if (locale == null) { 230 isReady = false; 231 mf = mf.withLocale(ULocale.getDefault()); 232 } else { 233 this.format = NumberFormat.getNumberInstance(locale); 234 mf = mf.withNumberFormat(this.format); 235 } 236 } else { 237 this.format = format; 238 mf = mf.withNumberFormat(this.format); 239 } 240 return this; 241 } 242 243 244 /** 245 * Format a TimeUnitAmount. 246 * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) 247 * @deprecated ICU 53 see {@link MeasureFormat}. 248 */ 249 @Deprecated 250 public StringBuffer format(Object obj, StringBuffer toAppendTo, 251 FieldPosition pos) { 252 return mf.format(obj, toAppendTo, pos); 253 } 254 255 /** 256 * Parse a TimeUnitAmount. 257 * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition) 258 * @deprecated ICU 53 see {@link MeasureFormat}. 259 */ 260 @Deprecated 261 @Override 262 public TimeUnitAmount parseObject(String source, ParsePosition pos) { 263 if (!isReady) { 264 setup(); 265 } 266 Number resultNumber = null; 267 TimeUnit resultTimeUnit = null; 268 int oldPos = pos.getIndex(); 269 int newPos = -1; 270 int longestParseDistance = 0; 271 String countOfLongestMatch = null; 272 // we don't worry too much about speed on parsing, but this can be optimized later if needed. 273 // Parse by iterating through all available patterns 274 // and looking for the longest match. 275 for (TimeUnit timeUnit : timeUnitToCountToPatterns.keySet()) { 276 Map<String, Object[]> countToPattern = timeUnitToCountToPatterns.get(timeUnit); 277 for (Entry<String, Object[]> patternEntry : countToPattern.entrySet()) { 278 String count = patternEntry.getKey(); 279 for (int styl = FULL_NAME; styl < TOTAL_STYLES; ++styl) { 280 MessageFormat pattern = (MessageFormat) (patternEntry.getValue())[styl]; 281 pos.setErrorIndex(-1); 282 pos.setIndex(oldPos); 283 // see if we can parse 284 Object parsed = pattern.parseObject(source, pos); 285 if (pos.getErrorIndex() != -1 || pos.getIndex() == oldPos) { 286 // nothing parsed 287 continue; 288 } 289 Number temp = null; 290 if (((Object[]) parsed).length != 0) { 291 // pattern with Number as beginning, 292 // such as "{0} d". 293 // check to make sure that the timeUnit is consistent 294 Object tempObj = ((Object[]) parsed)[0]; 295 if (tempObj instanceof Number) { 296 temp = (Number) tempObj; 297 } else { 298 // Since we now format the number ourselves, parseObject will likely give us back a String 299 // for 300 // the number. When this happens we must parse the formatted number ourselves. 301 try { 302 temp = format.parse(tempObj.toString()); 303 } catch (ParseException e) { 304 continue; 305 } 306 } 307 } 308 int parseDistance = pos.getIndex() - oldPos; 309 if (parseDistance > longestParseDistance) { 310 resultNumber = temp; 311 resultTimeUnit = timeUnit; 312 newPos = pos.getIndex(); 313 longestParseDistance = parseDistance; 314 countOfLongestMatch = count; 315 } 316 } 317 } 318 } 319 /* 320 * After find the longest match, parse the number. Result number could be null for the pattern without number 321 * pattern. such as unit pattern in Arabic. When result number is null, use plural rule to set the number. 322 */ 323 if (resultNumber == null && longestParseDistance != 0) { 324 // set the number using plurrual count 325 if (countOfLongestMatch.equals("zero")) { 326 resultNumber = Integer.valueOf(0); 327 } else if (countOfLongestMatch.equals("one")) { 328 resultNumber = Integer.valueOf(1); 329 } else if (countOfLongestMatch.equals("two")) { 330 resultNumber = Integer.valueOf(2); 331 } else { 332 // should not happen. 333 // TODO: how to handle? 334 resultNumber = Integer.valueOf(3); 335 } 336 } 337 if (longestParseDistance == 0) { 338 pos.setIndex(oldPos); 339 pos.setErrorIndex(0); 340 return null; 341 } else { 342 pos.setIndex(newPos); 343 pos.setErrorIndex(-1); 344 return new TimeUnitAmount(resultNumber, resultTimeUnit); 345 } 346 } 347 348 private void setup() { 349 if (locale == null) { 350 if (format != null) { 351 locale = format.getLocale(null); 352 } else { 353 locale = ULocale.getDefault(Category.FORMAT); 354 } 355 // Needed for getLocale(ULocale.VALID_LOCALE) 356 setLocale(locale, locale); 357 } 358 if (format == null) { 359 format = NumberFormat.getNumberInstance(locale); 360 } 361 pluralRules = PluralRules.forLocale(locale); 362 timeUnitToCountToPatterns = new HashMap<TimeUnit, Map<String, Object[]>>(); 363 Set<String> pluralKeywords = pluralRules.getKeywords(); 364 setup("units/duration", timeUnitToCountToPatterns, FULL_NAME, pluralKeywords); 365 setup("unitsShort/duration", timeUnitToCountToPatterns, ABBREVIATED_NAME, pluralKeywords); 366 isReady = true; 367 } 368 369 private static final class TimeUnitFormatSetupSink extends UResource.Sink { 370 Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns; 371 int style; 372 Set<String> pluralKeywords; 373 ULocale locale; 374 boolean beenHere; 375 376 TimeUnitFormatSetupSink(Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns, 377 int style, Set<String> pluralKeywords, ULocale locale) { 378 this.timeUnitToCountToPatterns = timeUnitToCountToPatterns; 379 this.style = style; 380 this.pluralKeywords = pluralKeywords; 381 this.locale = locale; 382 this.beenHere = false; 383 } 384 385 @Override 386 public void put(UResource.Key key, UResource.Value value, boolean noFallback) { 387 // Skip all put() calls except the first one -- discard all fallback data. 388 if (beenHere) { 389 return; 390 } else { 391 beenHere = true; 392 } 393 394 UResource.Table units = value.getTable(); 395 for (int i = 0; units.getKeyAndValue(i, key, value); ++i) { 396 String timeUnitName = key.toString(); 397 TimeUnit timeUnit = null; 398 399 if (timeUnitName.equals("year")) { 400 timeUnit = TimeUnit.YEAR; 401 } else if (timeUnitName.equals("month")) { 402 timeUnit = TimeUnit.MONTH; 403 } else if (timeUnitName.equals("day")) { 404 timeUnit = TimeUnit.DAY; 405 } else if (timeUnitName.equals("hour")) { 406 timeUnit = TimeUnit.HOUR; 407 } else if (timeUnitName.equals("minute")) { 408 timeUnit = TimeUnit.MINUTE; 409 } else if (timeUnitName.equals("second")) { 410 timeUnit = TimeUnit.SECOND; 411 } else if (timeUnitName.equals("week")) { 412 timeUnit = TimeUnit.WEEK; 413 } else { 414 continue; 415 } 416 417 Map<String, Object[]> countToPatterns = timeUnitToCountToPatterns.get(timeUnit); 418 if (countToPatterns == null) { 419 countToPatterns = new TreeMap<String, Object[]>(); 420 timeUnitToCountToPatterns.put(timeUnit, countToPatterns); 421 } 422 423 UResource.Table countsToPatternTable = value.getTable(); 424 for (int j = 0; countsToPatternTable.getKeyAndValue(j, key, value); ++j) { 425 String pluralCount = key.toString(); 426 if (!pluralKeywords.contains(pluralCount)) 427 continue; 428 // save both full name and abbreviated name in one table 429 // is good space-wise, but it degrades performance, 430 // since it needs to check whether the needed space 431 // is already allocated or not. 432 Object[] pair = countToPatterns.get(pluralCount); 433 if (pair == null) { 434 pair = new Object[2]; 435 countToPatterns.put(pluralCount, pair); 436 } 437 if (pair[style] == null) { 438 String pattern = value.getString(); 439 final MessageFormat messageFormat = new MessageFormat(pattern, locale); 440 pair[style] = messageFormat; 441 } 442 } 443 } 444 } 445 } 446 447 private void setup(String resourceKey, Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns, int style, 448 Set<String> pluralKeywords) { 449 // fill timeUnitToCountToPatterns from resource file 450 try { 451 452 ICUResourceBundle resource = (ICUResourceBundle) UResourceBundle.getBundleInstance( 453 ICUData.ICU_UNIT_BASE_NAME, locale); 454 455 TimeUnitFormatSetupSink sink = new TimeUnitFormatSetupSink( 456 timeUnitToCountToPatterns, style, pluralKeywords, locale); 457 resource.getAllItemsWithFallback(resourceKey, sink); 458 } catch (MissingResourceException e) { 459 } 460 // there should be patterns for each plural rule in each time unit. 461 // For each time unit, 462 // for each plural rule, following is unit pattern fall-back rule: 463 // ( for example: "one" hour ) 464 // look for its unit pattern in its locale tree. 465 // if pattern is not found in its own locale, such as de_DE, 466 // look for the pattern in its parent, such as de, 467 // keep looking till found or till root. 468 // if the pattern is not found in root either, 469 // fallback to plural count "other", 470 // look for the pattern of "other" in the locale tree: 471 // "de_DE" to "de" to "root". 472 // If not found, fall back to value of 473 // static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h". 474 // 475 // Following is consistency check to create pattern for each 476 // plural rule in each time unit using above fall-back rule. 477 // 478 final TimeUnit[] timeUnits = TimeUnit.values(); 479 Set<String> keywords = pluralRules.getKeywords(); 480 for (int i = 0; i < timeUnits.length; ++i) { 481 // for each time unit, 482 // get all the patterns for each plural rule in this locale. 483 final TimeUnit timeUnit = timeUnits[i]; 484 Map<String, Object[]> countToPatterns = timeUnitToCountToPatterns.get(timeUnit); 485 if (countToPatterns == null) { 486 countToPatterns = new TreeMap<String, Object[]>(); 487 timeUnitToCountToPatterns.put(timeUnit, countToPatterns); 488 } 489 for (String pluralCount : keywords) { 490 if (countToPatterns.get(pluralCount) == null || countToPatterns.get(pluralCount)[style] == null) { 491 // look through parents 492 searchInTree(resourceKey, style, timeUnit, pluralCount, pluralCount, countToPatterns); 493 } 494 } 495 } 496 } 497 498 // srcPluralCount is the original plural count on which the pattern is 499 // searched for. 500 // searchPluralCount is the fallback plural count. 501 // For example, to search for pattern for ""one" hour", 502 // "one" is the srcPluralCount, 503 // if the pattern is not found even in root, fallback to 504 // using patterns of plural count "other", 505 // then, "other" is the searchPluralCount. 506 private void searchInTree(String resourceKey, int styl, TimeUnit timeUnit, String srcPluralCount, 507 String searchPluralCount, Map<String, Object[]> countToPatterns) { 508 ULocale parentLocale = locale; 509 String srcTimeUnitName = timeUnit.toString(); 510 while (parentLocale != null) { 511 try { 512 // look for pattern for srcPluralCount in locale tree 513 ICUResourceBundle unitsRes = (ICUResourceBundle) UResourceBundle.getBundleInstance( 514 ICUData.ICU_UNIT_BASE_NAME, parentLocale); 515 unitsRes = unitsRes.getWithFallback(resourceKey); 516 ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(srcTimeUnitName); 517 String pattern = oneUnitRes.getStringWithFallback(searchPluralCount); 518 final MessageFormat messageFormat = new MessageFormat(pattern, locale); 519 Object[] pair = countToPatterns.get(srcPluralCount); 520 if (pair == null) { 521 pair = new Object[2]; 522 countToPatterns.put(srcPluralCount, pair); 523 } 524 pair[styl] = messageFormat; 525 return; 526 } catch (MissingResourceException e) { 527 } 528 parentLocale = parentLocale.getFallback(); 529 } 530 // if no unitsShort resource was found even after fallback to root locale 531 // then search the units resource fallback from the current level to root 532 if (parentLocale == null && resourceKey.equals("unitsShort")) { 533 searchInTree("units", styl, timeUnit, srcPluralCount, searchPluralCount, countToPatterns); 534 if (countToPatterns.get(srcPluralCount) != null 535 && countToPatterns.get(srcPluralCount)[styl] != null) { 536 return; 537 } 538 } 539 // if not found the pattern for this plural count at all, 540 // fall-back to plural count "other" 541 if (searchPluralCount.equals("other")) { 542 // set default fall back the same as the resource in root 543 MessageFormat messageFormat = null; 544 if (timeUnit == TimeUnit.SECOND) { 545 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_SECOND, locale); 546 } else if (timeUnit == TimeUnit.MINUTE) { 547 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_MINUTE, locale); 548 } else if (timeUnit == TimeUnit.HOUR) { 549 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_HOUR, locale); 550 } else if (timeUnit == TimeUnit.WEEK) { 551 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_WEEK, locale); 552 } else if (timeUnit == TimeUnit.DAY) { 553 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_DAY, locale); 554 } else if (timeUnit == TimeUnit.MONTH) { 555 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_MONTH, locale); 556 } else if (timeUnit == TimeUnit.YEAR) { 557 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_YEAR, locale); 558 } 559 Object[] pair = countToPatterns.get(srcPluralCount); 560 if (pair == null) { 561 pair = new Object[2]; 562 countToPatterns.put(srcPluralCount, pair); 563 } 564 pair[styl] = messageFormat; 565 } else { 566 // fall back to rule "other", and search in parents 567 searchInTree(resourceKey, styl, timeUnit, srcPluralCount, "other", countToPatterns); 568 } 569 } 570 571 // boilerplate code to make TimeUnitFormat otherwise follow the contract of 572 // MeasureFormat 573 574 575 /** 576 * @deprecated This API is ICU internal only. 577 * @hide draft / provisional / internal are hidden on Android 578 */ 579 @Deprecated 580 @Override 581 public StringBuilder formatMeasures( 582 StringBuilder appendTo, FieldPosition fieldPosition, Measure... measures) { 583 return mf.formatMeasures(appendTo, fieldPosition, measures); 584 } 585 586 /** 587 * @deprecated This API is ICU internal only. 588 * @hide draft / provisional / internal are hidden on Android 589 */ 590 @Deprecated 591 @Override 592 public MeasureFormat.FormatWidth getWidth() { 593 return mf.getWidth(); 594 } 595 596 /** 597 * @deprecated This API is ICU internal only. 598 * @hide draft / provisional / internal are hidden on Android 599 */ 600 @Deprecated 601 @Override 602 public NumberFormat getNumberFormat() { 603 return mf.getNumberFormat(); 604 } 605 606 /** 607 * @deprecated This API is ICU internal only. 608 * @hide draft / provisional / internal are hidden on Android 609 */ 610 @Deprecated 611 @Override 612 public Object clone() { 613 TimeUnitFormat result = (TimeUnitFormat) super.clone(); 614 result.format = (NumberFormat) format.clone(); 615 return result; 616 } 617 // End boilerplate. 618 619 // Serialization 620 621 private Object writeReplace() throws ObjectStreamException { 622 return mf.toTimeUnitProxy(); 623 } 624 625 // Preserve backward serialize backward compatibility. 626 private Object readResolve() throws ObjectStreamException { 627 return new TimeUnitFormat(locale, style, format); 628 } 629 } 630