• 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) 2006-2016, Google, International Business Machines Corporation
7  * and others. All Rights Reserved.
8  ********************************************************************************
9  */
10 package ohos.global.icu.text;
11 
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.BitSet;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.EnumSet;
18 import java.util.HashMap;
19 import java.util.HashSet;
20 import java.util.Iterator;
21 import java.util.LinkedHashMap;
22 import java.util.LinkedHashSet;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.Map;
26 import java.util.MissingResourceException;
27 import java.util.Set;
28 import java.util.TreeMap;
29 import java.util.TreeSet;
30 
31 import ohos.global.icu.impl.ICUCache;
32 import ohos.global.icu.impl.ICUData;
33 import ohos.global.icu.impl.ICUResourceBundle;
34 import ohos.global.icu.impl.PatternTokenizer;
35 import ohos.global.icu.impl.SimpleCache;
36 import ohos.global.icu.impl.SimpleFormatterImpl;
37 import ohos.global.icu.impl.UResource;
38 import ohos.global.icu.util.Calendar;
39 import ohos.global.icu.util.Freezable;
40 import ohos.global.icu.util.ICUCloneNotSupportedException;
41 import ohos.global.icu.util.Region;
42 import ohos.global.icu.util.ULocale;
43 import ohos.global.icu.util.ULocale.Category;
44 import ohos.global.icu.util.UResourceBundle;
45 
46 /**
47  * This class provides flexible generation of date format patterns, like
48  * "yy-MM-dd". The user can build up the generator by adding successive
49  * patterns. Once that is done, a query can be made using a "skeleton", which is
50  * a pattern which just includes the desired fields and lengths. The generator
51  * will return the "best fit" pattern corresponding to that skeleton.
52  * <p>
53  * The main method people will use is getBestPattern(String skeleton), since
54  * normally this class is pre-built with data from a particular locale. However,
55  * generators can be built directly from other data as well.
56  */
57 public class DateTimePatternGenerator implements Freezable<DateTimePatternGenerator>, Cloneable {
58     private static final boolean DEBUG = false;
59 
60     // debugging flags
61     //static boolean SHOW_DISTANCE = false;
62     // TODO add hack to fix months for CJK, as per bug ticket 1099
63 
64     /**
65      * Create empty generator, to be constructed with addPattern(...) etc.
66      */
getEmptyInstance()67     public static DateTimePatternGenerator getEmptyInstance() {
68         DateTimePatternGenerator instance = new DateTimePatternGenerator();
69         instance.addCanonicalItems();
70         instance.fillInMissing();
71         return instance;
72     }
73 
74     /**
75      * Only for use by subclasses
76      */
DateTimePatternGenerator()77     protected DateTimePatternGenerator() {
78     }
79 
80     /**
81      * Construct a flexible generator according to data for the default <code>FORMAT</code> locale.
82      * @see Category#FORMAT
83      */
getInstance()84     public static DateTimePatternGenerator getInstance() {
85         return getInstance(ULocale.getDefault(Category.FORMAT));
86     }
87 
88     /**
89      * Construct a flexible generator according to data for a given locale.
90      * @param uLocale The locale to pass.
91      */
getInstance(ULocale uLocale)92     public static DateTimePatternGenerator getInstance(ULocale uLocale) {
93         return getFrozenInstance(uLocale).cloneAsThawed();
94     }
95 
96     /**
97      * Construct a flexible generator according to data for a given locale.
98      * @param locale The {@link java.util.Locale} to pass.
99      */
getInstance(Locale locale)100     public static DateTimePatternGenerator getInstance(Locale locale) {
101         return getInstance(ULocale.forLocale(locale));
102     }
103 
104     /**
105      * Construct a frozen instance of DateTimePatternGenerator for a
106      * given locale.  This method returns a cached frozen instance of
107      * DateTimePatternGenerator, so less expensive than the regular
108      * factory method.
109      * @param uLocale The locale to pass.
110      * @return A frozen DateTimePatternGenerator.
111      * @deprecated This API is ICU internal only.
112      * @hide deprecated on icu4j-org
113      * @hide draft / provisional / internal are hidden on OHOS
114      */
115     @Deprecated
getFrozenInstance(ULocale uLocale)116     public static DateTimePatternGenerator getFrozenInstance(ULocale uLocale) {
117         String localeKey = uLocale.toString();
118         DateTimePatternGenerator result = DTPNG_CACHE.get(localeKey);
119         if (result != null) {
120             return result;
121         }
122 
123         result = new DateTimePatternGenerator();
124         result.initData(uLocale);
125 
126         // freeze and cache
127         result.freeze();
128         DTPNG_CACHE.put(localeKey, result);
129         return result;
130     }
131 
initData(ULocale uLocale)132     private void initData(ULocale uLocale) {
133         // This instance of PatternInfo is required for calling some functions.  It is used for
134         // passing additional information to the caller.  We won't use this extra information, but
135         // we still need to make a temporary instance.
136         PatternInfo returnInfo = new PatternInfo();
137 
138         addCanonicalItems();
139         addICUPatterns(returnInfo, uLocale);
140         addCLDRData(returnInfo, uLocale);
141         setDateTimeFromCalendar(uLocale);
142         setDecimalSymbols(uLocale);
143         getAllowedHourFormats(uLocale);
144         fillInMissing();
145     }
146 
addICUPatterns(PatternInfo returnInfo, ULocale uLocale)147     private void addICUPatterns(PatternInfo returnInfo, ULocale uLocale) {
148         // first load with the ICU patterns
149         for (int i = DateFormat.FULL; i <= DateFormat.SHORT; ++i) {
150             SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(i, uLocale);
151             addPattern(df.toPattern(), false, returnInfo);
152             df = (SimpleDateFormat) DateFormat.getTimeInstance(i, uLocale);
153             addPattern(df.toPattern(), false, returnInfo);
154 
155             if (i == DateFormat.SHORT) {
156                 consumeShortTimePattern(df.toPattern(), returnInfo);
157             }
158         }
159     }
160 
getCalendarTypeToUse(ULocale uLocale)161     private String getCalendarTypeToUse(ULocale uLocale) {
162         // Get the correct calendar type
163         // TODO: C++ and Java are inconsistent (see #9952).
164         String calendarTypeToUse = uLocale.getKeywordValue("calendar");
165         if ( calendarTypeToUse == null ) {
166             String[] preferredCalendarTypes = Calendar.getKeywordValuesForLocale("calendar", uLocale, true);
167             calendarTypeToUse = preferredCalendarTypes[0]; // the most preferred calendar
168         }
169         if ( calendarTypeToUse == null ) {
170             calendarTypeToUse = "gregorian"; // fallback
171         }
172         return calendarTypeToUse;
173     }
174 
consumeShortTimePattern(String shortTimePattern, PatternInfo returnInfo)175     private void consumeShortTimePattern(String shortTimePattern, PatternInfo returnInfo) {
176         // keep this pattern to populate other time field
177         // combination patterns by hackTimes later in this method.
178         // ICU-20383 No longer set defaultHourFormatChar to the hour format character from
179         // this pattern; instead it is set from LOCALE_TO_ALLOWED_HOUR which now
180         // includes entries for both preferred and allowed formats.
181 
182         // some languages didn't add mm:ss or HH:mm, so put in a hack to compute that from the short time.
183         hackTimes(returnInfo, shortTimePattern);
184     }
185 
186     private class AppendItemFormatsSink extends UResource.Sink {
187         @Override
put(UResource.Key key, UResource.Value value, boolean noFallback)188         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
189             UResource.Table itemsTable = value.getTable();
190             for (int i = 0; itemsTable.getKeyAndValue(i, key, value); ++i) {
191                 int field = getAppendFormatNumber(key);
192                 assert field != -1;
193                 if (getAppendItemFormat(field) == null) {
194                     setAppendItemFormat(field, value.toString());
195                 }
196             }
197         }
198     }
199 
200     private class AppendItemNamesSink extends UResource.Sink {
201         @Override
put(UResource.Key key, UResource.Value value, boolean noFallback)202         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
203             UResource.Table itemsTable = value.getTable();
204             for (int i = 0; itemsTable.getKeyAndValue(i, key, value); ++i) {
205                 if (value.getType() != UResourceBundle.TABLE) {
206                     // Typically get either UResourceBundle.TABLE = 2 or ICUResourceBundle.ALIAS = 3.
207                     // Currently fillInMissing() is being used instead of following the ALIAS, so
208                     // skip ALIAS entries which cause UResourceTypeMismatchException in the line
209                     // UResource.Table detailsTable = value.getTable()
210                     continue;
211                 }
212                 int fieldAndWidth = getCLDRFieldAndWidthNumber(key);
213                 if (fieldAndWidth == -1) { continue; }
214                 int field = fieldAndWidth / DisplayWidth.COUNT;
215                 DisplayWidth width = CLDR_FIELD_WIDTH[fieldAndWidth % DisplayWidth.COUNT];
216                 UResource.Table detailsTable = value.getTable();
217                 for (int j = 0; detailsTable.getKeyAndValue(j, key, value); ++j) {
218                     if (!key.contentEquals("dn")) continue;
219                     if (getFieldDisplayName(field, width) == null) {
220                         setFieldDisplayName(field, width, value.toString());
221                     }
222                     break;
223                 }
224             }
225         }
226     }
227 
fillInMissing()228     private void fillInMissing() {
229         for (int i = 0; i < TYPE_LIMIT; ++i) {
230             if (getAppendItemFormat(i) == null) {
231                 setAppendItemFormat(i, "{0} \u251C{2}: {1}\u2524");
232             }
233             if (getFieldDisplayName(i, DisplayWidth.WIDE) == null) {
234                 setFieldDisplayName(i, DisplayWidth.WIDE, "F" + i);
235             }
236             if (getFieldDisplayName(i, DisplayWidth.ABBREVIATED) == null) {
237                 setFieldDisplayName(i, DisplayWidth.ABBREVIATED, getFieldDisplayName(i, DisplayWidth.WIDE));
238             }
239             if (getFieldDisplayName(i, DisplayWidth.NARROW) == null) {
240                 setFieldDisplayName(i, DisplayWidth.NARROW, getFieldDisplayName(i, DisplayWidth.ABBREVIATED));
241             }
242        }
243     }
244 
245     private class AvailableFormatsSink extends UResource.Sink {
246         PatternInfo returnInfo;
AvailableFormatsSink(PatternInfo returnInfo)247         public AvailableFormatsSink(PatternInfo returnInfo) {
248             this.returnInfo = returnInfo;
249         }
250 
251         @Override
put(UResource.Key key, UResource.Value value, boolean isRoot)252         public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
253             UResource.Table formatsTable = value.getTable();
254             for (int i = 0; formatsTable.getKeyAndValue(i, key, value); ++i) {
255                 String formatKey = key.toString();
256                 if (!isAvailableFormatSet(formatKey)) {
257                     setAvailableFormat(formatKey);
258                     // Add pattern with its associated skeleton. Override any duplicate derived from std patterns,
259                     // but not a previous availableFormats entry:
260                     String formatValue = value.toString();
261                     addPatternWithSkeleton(formatValue, formatKey, !isRoot, returnInfo);
262                 }
263             }
264         }
265     }
266 
addCLDRData(PatternInfo returnInfo, ULocale uLocale)267     private void addCLDRData(PatternInfo returnInfo, ULocale uLocale) {
268         ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, uLocale);
269         String calendarTypeToUse = getCalendarTypeToUse(uLocale);
270 
271         //      ICU4J getWithFallback does not work well when
272         //      1) A nested table is an alias to /LOCALE/...
273         //      2) getWithFallback is called multiple times for going down hierarchical resource path
274         //      #9987 resolved the issue of alias table when full path is specified in getWithFallback,
275         //      but there is no easy solution when the equivalent operation is done by multiple operations.
276         //      This issue is addressed in #9964.
277 
278         // Load append item formats.
279         AppendItemFormatsSink appendItemFormatsSink = new AppendItemFormatsSink();
280         try {
281             rb.getAllItemsWithFallback(
282                     "calendar/" + calendarTypeToUse + "/appendItems",
283                     appendItemFormatsSink);
284         }catch(MissingResourceException e) {
285         }
286 
287         // Load CLDR item names.
288         AppendItemNamesSink appendItemNamesSink = new AppendItemNamesSink();
289         try {
290             rb.getAllItemsWithFallback(
291                     "fields",
292                     appendItemNamesSink);
293         }catch(MissingResourceException e) {
294         }
295 
296         // Load the available formats from CLDR.
297         AvailableFormatsSink availableFormatsSink = new AvailableFormatsSink(returnInfo);
298         try {
299             rb.getAllItemsWithFallback(
300                     "calendar/" + calendarTypeToUse + "/availableFormats",
301                     availableFormatsSink);
302         } catch (MissingResourceException e) {
303         }
304     }
305 
setDateTimeFromCalendar(ULocale uLocale)306     private void setDateTimeFromCalendar(ULocale uLocale) {
307         String dateTimeFormat = Calendar.getDateTimePattern(Calendar.getInstance(uLocale), uLocale, DateFormat.MEDIUM);
308         setDateTimeFormat(dateTimeFormat);
309     }
310 
setDecimalSymbols(ULocale uLocale)311     private void setDecimalSymbols(ULocale uLocale) {
312         // decimal point for seconds
313         DecimalFormatSymbols dfs = new DecimalFormatSymbols(uLocale);
314         setDecimal(String.valueOf(dfs.getDecimalSeparator()));
315     }
316 
317     private static final String[] LAST_RESORT_ALLOWED_HOUR_FORMAT = {"H"};
318 
getAllowedHourFormatsLangCountry(String language, String country)319     private String[] getAllowedHourFormatsLangCountry(String language, String country) {
320         String langCountry = language + "_" + country;
321         String[] list = LOCALE_TO_ALLOWED_HOUR.get(langCountry);
322         if (list == null) {
323             list = LOCALE_TO_ALLOWED_HOUR.get(country);
324         }
325         return list;
326     }
327 
getAllowedHourFormats(ULocale uLocale)328     private void getAllowedHourFormats(ULocale uLocale) {
329         // key can be either region or locale (lang_region)
330         //        ZW{
331         //            allowed{
332         //                "h",
333         //                "H",
334         //            }
335         //            preferred{"h"}
336         //        }
337         //        af_ZA{
338         //            allowed{
339         //                "h",
340         //                "H",
341         //                "hB",
342         //                "hb",
343         //            }
344         //            preferred{"h"}
345         //        }
346 
347         String language = uLocale.getLanguage();
348         String country = uLocale.getCountry();
349         if (language.isEmpty() || country.isEmpty()) {
350             // Note: addLikelySubtags is documented not to throw in Java,
351             // unlike in C++.
352             ULocale max = ULocale.addLikelySubtags(uLocale);
353             language = max.getLanguage();
354             country = max.getCountry();
355         }
356 
357         if (language.isEmpty()) {
358             // Unexpected, but fail gracefully
359             language = "und";
360         }
361         if (country.isEmpty()) {
362             country = "001";
363         }
364 
365         String[] list = getAllowedHourFormatsLangCountry(language, country);
366 
367         // We need to check if there is an hour cycle on locale
368         Character defaultCharFromLocale = null;
369         String hourCycle = uLocale.getKeywordValue("hours");
370         if (hourCycle != null) {
371             switch(hourCycle) {
372                 case "h24":
373                     defaultCharFromLocale = 'k';
374                     break;
375                 case "h23":
376                     defaultCharFromLocale = 'H';
377                     break;
378                 case "h12":
379                     defaultCharFromLocale = 'h';
380                     break;
381                 case "h11":
382                     defaultCharFromLocale = 'K';
383                     break;
384             }
385         }
386 
387         // Check if the region has an alias
388         if (list == null) {
389             try {
390                 Region region = Region.getInstance(country);
391                 country = region.toString();
392                 list = getAllowedHourFormatsLangCountry(language, country);
393             } catch (IllegalArgumentException e) {
394                 // invalid region; fall through
395             }
396         }
397 
398         if (list != null) {
399             defaultHourFormatChar = defaultCharFromLocale != null ? defaultCharFromLocale : list[0].charAt(0);
400             allowedHourFormats = Arrays.copyOfRange(list, 1, list.length - 1);
401         } else {
402             allowedHourFormats = LAST_RESORT_ALLOWED_HOUR_FORMAT;
403             defaultHourFormatChar = (defaultCharFromLocale != null) ? defaultCharFromLocale : allowedHourFormats[0].charAt(0);
404         }
405     }
406 
407     private static class DayPeriodAllowedHoursSink extends UResource.Sink {
408         HashMap<String, String[]> tempMap;
409 
DayPeriodAllowedHoursSink(HashMap<String, String[]> tempMap)410         private DayPeriodAllowedHoursSink(HashMap<String, String[]> tempMap) {
411             this.tempMap = tempMap;
412         }
413 
414         @Override
put(UResource.Key key, UResource.Value value, boolean noFallback)415         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
416             UResource.Table timeData = value.getTable();
417             for (int i = 0; timeData.getKeyAndValue(i, key, value); ++i) {
418                 String regionOrLocale = key.toString();
419                 UResource.Table formatList = value.getTable();
420                 String[] allowed = null;
421                 String preferred = null;
422                 for (int j = 0; formatList.getKeyAndValue(j, key, value); ++j) {
423                     if (key.contentEquals("allowed")) {
424                         allowed = value.getStringArrayOrStringAsArray();
425                     } else if (key.contentEquals("preferred")) {
426                         preferred = value.getString();
427                     }
428                 }
429                 // below we construct a list[] that has an entry for the "preferred" value at [0],
430                  // followed by 1 or more entries for the "allowed" values.
431                 String[] list = null;
432                 if (allowed!=null && allowed.length > 0) {
433                     list = new String[allowed.length + 1];
434                     list[0] = (preferred != null)? preferred:  allowed[0];
435                     System.arraycopy(allowed, 0, list, 1, allowed.length);
436                 } else {
437                     // fallback handling for missing data
438                     list = new String[2];
439                     list[0] = (preferred != null)? preferred: LAST_RESORT_ALLOWED_HOUR_FORMAT[0];
440                     list[1] = list[0];
441                 }
442                 tempMap.put(regionOrLocale, list);
443             }
444         }
445     }
446 
447     // Get the data for dayperiod C.
448     static final Map<String, String[]> LOCALE_TO_ALLOWED_HOUR;
449     static {
450         HashMap<String, String[]> temp = new HashMap<>();
451         ICUResourceBundle suppData = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(
452                 ICUData.ICU_BASE_NAME,
453                 "supplementalData",
454                 ICUResourceBundle.ICU_DATA_CLASS_LOADER);
455 
456         DayPeriodAllowedHoursSink allowedHoursSink = new DayPeriodAllowedHoursSink(temp);
457         suppData.getAllItemsWithFallback("timeData", allowedHoursSink);
458 
459         LOCALE_TO_ALLOWED_HOUR = Collections.unmodifiableMap(temp);
460     }
461 
462     /**
463      * @deprecated This API is ICU internal only.
464      * @hide deprecated on icu4j-org
465      * @hide draft / provisional / internal are hidden on OHOS
466      */
467     @Deprecated
getDefaultHourFormatChar()468     public char getDefaultHourFormatChar() {
469         return defaultHourFormatChar;
470     }
471 
472     /**
473      * @deprecated This API is ICU internal only.
474      * @hide deprecated on icu4j-org
475      * @hide draft / provisional / internal are hidden on OHOS
476      */
477     @Deprecated
setDefaultHourFormatChar(char defaultHourFormatChar)478     public void setDefaultHourFormatChar(char defaultHourFormatChar) {
479         this.defaultHourFormatChar = defaultHourFormatChar;
480     }
481 
hackTimes(PatternInfo returnInfo, String shortTimePattern)482     private void hackTimes(PatternInfo returnInfo, String shortTimePattern) {
483         fp.set(shortTimePattern);
484         StringBuilder mmss = new StringBuilder();
485         // to get mm:ss, we strip all but mm literal ss
486         boolean gotMm = false;
487         for (int i = 0; i < fp.items.size(); ++i) {
488             Object item = fp.items.get(i);
489             if (item instanceof String) {
490                 if (gotMm) {
491                     mmss.append(fp.quoteLiteral(item.toString()));
492                 }
493             } else {
494                 char ch = item.toString().charAt(0);
495                 if (ch == 'm') {
496                     gotMm = true;
497                     mmss.append(item);
498                 } else if (ch == 's') {
499                     if (!gotMm) {
500                         break; // failed
501                     }
502                     mmss.append(item);
503                     addPattern(mmss.toString(), false, returnInfo);
504                     break;
505                 } else if (gotMm || ch == 'z' || ch == 'Z' || ch == 'v' || ch == 'V') {
506                     break; // failed
507                 }
508             }
509         }
510         // to get hh:mm, we strip (literal ss) and (literal S)
511         // the easiest way to do this is to mark the stuff we want to nuke, then remove it in a second pass.
512         BitSet variables = new BitSet();
513         BitSet nuke = new BitSet();
514         for (int i = 0; i < fp.items.size(); ++i) {
515             Object item = fp.items.get(i);
516             if (item instanceof VariableField) {
517                 variables.set(i);
518                 char ch = item.toString().charAt(0);
519                 if (ch == 's' || ch == 'S') {
520                     nuke.set(i);
521                     for (int j = i-1; j >= 0; ++j) {
522                         if (variables.get(j)) break;
523                         nuke.set(i);
524                     }
525                 }
526             }
527         }
528         String hhmm = getFilteredPattern(fp, nuke);
529         addPattern(hhmm, false, returnInfo);
530     }
531 
getFilteredPattern(FormatParser fp, BitSet nuke)532     private static String getFilteredPattern(FormatParser fp, BitSet nuke) {
533         StringBuilder result = new StringBuilder();
534         for (int i = 0; i < fp.items.size(); ++i) {
535             if (nuke.get(i)) continue;
536             Object item = fp.items.get(i);
537             if (item instanceof String) {
538                 result.append(fp.quoteLiteral(item.toString()));
539             } else {
540                 result.append(item.toString());
541             }
542         }
543         return result.toString();
544     }
545 
546     /*private static int getAppendNameNumber(String string) {
547         for (int i = 0; i < CLDR_FIELD_NAME.length; ++i) {
548             if (CLDR_FIELD_NAME[i].equals(string)) return i;
549         }
550         return -1;
551     }*/
552 
553     /**
554      * @deprecated This API is ICU internal only.
555      * @hide draft / provisional / internal are hidden on OHOS
556      */
557     @Deprecated
getAppendFormatNumber(UResource.Key key)558     public static int getAppendFormatNumber(UResource.Key key) {
559         for (int i = 0; i < CLDR_FIELD_APPEND.length; ++i) {
560             if (key.contentEquals(CLDR_FIELD_APPEND[i])) {
561                 return i;
562             }
563         }
564         return -1;
565     }
566 
567     /**
568      * @deprecated This API is ICU internal only.
569      * @hide deprecated on icu4j-org
570      * @hide draft / provisional / internal are hidden on OHOS
571      */
572     @Deprecated
getAppendFormatNumber(String string)573     public static int getAppendFormatNumber(String string) {
574         for (int i = 0; i < CLDR_FIELD_APPEND.length; ++i) {
575             if (CLDR_FIELD_APPEND[i].equals(string)) {
576                 return i;
577             }
578         }
579         return -1;
580     }
581 
getCLDRFieldAndWidthNumber(UResource.Key key)582     private static int getCLDRFieldAndWidthNumber(UResource.Key key) {
583         for (int i = 0; i < CLDR_FIELD_NAME.length; ++i) {
584             for (int j = 0; j < DisplayWidth.COUNT; ++j) {
585                 String fullKey = CLDR_FIELD_NAME[i].concat(CLDR_FIELD_WIDTH[j].cldrKey());
586                 if (key.contentEquals(fullKey)) {
587                     return i * DisplayWidth.COUNT + j;
588                 }
589             }
590         }
591         return -1;
592     }
593 
594     /**
595      * Return the best pattern matching the input skeleton. It is guaranteed to
596      * have all of the fields in the skeleton.
597      * <p>Example code:{@sample external/icu/ohos_icu4j/src/samples/java/ohos/global/icu/samples/text/datetimepatterngenerator/DateTimePatternGeneratorSample.java getBestPatternExample}
598      * @param skeleton The skeleton is a pattern containing only the variable fields.
599      *            For example, "MMMdd" and "mmhh" are skeletons.
600      * @return Best pattern matching the input skeleton.
601      */
getBestPattern(String skeleton)602     public String getBestPattern(String skeleton) {
603         return getBestPattern(skeleton, null, MATCH_NO_OPTIONS);
604     }
605 
606     /**
607      * Return the best pattern matching the input skeleton. It is guaranteed to
608      * have all of the fields in the skeleton.
609      *
610      * @param skeleton The skeleton is a pattern containing only the variable fields.
611      *            For example, "MMMdd" and "mmhh" are skeletons.
612      * @param options MATCH_xxx options for forcing the length of specified fields in
613      *            the returned pattern to match those in the skeleton (when this would
614      *            not happen otherwise). For default behavior, use MATCH_NO_OPTIONS.
615      * @return Best pattern matching the input skeleton (and options).
616      */
getBestPattern(String skeleton, int options)617     public String getBestPattern(String skeleton, int options) {
618         return getBestPattern(skeleton, null, options);
619     }
620 
621     /*
622      * getBestPattern which takes optional skip matcher
623      */
getBestPattern(String skeleton, DateTimeMatcher skipMatcher, int options)624     private String getBestPattern(String skeleton, DateTimeMatcher skipMatcher, int options) {
625         EnumSet<DTPGflags> flags = EnumSet.noneOf(DTPGflags.class);
626         // Replace hour metacharacters 'j', 'C', and 'J', set flags as necessary
627         String skeletonMapped = mapSkeletonMetacharacters(skeleton, flags);
628         String datePattern, timePattern;
629         synchronized(this) {
630             current.set(skeletonMapped, fp, false);
631             PatternWithMatcher bestWithMatcher = getBestRaw(current, -1, _distanceInfo, skipMatcher);
632             if (_distanceInfo.missingFieldMask == 0 && _distanceInfo.extraFieldMask == 0) {
633                 // we have a good item. Adjust the field types
634                 return adjustFieldTypes(bestWithMatcher, current, flags, options);
635             }
636             int neededFields = current.getFieldMask();
637 
638             // otherwise break up by date and time.
639             datePattern = getBestAppending(current, neededFields & DATE_MASK, _distanceInfo, skipMatcher, flags, options);
640             timePattern = getBestAppending(current, neededFields & TIME_MASK, _distanceInfo, skipMatcher, flags, options);
641         }
642 
643         if (datePattern == null) return timePattern == null ? "" : timePattern;
644         if (timePattern == null) return datePattern;
645         return SimpleFormatterImpl.formatRawPattern(
646                 getDateTimeFormat(), 2, 2, timePattern, datePattern);
647     }
648 
649     /*
650      * Map a skeleton that may have metacharacters jJC to one without, by replacing
651      * the metacharacters with locale-appropriate fields of of h/H/k/K and of a/b/B
652      * (depends on defaultHourFormatChar and allowedHourFormats being set, which in
653      * turn depends on initData having been run). This method also updates the flags
654      * as necessary. Returns the updated skeleton.
655      */
mapSkeletonMetacharacters(String skeleton, EnumSet<DTPGflags> flags)656     private String mapSkeletonMetacharacters(String skeleton, EnumSet<DTPGflags> flags) {
657         StringBuilder skeletonCopy = new StringBuilder();
658         boolean inQuoted = false;
659         for (int patPos = 0; patPos < skeleton.length(); patPos++) {
660             char patChr = skeleton.charAt(patPos);
661             if (patChr == '\'') {
662                 inQuoted = !inQuoted;
663             } else if (!inQuoted) {
664                 // Handle special mappings for 'j' and 'C' in which fields lengths
665                 // 1,3,5 => hour field length 1
666                 // 2,4,6 => hour field length 2
667                 // 1,2 => abbreviated dayPeriod (field length 1..3)
668                 // 3,4 => long dayPeriod (field length 4)
669                 // 5,6 => narrow dayPeriod (field length 5)
670                 if (patChr == 'j' || patChr == 'C') {
671                     int extraLen = 0; // 1 less than total field length
672                     while (patPos+1 < skeleton.length() && skeleton.charAt(patPos+1) == patChr) {
673                         extraLen++;
674                         patPos++;
675                     }
676                     int hourLen = 1 + (extraLen & 1);
677                     int dayPeriodLen = (extraLen < 2)? 1: 3 + (extraLen >> 1);
678                     char hourChar = 'h';
679                     char dayPeriodChar = 'a';
680                     if (patChr == 'j') {
681                         hourChar = defaultHourFormatChar;
682                     } else { // patChr == 'C'
683                         String bestAllowed = allowedHourFormats[0];
684                         hourChar = bestAllowed.charAt(0);
685                         // in #13183 just add b/B to skeleton, no longer need to set special flags
686                         char last = bestAllowed.charAt(bestAllowed.length()-1);
687                         if (last=='b' || last=='B') {
688                             dayPeriodChar = last;
689                         }
690                     }
691                     if (hourChar=='H' || hourChar=='k') {
692                         dayPeriodLen = 0;
693                     }
694                     while (dayPeriodLen-- > 0) {
695                         skeletonCopy.append(dayPeriodChar);
696                     }
697                      while (hourLen-- > 0) {
698                         skeletonCopy.append(hourChar);
699                     }
700                 } else if (patChr == 'J') {
701                     // Get pattern for skeleton with H, then (in adjustFieldTypes)
702                     // replace H or k with defaultHourFormatChar
703                     skeletonCopy.append('H');
704                     flags.add(DTPGflags.SKELETON_USES_CAP_J);
705                 } else {
706                     skeletonCopy.append(patChr);
707                 }
708             }
709         }
710         return skeletonCopy.toString();
711     }
712 
713     /**
714      * PatternInfo supplies output parameters for addPattern(...). It is used because
715      * Java doesn't have real output parameters. It is treated like a struct (eg
716      * Point), so all fields are public.
717      */
718     public static final class PatternInfo { // struct for return information
719         /**
720          */
721         public static final int OK = 0;
722 
723         /**
724          */
725         public static final int BASE_CONFLICT = 1;
726 
727         /**
728          */
729         public static final int CONFLICT = 2;
730 
731         /**
732          */
733         public int status;
734 
735         /**
736          */
737         public String conflictingPattern;
738 
739         /**
740          * Simple constructor, since this is treated like a struct.
741          */
PatternInfo()742         public PatternInfo() {
743         }
744     }
745 
746     /**
747      * Adds a pattern to the generator. If the pattern has the same skeleton as
748      * an existing pattern, and the override parameter is set, then the previous
749      * value is overridden. Otherwise, the previous value is retained. In either
750      * case, the conflicting information is returned in PatternInfo.
751      * <p>
752      * Note that single-field patterns (like "MMM") are automatically added, and
753      * don't need to be added explicitly!
754      * * <p>Example code:{@sample external/icu/ohos_icu4j/src/samples/java/ohos/global/icu/samples/text/datetimepatterngenerator/DateTimePatternGeneratorSample.java addPatternExample}
755      * @param pattern Pattern to add.
756      * @param override When existing values are to be overridden use true, otherwise
757      *            use false.
758      * @param returnInfo Returned information.
759      */
addPattern(String pattern, boolean override, PatternInfo returnInfo)760     public DateTimePatternGenerator addPattern(String pattern, boolean override, PatternInfo returnInfo) {
761         return addPatternWithSkeleton(pattern, null, override, returnInfo);
762     }
763 
764     /**
765      * addPatternWithSkeleton:
766      * If skeletonToUse is specified, then an availableFormats entry is being added. In this case:
767      * 1. We pass that skeleton to DateTimeMatcher().set instead of having it derive a skeleton from the pattern.
768      * 2. If the new entry's skeleton or basePattern does match an existing entry but that entry also had a skeleton specified
769      * (i.e. it was also from availableFormats), then the new entry does not override it regardless of the value of the override
770      * parameter. This prevents later availableFormats entries from a parent locale overriding earlier ones from the actual
771      * specified locale. However, availableFormats entries *should* override entries with matching skeleton whose skeleton was
772      * derived (i.e. entries derived from the standard date/time patters for the specified locale).
773      * 3. When adding the pattern (skeleton2pattern.put, basePattern_pattern.put), we set a field to indicate that the added
774      * entry had a specified skeleton.
775      * @deprecated This API is ICU internal only.
776      * @hide deprecated on icu4j-org
777      * @hide draft / provisional / internal are hidden on OHOS
778      */
779     @Deprecated
addPatternWithSkeleton(String pattern, String skeletonToUse, boolean override, PatternInfo returnInfo)780     public DateTimePatternGenerator addPatternWithSkeleton(String pattern, String skeletonToUse, boolean override, PatternInfo returnInfo) {
781         checkFrozen();
782         DateTimeMatcher matcher;
783         if (skeletonToUse == null) {
784             matcher = new DateTimeMatcher().set(pattern, fp, false);
785         } else {
786             matcher = new DateTimeMatcher().set(skeletonToUse, fp, false);
787         }
788         String basePattern = matcher.getBasePattern();
789         // We only care about base conflicts - and replacing the pattern associated with a base - if:
790         // 1. the conflicting previous base pattern did *not* have an explicit skeleton; in that case the previous
791         // base + pattern combination was derived from either (a) a canonical item, (b) a standard format, or
792         // (c) a pattern specified programmatically with a previous call to addPattern (which would only happen
793         // if we are getting here from a subsequent call to addPattern).
794         // 2. a skeleton is specified for the current pattern, but override=false; in that case we are checking
795         // availableFormats items from root, which should not override any previous entry with the same base.
796         PatternWithSkeletonFlag previousPatternWithSameBase = basePattern_pattern.get(basePattern);
797         if (previousPatternWithSameBase != null && (!previousPatternWithSameBase.skeletonWasSpecified || (skeletonToUse != null && !override))) {
798             returnInfo.status = PatternInfo.BASE_CONFLICT;
799             returnInfo.conflictingPattern = previousPatternWithSameBase.pattern;
800             if (!override) {
801                 return this;
802             }
803         }
804         // The only time we get here with override=true and skeletonToUse!=null is when adding availableFormats
805         // items from CLDR data. In that case, we don't want an item from a parent locale to replace an item with
806         // same skeleton from the specified locale, so skip the current item if skeletonWasSpecified is true for
807         // the previously-specified conflicting item.
808         PatternWithSkeletonFlag previousValue = skeleton2pattern.get(matcher);
809         if (previousValue != null) {
810             returnInfo.status = PatternInfo.CONFLICT;
811             returnInfo.conflictingPattern = previousValue.pattern;
812             if (!override || (skeletonToUse != null && previousValue.skeletonWasSpecified)) return this;
813         }
814         returnInfo.status = PatternInfo.OK;
815         returnInfo.conflictingPattern = "";
816         PatternWithSkeletonFlag patWithSkelFlag = new PatternWithSkeletonFlag(pattern,skeletonToUse != null);
817         if (DEBUG) {
818             System.out.println(matcher + " => " + patWithSkelFlag);
819         }
820         skeleton2pattern.put(matcher, patWithSkelFlag);
821         basePattern_pattern.put(basePattern, patWithSkelFlag);
822         return this;
823     }
824 
825     /**
826      * Utility to return a unique skeleton from a given pattern. For example,
827      * both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd".
828      *
829      * @param pattern Input pattern, such as "dd/MMM"
830      * @return skeleton, such as "MMMdd"
831      */
getSkeleton(String pattern)832     public String getSkeleton(String pattern) {
833         synchronized (this) { // synchronized since a getter must be thread-safe
834             current.set(pattern, fp, false);
835             return current.toString();
836         }
837     }
838 
839     /**
840      * Same as getSkeleton, but allows duplicates
841      *
842      * @param pattern Input pattern, such as "dd/MMM"
843      * @return skeleton, such as "MMMdd"
844      * @deprecated This API is ICU internal only.
845      * @hide deprecated on icu4j-org
846      * @hide draft / provisional / internal are hidden on OHOS
847      */
848     @Deprecated
getSkeletonAllowingDuplicates(String pattern)849     public String getSkeletonAllowingDuplicates(String pattern) {
850         synchronized (this) { // synchronized since a getter must be thread-safe
851             current.set(pattern, fp, true);
852             return current.toString();
853         }
854     }
855 
856     /**
857      * Same as getSkeleton, but allows duplicates
858      * and returns a string using canonical pattern chars
859      *
860      * @param pattern Input pattern, such as "ccc, d LLL"
861      * @return skeleton, such as "MMMEd"
862      * @deprecated This API is ICU internal only.
863      * @hide deprecated on icu4j-org
864      * @hide draft / provisional / internal are hidden on OHOS
865      */
866     @Deprecated
getCanonicalSkeletonAllowingDuplicates(String pattern)867     public String getCanonicalSkeletonAllowingDuplicates(String pattern) {
868         synchronized (this) { // synchronized since a getter must be thread-safe
869             current.set(pattern, fp, true);
870             return current.toCanonicalString();
871         }
872     }
873 
874     /**
875      * Utility to return a unique base skeleton from a given pattern. This is
876      * the same as the skeleton, except that differences in length are minimized
877      * so as to only preserve the difference between string and numeric form. So
878      * for example, both "MMM-dd" and "d/MMM" produce the skeleton "MMMd"
879      * (notice the single d).
880      *
881      * @param pattern Input pattern, such as "dd/MMM"
882      * @return skeleton, such as "MMMdd"
883      */
getBaseSkeleton(String pattern)884     public String getBaseSkeleton(String pattern) {
885         synchronized (this) { // synchronized since a getter must be thread-safe
886             current.set(pattern, fp, false);
887             return current.getBasePattern();
888         }
889     }
890 
891     /**
892      * Return a list of all the skeletons (in canonical form) from this class,
893      * and the patterns that they map to.
894      *
895      * @param result an output Map in which to place the mapping from skeleton to
896      *            pattern. If you want to see the internal order being used,
897      *            supply a LinkedHashMap. If the input value is null, then a
898      *            LinkedHashMap is allocated.
899      *            <p>
900      *            <i>Issue: an alternate API would be to just return a list of
901      *            the skeletons, and then have a separate routine to get from
902      *            skeleton to pattern.</i>
903      * @return the input Map containing the values.
904      */
getSkeletons(Map<String, String> result)905     public Map<String, String> getSkeletons(Map<String, String> result) {
906         if (result == null) {
907             result = new LinkedHashMap<>();
908         }
909         for (DateTimeMatcher item : skeleton2pattern.keySet()) {
910             PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(item);
911             String pattern = patternWithSkelFlag.pattern;
912             if (CANONICAL_SET.contains(pattern)) {
913                 continue;
914             }
915             result.put(item.toString(), pattern);
916         }
917         return result;
918     }
919 
920     /**
921      * Return a list of all the base skeletons (in canonical form) from this class
922      */
getBaseSkeletons(Set<String> result)923     public Set<String> getBaseSkeletons(Set<String> result) {
924         if (result == null) {
925             result = new HashSet<>();
926         }
927         result.addAll(basePattern_pattern.keySet());
928         return result;
929     }
930 
931     /**
932      * Adjusts the field types (width and subtype) of a pattern to match what is
933      * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
934      * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
935      * "dd-MMMM hh:mm". This is used internally to get the best match for the
936      * input skeleton, but can also be used externally.
937      * <p>Example code:{@sample external/icu/ohos_icu4j/src/samples/java/ohos/global/icu/samples/text/datetimepatterngenerator/DateTimePatternGeneratorSample.java replaceFieldTypesExample}
938      * @param pattern input pattern
939      * @param skeleton For the pattern to match to.
940      * @return pattern adjusted to match the skeleton fields widths and subtypes.
941      */
replaceFieldTypes(String pattern, String skeleton)942     public String replaceFieldTypes(String pattern, String skeleton) {
943         return replaceFieldTypes(pattern, skeleton, MATCH_NO_OPTIONS);
944     }
945 
946     /**
947      * Adjusts the field types (width and subtype) of a pattern to match what is
948      * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
949      * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
950      * "dd-MMMM hh:mm". This is used internally to get the best match for the
951      * input skeleton, but can also be used externally.
952      *
953      * @param pattern input pattern
954      * @param skeleton For the pattern to match to.
955      * @param options MATCH_xxx options for forcing the length of specified fields in
956      *            the returned pattern to match those in the skeleton (when this would
957      *            not happen otherwise). For default behavior, use MATCH_NO_OPTIONS.
958      * @return pattern adjusted to match the skeleton fields widths and subtypes.
959      */
replaceFieldTypes(String pattern, String skeleton, int options)960     public String replaceFieldTypes(String pattern, String skeleton, int options) {
961         synchronized (this) { // synchronized since a getter must be thread-safe
962             PatternWithMatcher patternNoMatcher = new PatternWithMatcher(pattern, null);
963             return adjustFieldTypes(patternNoMatcher, current.set(skeleton, fp, false), EnumSet.noneOf(DTPGflags.class), options);
964         }
965     }
966 
967     /**
968      * The date time format is a message format pattern used to compose date and
969      * time patterns. The default value is "{1} {0}", where {1} will be replaced
970      * by the date pattern and {0} will be replaced by the time pattern.
971      * <p>
972      * This is used when the input skeleton contains both date and time fields,
973      * but there is not a close match among the added patterns. For example,
974      * suppose that this object was created by adding "dd-MMM" and "hh:mm", and
975      * its datetimeFormat is the default "{1} {0}". Then if the input skeleton
976      * is "MMMdhmm", there is not an exact match, so the input skeleton is
977      * broken up into two components "MMMd" and "hmm". There are close matches
978      * for those two skeletons, so the result is put together with this pattern,
979      * resulting in "d-MMM h:mm".
980      *
981      * @param dateTimeFormat message format pattern, where {1} will be replaced by the date
982      *            pattern and {0} will be replaced by the time pattern.
983      */
setDateTimeFormat(String dateTimeFormat)984     public void setDateTimeFormat(String dateTimeFormat) {
985         checkFrozen();
986         this.dateTimeFormat = dateTimeFormat;
987     }
988 
989     /**
990      * Getter corresponding to setDateTimeFormat.
991      *
992      * @return pattern
993      */
getDateTimeFormat()994     public String getDateTimeFormat() {
995         return dateTimeFormat;
996     }
997 
998     /**
999      * The decimal value is used in formatting fractions of seconds. If the
1000      * skeleton contains fractional seconds, then this is used with the
1001      * fractional seconds. For example, suppose that the input pattern is
1002      * "hhmmssSSSS", and the best matching pattern internally is "H:mm:ss", and
1003      * the decimal string is ",". Then the resulting pattern is modified to be
1004      * "H:mm:ss,SSSS"
1005      *
1006      * @param decimal The decimal to set to.
1007      */
setDecimal(String decimal)1008     public void setDecimal(String decimal) {
1009         checkFrozen();
1010         this.decimal = decimal;
1011     }
1012 
1013     /**
1014      * Getter corresponding to setDecimal.
1015      * @return string corresponding to the decimal point
1016      */
getDecimal()1017     public String getDecimal() {
1018         return decimal;
1019     }
1020 
1021     /**
1022      * Redundant patterns are those which if removed, make no difference in the
1023      * resulting getBestPattern values. This method returns a list of them, to
1024      * help check the consistency of the patterns used to build this generator.
1025      *
1026      * @param output stores the redundant patterns that are removed. To get these
1027      *            in internal order, supply a LinkedHashSet. If null, a
1028      *            collection is allocated.
1029      * @return the collection with added elements.
1030      * @deprecated This API is ICU internal only.
1031      * @hide deprecated on icu4j-org
1032      * @hide draft / provisional / internal are hidden on OHOS
1033      */
1034     @Deprecated
getRedundants(Collection<String> output)1035     public Collection<String> getRedundants(Collection<String> output) {
1036         synchronized (this) { // synchronized since a getter must be thread-safe
1037             if (output == null) {
1038                 output = new LinkedHashSet<>();
1039             }
1040             for (DateTimeMatcher cur : skeleton2pattern.keySet()) {
1041                 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(cur);
1042                 String pattern = patternWithSkelFlag.pattern;
1043                 if (CANONICAL_SET.contains(pattern)) {
1044                     continue;
1045                 }
1046                 String trial = getBestPattern(cur.toString(), cur, MATCH_NO_OPTIONS);
1047                 if (trial.equals(pattern)) {
1048                     output.add(pattern);
1049                 }
1050             }
1051             ///CLOVER:OFF
1052             //The following would never be called since the parameter is false
1053             //Eclipse stated the following is "dead code"
1054             /*if (false) { // ordered
1055                 DateTimePatternGenerator results = new DateTimePatternGenerator();
1056                 PatternInfo pinfo = new PatternInfo();
1057                 for (DateTimeMatcher cur : skeleton2pattern.keySet()) {
1058                     String pattern = skeleton2pattern.get(cur);
1059                     if (CANONICAL_SET.contains(pattern)) {
1060                         continue;
1061                     }
1062                     //skipMatcher = current;
1063                     String trial = results.getBestPattern(cur.toString());
1064                     if (trial.equals(pattern)) {
1065                         output.add(pattern);
1066                     } else {
1067                         results.addPattern(pattern, false, pinfo);
1068                     }
1069                 }
1070             }*/
1071             ///CLOVER:ON
1072             return output;
1073         }
1074     }
1075 
1076     // Field numbers, used for AppendItem functions
1077 
1078     /**
1079      */
1080     public static final int ERA = 0;
1081 
1082     /**
1083      */
1084     public static final int YEAR = 1;
1085 
1086     /**
1087      */
1088     public static final int QUARTER = 2;
1089 
1090     /**
1091      */
1092     public static final int MONTH = 3;
1093 
1094     /**
1095      */
1096     public static final int WEEK_OF_YEAR = 4;
1097 
1098     /**
1099      */
1100     public static final int WEEK_OF_MONTH = 5;
1101 
1102     /**
1103      */
1104     public static final int WEEKDAY = 6;
1105 
1106     /**
1107      */
1108     public static final int DAY = 7;
1109 
1110     /**
1111      */
1112     public static final int DAY_OF_YEAR = 8;
1113 
1114     /**
1115      */
1116     public static final int DAY_OF_WEEK_IN_MONTH = 9;
1117 
1118     /**
1119      */
1120     public static final int DAYPERIOD = 10;
1121 
1122     /**
1123      */
1124     public static final int HOUR = 11;
1125 
1126     /**
1127      */
1128     public static final int MINUTE = 12;
1129 
1130     /**
1131      */
1132     public static final int SECOND = 13;
1133 
1134     /**
1135      */
1136     public static final int FRACTIONAL_SECOND = 14;
1137 
1138     /**
1139      */
1140     public static final int ZONE = 15;
1141 
1142     /**
1143      * One more than the highest normal field number.
1144      * @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420.
1145      * @hide unsupported on OHOS
1146      */
1147     @Deprecated
1148     public static final int TYPE_LIMIT = 16;
1149 
1150     /**
1151      * Field display name width constants for getFieldDisplayName
1152      */
1153     public enum DisplayWidth {
1154         /**
1155          * The full field name
1156          */
1157         WIDE(""),
1158         /**
1159          * An abbreviated field name
1160          * (may be the same as the wide version, if short enough)
1161          */
1162         ABBREVIATED("-short"),
1163         /**
1164          * The shortest possible field name
1165          * (may be the same as the abbreviated version)
1166          */
1167         NARROW("-narrow");
1168         /**
1169          * The count of available widths
1170          * @deprecated This API is ICU internal only.
1171          * @hide draft / provisional / internal are hidden on OHOS
1172          */
1173         @Deprecated
1174         private static int COUNT = DisplayWidth.values().length;
1175         private final String cldrKey;
DisplayWidth(String cldrKey)1176         DisplayWidth(String cldrKey) {
1177             this.cldrKey = cldrKey;
1178         }
cldrKey()1179         private String cldrKey() {
1180             return cldrKey;
1181         }
1182     }
1183 
1184     /**
1185      * The field name width for use in appendItems
1186      */
1187     private static final DisplayWidth APPENDITEM_WIDTH = DisplayWidth.WIDE;
1188     private static final int APPENDITEM_WIDTH_INT = APPENDITEM_WIDTH.ordinal();
1189     private static final DisplayWidth[] CLDR_FIELD_WIDTH = DisplayWidth.values();
1190 
1191     // Option masks for getBestPattern, replaceFieldTypes (individual masks may be ORed together)
1192 
1193     /**
1194      * Default option mask used for {@link #getBestPattern(String, int)}
1195      * and {@link #replaceFieldTypes(String, String, int)}.
1196      * @see #getBestPattern(String, int)
1197      * @see #replaceFieldTypes(String, String, int)
1198      */
1199     public static final int MATCH_NO_OPTIONS = 0;
1200 
1201     /**
1202      * Option mask for forcing the width of hour field.
1203      * @see #getBestPattern(String, int)
1204      * @see #replaceFieldTypes(String, String, int)
1205      */
1206     public static final int MATCH_HOUR_FIELD_LENGTH = 1 << HOUR;
1207 
1208     /**
1209      * Option mask for forcing  the width of minute field.
1210      * @deprecated This API is ICU internal only.
1211      * @hide deprecated on icu4j-org
1212      * @hide draft / provisional / internal are hidden on OHOS
1213      */
1214     @Deprecated
1215     public static final int MATCH_MINUTE_FIELD_LENGTH = 1 << MINUTE;
1216 
1217     /**
1218      * Option mask for forcing  the width of second field.
1219      * @deprecated This API is ICU internal only.
1220      * @hide deprecated on icu4j-org
1221      * @hide draft / provisional / internal are hidden on OHOS
1222      */
1223     @Deprecated
1224     public static final int MATCH_SECOND_FIELD_LENGTH = 1 << SECOND;
1225 
1226     /**
1227      * Option mask for forcing the width of all date and time fields.
1228      * @see #getBestPattern(String, int)
1229      * @see #replaceFieldTypes(String, String, int)
1230      */
1231     public static final int MATCH_ALL_FIELDS_LENGTH = (1 << TYPE_LIMIT) - 1;
1232 
1233     /**
1234      * An AppendItem format is a pattern used to append a field if there is no
1235      * good match. For example, suppose that the input skeleton is "GyyyyMMMd",
1236      * and there is no matching pattern internally, but there is a pattern
1237      * matching "yyyyMMMd", say "d-MM-yyyy". Then that pattern is used, plus the
1238      * G. The way these two are conjoined is by using the AppendItemFormat for G
1239      * (era). So if that value is, say "{0}, {1}" then the final resulting
1240      * pattern is "d-MM-yyyy, G".
1241      * <p>
1242      * There are actually three available variables: {0} is the pattern so far,
1243      * {1} is the element we are adding, and {2} is the name of the element.
1244      * <p>
1245      * This reflects the way that the CLDR data is organized.
1246      *
1247      * @param field such as ERA
1248      * @param value pattern, such as "{0}, {1}"
1249      */
setAppendItemFormat(int field, String value)1250     public void setAppendItemFormat(int field, String value) {
1251         checkFrozen();
1252         appendItemFormats[field] = value;
1253     }
1254 
1255     /**
1256      * Getter corresponding to setAppendItemFormats. Values below 0 or at or
1257      * above TYPE_LIMIT are illegal arguments.
1258      *
1259      * @param field The index to retrieve the append item formats.
1260      * @return append pattern for field
1261      */
getAppendItemFormat(int field)1262     public String getAppendItemFormat(int field) {
1263         return appendItemFormats[field];
1264     }
1265 
1266     /**
1267      * Sets the names of fields, eg "era" in English for ERA. These are only
1268      * used if the corresponding AppendItemFormat is used, and if it contains a
1269      * {2} variable.
1270      * <p>
1271      * This reflects the way that the CLDR data is organized.
1272      *
1273      * @param field Index of the append item names.
1274      * @param value The value to set the item to.
1275      */
setAppendItemName(int field, String value)1276     public void setAppendItemName(int field, String value) {
1277         setFieldDisplayName(field, APPENDITEM_WIDTH, value);
1278     }
1279 
1280     /**
1281      * Getter corresponding to setAppendItemName. Values below 0 or at or above
1282      * TYPE_LIMIT are illegal arguments. Note: The more general method
1283      * for getting date/time field display names is getFieldDisplayName.
1284      *
1285      * @param field The index to get the append item name.
1286      * @return name for field
1287      * @see #getFieldDisplayName(int, DisplayWidth)
1288      */
getAppendItemName(int field)1289     public String getAppendItemName(int field) {
1290         return getFieldDisplayName(field, APPENDITEM_WIDTH);
1291     }
1292 
1293     /**
1294      * Return the default hour cycle.
1295      * @hide draft / provisional / internal are hidden on OHOS
1296      */
getDefaultHourCycle()1297     public DateFormat.HourCycle getDefaultHourCycle() {
1298       switch(getDefaultHourFormatChar()) {
1299         case 'h': return DateFormat.HourCycle.HOUR_CYCLE_12;
1300         case 'H': return DateFormat.HourCycle.HOUR_CYCLE_23;
1301         case 'k': return DateFormat.HourCycle.HOUR_CYCLE_24;
1302         case 'K': return DateFormat.HourCycle.HOUR_CYCLE_11;
1303         default: throw new AssertionError("should be unreachable");
1304       }
1305     }
1306 
1307     /**
1308      * The private interface to set a display name for a particular date/time field,
1309      * in one of several possible display widths.
1310      *
1311      * @param field The field type, such as ERA.
1312      * @param width The desired DisplayWidth, such as DisplayWidth.ABBREVIATED.
1313      * @param value The display name to set
1314      * @deprecated This API is ICU internal only.
1315      * @hide draft / provisional / internal are hidden on OHOS
1316      */
1317     @Deprecated
setFieldDisplayName(int field, DisplayWidth width, String value)1318     private void setFieldDisplayName(int field, DisplayWidth width, String value) {
1319         checkFrozen();
1320         if (field < TYPE_LIMIT && field >= 0) {
1321             fieldDisplayNames[field][width.ordinal()] = value;
1322         }
1323     }
1324 
1325     /**
1326      * The general interface to get a display name for a particular date/time field,
1327      * in one of several possible display widths.
1328      *
1329      * @param field The field type, such as ERA.
1330      * @param width The desired DisplayWidth, such as DisplayWidth.ABBREVIATED.
1331      * @return      The display name for the field
1332      */
getFieldDisplayName(int field, DisplayWidth width)1333     public String getFieldDisplayName(int field, DisplayWidth width) {
1334         if (field >= TYPE_LIMIT || field < 0) {
1335             return "";
1336         }
1337         return fieldDisplayNames[field][width.ordinal()];
1338     }
1339 
1340     /**
1341      * Determines whether a skeleton contains a single field
1342      *
1343      * @param skeleton The skeleton to determine if it contains a single field.
1344      * @return true or not
1345      * @deprecated This API is ICU internal only.
1346      * @hide deprecated on icu4j-org
1347      * @hide draft / provisional / internal are hidden on OHOS
1348      */
1349     @Deprecated
isSingleField(String skeleton)1350     public static boolean isSingleField(String skeleton) {
1351         char first = skeleton.charAt(0);
1352         for (int i = 1; i < skeleton.length(); ++i) {
1353             if (skeleton.charAt(i) != first) return false;
1354         }
1355         return true;
1356     }
1357 
1358     /**
1359      * Add key to HashSet cldrAvailableFormatKeys.
1360      *
1361      * @param key of the availableFormats in CLDR
1362      */
setAvailableFormat(String key)1363     private void setAvailableFormat(String key) {
1364         checkFrozen();
1365         cldrAvailableFormatKeys.add(key);
1366     }
1367 
1368     /**
1369      * This function checks the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
1370      * has been added to DateTimePatternGenerator.
1371      * The function is to avoid the duplicate availableFomats added to
1372      * the pattern map from parent locales.
1373      *
1374      * @param key of the availableFormatMask in CLDR
1375      * @return TRUE if the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
1376      * has been added to DateTimePatternGenerator.
1377      */
isAvailableFormatSet(String key)1378     private boolean isAvailableFormatSet(String key) {
1379         return cldrAvailableFormatKeys.contains(key);
1380     }
1381 
1382     /**
1383      * {@inheritDoc}
1384      */
1385     @Override
isFrozen()1386     public boolean isFrozen() {
1387         return frozen;
1388     }
1389 
1390     /**
1391      * {@inheritDoc}
1392      */
1393     @Override
freeze()1394     public DateTimePatternGenerator freeze() {
1395         frozen = true;
1396         return this;
1397     }
1398 
1399     /**
1400      * {@inheritDoc}
1401      */
1402     @Override
cloneAsThawed()1403     public DateTimePatternGenerator cloneAsThawed() {
1404         DateTimePatternGenerator result = (DateTimePatternGenerator) (this.clone());
1405         frozen = false;
1406         return result;
1407     }
1408 
1409     /**
1410      * Returns a copy of this <code>DateTimePatternGenerator</code> object.
1411      * @return A copy of this <code>DateTimePatternGenerator</code> object.
1412      */
1413     @Override
1414     @SuppressWarnings("unchecked")
clone()1415     public Object clone() {
1416         try {
1417             DateTimePatternGenerator result = (DateTimePatternGenerator) (super.clone());
1418             result.skeleton2pattern = (TreeMap<DateTimeMatcher, PatternWithSkeletonFlag>) skeleton2pattern.clone();
1419             result.basePattern_pattern = (TreeMap<String, PatternWithSkeletonFlag>) basePattern_pattern.clone();
1420             result.appendItemFormats = appendItemFormats.clone();
1421             result.fieldDisplayNames = fieldDisplayNames.clone();
1422             result.current = new DateTimeMatcher();
1423             result.fp = new FormatParser();
1424             result._distanceInfo = new DistanceInfo();
1425 
1426             result.frozen = false;
1427             return result;
1428         } catch (CloneNotSupportedException e) {
1429             ///CLOVER:OFF
1430             throw new ICUCloneNotSupportedException("Internal Error", e);
1431             ///CLOVER:ON
1432         }
1433     }
1434 
1435     /**
1436      * Utility class for FormatParser. Immutable class that is only used to mark
1437      * the difference between a variable field and a literal string. Each
1438      * variable field must consist of 1 to n variable characters, representing
1439      * date format fields. For example, "VVVV" is valid while "V4" is not, nor
1440      * is "44".
1441      *
1442      * @deprecated This API is ICU internal only.
1443      * @hide exposed on OHOS
1444      * @hide deprecated on icu4j-org
1445      * @hide draft / provisional / internal are hidden on OHOS
1446      */
1447     @Deprecated
1448     public static class VariableField {
1449         private final String string;
1450         private final int canonicalIndex;
1451 
1452         /**
1453          * Create a variable field: equivalent to VariableField(string,false);
1454          * @param string The string for the variable field.
1455          * @deprecated This API is ICU internal only.
1456          * @hide deprecated on icu4j-org
1457          * @hide draft / provisional / internal are hidden on OHOS
1458          */
1459         @Deprecated
VariableField(String string)1460         public VariableField(String string) {
1461             this(string, false);
1462         }
1463         /**
1464          * Create a variable field
1465          * @param string The string for the variable field
1466          * @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception.
1467          * @throws IllegalArgumentException if the variable field is not valid.
1468          * @deprecated This API is ICU internal only.
1469          * @hide deprecated on icu4j-org
1470          * @hide draft / provisional / internal are hidden on OHOS
1471          */
1472         @Deprecated
VariableField(String string, boolean strict)1473         public VariableField(String string, boolean strict) {
1474             canonicalIndex = DateTimePatternGenerator.getCanonicalIndex(string, strict);
1475             if (canonicalIndex < 0) {
1476                 throw new IllegalArgumentException("Illegal datetime field:\t"
1477                         + string);
1478             }
1479             this.string = string;
1480         }
1481 
1482         /**
1483          * Get the main type of this variable. These types are ERA, QUARTER,
1484          * MONTH, DAY, WEEK_OF_YEAR, WEEK_OF_MONTH, WEEKDAY, DAY, DAYPERIOD
1485          * (am/pm), HOUR, MINUTE, SECOND,FRACTIONAL_SECOND, ZONE.
1486          * @return main type.
1487          * @deprecated This API is ICU internal only.
1488          * @hide deprecated on icu4j-org
1489          * @hide draft / provisional / internal are hidden on OHOS
1490          */
1491         @Deprecated
getType()1492         public int getType() {
1493             return types[canonicalIndex][1];
1494         }
1495 
1496         /**
1497          * @deprecated This API is ICU internal only.
1498          * @hide deprecated on icu4j-org
1499          * @hide draft / provisional / internal are hidden on OHOS
1500          */
1501         @Deprecated
getCanonicalCode(int type)1502         public static String getCanonicalCode(int type) {
1503             try {
1504                 return CANONICAL_ITEMS[type];
1505             } catch (Exception e) {
1506                 return String.valueOf(type);
1507             }
1508         }
1509         /**
1510          * Check if the type of this variable field is numeric.
1511          * @return true if the type of this variable field is numeric.
1512          * @deprecated This API is ICU internal only.
1513          * @hide deprecated on icu4j-org
1514          * @hide draft / provisional / internal are hidden on OHOS
1515          */
1516         @Deprecated
isNumeric()1517         public boolean isNumeric() {
1518             return types[canonicalIndex][2] > 0;
1519         }
1520 
1521         /**
1522          * Private method.
1523          */
getCanonicalIndex()1524         private int getCanonicalIndex() {
1525             return canonicalIndex;
1526         }
1527 
1528         /**
1529          * Get the string represented by this variable.
1530          * @deprecated This API is ICU internal only.
1531          * @hide deprecated on icu4j-org
1532          * @hide draft / provisional / internal are hidden on OHOS
1533          */
1534         @Override
1535         @Deprecated
toString()1536         public String toString() {
1537             return string;
1538         }
1539     }
1540 
1541     /**
1542      * This class provides mechanisms for parsing a SimpleDateFormat pattern
1543      * or generating a new pattern, while handling the quoting. It represents
1544      * the result of the parse as a list of items, where each item is either a
1545      * literal string or a variable field. When parsing It can be used to find
1546      * out which variable fields are in a date format, and in what order, such
1547      * as for presentation in a UI as separate text entry fields. It can also be
1548      * used to construct new SimpleDateFormats.
1549      * <p>Example:
1550      * <pre>
1551     public boolean containsZone(String pattern) {
1552         for (Iterator it = formatParser.set(pattern).getItems().iterator(); it.hasNext();) {
1553             Object item = it.next();
1554             if (item instanceof VariableField) {
1555                 VariableField variableField = (VariableField) item;
1556                 if (variableField.getType() == DateTimePatternGenerator.ZONE) {
1557                     return true;
1558                 }
1559             }
1560         }
1561         return false;
1562     }
1563      *  </pre>
1564      * @deprecated This API is ICU internal only.
1565      * @hide exposed on OHOS
1566      * @hide deprecated on icu4j-org
1567      * @hide draft / provisional / internal are hidden on OHOS
1568      */
1569     @Deprecated
1570     static public class FormatParser {
1571         private static final UnicodeSet SYNTAX_CHARS = new UnicodeSet("[a-zA-Z]").freeze();
1572         private static final UnicodeSet QUOTING_CHARS = new UnicodeSet("[[[:script=Latn:][:script=Cyrl:]]&[[:L:][:M:]]]").freeze();
1573         private transient PatternTokenizer tokenizer = new PatternTokenizer()
1574         .setSyntaxCharacters(SYNTAX_CHARS)
1575         .setExtraQuotingCharacters(QUOTING_CHARS)
1576         .setUsingQuote(true);
1577         private List<Object> items = new ArrayList<>();
1578 
1579         /**
1580          * Construct an empty date format parser, to which strings and variables can be added with set(...).
1581          * @deprecated This API is ICU internal only.
1582          * @hide deprecated on icu4j-org
1583          * @hide draft / provisional / internal are hidden on OHOS
1584          */
1585         @Deprecated
FormatParser()1586         public FormatParser() {
1587         }
1588 
1589         /**
1590          * Parses the string into a list of items.
1591          * @param string The string to parse.
1592          * @return this, for chaining
1593          * @deprecated This API is ICU internal only.
1594          * @hide deprecated on icu4j-org
1595          * @hide draft / provisional / internal are hidden on OHOS
1596          */
1597         @Deprecated
set(String string)1598         final public FormatParser set(String string) {
1599             return set(string, false);
1600         }
1601 
1602         /**
1603          * Parses the string into a list of items, taking into account all of the quoting that may be going on.
1604          * @param string  The string to parse.
1605          * @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception.
1606          * @return this, for chaining
1607          * @deprecated This API is ICU internal only.
1608          * @hide deprecated on icu4j-org
1609          * @hide draft / provisional / internal are hidden on OHOS
1610          */
1611         @Deprecated
set(String string, boolean strict)1612         public FormatParser set(String string, boolean strict) {
1613             items.clear();
1614             if (string.length() == 0) return this;
1615             tokenizer.setPattern(string);
1616             StringBuffer buffer = new StringBuffer();
1617             StringBuffer variable = new StringBuffer();
1618             while (true) {
1619                 buffer.setLength(0);
1620                 int status = tokenizer.next(buffer);
1621                 if (status == PatternTokenizer.DONE) break;
1622                 if (status == PatternTokenizer.SYNTAX) {
1623                     if (variable.length() != 0 && buffer.charAt(0) != variable.charAt(0)) {
1624                         addVariable(variable, false);
1625                     }
1626                     variable.append(buffer);
1627                 } else {
1628                     addVariable(variable, false);
1629                     items.add(buffer.toString());
1630                 }
1631             }
1632             addVariable(variable, false);
1633             return this;
1634         }
1635 
addVariable(StringBuffer variable, boolean strict)1636         private void addVariable(StringBuffer variable, boolean strict) {
1637             if (variable.length() != 0) {
1638                 items.add(new VariableField(variable.toString(), strict));
1639                 variable.setLength(0);
1640             }
1641         }
1642 
1643         //        /** Private method. Return a collection of fields. These will be a mixture of literal Strings and VariableFields. Any "a" variable field is removed.
1644         //         * @param output List to append the items to. If null, is allocated as an ArrayList.
1645         //         * @return list
1646         //         */
1647         //        private List getVariableFields(List output) {
1648         //            if (output == null) output = new ArrayList();
1649         //            main:
1650         //                for (Iterator it = items.iterator(); it.hasNext();) {
1651         //                    Object item = it.next();
1652         //                    if (item instanceof VariableField) {
1653         //                        String s = item.toString();
1654         //                        switch(s.charAt(0)) {
1655         //                        //case 'Q': continue main; // HACK
1656         //                        case 'a': continue main; // remove
1657         //                        }
1658         //                        output.add(item);
1659         //                    }
1660         //                }
1661         //            //System.out.println(output);
1662         //            return output;
1663         //        }
1664 
1665         //        /**
1666         //         * Produce a string which concatenates all the variables. That is, it is the logically the same as the input with all literals removed.
1667         //         * @return a string which is a concatenation of all the variable fields
1668         //         */
1669         //        public String getVariableFieldString() {
1670         //            List list = getVariableFields(null);
1671         //            StringBuffer result = new StringBuffer();
1672         //            for (Iterator it = list.iterator(); it.hasNext();) {
1673         //                String item = it.next().toString();
1674         //                result.append(item);
1675         //            }
1676         //            return result.toString();
1677         //        }
1678 
1679         /**
1680          * Returns modifiable list which is a mixture of Strings and VariableFields, in the order found during parsing. The strings represent literals, and have all quoting removed. Thus the string "dd 'de' MM" will parse into three items:
1681          * <pre>
1682          * VariableField: dd
1683          * String: " de "
1684          * VariableField: MM
1685          * </pre>
1686          * The list is modifiable, so you can add any strings or variables to it, or remove any items.
1687          * @return modifiable list of items.
1688          * @deprecated This API is ICU internal only.
1689          * @hide deprecated on icu4j-org
1690          * @hide draft / provisional / internal are hidden on OHOS
1691          */
1692         @Deprecated
getItems()1693         public List<Object> getItems() {
1694             return items;
1695         }
1696 
1697         /** Provide display form of formatted input. Each literal string is quoted if necessary.. That is, if the input was "hh':'mm", the result would be "hh:mm", since the ":" doesn't need quoting. See quoteLiteral().
1698          * @return printable output string
1699          * @deprecated This API is ICU internal only.
1700          * @hide deprecated on icu4j-org
1701          * @hide draft / provisional / internal are hidden on OHOS
1702          */
1703         @Override
1704         @Deprecated
toString()1705         public String toString() {
1706             return toString(0, items.size());
1707         }
1708 
1709         /**
1710          * Provide display form of a segment of the parsed input. Each literal string is minimally quoted. That is, if the input was "hh':'mm", the result would be "hh:mm", since the ":" doesn't need quoting. See quoteLiteral().
1711          * @param start item to start from
1712          * @param limit last item +1
1713          * @return printable output string
1714          * @deprecated This API is ICU internal only.
1715          * @hide deprecated on icu4j-org
1716          * @hide draft / provisional / internal are hidden on OHOS
1717          */
1718         @Deprecated
toString(int start, int limit)1719         public String toString(int start, int limit) {
1720             StringBuilder result = new StringBuilder();
1721             for (int i = start; i < limit; ++i) {
1722                 Object item = items.get(i);
1723                 if (item instanceof String) {
1724                     String itemString = (String) item;
1725                     result.append(tokenizer.quoteLiteral(itemString));
1726                 } else {
1727                     result.append(items.get(i).toString());
1728                 }
1729             }
1730             return result.toString();
1731         }
1732 
1733         /**
1734          * Returns true if it has a mixture of date and time variable fields: that is, at least one date variable and at least one time variable.
1735          * @return true or false
1736          * @deprecated This API is ICU internal only.
1737          * @hide deprecated on icu4j-org
1738          * @hide draft / provisional / internal are hidden on OHOS
1739          */
1740         @Deprecated
hasDateAndTimeFields()1741         public boolean hasDateAndTimeFields() {
1742             int foundMask = 0;
1743             for (Object item : items) {
1744                 if (item instanceof VariableField) {
1745                     int type = ((VariableField)item).getType();
1746                     foundMask |= 1 << type;
1747                 }
1748             }
1749             boolean isDate = (foundMask & DATE_MASK) != 0;
1750             boolean isTime = (foundMask & TIME_MASK) != 0;
1751             return isDate && isTime;
1752         }
1753 
1754         //        /**
1755         //         * Internal routine
1756         //         * @param value
1757         //         * @param result
1758         //         * @return list
1759         //         */
1760         //        public List getAutoPatterns(String value, List result) {
1761         //            if (result == null) result = new ArrayList();
1762         //            int fieldCount = 0;
1763         //            int minField = Integer.MAX_VALUE;
1764         //            int maxField = Integer.MIN_VALUE;
1765         //            for (Iterator it = items.iterator(); it.hasNext();) {
1766         //                Object item = it.next();
1767         //                if (item instanceof VariableField) {
1768         //                    try {
1769         //                        int type = ((VariableField)item).getType();
1770         //                        if (minField > type) minField = type;
1771         //                        if (maxField < type) maxField = type;
1772         //                        if (type == ZONE || type == DAYPERIOD || type == WEEKDAY) return result; // skip anything with zones
1773         //                        fieldCount++;
1774         //                    } catch (Exception e) {
1775         //                        return result; // if there are any funny fields, return
1776         //                    }
1777         //                }
1778         //            }
1779         //            if (fieldCount < 3) return result; // skip
1780         //            // trim from start
1781         //            // trim first field IF there are no letters around it
1782         //            // and it is either the min or the max field
1783         //            // first field is either 0 or 1
1784         //            for (int i = 0; i < items.size(); ++i) {
1785         //                Object item = items.get(i);
1786         //                if (item instanceof VariableField) {
1787         //                    int type = ((VariableField)item).getType();
1788         //                    if (type != minField && type != maxField) break;
1789         //
1790         //                    if (i > 0) {
1791         //                        Object previousItem = items.get(0);
1792         //                        if (alpha.containsSome(previousItem.toString())) break;
1793         //                    }
1794         //                    int start = i+1;
1795         //                    if (start < items.size()) {
1796         //                        Object nextItem = items.get(start);
1797         //                        if (nextItem instanceof String) {
1798         //                            if (alpha.containsSome(nextItem.toString())) break;
1799         //                            start++; // otherwise skip over string
1800         //                        }
1801         //                    }
1802         //                    result.add(toString(start, items.size()));
1803         //                    break;
1804         //                }
1805         //            }
1806         //            // now trim from end
1807         //            for (int i = items.size()-1; i >= 0; --i) {
1808         //                Object item = items.get(i);
1809         //                if (item instanceof VariableField) {
1810         //                    int type = ((VariableField)item).getType();
1811         //                    if (type != minField && type != maxField) break;
1812         //                    if (i < items.size() - 1) {
1813         //                        Object previousItem = items.get(items.size() - 1);
1814         //                        if (alpha.containsSome(previousItem.toString())) break;
1815         //                    }
1816         //                    int end = i-1;
1817         //                    if (end > 0) {
1818         //                        Object nextItem = items.get(end);
1819         //                        if (nextItem instanceof String) {
1820         //                            if (alpha.containsSome(nextItem.toString())) break;
1821         //                            end--; // otherwise skip over string
1822         //                        }
1823         //                    }
1824         //                    result.add(toString(0, end+1));
1825         //                    break;
1826         //                }
1827         //            }
1828         //
1829         //            return result;
1830         //        }
1831 
1832         //        private static UnicodeSet alpha = new UnicodeSet("[:alphabetic:]");
1833 
1834         //        private int getType(Object item) {
1835         //            String s = item.toString();
1836         //            int canonicalIndex = getCanonicalIndex(s);
1837         //            if (canonicalIndex < 0) {
1838         //                throw new IllegalArgumentException("Illegal field:\t"
1839         //                        + s);
1840         //            }
1841         //            int type = types[canonicalIndex][1];
1842         //            return type;
1843         //        }
1844 
1845         /**
1846          *  Each literal string is quoted as needed. That is, the ' quote marks will only be added if needed. The exact pattern of quoting is not guaranteed, thus " de la " could be quoted as " 'de la' " or as " 'de' 'la' ".
1847          * @param string The string to check.
1848          * @return string with quoted literals
1849          * @deprecated This API is ICU internal only.
1850          * @hide deprecated on icu4j-org
1851          * @hide draft / provisional / internal are hidden on OHOS
1852          */
1853         @Deprecated
quoteLiteral(String string)1854         public Object quoteLiteral(String string) {
1855             return tokenizer.quoteLiteral(string);
1856         }
1857 
1858     }
1859 
1860     /**
1861      * Used by CLDR tooling; not in ICU4C.
1862      * Note, this will not work correctly with normal skeletons, since fields
1863      * that should be related in the two skeletons being compared - like EEE and
1864      * ccc, or y and U - will not be sorted in the same relative place as each
1865      * other when iterating over both TreeSets being compare, using TreeSet's
1866      * "natural" code point ordering (this could be addressed by initializing
1867      * the TreeSet with a comparator that compares fields first by their index
1868      * from getCanonicalIndex()). However if comparing canonical skeletons from
1869      * getCanonicalSkeletonAllowingDuplicates it will be OK regardless, since
1870      * in these skeletons all fields are normalized to the canonical pattern
1871      * char for those fields - M or L to M, E or c to E, y or U to y, etc. -
1872      * so corresponding fields will sort in the same way for both TreeMaps.
1873      * @deprecated This API is ICU internal only.
1874      * @hide deprecated on icu4j-org
1875      * @hide draft / provisional / internal are hidden on OHOS
1876      */
1877     @Deprecated
skeletonsAreSimilar(String id, String skeleton)1878     public boolean skeletonsAreSimilar(String id, String skeleton) {
1879         if (id.equals(skeleton)) {
1880             return true; // fast path
1881         }
1882         // must clone array, make sure items are in same order.
1883         TreeSet<String> parser1 = getSet(id);
1884         TreeSet<String> parser2 = getSet(skeleton);
1885         if (parser1.size() != parser2.size()) {
1886             return false;
1887         }
1888         Iterator<String> it2 = parser2.iterator();
1889         for (String item : parser1) {
1890             int index1 = getCanonicalIndex(item, false);
1891             String item2 = it2.next(); // same length so safe
1892             int index2 = getCanonicalIndex(item2, false);
1893             if (types[index1][1] != types[index2][1]) {
1894                 return false;
1895             }
1896         }
1897         return true;
1898     }
1899 
getSet(String id)1900     private TreeSet<String> getSet(String id) {
1901         final List<Object> items = fp.set(id).getItems();
1902         TreeSet<String> result = new TreeSet<>();
1903         for (Object obj : items) {
1904             final String item = obj.toString();
1905             if (item.startsWith("G") || item.startsWith("a")) {
1906                 continue;
1907             }
1908             result.add(item);
1909         }
1910         return result;
1911     }
1912 
1913     // ========= PRIVATES ============
1914 
1915     private static class PatternWithMatcher {
1916         public String pattern;
1917         public DateTimeMatcher matcherWithSkeleton;
1918         // Simple constructor
PatternWithMatcher(String pat, DateTimeMatcher matcher)1919         public PatternWithMatcher(String pat, DateTimeMatcher matcher) {
1920             pattern = pat;
1921             matcherWithSkeleton = matcher;
1922         }
1923     }
1924     private static class PatternWithSkeletonFlag {
1925         public String pattern;
1926         public boolean skeletonWasSpecified;
1927         // Simple constructor
PatternWithSkeletonFlag(String pat, boolean skelSpecified)1928         public PatternWithSkeletonFlag(String pat, boolean skelSpecified) {
1929             pattern = pat;
1930             skeletonWasSpecified = skelSpecified;
1931         }
1932         @Override
toString()1933         public String toString() {
1934             return pattern + "," + skeletonWasSpecified;
1935         }
1936     }
1937     private TreeMap<DateTimeMatcher, PatternWithSkeletonFlag> skeleton2pattern = new TreeMap<>(); // items are in priority order
1938     private TreeMap<String, PatternWithSkeletonFlag> basePattern_pattern = new TreeMap<>(); // items are in priority order
1939     private String decimal = "?";
1940     private String dateTimeFormat = "{1} {0}";
1941     private String[] appendItemFormats = new String[TYPE_LIMIT];
1942     private String[][] fieldDisplayNames = new String[TYPE_LIMIT][DisplayWidth.COUNT];
1943     private char defaultHourFormatChar = 'H';
1944     //private boolean chineseMonthHack = false;
1945     //private boolean isComplete = false;
1946     private volatile boolean frozen = false;
1947 
1948     private transient DateTimeMatcher current = new DateTimeMatcher();
1949     private transient FormatParser fp = new FormatParser();
1950     private transient DistanceInfo _distanceInfo = new DistanceInfo();
1951 
1952     private String[] allowedHourFormats;
1953 
1954     private static final int FRACTIONAL_MASK = 1<<FRACTIONAL_SECOND;
1955     private static final int SECOND_AND_FRACTIONAL_MASK = (1<<SECOND) | (1<<FRACTIONAL_SECOND);
1956 
1957     // Cache for DateTimePatternGenerator
1958     private static ICUCache<String, DateTimePatternGenerator> DTPNG_CACHE = new SimpleCache<>();
1959 
checkFrozen()1960     private void checkFrozen() {
1961         if (isFrozen()) {
1962             throw new UnsupportedOperationException("Attempt to modify frozen object");
1963         }
1964     }
1965 
1966     /**
1967      * We only get called here if we failed to find an exact skeleton. We have broken it into date + time, and look for the pieces.
1968      * If we fail to find a complete skeleton, we compose in a loop until we have all the fields.
1969      */
getBestAppending(DateTimeMatcher source, int missingFields, DistanceInfo distInfo, DateTimeMatcher skipMatcher, EnumSet<DTPGflags> flags, int options)1970     private String getBestAppending(DateTimeMatcher source, int missingFields, DistanceInfo distInfo, DateTimeMatcher skipMatcher, EnumSet<DTPGflags> flags, int options) {
1971         String resultPattern = null;
1972         if (missingFields != 0) {
1973             PatternWithMatcher resultPatternWithMatcher = getBestRaw(source, missingFields, distInfo, skipMatcher);
1974             resultPattern = adjustFieldTypes(resultPatternWithMatcher, source, flags, options);
1975 
1976             while (distInfo.missingFieldMask != 0) { // precondition: EVERY single field must work!
1977 
1978                 // special hack for SSS. If we are missing SSS, and we had ss but found it, replace the s field according to the
1979                 // number separator
1980                 if ((distInfo.missingFieldMask & SECOND_AND_FRACTIONAL_MASK) == FRACTIONAL_MASK
1981                         && (missingFields & SECOND_AND_FRACTIONAL_MASK) == SECOND_AND_FRACTIONAL_MASK) {
1982                     resultPatternWithMatcher.pattern = resultPattern;
1983                     flags = EnumSet.copyOf(flags);
1984                     flags.add(DTPGflags.FIX_FRACTIONAL_SECONDS);
1985                     resultPattern = adjustFieldTypes(resultPatternWithMatcher, source, flags, options);
1986                     distInfo.missingFieldMask &= ~FRACTIONAL_MASK; // remove bit
1987                     continue;
1988                 }
1989 
1990                 int startingMask = distInfo.missingFieldMask;
1991                 PatternWithMatcher tempWithMatcher = getBestRaw(source, distInfo.missingFieldMask, distInfo, skipMatcher);
1992                 String temp = adjustFieldTypes(tempWithMatcher, source, flags, options);
1993                 int foundMask = startingMask & ~distInfo.missingFieldMask;
1994                 int topField = getTopBitNumber(foundMask);
1995                 resultPattern = SimpleFormatterImpl.formatRawPattern(
1996                         getAppendFormat(topField), 2, 3, resultPattern, temp, getAppendName(topField));
1997             }
1998         }
1999         return resultPattern;
2000     }
2001 
getAppendName(int foundMask)2002     private String getAppendName(int foundMask) {
2003         return "'" + fieldDisplayNames[foundMask][APPENDITEM_WIDTH_INT] + "'";
2004     }
getAppendFormat(int foundMask)2005     private String getAppendFormat(int foundMask) {
2006         return appendItemFormats[foundMask];
2007     }
2008 
2009     //    /**
2010     //     * @param current2
2011     //     * @return
2012     //     */
2013     //    private String adjustSeconds(DateTimeMatcher current2) {
2014     //        // TODO Auto-generated method stub
2015     //        return null;
2016     //    }
2017 
2018     /**
2019      * @param foundMask
2020      */
getTopBitNumber(int foundMask)2021     private int getTopBitNumber(int foundMask) {
2022         int i = 0;
2023         while (foundMask != 0) {
2024             foundMask >>>= 1;
2025     ++i;
2026         }
2027         return i-1;
2028     }
2029 
addCanonicalItems()2030     private void addCanonicalItems() {
2031         PatternInfo patternInfo = new PatternInfo();
2032         // make sure that every valid field occurs once, with a "default" length
2033         for (int i = 0; i < CANONICAL_ITEMS.length; ++i) {
2034             addPattern(String.valueOf(CANONICAL_ITEMS[i]), false, patternInfo);
2035         }
2036     }
2037 
getBestRaw(DateTimeMatcher source, int includeMask, DistanceInfo missingFields, DateTimeMatcher skipMatcher)2038     private PatternWithMatcher getBestRaw(DateTimeMatcher source, int includeMask, DistanceInfo missingFields, DateTimeMatcher skipMatcher) {
2039         //      if (SHOW_DISTANCE) System.out.println("Searching for: " + source.pattern
2040         //      + ", mask: " + showMask(includeMask));
2041         int bestDistance = Integer.MAX_VALUE;
2042         int bestMissingFieldMask = Integer.MIN_VALUE;
2043         PatternWithMatcher bestPatternWithMatcher = new PatternWithMatcher("", null);
2044         DistanceInfo tempInfo = new DistanceInfo();
2045         for (DateTimeMatcher trial : skeleton2pattern.keySet()) {
2046             if (trial.equals(skipMatcher)) {
2047                 continue;
2048             }
2049             int distance = source.getDistance(trial, includeMask, tempInfo);
2050             //          if (SHOW_DISTANCE) System.out.println("\tDistance: " + trial.pattern + ":\t"
2051             //          + distance + ",\tmissing fields: " + tempInfo);
2052 
2053             // Because we iterate over a map the order is undefined. Can change between implementations,
2054             // versions, and will very likely be different between Java and C/C++.
2055             // So if we have patterns with the same distance we also look at the missingFieldMask,
2056             // and we favour the smallest one. Because the field is a bitmask this technically means we
2057             // favour differences in the "least significant fields". For example we prefer the one with differences
2058             // in seconds field vs one with difference in the hours field.
2059             if (distance < bestDistance || (distance == bestDistance && bestMissingFieldMask < tempInfo.missingFieldMask)) {
2060                 bestDistance = distance;
2061                 bestMissingFieldMask = tempInfo.missingFieldMask;
2062                 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(trial);
2063                 bestPatternWithMatcher.pattern = patternWithSkelFlag.pattern;
2064                 // If the best raw match had a specified skeleton then return it too.
2065                 // This can be passed through to adjustFieldTypes to help it do a better job.
2066                 if (patternWithSkelFlag.skeletonWasSpecified) {
2067                     bestPatternWithMatcher.matcherWithSkeleton = trial;
2068                 } else {
2069                     bestPatternWithMatcher.matcherWithSkeleton = null;
2070                 }
2071                 missingFields.setTo(tempInfo);
2072                 if (distance == 0) {
2073                     break;
2074                 }
2075             }
2076         }
2077         return bestPatternWithMatcher;
2078     }
2079 
2080     /*
2081      * @param fixFractionalSeconds TODO
2082      */
2083     // flags values
2084     private enum DTPGflags {
2085         FIX_FRACTIONAL_SECONDS,
2086         SKELETON_USES_CAP_J,
2087         // with #13183, no longer need flags for b, B
2088         ;
2089     };
2090 
adjustFieldTypes(PatternWithMatcher patternWithMatcher, DateTimeMatcher inputRequest, EnumSet<DTPGflags> flags, int options)2091     private String adjustFieldTypes(PatternWithMatcher patternWithMatcher, DateTimeMatcher inputRequest, EnumSet<DTPGflags> flags, int options) {
2092         fp.set(patternWithMatcher.pattern);
2093         StringBuilder newPattern = new StringBuilder();
2094         for (Object item : fp.getItems()) {
2095             if (item instanceof String) {
2096                 newPattern.append(fp.quoteLiteral((String)item));
2097             } else {
2098                 final VariableField variableField = (VariableField) item;
2099 
2100                 StringBuilder fieldBuilder = new StringBuilder(variableField.toString());
2101                 //                int canonicalIndex = getCanonicalIndex(field, true);
2102                 //                if (canonicalIndex < 0) {
2103                 //                    continue; // don't adjust
2104                 //                }
2105                 //                int type = types[canonicalIndex][1];
2106                 int type = variableField.getType();
2107 
2108                 // handle day periods - with #13183, no longer need special handling here, integrated with normal types
2109 
2110                 if (flags.contains(DTPGflags.FIX_FRACTIONAL_SECONDS) && type == SECOND) {
2111                     fieldBuilder.append(decimal);
2112                     inputRequest.original.appendFieldTo(FRACTIONAL_SECOND, fieldBuilder);
2113                 } else if (inputRequest.type[type] != 0) {
2114                     // Here:
2115                     // - "reqField" is the field from the originally requested skeleton, with length
2116                     // "reqFieldLen".
2117                     // - "field" is the field from the found pattern.
2118                     //
2119                     // The adjusted field should consist of characters from the originally requested
2120                     // skeleton, except in the case of MONTH or WEEKDAY or YEAR, in which case it
2121                     // should consist of characters from the found pattern. There is some adjustment
2122                     // in some cases of HOUR to "defaultHourFormatChar". There is explanation
2123                     // how it is done below.
2124                     //
2125                     // The length of the adjusted field (adjFieldLen) should match that in the originally
2126                     // requested skeleton, except that in the following cases the length of the adjusted field
2127                     // should match that in the found pattern (i.e. the length of this pattern field should
2128                     // not be adjusted):
2129                     // 1. type is HOUR and the corresponding bit in options is not set (ticket #7180).
2130                     //    Note, we may want to implement a similar change for other numeric fields (MM, dd,
2131                     //    etc.) so the default behavior is to get locale preference for field length, but
2132                     //    options bits can be used to override this.
2133                     // 2. There is a specified skeleton for the found pattern and one of the following is true:
2134                     //    a) The length of the field in the skeleton (skelFieldLen) is equal to reqFieldLen.
2135                     //    b) The pattern field is numeric and the skeleton field is not, or vice versa.
2136                     //
2137                     // Old behavior was:
2138                     // normally we just replace the field. However HOUR is special; we only change the length
2139 
2140                     char reqFieldChar = inputRequest.original.getFieldChar(type);
2141                     int reqFieldLen = inputRequest.original.getFieldLength(type);
2142                     if ( reqFieldChar == 'E' && reqFieldLen < 3 ) {
2143                         reqFieldLen = 3; // 1-3 for E are equivalent to 3 for c,e
2144                     }
2145                     int adjFieldLen = reqFieldLen;
2146                     DateTimeMatcher matcherWithSkeleton = patternWithMatcher.matcherWithSkeleton;
2147                     if ( (type == HOUR && (options & MATCH_HOUR_FIELD_LENGTH)==0) ||
2148                             (type == MINUTE && (options & MATCH_MINUTE_FIELD_LENGTH)==0) ||
2149                             (type == SECOND && (options & MATCH_SECOND_FIELD_LENGTH)==0) ) {
2150                         adjFieldLen = fieldBuilder.length();
2151                     } else if (matcherWithSkeleton != null) {
2152                         int skelFieldLen = matcherWithSkeleton.original.getFieldLength(type);
2153                         boolean patFieldIsNumeric = variableField.isNumeric();
2154                         boolean skelFieldIsNumeric = matcherWithSkeleton.fieldIsNumeric(type);
2155                         if (skelFieldLen == reqFieldLen
2156                                 || (patFieldIsNumeric && !skelFieldIsNumeric)
2157                                 || (skelFieldIsNumeric && !patFieldIsNumeric)) {
2158                             // don't adjust the field length in the found pattern
2159                             adjFieldLen = fieldBuilder.length();
2160                         }
2161                     }
2162                     char c = (type != HOUR
2163                             && type != MONTH
2164                             && type != WEEKDAY
2165                             && (type != YEAR || reqFieldChar=='Y'))
2166                             ? reqFieldChar
2167                             : fieldBuilder.charAt(0);
2168                     if (type == HOUR) {
2169                         // The adjustment here is required to match spec (https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-hour).
2170                         // It is necessary to match the hour-cycle preferred by the Locale.
2171                         // Given that, we need to do the following adjustments:
2172                         // 1. When hour-cycle is h11 it should replace 'h' by 'K'.
2173                         // 2. When hour-cycle is h23 it should replace 'H' by 'k'.
2174                         // 3. When hour-cycle is h24 it should replace 'k' by 'H'.
2175                         // 4. When hour-cycle is h12 it should replace 'K' by 'h'.
2176                         if (flags.contains(DTPGflags.SKELETON_USES_CAP_J) || reqFieldChar == defaultHourFormatChar) {
2177                             c = defaultHourFormatChar;
2178                         } else if (reqFieldChar == 'h' && defaultHourFormatChar == 'K') {
2179                             c = 'K';
2180                         } else if (reqFieldChar == 'H' && defaultHourFormatChar == 'k') {
2181                             c = 'k';
2182                         } else if (reqFieldChar == 'k' && defaultHourFormatChar == 'H') {
2183                             c = 'H';
2184                         } else if (reqFieldChar == 'K' && defaultHourFormatChar == 'h') {
2185                             c = 'h';
2186                         }
2187                     }
2188                     fieldBuilder = new StringBuilder();
2189                     for (int i = adjFieldLen; i > 0; --i) fieldBuilder.append(c);
2190                 }
2191                 newPattern.append(fieldBuilder);
2192             }
2193         }
2194         //if (SHOW_DISTANCE) System.out.println("\tRaw: " + pattern);
2195         return newPattern.toString();
2196     }
2197 
2198     //  public static String repeat(String s, int count) {
2199     //  StringBuffer result = new StringBuffer();
2200     //  for (int i = 0; i < count; ++i) {
2201     //  result.append(s);
2202     //  }
2203     //  return result.toString();
2204     //  }
2205 
2206     /**
2207      * internal routine
2208      * @param pattern The pattern that is passed.
2209      * @return field value
2210      * @deprecated This API is ICU internal only.
2211      * @hide deprecated on icu4j-org
2212      * @hide draft / provisional / internal are hidden on OHOS
2213      */
2214     @Deprecated
getFields(String pattern)2215     public String getFields(String pattern) {
2216         fp.set(pattern);
2217         StringBuilder newPattern = new StringBuilder();
2218         for (Object item : fp.getItems()) {
2219             if (item instanceof String) {
2220                 newPattern.append(fp.quoteLiteral((String)item));
2221             } else {
2222                 newPattern.append("{" + getName(item.toString()) + "}");
2223             }
2224         }
2225         return newPattern.toString();
2226     }
2227 
showMask(int mask)2228     private static String showMask(int mask) {
2229         StringBuilder result = new StringBuilder();
2230         for (int i = 0; i < TYPE_LIMIT; ++i) {
2231             if ((mask & (1<<i)) == 0)
2232                 continue;
2233             if (result.length() != 0)
2234                 result.append(" | ");
2235             result.append(FIELD_NAME[i]);
2236             result.append(" ");
2237         }
2238         return result.toString();
2239     }
2240 
2241     private static final String[] CLDR_FIELD_APPEND = {
2242         "Era", "Year", "Quarter", "Month", "Week", "*", "Day-Of-Week",
2243         "Day", "*", "*", "*",
2244         "Hour", "Minute", "Second", "*", "Timezone"
2245     };
2246 
2247     private static final String[] CLDR_FIELD_NAME = {
2248         "era", "year", "quarter", "month", "week", "weekOfMonth", "weekday",
2249         "day", "dayOfYear", "weekdayOfMonth", "dayperiod",
2250         "hour", "minute", "second", "*", "zone"
2251     };
2252 
2253     private static final String[] FIELD_NAME = {
2254         "Era", "Year", "Quarter", "Month", "Week_in_Year", "Week_in_Month", "Weekday",
2255         "Day", "Day_Of_Year", "Day_of_Week_in_Month", "Dayperiod",
2256         "Hour", "Minute", "Second", "Fractional_Second", "Zone"
2257     };
2258 
2259 
2260     private static final String[] CANONICAL_ITEMS = {
2261         "G", "y", "Q", "M", "w", "W", "E",
2262         "d", "D", "F", "a",
2263         "H", "m", "s", "S", "v"
2264     };
2265 
2266     // canon    DateTimePatternGen      CLDR fields
2267     // char     field                   bundle key
2268     // ----     --------------------    ----------------
2269     // 'G', //  0 ERA                   "era"
2270     // 'y', //  1 YEAR                  "year"
2271     // 'Q', //  2 QUARTER               "quarter"
2272     // 'M', //  3 MONTH                 "month"
2273     // 'w', //  4 WEEK_OF_YEAR,         "week"
2274     // 'W', //  5 WEEK_OF_MONTH         "weekOfMonth"
2275     // 'E', //  6 WEEKDAY               "weekday"
2276     // 'd', //  7 DAY                   "day"
2277     // 'D', //  8 DAY_OF_YEAR           "dayOfYear"
2278     // 'F', //  9 DAY_OF_WEEK_IN_MONTH  "weekdayOfMonth"
2279     // 'a', // 10 DAYPERIOD             "dayperiod"
2280     // 'H', // 11 HOUR                  "hour"
2281     // 'm', // 12 MINUTE                "minute"
2282     // 's', // 13 SECOND                "second"
2283     // 'S', // 14 FRACTIONAL_SECOND
2284     // 'v', // 15 ZONE                  "zone"
2285 
2286     private static final Set<String> CANONICAL_SET = new HashSet<>(Arrays.asList(CANONICAL_ITEMS));
2287     private Set<String> cldrAvailableFormatKeys = new HashSet<>(20);
2288 
2289     private static final int
2290     DATE_MASK = (1<<DAYPERIOD) - 1,
2291     TIME_MASK = (1<<TYPE_LIMIT) - 1 - DATE_MASK;
2292 
2293     private static final int // numbers are chosen to express 'distance'
2294     DELTA = 0x10,
2295     NUMERIC = 0x100,
2296     NONE = 0,
2297     NARROW = -0x101,
2298     SHORTER = -0x102,
2299     SHORT = -0x103,
2300     LONG = -0x104,
2301     EXTRA_FIELD =   0x10000,
2302     MISSING_FIELD = 0x1000;
2303 
2304 
getName(String s)2305     private static String getName(String s) {
2306         int i = getCanonicalIndex(s, true);
2307         String name = FIELD_NAME[types[i][1]];
2308         if (types[i][2] < 0) {
2309             name += ":S"; // string
2310         }
2311         else {
2312             name += ":N";
2313         }
2314         return name;
2315     }
2316 
2317     /**
2318      * Get the canonical index, or return -1 if illegal.
2319      * @param s
2320      * @param strict TODO
2321      */
getCanonicalIndex(String s, boolean strict)2322     private static int getCanonicalIndex(String s, boolean strict) {
2323         int len = s.length();
2324         if (len == 0) {
2325             return -1;
2326         }
2327         int ch = s.charAt(0);
2328         //      verify that all are the same character
2329         for (int i = 1; i < len; ++i) {
2330             if (s.charAt(i) != ch) {
2331                 return -1;
2332             }
2333         }
2334         int bestRow = -1;
2335         for (int i = 0; i < types.length; ++i) {
2336             int[] row = types[i];
2337             if (row[0] != ch) continue;
2338             bestRow = i;
2339             if (row[3] > len) continue;
2340             if (row[row.length-1] < len) continue;
2341             return i;
2342         }
2343         return strict ? -1 : bestRow;
2344     }
2345 
2346     /**
2347      * Gets the canonical character associated with the specified field (ERA, YEAR, etc).
2348      */
getCanonicalChar(int field, char reference)2349     private static char getCanonicalChar(int field, char reference) {
2350         // Special case: distinguish between 12-hour and 24-hour
2351         if (reference == 'h' || reference == 'K') {
2352             return 'h';
2353         }
2354 
2355         // Linear search over types (return the top entry for each field)
2356         for (int i = 0; i < types.length; ++i) {
2357             int[] row = types[i];
2358             if (row[1] == field) {
2359                 return (char) row[0];
2360             }
2361         }
2362         throw new IllegalArgumentException("Could not find field " + field);
2363     }
2364 
2365     private static final int[][] types = {
2366         // the order here makes a difference only when searching for single field.
2367         // format is:
2368         // pattern character, main type, weight, min length, weight
2369         {'G', ERA, SHORT, 1, 3},
2370         {'G', ERA, LONG, 4},
2371         {'G', ERA, NARROW, 5},
2372 
2373         {'y', YEAR, NUMERIC, 1, 20},
2374         {'Y', YEAR, NUMERIC + DELTA, 1, 20},
2375         {'u', YEAR, NUMERIC + 2*DELTA, 1, 20},
2376         {'r', YEAR, NUMERIC + 3*DELTA, 1, 20},
2377         {'U', YEAR, SHORT, 1, 3},
2378         {'U', YEAR, LONG, 4},
2379         {'U', YEAR, NARROW, 5},
2380 
2381         {'Q', QUARTER, NUMERIC, 1, 2},
2382         {'Q', QUARTER, SHORT, 3},
2383         {'Q', QUARTER, LONG, 4},
2384         {'Q', QUARTER, NARROW, 5},
2385         {'q', QUARTER, NUMERIC + DELTA, 1, 2},
2386         {'q', QUARTER, SHORT - DELTA, 3},
2387         {'q', QUARTER, LONG - DELTA, 4},
2388         {'q', QUARTER, NARROW - DELTA, 5},
2389 
2390         {'M', MONTH, NUMERIC, 1, 2},
2391         {'M', MONTH, SHORT, 3},
2392         {'M', MONTH, LONG, 4},
2393         {'M', MONTH, NARROW, 5},
2394         {'L', MONTH, NUMERIC + DELTA, 1, 2},
2395         {'L', MONTH, SHORT - DELTA, 3},
2396         {'L', MONTH, LONG - DELTA, 4},
2397         {'L', MONTH, NARROW - DELTA, 5},
2398         {'l', MONTH, NUMERIC + DELTA, 1, 1},
2399 
2400         {'w', WEEK_OF_YEAR, NUMERIC, 1, 2},
2401 
2402         {'W', WEEK_OF_MONTH, NUMERIC, 1},
2403 
2404         {'E', WEEKDAY, SHORT, 1, 3},
2405         {'E', WEEKDAY, LONG, 4},
2406         {'E', WEEKDAY, NARROW, 5},
2407         {'E', WEEKDAY, SHORTER, 6},
2408         {'c', WEEKDAY, NUMERIC + 2*DELTA, 1, 2},
2409         {'c', WEEKDAY, SHORT - 2*DELTA, 3},
2410         {'c', WEEKDAY, LONG - 2*DELTA, 4},
2411         {'c', WEEKDAY, NARROW - 2*DELTA, 5},
2412         {'c', WEEKDAY, SHORTER - 2*DELTA, 6},
2413         {'e', WEEKDAY, NUMERIC + DELTA, 1, 2}, // 'e' is currently not used in CLDR data, should not be canonical
2414         {'e', WEEKDAY, SHORT - DELTA, 3},
2415         {'e', WEEKDAY, LONG - DELTA, 4},
2416         {'e', WEEKDAY, NARROW - DELTA, 5},
2417         {'e', WEEKDAY, SHORTER - DELTA, 6},
2418 
2419         {'d', DAY, NUMERIC, 1, 2},
2420         {'g', DAY, NUMERIC + DELTA, 1, 20}, // really internal use, so we don't care
2421 
2422         {'D', DAY_OF_YEAR, NUMERIC, 1, 3},
2423 
2424         {'F', DAY_OF_WEEK_IN_MONTH, NUMERIC, 1},
2425 
2426         {'a', DAYPERIOD, SHORT, 1, 3},
2427         {'a', DAYPERIOD, LONG, 4},
2428         {'a', DAYPERIOD, NARROW, 5},
2429         {'b', DAYPERIOD, SHORT - DELTA, 1, 3},
2430         {'b', DAYPERIOD, LONG - DELTA, 4},
2431         {'b', DAYPERIOD, NARROW - DELTA, 5},
2432         // b needs to be closer to a than to B, so we make this 3*DELTA
2433         {'B', DAYPERIOD, SHORT - 3*DELTA, 1, 3},
2434         {'B', DAYPERIOD, LONG - 3*DELTA, 4},
2435         {'B', DAYPERIOD, NARROW - 3*DELTA, 5},
2436 
2437         {'H', HOUR, NUMERIC + 10*DELTA, 1, 2}, // 24 hour
2438         {'k', HOUR, NUMERIC + 11*DELTA, 1, 2},
2439         {'h', HOUR, NUMERIC, 1, 2}, // 12 hour
2440         {'K', HOUR, NUMERIC + DELTA, 1, 2},
2441 
2442         {'m', MINUTE, NUMERIC, 1, 2},
2443 
2444         {'s', SECOND, NUMERIC, 1, 2},
2445         {'A', SECOND, NUMERIC + DELTA, 1, 1000},
2446 
2447         {'S', FRACTIONAL_SECOND, NUMERIC, 1, 1000},
2448 
2449         {'v', ZONE, SHORT - 2*DELTA, 1},
2450         {'v', ZONE, LONG - 2*DELTA, 4},
2451         {'z', ZONE, SHORT, 1, 3},
2452         {'z', ZONE, LONG, 4},
2453         {'Z', ZONE, NARROW - DELTA, 1, 3},
2454         {'Z', ZONE, LONG - DELTA, 4},
2455         {'Z', ZONE, SHORT - DELTA, 5},
2456         {'O', ZONE, SHORT - DELTA, 1},
2457         {'O', ZONE, LONG - DELTA, 4},
2458         {'V', ZONE, SHORT - DELTA, 1},
2459         {'V', ZONE, LONG - DELTA, 2},
2460         {'V', ZONE, LONG-1 - DELTA, 3},
2461         {'V', ZONE, LONG-2 - DELTA, 4},
2462         {'X', ZONE, NARROW - DELTA, 1},
2463         {'X', ZONE, SHORT - DELTA, 2},
2464         {'X', ZONE, LONG - DELTA, 4},
2465         {'x', ZONE, NARROW - DELTA, 1},
2466         {'x', ZONE, SHORT - DELTA, 2},
2467         {'x', ZONE, LONG - DELTA, 4},
2468     };
2469 
2470 
2471     /**
2472      * A compact storage mechanism for skeleton field strings.  Several dozen of these will be created
2473      * for a typical DateTimePatternGenerator instance.
2474      * @author sffc
2475      */
2476     private static class SkeletonFields {
2477         private byte[] chars = new byte[TYPE_LIMIT];
2478         private byte[] lengths = new byte[TYPE_LIMIT];
2479         private static final byte DEFAULT_CHAR = '\0';
2480         private static final byte DEFAULT_LENGTH = 0;
2481 
clear()2482         public void clear() {
2483             Arrays.fill(chars, DEFAULT_CHAR);
2484             Arrays.fill(lengths, DEFAULT_LENGTH);
2485         }
2486 
copyFieldFrom(SkeletonFields other, int field)2487         void copyFieldFrom(SkeletonFields other, int field) {
2488             chars[field] = other.chars[field];
2489             lengths[field] = other.lengths[field];
2490         }
2491 
clearField(int field)2492         void clearField(int field) {
2493             chars[field] = DEFAULT_CHAR;
2494             lengths[field] = DEFAULT_LENGTH;
2495         }
2496 
getFieldChar(int field)2497         char getFieldChar(int field) {
2498             return (char) chars[field];
2499         }
2500 
getFieldLength(int field)2501         int getFieldLength(int field) {
2502             return lengths[field];
2503         }
2504 
populate(int field, String value)2505         void populate(int field, String value) {
2506             // Ensure no loss in character data
2507             for (char ch : value.toCharArray()) {
2508                 assert ch == value.charAt(0);
2509             }
2510 
2511             populate(field, value.charAt(0), value.length());
2512         }
2513 
populate(int field, char ch, int length)2514         void populate(int field, char ch, int length) {
2515             assert ch <= Byte.MAX_VALUE;
2516             assert length <= Byte.MAX_VALUE;
2517 
2518             chars[field] = (byte) ch;
2519             lengths[field] = (byte) length;
2520         }
2521 
isFieldEmpty(int field)2522         public boolean isFieldEmpty(int field) {
2523             return lengths[field] == DEFAULT_LENGTH;
2524         }
2525 
2526         @Override
toString()2527         public String toString() {
2528             return appendTo(new StringBuilder(), false, false).toString();
2529         }
2530 
toString(boolean skipDayPeriod)2531         public String toString(boolean skipDayPeriod) {
2532             return appendTo(new StringBuilder(), false, skipDayPeriod).toString();
2533         }
2534 
2535         @SuppressWarnings("unused")
toCanonicalString()2536         public String toCanonicalString() {
2537             return appendTo(new StringBuilder(), true, false).toString();
2538         }
2539 
toCanonicalString(boolean skipDayPeriod)2540         public String toCanonicalString(boolean skipDayPeriod) {
2541             return appendTo(new StringBuilder(), true, skipDayPeriod).toString();
2542         }
2543 
2544         @SuppressWarnings("unused")
appendTo(StringBuilder sb)2545         public StringBuilder appendTo(StringBuilder sb) {
2546             return appendTo(sb, false, false);
2547         }
2548 
appendTo(StringBuilder sb, boolean canonical, boolean skipDayPeriod)2549         private StringBuilder appendTo(StringBuilder sb, boolean canonical, boolean skipDayPeriod) {
2550             for (int i=0; i<TYPE_LIMIT; ++i) {
2551                 if (skipDayPeriod && i == DAYPERIOD) {
2552                     continue;
2553                 }
2554                 appendFieldTo(i, sb, canonical);
2555             }
2556             return sb;
2557         }
2558 
appendFieldTo(int field, StringBuilder sb)2559         public StringBuilder appendFieldTo(int field, StringBuilder sb) {
2560             return appendFieldTo(field, sb, false);
2561         }
2562 
appendFieldTo(int field, StringBuilder sb, boolean canonical)2563         private StringBuilder appendFieldTo(int field, StringBuilder sb, boolean canonical) {
2564             char ch = (char) chars[field];
2565             int length = lengths[field];
2566 
2567             if (canonical) {
2568                 ch = getCanonicalChar(field, ch);
2569             }
2570 
2571             for (int i=0; i<length; i++) {
2572                 sb.append(ch);
2573             }
2574             return sb;
2575         }
2576 
compareTo(SkeletonFields other)2577         public int compareTo(SkeletonFields other) {
2578             for (int i = 0; i < TYPE_LIMIT; ++i) {
2579                 int charDiff = chars[i] - other.chars[i];
2580                 if (charDiff != 0) {
2581                     return charDiff;
2582                 }
2583                 int lengthDiff = lengths[i] - other.lengths[i];
2584                 if (lengthDiff != 0) {
2585                     return lengthDiff;
2586                 }
2587             }
2588             return 0;
2589         }
2590 
2591         @Override
equals(Object other)2592         public boolean equals(Object other) {
2593             return this == other || (other != null && other instanceof SkeletonFields
2594                 && compareTo((SkeletonFields) other) == 0);
2595         }
2596 
2597         @Override
hashCode()2598         public int hashCode() {
2599             return Arrays.hashCode(chars) ^ Arrays.hashCode(lengths);
2600         }
2601     }
2602 
2603 
2604     private static class DateTimeMatcher implements Comparable<DateTimeMatcher> {
2605         //private String pattern = null;
2606         private int[] type = new int[TYPE_LIMIT];
2607         private SkeletonFields original = new SkeletonFields();
2608         private SkeletonFields baseOriginal = new SkeletonFields();
2609         private boolean addedDefaultDayPeriod = false;
2610 
2611         // just for testing; fix to make multi-threaded later
2612         // private static FormatParser fp = new FormatParser();
2613 
fieldIsNumeric(int field)2614         public boolean fieldIsNumeric(int field) {
2615             return type[field] > 0;
2616         }
2617 
2618         @Override
toString()2619         public String toString() {
2620             // for backward compatibility: addedDefaultDayPeriod true => DateTimeMatcher.set
2621             // added a single 'a' that was not in the provided skeleton, and it will be
2622             // removed when generating the skeleton to return.
2623             return original.toString(addedDefaultDayPeriod);
2624         }
2625 
2626         // returns a string like toString but using the canonical character for most types,
2627         // e.g. M for M or L, E for E or c, y for y or U, etc. The hour field is canonicalized
2628         // to 'H' (for 24-hour types) or 'h' (for 12-hour types)
toCanonicalString()2629         public String toCanonicalString() {
2630             // for backward compatibility: addedDefaultDayPeriod true => DateTimeMatcher.set
2631             // added a single 'a' that was not in the provided skeleton, and it will be
2632             // removed when generating the skeleton to return.
2633             return original.toCanonicalString(addedDefaultDayPeriod);
2634         }
2635 
getBasePattern()2636         String getBasePattern() {
2637             // for backward compatibility: addedDefaultDayPeriod true => DateTimeMatcher.set
2638             // added a single 'a' that was not in the provided skeleton, and it will be
2639             // removed when generating the skeleton to return.
2640             return baseOriginal.toString(addedDefaultDayPeriod);
2641         }
2642 
set(String pattern, FormatParser fp, boolean allowDuplicateFields)2643         DateTimeMatcher set(String pattern, FormatParser fp, boolean allowDuplicateFields) {
2644             // Reset any data stored in this instance
2645             Arrays.fill(type, NONE);
2646             original.clear();
2647             baseOriginal.clear();
2648             addedDefaultDayPeriod = false;
2649 
2650             fp.set(pattern);
2651             for (Object obj : fp.getItems()) {
2652                 if (!(obj instanceof VariableField)) {
2653                     continue;
2654                 }
2655                 VariableField item = (VariableField)obj;
2656                 String value = item.toString();
2657                 // don't skip 'a' anymore, dayPeriod handled specially below
2658                 int canonicalIndex = item.getCanonicalIndex();
2659                 //                if (canonicalIndex < 0) {
2660                 //                    throw new IllegalArgumentException("Illegal field:\t"
2661                 //                            + field + "\t in " + pattern);
2662                 //                }
2663                 int[] row = types[canonicalIndex];
2664                 int field = row[1];
2665                 if (!original.isFieldEmpty(field)) {
2666                     char ch1 = original.getFieldChar(field);
2667                     char ch2 = value.charAt(0);
2668                     if ( allowDuplicateFields ||
2669                             (ch1 == 'r' && ch2 == 'U') ||
2670                             (ch1 == 'U' && ch2 == 'r') ) {
2671                         continue;
2672                     }
2673                     throw new IllegalArgumentException("Conflicting fields:\t"
2674                             + ch1 + ", " + value + "\t in " + pattern);
2675                 }
2676                 original.populate(field, value);
2677                 char repeatChar = (char)row[0];
2678                 int repeatCount = row[3];
2679                 if ("GEzvQ".indexOf(repeatChar) >= 0) repeatCount = 1;
2680                 baseOriginal.populate(field, repeatChar, repeatCount);
2681                 int subField = row[2];
2682                 if (subField > 0) subField += value.length();
2683                 type[field] = subField;
2684             }
2685 
2686             // #20739, we have a skeleton with minutes and milliseconds, but no seconds
2687             //
2688             // Theoretically we would need to check and fix all fields with "gaps":
2689             // for example year-day (no month), month-hour (no day), and so on, All the possible field combinations.
2690             // Plus some smartness: year + hour => should we add month, or add day-of-year?
2691             // What about month + day-of-week, or month + am/pm indicator.
2692             // I think beyond a certain point we should not try to fix bad developer input and try guessing what they mean.
2693             // Garbage in, garbage out.
2694             if (!original.isFieldEmpty(MINUTE) && !original.isFieldEmpty(FRACTIONAL_SECOND) && original.isFieldEmpty(SECOND)) {
2695                 // Force the use of seconds
2696                 for (int i = 0; i < types.length; ++i) {
2697                     int[] row = types[i];
2698                     if (row[1] == SECOND) {
2699                         // first entry for SECOND
2700                         original.populate(SECOND, (char)row[0], row[3]);
2701                         baseOriginal.populate(SECOND, (char)row[0], row[3]);
2702                         // We add value.length, same as above, when type is first initialized.
2703                         // The value we want to "fake" here is "s", and 1 means "s".length()
2704                         int subField = row[2];
2705                         type[SECOND] = (subField > 0) ? subField + 1 : subField;
2706                         break;
2707                     }
2708                 }
2709             }
2710 
2711             // #13183, handle special behavior for day period characters (a, b, B)
2712             if (!original.isFieldEmpty(HOUR)) {
2713                 if (original.getFieldChar(HOUR)=='h' || original.getFieldChar(HOUR)=='K') {
2714                     // We have a skeleton with 12-hour-cycle format
2715                     if (original.isFieldEmpty(DAYPERIOD)) {
2716                         // But we do not have a day period in the skeleton; add the default DAYPERIOD (currently "a")
2717                         for (int i = 0; i < types.length; ++i) {
2718                             int[] row = types[i];
2719                             if (row[1] == DAYPERIOD) {
2720                                 // first entry for DAYPERIOD
2721                                 original.populate(DAYPERIOD, (char)row[0], row[3]);
2722                                 baseOriginal.populate(DAYPERIOD, (char)row[0], row[3]);
2723                                 type[DAYPERIOD] = row[2];
2724                                 addedDefaultDayPeriod = true;
2725                                 break;
2726                             }
2727                         }
2728                     }
2729                 } else if (!original.isFieldEmpty(DAYPERIOD)) {
2730                     // Skeleton has 24-hour-cycle hour format and has dayPeriod, delete dayPeriod (i.e. ignore it)
2731                     original.clearField(DAYPERIOD);
2732                     baseOriginal.clearField(DAYPERIOD);
2733                     type[DAYPERIOD] = NONE;
2734                 }
2735             }
2736             return this;
2737         }
2738 
getFieldMask()2739         int getFieldMask() {
2740             int result = 0;
2741             for (int i = 0; i < type.length; ++i) {
2742                 if (type[i] != 0) result |= (1<<i);
2743             }
2744             return result;
2745         }
2746 
2747         @SuppressWarnings("unused")
extractFrom(DateTimeMatcher source, int fieldMask)2748         void extractFrom(DateTimeMatcher source, int fieldMask) {
2749             for (int i = 0; i < type.length; ++i) {
2750                 if ((fieldMask & (1<<i)) != 0) {
2751                     type[i] = source.type[i];
2752                     original.copyFieldFrom(source.original, i);
2753                 } else {
2754                     type[i] = NONE;
2755                     original.clearField(i);
2756                 }
2757             }
2758         }
2759 
getDistance(DateTimeMatcher other, int includeMask, DistanceInfo distanceInfo)2760         int getDistance(DateTimeMatcher other, int includeMask, DistanceInfo distanceInfo) {
2761             int result = 0;
2762             distanceInfo.clear();
2763             for (int i = 0; i < TYPE_LIMIT; ++i) {
2764                 int myType = (includeMask & (1<<i)) == 0 ? 0 : type[i];
2765                 int otherType = other.type[i];
2766                 if (myType == otherType) continue; // identical (maybe both zero) add 0
2767                 if (myType == 0) { // and other is not
2768                     result += EXTRA_FIELD;
2769                     distanceInfo.addExtra(i);
2770                 } else if (otherType == 0) { // and mine is not
2771                     result += MISSING_FIELD;
2772                     distanceInfo.addMissing(i);
2773                 } else {
2774                     result += Math.abs(myType - otherType); // square of mismatch
2775                 }
2776             }
2777             return result;
2778         }
2779 
2780         @Override
compareTo(DateTimeMatcher that)2781         public int compareTo(DateTimeMatcher that) {
2782             int result = original.compareTo(that.original);
2783             return result > 0 ? -1 : result < 0 ? 1 : 0; // Reverse the order.
2784         }
2785 
2786         @Override
equals(Object other)2787         public boolean equals(Object other) {
2788             return this == other || (other != null && other instanceof DateTimeMatcher
2789                 && original.equals(((DateTimeMatcher) other).original));
2790         }
2791 
2792         @Override
hashCode()2793         public int hashCode() {
2794             return original.hashCode();
2795         }
2796     }
2797 
2798     private static class DistanceInfo {
2799         int missingFieldMask;
2800         int extraFieldMask;
DistanceInfo()2801         private DistanceInfo() {
2802         }
clear()2803         void clear() {
2804             missingFieldMask = extraFieldMask = 0;
2805         }
setTo(DistanceInfo other)2806         void setTo(DistanceInfo other) {
2807             missingFieldMask = other.missingFieldMask;
2808             extraFieldMask = other.extraFieldMask;
2809         }
addMissing(int field)2810         void addMissing(int field) {
2811             missingFieldMask |= (1<<field);
2812         }
addExtra(int field)2813         void addExtra(int field) {
2814             extraFieldMask |= (1<<field);
2815         }
2816         @Override
toString()2817         public String toString() {
2818             return "missingFieldMask: " + DateTimePatternGenerator.showMask(missingFieldMask)
2819                     + ", extraFieldMask: " + DateTimePatternGenerator.showMask(extraFieldMask);
2820         }
2821     }
2822 }
2823 //eof
2824