• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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