1/* GENERATED SOURCE. DO NOT MODIFY. */ 2/* 3 ******************************************************************************* 4 * Copyright (C) 2013-2016, International Business Machines Corporation and 5 * others. All Rights Reserved. 6 ******************************************************************************* 7 */ 8package android.icu.text; 9 10import java.util.EnumMap; 11import java.util.Locale; 12 13import android.icu.impl.CalendarData; 14import android.icu.impl.DontCareFieldPosition; 15import android.icu.impl.ICUCache; 16import android.icu.impl.ICUResourceBundle; 17import android.icu.impl.SimpleCache; 18import android.icu.impl.SimplePatternFormatter; 19import android.icu.impl.StandardPlural; 20import android.icu.lang.UCharacter; 21import android.icu.util.Calendar; 22import android.icu.util.ICUException; 23import android.icu.util.ULocale; 24import android.icu.impl.UResource; 25import android.icu.util.UResourceBundle; 26 27 28/** 29 * Formats simple relative dates. There are two types of relative dates that 30 * it handles: 31 * <ul> 32 * <li>relative dates with a quantity e.g "in 5 days"</li> 33 * <li>relative dates without a quantity e.g "next Tuesday"</li> 34 * </ul> 35 * <p> 36 * This API is very basic and is intended to be a building block for more 37 * fancy APIs. The caller tells it exactly what to display in a locale 38 * independent way. While this class automatically provides the correct plural 39 * forms, the grammatical form is otherwise as neutral as possible. It is the 40 * caller's responsibility to handle cut-off logic such as deciding between 41 * displaying "in 7 days" or "in 1 week." This API supports relative dates 42 * involving one single unit. This API does not support relative dates 43 * involving compound units. 44 * e.g "in 5 days and 4 hours" nor does it support parsing. 45 * This class is both immutable and thread-safe. 46 * <p> 47 * Here are some examples of use: 48 * <blockquote> 49 * <pre> 50 * RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance(); 51 * fmt.format(1, Direction.NEXT, RelativeUnit.DAYS); // "in 1 day" 52 * fmt.format(3, Direction.NEXT, RelativeUnit.DAYS); // "in 3 days" 53 * fmt.format(3.2, Direction.LAST, RelativeUnit.YEARS); // "3.2 years ago" 54 * 55 * fmt.format(Direction.LAST, AbsoluteUnit.SUNDAY); // "last Sunday" 56 * fmt.format(Direction.THIS, AbsoluteUnit.SUNDAY); // "this Sunday" 57 * fmt.format(Direction.NEXT, AbsoluteUnit.SUNDAY); // "next Sunday" 58 * fmt.format(Direction.PLAIN, AbsoluteUnit.SUNDAY); // "Sunday" 59 * 60 * fmt.format(Direction.LAST, AbsoluteUnit.DAY); // "yesterday" 61 * fmt.format(Direction.THIS, AbsoluteUnit.DAY); // "today" 62 * fmt.format(Direction.NEXT, AbsoluteUnit.DAY); // "tomorrow" 63 * 64 * fmt.format(Direction.PLAIN, AbsoluteUnit.NOW); // "now" 65 * </pre> 66 * </blockquote> 67 * <p> 68 * In the future, we may add more forms, such as abbreviated/short forms 69 * (3 secs ago), and relative day periods ("yesterday afternoon"), etc. 70 */ 71public final class RelativeDateTimeFormatter { 72 73 /** 74 * The formatting style 75 * 76 */ 77 public static enum Style { 78 79 /** 80 * Everything spelled out. 81 */ 82 LONG, 83 84 /** 85 * Abbreviations used when possible. 86 */ 87 SHORT, 88 89 /** 90 * Use single letters when possible. 91 */ 92 NARROW; 93 94 private static final int INDEX_COUNT = 3; // NARROW.ordinal() + 1 95 } 96 97 /** 98 * Represents the unit for formatting a relative date. e.g "in 5 days" 99 * or "in 3 months" 100 */ 101 public static enum RelativeUnit { 102 103 /** 104 * Seconds 105 */ 106 SECONDS, 107 108 /** 109 * Minutes 110 */ 111 MINUTES, 112 113 /** 114 * Hours 115 */ 116 HOURS, 117 118 /** 119 * Days 120 */ 121 DAYS, 122 123 /** 124 * Weeks 125 */ 126 WEEKS, 127 128 /** 129 * Months 130 */ 131 MONTHS, 132 133 /** 134 * Years 135 */ 136 YEARS, 137 138 /** 139 * Quarters 140 * @deprecated This API is ICU internal only. 141 * @hide draft / provisional / internal are hidden on Android 142 */ 143 @Deprecated 144 QUARTERS, 145 } 146 147 /** 148 * Represents an absolute unit. 149 */ 150 public static enum AbsoluteUnit { 151 152 /** 153 * Sunday 154 */ 155 SUNDAY, 156 157 /** 158 * Monday 159 */ 160 MONDAY, 161 162 /** 163 * Tuesday 164 */ 165 TUESDAY, 166 167 /** 168 * Wednesday 169 */ 170 WEDNESDAY, 171 172 /** 173 * Thursday 174 */ 175 THURSDAY, 176 177 /** 178 * Friday 179 */ 180 FRIDAY, 181 182 /** 183 * Saturday 184 */ 185 SATURDAY, 186 187 /** 188 * Day 189 */ 190 DAY, 191 192 /** 193 * Week 194 */ 195 WEEK, 196 197 /** 198 * Month 199 */ 200 MONTH, 201 202 /** 203 * Year 204 */ 205 YEAR, 206 207 /** 208 * Now 209 */ 210 NOW, 211 212 /** 213 * Quarter 214 * @deprecated This API is ICU internal only. 215 * @hide draft / provisional / internal are hidden on Android 216 */ 217 @Deprecated 218 QUARTER, 219 } 220 221 /** 222 * Represents a direction for an absolute unit e.g "Next Tuesday" 223 * or "Last Tuesday" 224 */ 225 public static enum Direction { 226 /** 227 * Two before. Not fully supported in every locale 228 */ 229 LAST_2, 230 231 /** 232 * Last 233 */ 234 LAST, 235 236 /** 237 * This 238 */ 239 THIS, 240 241 /** 242 * Next 243 */ 244 NEXT, 245 246 /** 247 * Two after. Not fully supported in every locale 248 */ 249 NEXT_2, 250 251 /** 252 * Plain, which means the absence of a qualifier 253 */ 254 PLAIN; 255 } 256 257 /** 258 * Returns a RelativeDateTimeFormatter for the default locale. 259 */ 260 public static RelativeDateTimeFormatter getInstance() { 261 return getInstance(ULocale.getDefault(), null, Style.LONG, DisplayContext.CAPITALIZATION_NONE); 262 } 263 264 /** 265 * Returns a RelativeDateTimeFormatter for a particular locale. 266 * 267 * @param locale the locale. 268 * @return An instance of RelativeDateTimeFormatter. 269 */ 270 public static RelativeDateTimeFormatter getInstance(ULocale locale) { 271 return getInstance(locale, null, Style.LONG, DisplayContext.CAPITALIZATION_NONE); 272 } 273 274 /** 275 * Returns a RelativeDateTimeFormatter for a particular {@link java.util.Locale}. 276 * 277 * @param locale the {@link java.util.Locale}. 278 * @return An instance of RelativeDateTimeFormatter. 279 */ 280 public static RelativeDateTimeFormatter getInstance(Locale locale) { 281 return getInstance(ULocale.forLocale(locale)); 282 } 283 284 /** 285 * Returns a RelativeDateTimeFormatter for a particular locale that uses a particular 286 * NumberFormat object. 287 * 288 * @param locale the locale 289 * @param nf the number format object. It is defensively copied to ensure thread-safety 290 * and immutability of this class. 291 * @return An instance of RelativeDateTimeFormatter. 292 */ 293 public static RelativeDateTimeFormatter getInstance(ULocale locale, NumberFormat nf) { 294 return getInstance(locale, nf, Style.LONG, DisplayContext.CAPITALIZATION_NONE); 295 } 296 297 /** 298 * Returns a RelativeDateTimeFormatter for a particular locale that uses a particular 299 * NumberFormat object, style, and capitalization context 300 * 301 * @param locale the locale 302 * @param nf the number format object. It is defensively copied to ensure thread-safety 303 * and immutability of this class. May be null. 304 * @param style the style. 305 * @param capitalizationContext the capitalization context. 306 */ 307 public static RelativeDateTimeFormatter getInstance( 308 ULocale locale, 309 NumberFormat nf, 310 Style style, 311 DisplayContext capitalizationContext) { 312 RelativeDateTimeFormatterData data = cache.get(locale); 313 if (nf == null) { 314 nf = NumberFormat.getInstance(locale); 315 } else { 316 nf = (NumberFormat) nf.clone(); 317 } 318 return new RelativeDateTimeFormatter( 319 data.qualitativeUnitMap, 320 data.relUnitPatternMap, 321 new MessageFormat(data.dateTimePattern), 322 PluralRules.forLocale(locale), 323 nf, 324 style, 325 capitalizationContext, 326 capitalizationContext == DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ? 327 BreakIterator.getSentenceInstance(locale) : null, 328 locale); 329 } 330 331 /** 332 * Returns a RelativeDateTimeFormatter for a particular {@link java.util.Locale} that uses a 333 * particular NumberFormat object. 334 * 335 * @param locale the {@link java.util.Locale} 336 * @param nf the number format object. It is defensively copied to ensure thread-safety 337 * and immutability of this class. 338 * @return An instance of RelativeDateTimeFormatter. 339 */ 340 public static RelativeDateTimeFormatter getInstance(Locale locale, NumberFormat nf) { 341 return getInstance(ULocale.forLocale(locale), nf); 342 } 343 344 /** 345 * Formats a relative date with a quantity such as "in 5 days" or 346 * "3 months ago" 347 * @param quantity The numerical amount e.g 5. This value is formatted 348 * according to this object's {@link NumberFormat} object. 349 * @param direction NEXT means a future relative date; LAST means a past 350 * relative date. 351 * @param unit the unit e.g day? month? year? 352 * @return the formatted string 353 * @throws IllegalArgumentException if direction is something other than 354 * NEXT or LAST. 355 */ 356 public String format(double quantity, Direction direction, RelativeUnit unit) { 357 if (direction != Direction.LAST && direction != Direction.NEXT) { 358 throw new IllegalArgumentException("direction must be NEXT or LAST"); 359 } 360 String result; 361 int pastFutureIndex = (direction == Direction.NEXT ? 1 : 0); 362 363 // This class is thread-safe, yet numberFormat is not. To ensure thread-safety of this 364 // class we must guarantee that only one thread at a time uses our numberFormat. 365 synchronized (numberFormat) { 366 StringBuffer formatStr = new StringBuffer(); 367 DontCareFieldPosition fieldPosition = DontCareFieldPosition.INSTANCE; 368 StandardPlural pluralForm = QuantityFormatter.selectPlural(quantity, 369 numberFormat, pluralRules, formatStr, fieldPosition); 370 371 String formatter = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm); 372 result = SimplePatternFormatter.formatCompiledPattern(formatter, formatStr); 373 } 374 return adjustForContext(result); 375 376 } 377 378 private int[] styleToDateFormatSymbolsWidth = { 379 DateFormatSymbols.WIDE, DateFormatSymbols.SHORT, DateFormatSymbols.NARROW 380 }; 381 382 /** 383 * Formats a relative date without a quantity. 384 * @param direction NEXT, LAST, THIS, etc. 385 * @param unit e.g SATURDAY, DAY, MONTH 386 * @return the formatted string. If direction has a value that is documented as not being 387 * fully supported in every locale (for example NEXT_2 or LAST_2) then this function may 388 * return null to signal that no formatted string is available. 389 * @throws IllegalArgumentException if the direction is incompatible with 390 * unit this can occur with NOW which can only take PLAIN. 391 */ 392 public String format(Direction direction, AbsoluteUnit unit) { 393 if (unit == AbsoluteUnit.NOW && direction != Direction.PLAIN) { 394 throw new IllegalArgumentException("NOW can only accept direction PLAIN."); 395 } 396 String result; 397 // Get plain day of week names from DateFormatSymbols. 398 if ((direction == Direction.PLAIN) && (AbsoluteUnit.SUNDAY.ordinal() <= unit.ordinal() && 399 unit.ordinal() <= AbsoluteUnit.SATURDAY.ordinal())) { 400 // Convert from AbsoluteUnit days to Calendar class indexing. 401 int dateSymbolsDayOrdinal = (unit.ordinal() - AbsoluteUnit.SUNDAY.ordinal()) + Calendar.SUNDAY; 402 String[] dayNames = 403 dateFormatSymbols.getWeekdays(DateFormatSymbols.STANDALONE, 404 styleToDateFormatSymbolsWidth[style.ordinal()]); 405 result = dayNames[dateSymbolsDayOrdinal]; 406 } else { 407 // Not PLAIN, or not a weekday. 408 result = getAbsoluteUnitString(style, unit, direction); 409 } 410 return result != null ? adjustForContext(result) : null; 411 } 412 413 /** 414 * Gets the string value from qualitativeUnitMap with fallback based on style. 415 * @param style 416 * @param unit 417 * @param direction 418 * @return 419 */ 420 private String getAbsoluteUnitString(Style style, AbsoluteUnit unit, Direction direction) { 421 EnumMap<AbsoluteUnit, EnumMap<Direction, String>> unitMap; 422 EnumMap<Direction, String> dirMap; 423 424 do { 425 unitMap = qualitativeUnitMap.get(style); 426 if (unitMap != null) { 427 dirMap = unitMap.get(unit); 428 if (dirMap != null) { 429 String result = dirMap.get(direction); 430 if (result != null) { 431 return result; 432 } 433 } 434 435 } 436 437 // Consider other styles from alias fallback. 438 // Data loading guaranteed no endless loops. 439 } while ((style = fallbackCache[style.ordinal()]) != null); 440 return null; 441 } 442 443 /** 444 * Combines a relative date string and a time string in this object's 445 * locale. This is done with the same date-time separator used for the 446 * default calendar in this locale. 447 * @param relativeDateString the relative date e.g 'yesterday' 448 * @param timeString the time e.g '3:45' 449 * @return the date and time concatenated according to the default 450 * calendar in this locale e.g 'yesterday, 3:45' 451 */ 452 public String combineDateAndTime(String relativeDateString, String timeString) { 453 return this.combinedDateAndTime.format( 454 new Object[]{timeString, relativeDateString}, new StringBuffer(), null).toString(); 455 } 456 457 /** 458 * Returns a copy of the NumberFormat this object is using. 459 * @return A copy of the NumberFormat. 460 */ 461 public NumberFormat getNumberFormat() { 462 // This class is thread-safe, yet numberFormat is not. To ensure thread-safety of this 463 // class we must guarantee that only one thread at a time uses our numberFormat. 464 synchronized (numberFormat) { 465 return (NumberFormat) numberFormat.clone(); 466 } 467 } 468 469 /** 470 * Return capitalization context. 471 */ 472 public DisplayContext getCapitalizationContext() { 473 return capitalizationContext; 474 } 475 476 /** 477 * Return style 478 */ 479 public Style getFormatStyle() { 480 return style; 481 } 482 483 private String adjustForContext(String originalFormattedString) { 484 if (breakIterator == null || originalFormattedString.length() == 0 485 || !UCharacter.isLowerCase(UCharacter.codePointAt(originalFormattedString, 0))) { 486 return originalFormattedString; 487 } 488 synchronized (breakIterator) { 489 return UCharacter.toTitleCase( 490 locale, 491 originalFormattedString, 492 breakIterator, 493 UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT); 494 } 495 } 496 497 private RelativeDateTimeFormatter( 498 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, 499 EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap, 500 MessageFormat combinedDateAndTime, 501 PluralRules pluralRules, 502 NumberFormat numberFormat, 503 Style style, 504 DisplayContext capitalizationContext, 505 BreakIterator breakIterator, 506 ULocale locale) { 507 this.qualitativeUnitMap = qualitativeUnitMap; 508 this.patternMap = patternMap; 509 this.combinedDateAndTime = combinedDateAndTime; 510 this.pluralRules = pluralRules; 511 this.numberFormat = numberFormat; 512 this.style = style; 513 if (capitalizationContext.type() != DisplayContext.Type.CAPITALIZATION) { 514 throw new IllegalArgumentException(capitalizationContext.toString()); 515 } 516 this.capitalizationContext = capitalizationContext; 517 this.breakIterator = breakIterator; 518 this.locale = locale; 519 this.dateFormatSymbols = new DateFormatSymbols(locale); 520 } 521 522 private String getRelativeUnitPluralPattern( 523 Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm) { 524 if (pluralForm != StandardPlural.OTHER) { 525 String formatter = getRelativeUnitPattern(style, unit, pastFutureIndex, pluralForm); 526 if (formatter != null) { 527 return formatter; 528 } 529 } 530 return getRelativeUnitPattern(style, unit, pastFutureIndex, StandardPlural.OTHER); 531 } 532 533 private String getRelativeUnitPattern( 534 Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm) { 535 int pluralIndex = pluralForm.ordinal(); 536 do { 537 EnumMap<RelativeUnit, String[][]> unitMap = patternMap.get(style); 538 if (unitMap != null) { 539 String[][] spfCompiledPatterns = unitMap.get(unit); 540 if (spfCompiledPatterns != null) { 541 if (spfCompiledPatterns[pastFutureIndex][pluralIndex] != null) { 542 return spfCompiledPatterns[pastFutureIndex][pluralIndex]; 543 } 544 } 545 546 } 547 548 // Consider other styles from alias fallback. 549 // Data loading guaranteed no endless loops. 550 } while ((style = fallbackCache[style.ordinal()]) != null); 551 return null; 552 } 553 554 private final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap; 555 private final EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap; 556 557 private final MessageFormat combinedDateAndTime; 558 private final PluralRules pluralRules; 559 private final NumberFormat numberFormat; 560 561 private final Style style; 562 private final DisplayContext capitalizationContext; 563 private final BreakIterator breakIterator; 564 private final ULocale locale; 565 566 private final DateFormatSymbols dateFormatSymbols; 567 568 private static final Style fallbackCache[] = new Style[Style.INDEX_COUNT]; 569 570 private static class RelativeDateTimeFormatterData { 571 public RelativeDateTimeFormatterData( 572 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, 573 EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap, 574 String dateTimePattern) { 575 this.qualitativeUnitMap = qualitativeUnitMap; 576 this.relUnitPatternMap = relUnitPatternMap; 577 578 this.dateTimePattern = dateTimePattern; 579 } 580 581 public final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap; 582 EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap; 583 public final String dateTimePattern; // Example: "{1}, {0}" 584 } 585 586 private static class Cache { 587 private final ICUCache<String, RelativeDateTimeFormatterData> cache = 588 new SimpleCache<String, RelativeDateTimeFormatterData>(); 589 590 public RelativeDateTimeFormatterData get(ULocale locale) { 591 String key = locale.toString(); 592 RelativeDateTimeFormatterData result = cache.get(key); 593 if (result == null) { 594 result = new Loader(locale).load(); 595 cache.put(key, result); 596 } 597 return result; 598 } 599 } 600 601 private static Direction keyToDirection(UResource.Key key) { 602 if (key.contentEquals("-2")) { 603 return Direction.LAST_2; 604 } 605 if (key.contentEquals("-1")) { 606 return Direction.LAST; 607 } 608 if (key.contentEquals("0")) { 609 return Direction.THIS; 610 } 611 if (key.contentEquals("1")) { 612 return Direction.NEXT; 613 } 614 if (key.contentEquals("2")) { 615 return Direction.NEXT_2; 616 } 617 return null; 618 } 619 620 /** 621 * Sink for enumerating all of the relative data time formatter names. 622 * Contains inner sink classes, each one corresponding to a type of resource table. 623 * The outer sink handles the top-level 'fields'. 624 * 625 * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root): 626 * Only store a value if it is still missing, that is, it has not been overridden. 627 * 628 * C++: Each inner sink class has a reference to the main outer sink. 629 * Java: Use non-static inner classes instead. 630 */ 631 private static final class RelDateTimeFmtDataSink extends UResource.TableSink { 632 // For white list of units to handle in RelativeDateTimeFormatter. 633 private static enum DateTimeUnit { 634 SECOND(RelativeUnit.SECONDS, null), 635 MINUTE(RelativeUnit.MINUTES, null), 636 HOUR(RelativeUnit.HOURS, null), 637 DAY(RelativeUnit.DAYS, AbsoluteUnit.DAY), 638 WEEK(RelativeUnit.WEEKS, AbsoluteUnit.WEEK), 639 MONTH(RelativeUnit.MONTHS, AbsoluteUnit.MONTH), 640 QUARTER(RelativeUnit.QUARTERS, AbsoluteUnit.QUARTER), 641 YEAR(RelativeUnit.YEARS, AbsoluteUnit.YEAR), 642 SUNDAY(null, AbsoluteUnit.SUNDAY), 643 MONDAY(null, AbsoluteUnit.MONDAY), 644 TUESDAY(null, AbsoluteUnit.TUESDAY), 645 WEDNESDAY(null, AbsoluteUnit.WEDNESDAY), 646 THURSDAY(null, AbsoluteUnit.THURSDAY), 647 FRIDAY(null, AbsoluteUnit.FRIDAY), 648 SATURDAY(null, AbsoluteUnit.SATURDAY); 649 650 RelativeUnit relUnit; 651 AbsoluteUnit absUnit; 652 653 DateTimeUnit(RelativeUnit relUnit, AbsoluteUnit absUnit) { 654 this.relUnit = relUnit; 655 this.absUnit = absUnit; 656 } 657 658 private static final DateTimeUnit orNullFromString(CharSequence keyword) { 659 // Quick check from string to enum. 660 switch (keyword.length()) { 661 case 3: 662 if ("day".contentEquals(keyword)) { 663 return DAY; 664 } else if ("sun".contentEquals(keyword)) { 665 return SUNDAY; 666 } else if ("mon".contentEquals(keyword)) { 667 return MONDAY; 668 } else if ("tue".contentEquals(keyword)) { 669 return TUESDAY; 670 } else if ("wed".contentEquals(keyword)) { 671 return WEDNESDAY; 672 } else if ("thu".contentEquals(keyword)) { 673 return THURSDAY; 674 } else if ("fri".contentEquals(keyword)) { 675 return FRIDAY; 676 } else if ("sat".contentEquals(keyword)) { 677 return SATURDAY; 678 } 679 break; 680 case 4: 681 if ("hour".contentEquals(keyword)) { 682 return HOUR; 683 } else if ("week".contentEquals(keyword)) { 684 return WEEK; 685 } else if ("year".contentEquals(keyword)) { 686 return YEAR; 687 } 688 break; 689 case 5: 690 if ("month".contentEquals(keyword)) { 691 return MONTH; 692 } 693 break; 694 case 6: 695 if ("minute".contentEquals(keyword)) { 696 return MINUTE; 697 }else if ("second".contentEquals(keyword)) { 698 return SECOND; 699 } 700 break; 701 case 7: 702 if ("quarter".contentEquals(keyword)) { 703 return QUARTER; // TODO: Check @provisional 704 } 705 break; 706 default: 707 break; 708 } 709 return null; 710 } 711 } 712 713 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap = 714 new EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>>(Style.class); 715 EnumMap<Style, EnumMap<RelativeUnit, String[][]>> styleRelUnitPatterns = 716 new EnumMap<Style, EnumMap<RelativeUnit, String[][]>>(Style.class); 717 718 private ULocale ulocale = null; 719 720 StringBuilder sb = new StringBuilder(); 721 722 public RelDateTimeFmtDataSink(ULocale locale) { 723 ulocale = locale; 724 } 725 726 // Values keep between levels of parsing the CLDR data. 727 int pastFutureIndex; 728 Style style; // {LONG, SHORT, NARROW} Derived from unit key string. 729 DateTimeUnit unit; // From the unit key string, with the style (e.g., "-short") separated out. 730 731 private Style styleFromKey(UResource.Key key) { 732 if (key.endsWith("-short")) { 733 return Style.SHORT; 734 } else if (key.endsWith("-narrow")) { 735 return Style.NARROW; 736 } else { 737 return Style.LONG; 738 } 739 } 740 741 private Style styleFromAlias(UResource.Value value) { 742 String s = value.getAliasString(); 743 if (s.endsWith("-short")) { 744 return Style.SHORT; 745 } else if (s.endsWith("-narrow")) { 746 return Style.NARROW; 747 } else { 748 return Style.LONG; 749 } 750 } 751 752 private static int styleSuffixLength(Style style) { 753 switch (style) { 754 case SHORT: return 6; 755 case NARROW: return 7; 756 default: return 0; 757 } 758 } 759 760 @Override 761 public void put(UResource.Key key, UResource.Value value) { 762 // Parse and store aliases. 763 if (value.getType() != ICUResourceBundle.ALIAS) { return; } 764 765 Style sourceStyle = styleFromKey(key); 766 int limit = key.length() - styleSuffixLength(sourceStyle); 767 DateTimeUnit unit = DateTimeUnit.orNullFromString(key.substring(0, limit)); 768 if (unit != null) { 769 // Record the fallback chain for the values. 770 // At formatting time, limit to 2 levels of fallback. 771 Style targetStyle = styleFromAlias(value); 772 if (sourceStyle == targetStyle) { 773 throw new ICUException("Invalid style fallback from " + sourceStyle + " to itself"); 774 } 775 776 // Check for inconsistent fallbacks. 777 if (fallbackCache[sourceStyle.ordinal()] == null) { 778 fallbackCache[sourceStyle.ordinal()] = targetStyle; 779 } else if (fallbackCache[sourceStyle.ordinal()] != targetStyle) { 780 throw new ICUException( 781 "Inconsistent style fallback for style " + sourceStyle + " to " + targetStyle); 782 } 783 } 784 } 785 786 @Override 787 public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) { 788 // Get base unit and style from the key value. 789 style = styleFromKey(key); 790 int limit = key.length() - styleSuffixLength(style); 791 String unitString = key.substring(0, limit); 792 793 // Process only if unitString is in the white list. 794 unit = DateTimeUnit.orNullFromString(unitString); 795 if (unit == null) { 796 return null; 797 } 798 return unitSink; // Continue parsing this path. 799 } 800 801 // Sinks for additional levels under /fields/*/relative/ and /fields/*/relativeTime/ 802 803 // Sets values under relativeTime paths, e.g., "hour/relativeTime/future/one" 804 class RelativeTimeDetailSink extends UResource.TableSink { 805 @Override 806 public void put(UResource.Key key, UResource.Value value) { 807 /* Make two lists of simplePatternFmtList, one for past and one for future. 808 * Set a SimplePatternFormatter for the <style, relative unit, plurality> 809 * 810 * Fill in values for the particular plural given, e.g., ONE, FEW, OTHER, etc. 811 */ 812 EnumMap<RelativeUnit, String[][]> unitPatterns = 813 styleRelUnitPatterns.get(style); 814 if (unitPatterns == null) { 815 unitPatterns = new EnumMap<RelativeUnit, String[][]>(RelativeUnit.class); 816 styleRelUnitPatterns.put(style, unitPatterns); 817 } 818 String[][] patterns = unitPatterns.get(unit.relUnit); 819 if (patterns == null) { 820 patterns = new String[2][StandardPlural.COUNT]; 821 unitPatterns.put(unit.relUnit, patterns); 822 } 823 int pluralIndex = StandardPlural.indexFromString(key.toString()); 824 if (patterns[pastFutureIndex][pluralIndex] == null) { 825 patterns[pastFutureIndex][pluralIndex] = 826 SimplePatternFormatter.compileToStringMinMaxPlaceholders(value.getString(), 827 sb, 0, 1); 828 } 829 } 830 } 831 RelativeTimeDetailSink relativeTimeDetailSink = new RelativeTimeDetailSink(); 832 833 // Handles "relativeTime" entries, e.g., under "day", "hour", "minute", "minute-short", etc. 834 class RelativeTimeSink extends UResource.TableSink { 835 @Override 836 public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) { 837 if (key.contentEquals("past")) { 838 pastFutureIndex = 0; 839 } else if (key.contentEquals("future")) { 840 pastFutureIndex = 1; 841 } else { 842 return null; 843 } 844 if (unit.relUnit == null) { 845 return null; 846 } 847 return relativeTimeDetailSink; 848 } 849 } 850 RelativeTimeSink relativeTimeSink = new RelativeTimeSink(); 851 852 // Handles "relative" entries, e.g., under "day", "day-short", "fri", "fri-narrow", "fri-short", etc. 853 class RelativeSink extends UResource.TableSink { 854 @Override 855 public void put(UResource.Key key, UResource.Value value) { 856 857 EnumMap<AbsoluteUnit, EnumMap<Direction, String>> absMap = qualitativeUnitMap.get(style); 858 859 if (unit.relUnit == RelativeUnit.SECONDS) { 860 if (key.contentEquals("0")) { 861 // Handle Zero seconds for "now". 862 EnumMap<Direction, String> unitStrings = absMap.get(AbsoluteUnit.NOW); 863 if (unitStrings == null) { 864 unitStrings = new EnumMap<Direction, String>(Direction.class); 865 absMap.put(AbsoluteUnit.NOW, unitStrings); 866 } 867 if (unitStrings.get(Direction.PLAIN) == null) { 868 unitStrings.put(Direction.PLAIN, value.getString()); 869 } 870 return; 871 } 872 } 873 Direction keyDirection = keyToDirection(key); 874 if (keyDirection == null) { 875 return; 876 } 877 AbsoluteUnit absUnit = unit.absUnit; 878 if (absUnit == null) { 879 return; 880 } 881 882 if (absMap == null) { 883 absMap = new EnumMap<AbsoluteUnit, EnumMap<Direction, String>>(AbsoluteUnit.class); 884 qualitativeUnitMap.put(style, absMap); 885 } 886 EnumMap<Direction, String> dirMap = absMap.get(absUnit); 887 if (dirMap == null) { 888 dirMap = new EnumMap<Direction, String>(Direction.class); 889 absMap.put(absUnit, dirMap); 890 } 891 if (dirMap.get(keyDirection) == null) { 892 // Do not override values already entered. 893 dirMap.put(keyDirection, value.getString()); 894 } 895 } 896 } 897 RelativeSink relativeSink = new RelativeSink(); 898 899 // Handles entries under units, recognizing "relative" and "relativeTime" entries. 900 class UnitSink extends UResource.TableSink { 901 @Override 902 public void put(UResource.Key key, UResource.Value value) { 903 if (key.contentEquals("dn")) { 904 // Handle Display Name for PLAIN direction for some units. 905 AbsoluteUnit absUnit = unit.absUnit; 906 if (absUnit == null) { 907 return; // Not interesting. 908 } 909 EnumMap<AbsoluteUnit, EnumMap<Direction, String>> unitMap = 910 qualitativeUnitMap.get(style); 911 if (unitMap == null) { 912 unitMap = new EnumMap<AbsoluteUnit, EnumMap<Direction, String>>(AbsoluteUnit.class); 913 qualitativeUnitMap.put(style, unitMap); 914 } 915 EnumMap<Direction,String> dirMap = unitMap.get(absUnit); 916 if (dirMap == null) { 917 dirMap = new EnumMap<Direction,String>(Direction.class); 918 unitMap.put(absUnit, dirMap); 919 } 920 if (dirMap.get(Direction.PLAIN) == null) { 921 String displayName = value.toString(); 922 // TODO(Travis Keep): This is a hack to get around CLDR bug 6818. 923 if (ulocale.getLanguage().equals("en")) { 924 displayName = displayName.toLowerCase(Locale.ROOT); 925 } 926 dirMap.put(Direction.PLAIN, displayName); 927 } 928 } 929 } 930 931 @Override 932 public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) { 933 if (key.contentEquals("relative")) { 934 return relativeSink; 935 } else if (key.contentEquals("relativeTime")) { 936 return relativeTimeSink; 937 } 938 return null; 939 } 940 } 941 UnitSink unitSink = new UnitSink(); 942 } 943 944 private static class Loader { 945 private final ULocale ulocale; 946 947 public Loader(ULocale ulocale) { 948 this.ulocale = ulocale; 949 } 950 951 public RelativeDateTimeFormatterData load() { 952 // Sink for traversing data. 953 RelDateTimeFmtDataSink sink = new RelDateTimeFmtDataSink(ulocale); 954 ICUResourceBundle r = (ICUResourceBundle)UResourceBundle. 955 getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale); 956 957 // Use sink mechanism to traverse data structure. 958 r.getAllTableItemsWithFallback("fields", sink); 959 960 // Check fallbacks array for loops or too many levels. 961 for (Style testStyle : Style.values()) { 962 Style newStyle1 = fallbackCache[testStyle.ordinal()]; 963 // Data loading guaranteed newStyle1 != testStyle. 964 if (newStyle1 != null) { 965 Style newStyle2 = fallbackCache[newStyle1.ordinal()]; 966 if (newStyle2 != null) { 967 // No fallback should take more than 2 steps. 968 if (fallbackCache[newStyle2.ordinal()] != null) { 969 throw new IllegalStateException("Style fallback too deep"); 970 } 971 } 972 } 973 } 974 975 // TODO: Replace this use of CalendarData. 976 CalendarData calData = new CalendarData( 977 ulocale, r.getStringWithFallback("calendar/default")); 978 979 return new RelativeDateTimeFormatterData( 980 sink.qualitativeUnitMap, sink.styleRelUnitPatterns, 981 calData.getDateTimePattern()); 982 } 983 } 984 985 private static final Cache cache = new Cache(); 986} 987