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