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