1 /*
2  ********************************************************************************
3  * Copyright (C) 2006-2015, Google, International Business Machines Corporation *
4  * and others. All Rights Reserved.                                             *
5  ********************************************************************************
6  */
7 package com.ibm.icu.text;
8 
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.BitSet;
12 import java.util.Collection;
13 import java.util.EnumSet;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.LinkedHashMap;
17 import java.util.LinkedHashSet;
18 import java.util.List;
19 import java.util.Locale;
20 import java.util.Map;
21 import java.util.MissingResourceException;
22 import java.util.Set;
23 import java.util.TreeMap;
24 import java.util.TreeSet;
25 
26 import com.ibm.icu.impl.ICUCache;
27 import com.ibm.icu.impl.ICUResourceBundle;
28 import com.ibm.icu.impl.PatternTokenizer;
29 import com.ibm.icu.impl.SimpleCache;
30 import com.ibm.icu.impl.Utility;
31 import com.ibm.icu.util.Calendar;
32 import com.ibm.icu.util.Freezable;
33 import com.ibm.icu.util.ICUCloneNotSupportedException;
34 import com.ibm.icu.util.ULocale;
35 import com.ibm.icu.util.ULocale.Category;
36 import com.ibm.icu.util.UResourceBundle;
37 
38 /**
39  * This class provides flexible generation of date format patterns, like
40  * "yy-MM-dd". The user can build up the generator by adding successive
41  * patterns. Once that is done, a query can be made using a "skeleton", which is
42  * a pattern which just includes the desired fields and lengths. The generator
43  * will return the "best fit" pattern corresponding to that skeleton.
44  * <p>
45  * The main method people will use is getBestPattern(String skeleton), since
46  * normally this class is pre-built with data from a particular locale. However,
47  * generators can be built directly from other data as well.
48  * @stable ICU 3.6
49  */
50 public class DateTimePatternGenerator implements Freezable<DateTimePatternGenerator>, Cloneable {
51     private static final boolean DEBUG = false;
52 
53     // debugging flags
54     //static boolean SHOW_DISTANCE = false;
55     // TODO add hack to fix months for CJK, as per bug ticket 1099
56 
57     /**
58      * Create empty generator, to be constructed with addPattern(...) etc.
59      * @stable ICU 3.6
60      */
getEmptyInstance()61     public static DateTimePatternGenerator getEmptyInstance() {
62         return new DateTimePatternGenerator();
63     }
64 
65     /**
66      * Only for use by subclasses
67      * @stable ICU 3.6
68      */
DateTimePatternGenerator()69     protected DateTimePatternGenerator() {
70     }
71 
72     /**
73      * Construct a flexible generator according to data for the default <code>FORMAT</code> locale.
74      * @see Category#FORMAT
75      * @stable ICU 3.6
76      */
getInstance()77     public static DateTimePatternGenerator getInstance() {
78         return getInstance(ULocale.getDefault(Category.FORMAT));
79     }
80 
81     /**
82      * Construct a flexible generator according to data for a given locale.
83      * @param uLocale The locale to pass.
84      * @stable ICU 3.6
85      */
getInstance(ULocale uLocale)86     public static DateTimePatternGenerator getInstance(ULocale uLocale) {
87         return getFrozenInstance(uLocale).cloneAsThawed();
88     }
89 
90     /**
91      * Construct a flexible generator according to data for a given locale.
92      * @param locale The JDK locale to pass.
93      * @draft ICU 54
94      * @provisional This API might change or be removed in a future release.
95      */
getInstance(Locale locale)96     public static DateTimePatternGenerator getInstance(Locale locale) {
97         return getInstance(ULocale.forLocale(locale));
98     }
99 
100     /**
101      * Construct a frozen instance of DateTimePatternGenerator for a
102      * given locale.  This method returns a cached frozen instance of
103      * DateTimePatternGenerator, so less expensive than the regular
104      * factory method.
105      * @param uLocale The locale to pass.
106      * @return A frozen DateTimePatternGenerator.
107      * @internal
108      * @deprecated This API is ICU internal only.
109      */
110     @Deprecated
getFrozenInstance(ULocale uLocale)111     public static DateTimePatternGenerator getFrozenInstance(ULocale uLocale) {
112         String localeKey = uLocale.toString();
113         DateTimePatternGenerator result = DTPNG_CACHE.get(localeKey);
114         if (result != null) {
115             return result;
116         }
117         result = new DateTimePatternGenerator();
118         PatternInfo returnInfo = new PatternInfo();
119         String shortTimePattern = null;
120         // first load with the ICU patterns
121         for (int i = DateFormat.FULL; i <= DateFormat.SHORT; ++i) {
122             SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(i, uLocale);
123             result.addPattern(df.toPattern(), false, returnInfo);
124             df = (SimpleDateFormat) DateFormat.getTimeInstance(i, uLocale);
125             result.addPattern(df.toPattern(), false, returnInfo);
126             if (i == DateFormat.SHORT) {
127                 // keep this pattern to populate other time field
128                 // combination patterns by hackTimes later in this method.
129                 shortTimePattern = df.toPattern();
130 
131                 // use hour style in SHORT time pattern as the default
132                 // hour style for the locale
133                 FormatParser fp = new FormatParser();
134                 fp.set(shortTimePattern);
135                 List<Object> items = fp.getItems();
136                 for (int idx = 0; idx < items.size(); idx++) {
137                     Object item = items.get(idx);
138                     if (item instanceof VariableField) {
139                         VariableField fld = (VariableField)item;
140                         if (fld.getType() == HOUR) {
141                             result.defaultHourFormatChar = fld.toString().charAt(0);
142                             break;
143                         }
144                     }
145                 }
146             }
147         }
148 
149         ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, uLocale);
150         // Get the correct calendar type
151         String calendarTypeToUse = uLocale.getKeywordValue("calendar");
152         if ( calendarTypeToUse == null ) {
153             String[] preferredCalendarTypes = Calendar.getKeywordValuesForLocale("calendar", uLocale, true);
154             calendarTypeToUse = preferredCalendarTypes[0]; // the most preferred calendar
155         }
156         if ( calendarTypeToUse == null ) {
157             calendarTypeToUse = "gregorian"; // fallback
158         }
159 
160         // Get data for that calendar
161         try {
162             //      ICU4J getWithFallback does not work well when
163             //      1) A nested table is an alias to /LOCALE/...
164             //      2) getWithFallback is called multiple times for going down hierarchical resource path
165             //      #9987 resolved the issue of alias table when full path is specified in getWithFallback,
166             //      but there is no easy solution when the equivalent operation is done by multiple operations.
167             //      This issue is addressed in #9964.
168             ICUResourceBundle itemBundle = rb.getWithFallback("calendar/" + calendarTypeToUse + "/appendItems");
169             for (int i=0; i<itemBundle.getSize(); ++i) {
170                 ICUResourceBundle formatBundle = (ICUResourceBundle)itemBundle.get(i);
171                 String formatName = itemBundle.get(i).getKey();
172                 String value = formatBundle.getString();
173                 result.setAppendItemFormat(getAppendFormatNumber(formatName), value);
174             }
175         }catch(MissingResourceException e) {
176         }
177 
178         // CLDR item names
179         try {
180             ICUResourceBundle itemBundle = rb.getWithFallback("fields");
181             ICUResourceBundle fieldBundle, dnBundle;
182             for (int i=0; i<TYPE_LIMIT; ++i) {
183                 if ( isCLDRFieldName(i) ) {
184                     fieldBundle = itemBundle.getWithFallback(CLDR_FIELD_NAME[i]);
185                     dnBundle = fieldBundle.getWithFallback("dn");
186                     String value = dnBundle.getString();
187                     //System.out.println("Field name:"+value);
188                     result.setAppendItemName(i, value);
189                 }
190             }
191         }catch(MissingResourceException e) {
192         }
193 
194         // set the AvailableFormat in CLDR
195         ICUResourceBundle availFormatsBundle = null;
196         try {
197             //      ICU4J getWithFallback does not work well when
198             //      1) A nested table is an alias to /LOCALE/...
199             //      2) getWithFallback is called multiple times for going down hierarchical resource path
200             //      #9987 resolved the issue of alias table when full path is specified in getWithFallback,
201             //      but there is no easy solution when the equivalent operation is done by multiple operations.
202             //      This issue is addressed in #9964.
203             availFormatsBundle = rb.getWithFallback("calendar/" + calendarTypeToUse + "/availableFormats");
204         } catch (MissingResourceException e) {
205             // fall through
206         }
207 
208         boolean override = true;
209         while (availFormatsBundle != null) {
210             for (int i = 0; i < availFormatsBundle.getSize(); i++) {
211                 String formatKey = availFormatsBundle.get(i).getKey();
212 
213                 if (!result.isAvailableFormatSet(formatKey)) {
214                     result.setAvailableFormat(formatKey);
215                     // Add pattern with its associated skeleton. Override any duplicate derived from std patterns,
216                     // but not a previous availableFormats entry:
217                     String formatValue = availFormatsBundle.get(i).getString();
218                     result.addPatternWithSkeleton(formatValue, formatKey, override, returnInfo);
219                 }
220             }
221 
222             ICUResourceBundle pbundle = (ICUResourceBundle)availFormatsBundle.getParent();
223             if (pbundle == null) {
224                 break;
225             }
226             try {
227                 availFormatsBundle = pbundle.getWithFallback("calendar/" + calendarTypeToUse + "/availableFormats");
228             } catch (MissingResourceException e) {
229                 availFormatsBundle = null;
230             }
231             if (availFormatsBundle != null && pbundle.getULocale().getBaseName().equals("root")) {
232                 override = false;
233             }
234         }
235 
236         // assume it is always big endian (ok for CLDR right now)
237         // some languages didn't add mm:ss or HH:mm, so put in a hack to compute that from the short time.
238         if (shortTimePattern != null) {
239             hackTimes(result, returnInfo, shortTimePattern);
240         }
241 
242         result.setDateTimeFormat(Calendar.getDateTimePattern(Calendar.getInstance(uLocale), uLocale, DateFormat.MEDIUM));
243 
244         // decimal point for seconds
245         DecimalFormatSymbols dfs = new DecimalFormatSymbols(uLocale);
246         result.setDecimal(String.valueOf(dfs.getDecimalSeparator()));
247 
248         // freeze and cache
249         result.freeze();
250         DTPNG_CACHE.put(localeKey, result);
251         return result;
252     }
253 
254     /**
255      * @internal
256      * @deprecated This API is ICU internal only.
257      */
258     @Deprecated
getDefaultHourFormatChar()259     public char getDefaultHourFormatChar() {
260         return defaultHourFormatChar;
261     }
262 
263     /**
264      * @internal
265      * @deprecated This API is ICU internal only.
266      */
267     @Deprecated
setDefaultHourFormatChar(char defaultHourFormatChar)268     public void setDefaultHourFormatChar(char defaultHourFormatChar) {
269         this.defaultHourFormatChar = defaultHourFormatChar;
270     }
271 
hackTimes(DateTimePatternGenerator result, PatternInfo returnInfo, String hackPattern)272     private static void hackTimes(DateTimePatternGenerator result, PatternInfo returnInfo, String hackPattern) {
273         result.fp.set(hackPattern);
274         StringBuilder mmss = new StringBuilder();
275         // to get mm:ss, we strip all but mm literal ss
276         boolean gotMm = false;
277         for (int i = 0; i < result.fp.items.size(); ++i) {
278             Object item = result.fp.items.get(i);
279             if (item instanceof String) {
280                 if (gotMm) {
281                     mmss.append(result.fp.quoteLiteral(item.toString()));
282                 }
283             } else {
284                 char ch = item.toString().charAt(0);
285                 if (ch == 'm') {
286                     gotMm = true;
287                     mmss.append(item);
288                 } else if (ch == 's') {
289                     if (!gotMm) {
290                         break; // failed
291                     }
292                     mmss.append(item);
293                     result.addPattern(mmss.toString(), false, returnInfo);
294                     break;
295                 } else if (gotMm || ch == 'z' || ch == 'Z' || ch == 'v' || ch == 'V') {
296                     break; // failed
297                 }
298             }
299         }
300         // to get hh:mm, we strip (literal ss) and (literal S)
301         // the easiest way to do this is to mark the stuff we want to nuke, then remove it in a second pass.
302         BitSet variables = new BitSet();
303         BitSet nuke = new BitSet();
304         for (int i = 0; i < result.fp.items.size(); ++i) {
305             Object item = result.fp.items.get(i);
306             if (item instanceof VariableField) {
307                 variables.set(i);
308                 char ch = item.toString().charAt(0);
309                 if (ch == 's' || ch == 'S') {
310                     nuke.set(i);
311                     for (int j = i-1; j >= 0; ++j) {
312                         if (variables.get(j)) break;
313                         nuke.set(i);
314                     }
315                 }
316             }
317         }
318         String hhmm = getFilteredPattern(result.fp, nuke);
319         result.addPattern(hhmm, false, returnInfo);
320     }
321 
getFilteredPattern(FormatParser fp, BitSet nuke)322     private static String getFilteredPattern(FormatParser fp, BitSet nuke) {
323         StringBuilder result = new StringBuilder();
324         for (int i = 0; i < fp.items.size(); ++i) {
325             if (nuke.get(i)) continue;
326             Object item = fp.items.get(i);
327             if (item instanceof String) {
328                 result.append(fp.quoteLiteral(item.toString()));
329             } else {
330                 result.append(item.toString());
331             }
332         }
333         return result.toString();
334     }
335 
336     /*private static int getAppendNameNumber(String string) {
337         for (int i = 0; i < CLDR_FIELD_NAME.length; ++i) {
338             if (CLDR_FIELD_NAME[i].equals(string)) return i;
339         }
340         return -1;
341     }*/
342 
343     /**
344      * @internal CLDR
345      * @deprecated This API is ICU internal only.
346      */
347     @Deprecated
getAppendFormatNumber(String string)348     public static int getAppendFormatNumber(String string) {
349         for (int i = 0; i < CLDR_FIELD_APPEND.length; ++i) {
350             if (CLDR_FIELD_APPEND[i].equals(string)) return i;
351         }
352         return -1;
353 
354     }
355 
isCLDRFieldName(int index)356     private static boolean isCLDRFieldName(int index) {
357         if ((index<0) && (index>=TYPE_LIMIT)) {
358             return false;
359         }
360         if (CLDR_FIELD_NAME[index].charAt(0) == '*') {
361             return false;
362         }
363         else {
364             return true;
365         }
366     }
367 
368     /**
369      * Return the best pattern matching the input skeleton. It is guaranteed to
370      * have all of the fields in the skeleton.
371      * <p>Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---getBestPatternExample}
372      * @param skeleton The skeleton is a pattern containing only the variable fields.
373      *            For example, "MMMdd" and "mmhh" are skeletons.
374      * @return Best pattern matching the input skeleton.
375      * @stable ICU 3.6
376      */
getBestPattern(String skeleton)377     public String getBestPattern(String skeleton) {
378         return getBestPattern(skeleton, null, MATCH_NO_OPTIONS);
379     }
380 
381     /**
382      * Return the best pattern matching the input skeleton. It is guaranteed to
383      * have all of the fields in the skeleton.
384      *
385      * @param skeleton The skeleton is a pattern containing only the variable fields.
386      *            For example, "MMMdd" and "mmhh" are skeletons.
387      * @param options MATCH_xxx options for forcing the length of specified fields in
388      *            the returned pattern to match those in the skeleton (when this would
389      *            not happen otherwise). For default behavior, use MATCH_NO_OPTIONS.
390      * @return Best pattern matching the input skeleton (and options).
391      * @stable ICU 4.4
392      */
getBestPattern(String skeleton, int options)393     public String getBestPattern(String skeleton, int options) {
394         return getBestPattern(skeleton, null, options);
395     }
396 
397     /*
398      * getBestPattern which takes optional skip matcher
399      */
getBestPattern(String skeleton, DateTimeMatcher skipMatcher, int options)400     private String getBestPattern(String skeleton, DateTimeMatcher skipMatcher, int options) {
401         EnumSet<DTPGflags> flags = EnumSet.noneOf(DTPGflags.class);
402         // Replace hour metacharacters 'j' and 'J', set flags as necessary
403         StringBuilder skeletonCopy = new StringBuilder(skeleton);
404         boolean inQuoted = false;
405         for (int patPos = 0; patPos < skeletonCopy.length(); patPos++) {
406             char patChr = skeletonCopy.charAt(patPos);
407             if (patChr == '\'') {
408                 inQuoted = !inQuoted;
409             } else if (!inQuoted) {
410                 if (patChr == 'j') {
411                     skeletonCopy.setCharAt(patPos, defaultHourFormatChar);
412                 } else if (patChr == 'J') {
413                 	// Get pattern for skeleton with H, then (in adjustFieldTypes)
414                 	// replace H or k with defaultHourFormatChar
415                 	skeletonCopy.setCharAt(patPos, 'H');
416                 	flags.add(DTPGflags.SKELETON_USES_CAP_J);
417                 }
418             }
419         }
420 
421         String datePattern, timePattern;
422         synchronized(this) {
423             current.set(skeletonCopy.toString(), fp, false);
424             PatternWithMatcher bestWithMatcher = getBestRaw(current, -1, _distanceInfo, skipMatcher);
425             if (_distanceInfo.missingFieldMask == 0 && _distanceInfo.extraFieldMask == 0) {
426                 // we have a good item. Adjust the field types
427                 return adjustFieldTypes(bestWithMatcher, current, flags, options);
428             }
429             int neededFields = current.getFieldMask();
430 
431             // otherwise break up by date and time.
432             datePattern = getBestAppending(current, neededFields & DATE_MASK, _distanceInfo, skipMatcher, flags, options);
433             timePattern = getBestAppending(current, neededFields & TIME_MASK, _distanceInfo, skipMatcher, flags, options);
434         }
435 
436         if (datePattern == null) return timePattern == null ? "" : timePattern;
437         if (timePattern == null) return datePattern;
438         return MessageFormat.format(getDateTimeFormat(), new Object[]{timePattern, datePattern});
439     }
440 
441     /**
442      * PatternInfo supplies output parameters for addPattern(...). It is used because
443      * Java doesn't have real output parameters. It is treated like a struct (eg
444      * Point), so all fields are public.
445      *
446      * @stable ICU 3.6
447      */
448     public static final class PatternInfo { // struct for return information
449         /**
450          * @stable ICU 3.6
451          */
452         public static final int OK = 0;
453 
454         /**
455          * @stable ICU 3.6
456          */
457         public static final int BASE_CONFLICT = 1;
458 
459         /**
460          * @stable ICU 3.6
461          */
462         public static final int CONFLICT = 2;
463 
464         /**
465          * @stable ICU 3.6
466          */
467         public int status;
468 
469         /**
470          * @stable ICU 3.6
471          */
472         public String conflictingPattern;
473 
474         /**
475          * Simple constructor, since this is treated like a struct.
476          * @stable ICU 3.6
477          */
PatternInfo()478         public PatternInfo() {
479         }
480     }
481 
482     /**
483      * Adds a pattern to the generator. If the pattern has the same skeleton as
484      * an existing pattern, and the override parameter is set, then the previous
485      * value is overridden. Otherwise, the previous value is retained. In either
486      * case, the conflicting information is returned in PatternInfo.
487      * <p>
488      * Note that single-field patterns (like "MMM") are automatically added, and
489      * don't need to be added explicitly!
490      * * <p>Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---addPatternExample}
491      * @param pattern Pattern to add.
492      * @param override When existing values are to be overridden use true, otherwise
493      *            use false.
494      * @param returnInfo Returned information.
495      * @stable ICU 3.6
496      */
addPattern(String pattern, boolean override, PatternInfo returnInfo)497     public DateTimePatternGenerator addPattern(String pattern, boolean override, PatternInfo returnInfo) {
498         return addPatternWithSkeleton(pattern, null, override, returnInfo);
499     }
500 
501     /**
502      * addPatternWithSkeleton:
503      * If skeletonToUse is specified, then an availableFormats entry is being added. In this case:
504      * 1. We pass that skeleton to DateTimeMatcher().set instead of having it derive a skeleton from the pattern.
505      * 2. If the new entry's skeleton or basePattern does match an existing entry but that entry also had a skeleton specified
506      * (i.e. it was also from availableFormats), then the new entry does not override it regardless of the value of the override
507      * parameter. This prevents later availableFormats entries from a parent locale overriding earlier ones from the actual
508      * specified locale. However, availableFormats entries *should* override entries with matching skeleton whose skeleton was
509      * derived (i.e. entries derived from the standard date/time patters for the specified locale).
510      * 3. When adding the pattern (skeleton2pattern.put, basePattern_pattern.put), we set a field to indicate that the added
511      * entry had a specified skeleton.
512      * @internal
513      * @deprecated This API is ICU internal only.
514      */
515     @Deprecated
addPatternWithSkeleton(String pattern, String skeletonToUse, boolean override, PatternInfo returnInfo)516     public DateTimePatternGenerator addPatternWithSkeleton(String pattern, String skeletonToUse, boolean override, PatternInfo returnInfo) {
517         checkFrozen();
518         DateTimeMatcher matcher;
519         if (skeletonToUse == null) {
520             matcher = new DateTimeMatcher().set(pattern, fp, false);
521         } else {
522             matcher = new DateTimeMatcher().set(skeletonToUse, fp, false);
523         }
524         String basePattern = matcher.getBasePattern();
525         // We only care about base conflicts - and replacing the pattern associated with a base - if:
526         // 1. the conflicting previous base pattern did *not* have an explicit skeleton; in that case the previous
527         // base + pattern combination was derived from either (a) a canonical item, (b) a standard format, or
528         // (c) a pattern specified programmatically with a previous call to addPattern (which would only happen
529         // if we are getting here from a subsequent call to addPattern).
530         // 2. a skeleton is specified for the current pattern, but override=false; in that case we are checking
531         // availableFormats items from root, which should not override any previous entry with the same base.
532         PatternWithSkeletonFlag previousPatternWithSameBase = basePattern_pattern.get(basePattern);
533         if (previousPatternWithSameBase != null && (!previousPatternWithSameBase.skeletonWasSpecified || (skeletonToUse != null && !override))) {
534             returnInfo.status = PatternInfo.BASE_CONFLICT;
535             returnInfo.conflictingPattern = previousPatternWithSameBase.pattern;
536             if (!override) {
537                 return this;
538             }
539         }
540         // The only time we get here with override=true and skeletonToUse!=null is when adding availableFormats
541         // items from CLDR data. In that case, we don't want an item from a parent locale to replace an item with
542         // same skeleton from the specified locale, so skip the current item if skeletonWasSpecified is true for
543         // the previously-specified conflicting item.
544         PatternWithSkeletonFlag previousValue = skeleton2pattern.get(matcher);
545         if (previousValue != null) {
546             returnInfo.status = PatternInfo.CONFLICT;
547             returnInfo.conflictingPattern = previousValue.pattern;
548             if (!override || (skeletonToUse != null && previousValue.skeletonWasSpecified)) return this;
549         }
550         returnInfo.status = PatternInfo.OK;
551         returnInfo.conflictingPattern = "";
552         PatternWithSkeletonFlag patWithSkelFlag = new PatternWithSkeletonFlag(pattern,skeletonToUse != null);
553         if (DEBUG) {
554             System.out.println(matcher + " => " + patWithSkelFlag);
555         }
556         skeleton2pattern.put(matcher, patWithSkelFlag);
557         basePattern_pattern.put(basePattern, patWithSkelFlag);
558         return this;
559     }
560 
561     /**
562      * Utility to return a unique skeleton from a given pattern. For example,
563      * both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd".
564      *
565      * @param pattern Input pattern, such as "dd/MMM"
566      * @return skeleton, such as "MMMdd"
567      * @stable ICU 3.6
568      */
getSkeleton(String pattern)569     public String getSkeleton(String pattern) {
570         synchronized (this) { // synchronized since a getter must be thread-safe
571             current.set(pattern, fp, false);
572             return current.toString();
573         }
574     }
575 
576     /**
577      * Same as getSkeleton, but allows duplicates
578      *
579      * @param pattern Input pattern, such as "dd/MMM"
580      * @return skeleton, such as "MMMdd"
581      * @internal
582      * @deprecated This API is ICU internal only.
583      */
584     @Deprecated
getSkeletonAllowingDuplicates(String pattern)585     public String getSkeletonAllowingDuplicates(String pattern) {
586         synchronized (this) { // synchronized since a getter must be thread-safe
587             current.set(pattern, fp, true);
588             return current.toString();
589         }
590     }
591 
592     /**
593      * Same as getSkeleton, but allows duplicates
594      * and returns a string using canonical pattern chars
595      *
596      * @param pattern Input pattern, such as "ccc, d LLL"
597      * @return skeleton, such as "MMMEd"
598      * @internal
599      * @deprecated This API is ICU internal only.
600      */
601     @Deprecated
getCanonicalSkeletonAllowingDuplicates(String pattern)602     public String getCanonicalSkeletonAllowingDuplicates(String pattern) {
603         synchronized (this) { // synchronized since a getter must be thread-safe
604             current.set(pattern, fp, true);
605             return current.toCanonicalString();
606         }
607     }
608 
609     /**
610      * Utility to return a unique base skeleton from a given pattern. This is
611      * the same as the skeleton, except that differences in length are minimized
612      * so as to only preserve the difference between string and numeric form. So
613      * for example, both "MMM-dd" and "d/MMM" produce the skeleton "MMMd"
614      * (notice the single d).
615      *
616      * @param pattern Input pattern, such as "dd/MMM"
617      * @return skeleton, such as "MMMdd"
618      * @stable ICU 3.6
619      */
getBaseSkeleton(String pattern)620     public String getBaseSkeleton(String pattern) {
621         synchronized (this) { // synchronized since a getter must be thread-safe
622             current.set(pattern, fp, false);
623             return current.getBasePattern();
624         }
625     }
626 
627     /**
628      * Return a list of all the skeletons (in canonical form) from this class,
629      * and the patterns that they map to.
630      *
631      * @param result an output Map in which to place the mapping from skeleton to
632      *            pattern. If you want to see the internal order being used,
633      *            supply a LinkedHashMap. If the input value is null, then a
634      *            LinkedHashMap is allocated.
635      *            <p>
636      *            <i>Issue: an alternate API would be to just return a list of
637      *            the skeletons, and then have a separate routine to get from
638      *            skeleton to pattern.</i>
639      * @return the input Map containing the values.
640      * @stable ICU 3.6
641      */
getSkeletons(Map<String, String> result)642     public Map<String, String> getSkeletons(Map<String, String> result) {
643         if (result == null) {
644             result = new LinkedHashMap<String, String>();
645         }
646         for (DateTimeMatcher item : skeleton2pattern.keySet()) {
647             PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(item);
648             String pattern = patternWithSkelFlag.pattern;
649             if (CANONICAL_SET.contains(pattern)) {
650                 continue;
651             }
652             result.put(item.toString(), pattern);
653         }
654         return result;
655     }
656 
657     /**
658      * Return a list of all the base skeletons (in canonical form) from this class
659      * @stable ICU 3.6
660      */
getBaseSkeletons(Set<String> result)661     public Set<String> getBaseSkeletons(Set<String> result) {
662         if (result == null) {
663             result = new HashSet<String>();
664         }
665         result.addAll(basePattern_pattern.keySet());
666         return result;
667     }
668 
669     /**
670      * Adjusts the field types (width and subtype) of a pattern to match what is
671      * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
672      * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
673      * "dd-MMMM hh:mm". This is used internally to get the best match for the
674      * input skeleton, but can also be used externally.
675      * <p>Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---replaceFieldTypesExample}
676      * @param pattern input pattern
677      * @param skeleton For the pattern to match to.
678      * @return pattern adjusted to match the skeleton fields widths and subtypes.
679      * @stable ICU 3.6
680      */
replaceFieldTypes(String pattern, String skeleton)681     public String replaceFieldTypes(String pattern, String skeleton) {
682         return replaceFieldTypes(pattern, skeleton, MATCH_NO_OPTIONS);
683     }
684 
685     /**
686      * Adjusts the field types (width and subtype) of a pattern to match what is
687      * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
688      * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
689      * "dd-MMMM hh:mm". This is used internally to get the best match for the
690      * input skeleton, but can also be used externally.
691      *
692      * @param pattern input pattern
693      * @param skeleton For the pattern to match to.
694      * @param options MATCH_xxx options for forcing the length of specified fields in
695      *            the returned pattern to match those in the skeleton (when this would
696      *            not happen otherwise). For default behavior, use MATCH_NO_OPTIONS.
697      * @return pattern adjusted to match the skeleton fields widths and subtypes.
698      * @stable ICU 4.4
699      */
replaceFieldTypes(String pattern, String skeleton, int options)700     public String replaceFieldTypes(String pattern, String skeleton, int options) {
701         synchronized (this) { // synchronized since a getter must be thread-safe
702             PatternWithMatcher patternNoMatcher = new PatternWithMatcher(pattern, null);
703             return adjustFieldTypes(patternNoMatcher, current.set(skeleton, fp, false), EnumSet.noneOf(DTPGflags.class), options);
704         }
705     }
706 
707     /**
708      * The date time format is a message format pattern used to compose date and
709      * time patterns. The default value is "{1} {0}", where {1} will be replaced
710      * by the date pattern and {0} will be replaced by the time pattern.
711      * <p>
712      * This is used when the input skeleton contains both date and time fields,
713      * but there is not a close match among the added patterns. For example,
714      * suppose that this object was created by adding "dd-MMM" and "hh:mm", and
715      * its datetimeFormat is the default "{1} {0}". Then if the input skeleton
716      * is "MMMdhmm", there is not an exact match, so the input skeleton is
717      * broken up into two components "MMMd" and "hmm". There are close matches
718      * for those two skeletons, so the result is put together with this pattern,
719      * resulting in "d-MMM h:mm".
720      *
721      * @param dateTimeFormat message format pattern, where {1} will be replaced by the date
722      *            pattern and {0} will be replaced by the time pattern.
723      * @stable ICU 3.6
724      */
setDateTimeFormat(String dateTimeFormat)725     public void setDateTimeFormat(String dateTimeFormat) {
726         checkFrozen();
727         this.dateTimeFormat = dateTimeFormat;
728     }
729 
730     /**
731      * Getter corresponding to setDateTimeFormat.
732      *
733      * @return pattern
734      * @stable ICU 3.6
735      */
getDateTimeFormat()736     public String getDateTimeFormat() {
737         return dateTimeFormat;
738     }
739 
740     /**
741      * The decimal value is used in formatting fractions of seconds. If the
742      * skeleton contains fractional seconds, then this is used with the
743      * fractional seconds. For example, suppose that the input pattern is
744      * "hhmmssSSSS", and the best matching pattern internally is "H:mm:ss", and
745      * the decimal string is ",". Then the resulting pattern is modified to be
746      * "H:mm:ss,SSSS"
747      *
748      * @param decimal The decimal to set to.
749      * @stable ICU 3.6
750      */
setDecimal(String decimal)751     public void setDecimal(String decimal) {
752         checkFrozen();
753         this.decimal = decimal;
754     }
755 
756     /**
757      * Getter corresponding to setDecimal.
758      * @return string corresponding to the decimal point
759      * @stable ICU 3.6
760      */
getDecimal()761     public String getDecimal() {
762         return decimal;
763     }
764 
765     /**
766      * Redundant patterns are those which if removed, make no difference in the
767      * resulting getBestPattern values. This method returns a list of them, to
768      * help check the consistency of the patterns used to build this generator.
769      *
770      * @param output stores the redundant patterns that are removed. To get these
771      *            in internal order, supply a LinkedHashSet. If null, a
772      *            collection is allocated.
773      * @return the collection with added elements.
774      * @internal
775      * @deprecated This API is ICU internal only.
776      */
777     @Deprecated
getRedundants(Collection<String> output)778     public Collection<String> getRedundants(Collection<String> output) {
779         synchronized (this) { // synchronized since a getter must be thread-safe
780             if (output == null) {
781                 output = new LinkedHashSet<String>();
782             }
783             for (DateTimeMatcher cur : skeleton2pattern.keySet()) {
784                 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(cur);
785                 String pattern = patternWithSkelFlag.pattern;
786                 if (CANONICAL_SET.contains(pattern)) {
787                     continue;
788                 }
789                 String trial = getBestPattern(cur.toString(), cur, MATCH_NO_OPTIONS);
790                 if (trial.equals(pattern)) {
791                     output.add(pattern);
792                 }
793             }
794             ///CLOVER:OFF
795             //The following would never be called since the parameter is false
796             //Eclipse stated the following is "dead code"
797             /*if (false) { // ordered
798                 DateTimePatternGenerator results = new DateTimePatternGenerator();
799                 PatternInfo pinfo = new PatternInfo();
800                 for (DateTimeMatcher cur : skeleton2pattern.keySet()) {
801                     String pattern = skeleton2pattern.get(cur);
802                     if (CANONICAL_SET.contains(pattern)) {
803                         continue;
804                     }
805                     //skipMatcher = current;
806                     String trial = results.getBestPattern(cur.toString());
807                     if (trial.equals(pattern)) {
808                         output.add(pattern);
809                     } else {
810                         results.addPattern(pattern, false, pinfo);
811                     }
812                 }
813             }*/
814             ///CLOVER:ON
815             return output;
816         }
817     }
818 
819     // Field numbers, used for AppendItem functions
820 
821     /**
822      * @stable ICU 3.6
823      */
824     static final public int ERA = 0;
825 
826     /**
827      * @stable ICU 3.6
828      */
829     static final public int YEAR = 1;
830 
831     /**
832      * @stable ICU 3.6
833      */
834     static final public int QUARTER = 2;
835 
836     /**
837      * @stable ICU 3.6
838      */
839     static final public int MONTH = 3;
840 
841     /**
842      * @stable ICU 3.6
843      */
844     static final public int WEEK_OF_YEAR = 4;
845 
846     /**
847      * @stable ICU 3.6
848      */
849     static final public int WEEK_OF_MONTH = 5;
850 
851     /**
852      * @stable ICU 3.6
853      */
854     static final public int WEEKDAY = 6;
855 
856     /**
857      * @stable ICU 3.6
858      */
859     static final public int DAY = 7;
860 
861     /**
862      * @stable ICU 3.6
863      */
864     static final public int DAY_OF_YEAR = 8;
865 
866     /**
867      * @stable ICU 3.6
868      */
869     static final public int DAY_OF_WEEK_IN_MONTH = 9;
870 
871     /**
872      * @stable ICU 3.6
873      */
874     static final public int DAYPERIOD = 10;
875 
876     /**
877      * @stable ICU 3.6
878      */
879     static final public int HOUR = 11;
880 
881     /**
882      * @stable ICU 3.6
883      */
884     static final public int MINUTE = 12;
885 
886     /**
887      * @stable ICU 3.6
888      */
889     static final public int SECOND = 13;
890 
891     /**
892      * @stable ICU 3.6
893      */
894     static final public int FRACTIONAL_SECOND = 14;
895 
896     /**
897      * @stable ICU 3.6
898      */
899     static final public int ZONE = 15;
900 
901     /**
902      * @stable ICU 3.6
903      */
904     static final public int TYPE_LIMIT = 16;
905 
906     // Option masks for getBestPattern, replaceFieldTypes (individual masks may be ORed together)
907 
908     /**
909      * Default option mask used for {@link #getBestPattern(String, int)}
910      * and {@link #replaceFieldTypes(String, String, int)}.
911      * @stable ICU 4.4
912      * @see #getBestPattern(String, int)
913      * @see #replaceFieldTypes(String, String, int)
914      */
915     public static final int MATCH_NO_OPTIONS = 0;
916 
917     /**
918      * Option mask for forcing the width of hour field.
919      * @stable ICU 4.4
920      * @see #getBestPattern(String, int)
921      * @see #replaceFieldTypes(String, String, int)
922      */
923     public static final int MATCH_HOUR_FIELD_LENGTH = 1 << HOUR;
924 
925     /**
926      * Option mask for forcing  the width of minute field.
927      * @internal
928      * @deprecated This API is ICU internal only.
929      */
930     @Deprecated
931     public static final int MATCH_MINUTE_FIELD_LENGTH = 1 << MINUTE;
932 
933     /**
934      * Option mask for forcing  the width of second field.
935      * @internal
936      * @deprecated This API is ICU internal only.
937      */
938     @Deprecated
939     public static final int MATCH_SECOND_FIELD_LENGTH = 1 << SECOND;
940 
941     /**
942      * Option mask for forcing the width of all date and time fields.
943      * @stable ICU 4.4
944      * @see #getBestPattern(String, int)
945      * @see #replaceFieldTypes(String, String, int)
946      */
947     public static final int MATCH_ALL_FIELDS_LENGTH = (1 << TYPE_LIMIT) - 1;
948 
949     /**
950      * An AppendItem format is a pattern used to append a field if there is no
951      * good match. For example, suppose that the input skeleton is "GyyyyMMMd",
952      * and there is no matching pattern internally, but there is a pattern
953      * matching "yyyyMMMd", say "d-MM-yyyy". Then that pattern is used, plus the
954      * G. The way these two are conjoined is by using the AppendItemFormat for G
955      * (era). So if that value is, say "{0}, {1}" then the final resulting
956      * pattern is "d-MM-yyyy, G".
957      * <p>
958      * There are actually three available variables: {0} is the pattern so far,
959      * {1} is the element we are adding, and {2} is the name of the element.
960      * <p>
961      * This reflects the way that the CLDR data is organized.
962      *
963      * @param field such as ERA
964      * @param value pattern, such as "{0}, {1}"
965      * @stable ICU 3.6
966      */
setAppendItemFormat(int field, String value)967     public void setAppendItemFormat(int field, String value) {
968         checkFrozen();
969         appendItemFormats[field] = value;
970     }
971 
972     /**
973      * Getter corresponding to setAppendItemFormats. Values below 0 or at or
974      * above TYPE_LIMIT are illegal arguments.
975      *
976      * @param field The index to retrieve the append item formats.
977      * @return append pattern for field
978      * @stable ICU 3.6
979      */
getAppendItemFormat(int field)980     public String getAppendItemFormat(int field) {
981         return appendItemFormats[field];
982     }
983 
984     /**
985      * Sets the names of fields, eg "era" in English for ERA. These are only
986      * used if the corresponding AppendItemFormat is used, and if it contains a
987      * {2} variable.
988      * <p>
989      * This reflects the way that the CLDR data is organized.
990      *
991      * @param field Index of the append item names.
992      * @param value The value to set the item to.
993      * @stable ICU 3.6
994      */
setAppendItemName(int field, String value)995     public void setAppendItemName(int field, String value) {
996         checkFrozen();
997         appendItemNames[field] = value;
998     }
999 
1000     /**
1001      * Getter corresponding to setAppendItemNames. Values below 0 or at or above
1002      * TYPE_LIMIT are illegal arguments.
1003      *
1004      * @param field The index to get the append item name.
1005      * @return name for field
1006      * @stable ICU 3.6
1007      */
getAppendItemName(int field)1008     public String getAppendItemName(int field) {
1009         return appendItemNames[field];
1010     }
1011 
1012     /**
1013      * Determines whether a skeleton contains a single field
1014      *
1015      * @param skeleton The skeleton to determine if it contains a single field.
1016      * @return true or not
1017      * @internal
1018      * @deprecated This API is ICU internal only.
1019      */
1020     @Deprecated
isSingleField(String skeleton)1021     public static boolean isSingleField(String skeleton) {
1022         char first = skeleton.charAt(0);
1023         for (int i = 1; i < skeleton.length(); ++i) {
1024             if (skeleton.charAt(i) != first) return false;
1025         }
1026         return true;
1027     }
1028 
1029     /**
1030      * Add key to HashSet cldrAvailableFormatKeys.
1031      *
1032      * @param key of the availableFormats in CLDR
1033      * @stable ICU 3.6
1034      */
setAvailableFormat(String key)1035     private void setAvailableFormat(String key) {
1036         checkFrozen();
1037         cldrAvailableFormatKeys.add(key);
1038     }
1039 
1040     /**
1041      * This function checks the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
1042      * has been added to DateTimePatternGenerator.
1043      * The function is to avoid the duplicate availableFomats added to
1044      * the pattern map from parent locales.
1045      *
1046      * @param key of the availableFormatMask in CLDR
1047      * @return TRUE if the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
1048      * has been added to DateTimePatternGenerator.
1049      * @stable ICU 3.6
1050      */
isAvailableFormatSet(String key)1051     private boolean isAvailableFormatSet(String key) {
1052         return cldrAvailableFormatKeys.contains(key);
1053     }
1054 
1055     /**
1056      * Boilerplate for Freezable
1057      * @stable ICU 3.6
1058      */
isFrozen()1059     public boolean isFrozen() {
1060         return frozen;
1061     }
1062 
1063     /**
1064      * Boilerplate for Freezable
1065      * @stable ICU 4.4
1066      */
freeze()1067     public DateTimePatternGenerator freeze() {
1068         frozen = true;
1069         return this;
1070     }
1071 
1072     /**
1073      * Boilerplate for Freezable
1074      * @stable ICU 4.4
1075      */
cloneAsThawed()1076     public DateTimePatternGenerator cloneAsThawed() {
1077         DateTimePatternGenerator result = (DateTimePatternGenerator) (this.clone());
1078         frozen = false;
1079         return result;
1080     }
1081 
1082     /**
1083      * Boilerplate
1084      * @stable ICU 3.6
1085      */
1086     @SuppressWarnings("unchecked")
clone()1087     public Object clone() {
1088         try {
1089             DateTimePatternGenerator result = (DateTimePatternGenerator) (super.clone());
1090             result.skeleton2pattern = (TreeMap<DateTimeMatcher, PatternWithSkeletonFlag>) skeleton2pattern.clone();
1091             result.basePattern_pattern = (TreeMap<String, PatternWithSkeletonFlag>) basePattern_pattern.clone();
1092             result.appendItemFormats = appendItemFormats.clone();
1093             result.appendItemNames = appendItemNames.clone();
1094             result.current = new DateTimeMatcher();
1095             result.fp = new FormatParser();
1096             result._distanceInfo = new DistanceInfo();
1097 
1098             result.frozen = false;
1099             return result;
1100         } catch (CloneNotSupportedException e) {
1101             ///CLOVER:OFF
1102             throw new ICUCloneNotSupportedException("Internal Error", e);
1103             ///CLOVER:ON
1104         }
1105     }
1106 
1107     /**
1108      * Utility class for FormatParser. Immutable class that is only used to mark
1109      * the difference between a variable field and a literal string. Each
1110      * variable field must consist of 1 to n variable characters, representing
1111      * date format fields. For example, "VVVV" is valid while "V4" is not, nor
1112      * is "44".
1113      *
1114      * @internal
1115      * @deprecated This API is ICU internal only.
1116      */
1117     @Deprecated
1118     public static class VariableField {
1119         private final String string;
1120         private final int canonicalIndex;
1121 
1122         /**
1123          * Create a variable field: equivalent to VariableField(string,false);
1124          * @param string The string for the variable field.
1125          * @internal
1126          * @deprecated This API is ICU internal only.
1127          */
1128         @Deprecated
VariableField(String string)1129         public VariableField(String string) {
1130             this(string, false);
1131         }
1132         /**
1133          * Create a variable field
1134          * @param string The string for the variable field
1135          * @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception.
1136          * @throws IllegalArgumentException if the variable field is not valid.
1137          * @internal
1138          * @deprecated This API is ICU internal only.
1139          */
1140         @Deprecated
VariableField(String string, boolean strict)1141         public VariableField(String string, boolean strict) {
1142             canonicalIndex = DateTimePatternGenerator.getCanonicalIndex(string, strict);
1143             if (canonicalIndex < 0) {
1144                 throw new IllegalArgumentException("Illegal datetime field:\t"
1145                         + string);
1146             }
1147             this.string = string;
1148         }
1149 
1150         /**
1151          * Get the main type of this variable. These types are ERA, QUARTER,
1152          * MONTH, DAY, WEEK_OF_YEAR, WEEK_OF_MONTH, WEEKDAY, DAY, DAYPERIOD
1153          * (am/pm), HOUR, MINUTE, SECOND,FRACTIONAL_SECOND, ZONE.
1154          * @return main type.
1155          * @internal
1156          * @deprecated This API is ICU internal only.
1157          */
1158         @Deprecated
getType()1159         public int getType() {
1160             return types[canonicalIndex][1];
1161         }
1162 
1163         /**
1164          * @internal
1165          * @deprecated This API is ICU internal only.
1166          */
1167         @Deprecated
getCanonicalCode(int type)1168         public static String getCanonicalCode(int type) {
1169             try {
1170                 return CANONICAL_ITEMS[type];
1171             } catch (Exception e) {
1172                 return String.valueOf(type);
1173             }
1174         }
1175         /**
1176          * Check if the type of this variable field is numeric.
1177          * @return true if the type of this variable field is numeric.
1178          * @internal
1179          * @deprecated This API is ICU internal only.
1180          */
1181         @Deprecated
isNumeric()1182         public boolean isNumeric() {
1183             return types[canonicalIndex][2] > 0;
1184         }
1185 
1186         /**
1187          * Private method.
1188          */
getCanonicalIndex()1189         private int getCanonicalIndex() {
1190             return canonicalIndex;
1191         }
1192 
1193         /**
1194          * Get the string represented by this variable.
1195          * @internal
1196          * @deprecated This API is ICU internal only.
1197          */
1198         @Deprecated
toString()1199         public String toString() {
1200             return string;
1201         }
1202     }
1203 
1204     /**
1205      * This class provides mechanisms for parsing a SimpleDateFormat pattern
1206      * or generating a new pattern, while handling the quoting. It represents
1207      * the result of the parse as a list of items, where each item is either a
1208      * literal string or a variable field. When parsing It can be used to find
1209      * out which variable fields are in a date format, and in what order, such
1210      * as for presentation in a UI as separate text entry fields. It can also be
1211      * used to construct new SimpleDateFormats.
1212      * <p>Example:
1213      * <pre>
1214     public boolean containsZone(String pattern) {
1215         for (Iterator it = formatParser.set(pattern).getItems().iterator(); it.hasNext();) {
1216             Object item = it.next();
1217             if (item instanceof VariableField) {
1218                 VariableField variableField = (VariableField) item;
1219                 if (variableField.getType() == DateTimePatternGenerator.ZONE) {
1220                     return true;
1221                 }
1222             }
1223         }
1224         return false;
1225     }
1226      *  </pre>
1227      * @internal
1228      * @deprecated This API is ICU internal only.
1229      */
1230     @Deprecated
1231     static public class FormatParser {
1232         private static final UnicodeSet SYNTAX_CHARS = new UnicodeSet("[a-zA-Z]").freeze();
1233         private static final UnicodeSet QUOTING_CHARS = new UnicodeSet("[[[:script=Latn:][:script=Cyrl:]]&[[:L:][:M:]]]").freeze();
1234         private transient PatternTokenizer tokenizer = new PatternTokenizer()
1235         .setSyntaxCharacters(SYNTAX_CHARS)
1236         .setExtraQuotingCharacters(QUOTING_CHARS)
1237         .setUsingQuote(true);
1238         private List<Object> items = new ArrayList<Object>();
1239 
1240         /**
1241          * Construct an empty date format parser, to which strings and variables can be added with set(...).
1242          * @internal
1243          * @deprecated This API is ICU internal only.
1244          */
1245         @Deprecated
FormatParser()1246         public FormatParser() {
1247         }
1248 
1249         /**
1250          * Parses the string into a list of items.
1251          * @param string The string to parse.
1252          * @return this, for chaining
1253          * @internal
1254          * @deprecated This API is ICU internal only.
1255          */
1256         @Deprecated
set(String string)1257         final public FormatParser set(String string) {
1258             return set(string, false);
1259         }
1260 
1261         /**
1262          * Parses the string into a list of items, taking into account all of the quoting that may be going on.
1263          * @param string  The string to parse.
1264          * @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception.
1265          * @return this, for chaining
1266          * @internal
1267          * @deprecated This API is ICU internal only.
1268          */
1269         @Deprecated
set(String string, boolean strict)1270         public FormatParser set(String string, boolean strict) {
1271             items.clear();
1272             if (string.length() == 0) return this;
1273             tokenizer.setPattern(string);
1274             StringBuffer buffer = new StringBuffer();
1275             StringBuffer variable = new StringBuffer();
1276             while (true) {
1277                 buffer.setLength(0);
1278                 int status = tokenizer.next(buffer);
1279                 if (status == PatternTokenizer.DONE) break;
1280                 if (status == PatternTokenizer.SYNTAX) {
1281                     if (variable.length() != 0 && buffer.charAt(0) != variable.charAt(0)) {
1282                         addVariable(variable, false);
1283                     }
1284                     variable.append(buffer);
1285                 } else {
1286                     addVariable(variable, false);
1287                     items.add(buffer.toString());
1288                 }
1289             }
1290             addVariable(variable, false);
1291             return this;
1292         }
1293 
addVariable(StringBuffer variable, boolean strict)1294         private void addVariable(StringBuffer variable, boolean strict) {
1295             if (variable.length() != 0) {
1296                 items.add(new VariableField(variable.toString(), strict));
1297                 variable.setLength(0);
1298             }
1299         }
1300 
1301         //        /** Private method. Return a collection of fields. These will be a mixture of literal Strings and VariableFields. Any "a" variable field is removed.
1302         //         * @param output List to append the items to. If null, is allocated as an ArrayList.
1303         //         * @return list
1304         //         */
1305         //        private List getVariableFields(List output) {
1306         //            if (output == null) output = new ArrayList();
1307         //            main:
1308         //                for (Iterator it = items.iterator(); it.hasNext();) {
1309         //                    Object item = it.next();
1310         //                    if (item instanceof VariableField) {
1311         //                        String s = item.toString();
1312         //                        switch(s.charAt(0)) {
1313         //                        //case 'Q': continue main; // HACK
1314         //                        case 'a': continue main; // remove
1315         //                        }
1316         //                        output.add(item);
1317         //                    }
1318         //                }
1319         //            //System.out.println(output);
1320         //            return output;
1321         //        }
1322 
1323         //        /**
1324         //         * Produce a string which concatenates all the variables. That is, it is the logically the same as the input with all literals removed.
1325         //         * @return a string which is a concatenation of all the variable fields
1326         //         */
1327         //        public String getVariableFieldString() {
1328         //            List list = getVariableFields(null);
1329         //            StringBuffer result = new StringBuffer();
1330         //            for (Iterator it = list.iterator(); it.hasNext();) {
1331         //                String item = it.next().toString();
1332         //                result.append(item);
1333         //            }
1334         //            return result.toString();
1335         //        }
1336 
1337         /**
1338          * 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:
1339          * <pre>
1340          * VariableField: dd
1341          * String: " de "
1342          * VariableField: MM
1343          * </pre>
1344          * The list is modifiable, so you can add any strings or variables to it, or remove any items.
1345          * @return modifiable list of items.
1346          * @internal
1347          * @deprecated This API is ICU internal only.
1348          */
1349         @Deprecated
getItems()1350         public List<Object> getItems() {
1351             return items;
1352         }
1353 
1354         /** 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().
1355          * @return printable output string
1356          * @internal
1357          * @deprecated This API is ICU internal only.
1358          */
1359         @Deprecated
toString()1360         public String toString() {
1361             return toString(0, items.size());
1362         }
1363 
1364         /**
1365          * 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().
1366          * @param start item to start from
1367          * @param limit last item +1
1368          * @return printable output string
1369          * @internal
1370          * @deprecated This API is ICU internal only.
1371          */
1372         @Deprecated
toString(int start, int limit)1373         public String toString(int start, int limit) {
1374             StringBuilder result = new StringBuilder();
1375             for (int i = start; i < limit; ++i) {
1376                 Object item = items.get(i);
1377                 if (item instanceof String) {
1378                     String itemString = (String) item;
1379                     result.append(tokenizer.quoteLiteral(itemString));
1380                 } else {
1381                     result.append(items.get(i).toString());
1382                 }
1383             }
1384             return result.toString();
1385         }
1386 
1387         /**
1388          * 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.
1389          * @return true or false
1390          * @internal
1391          * @deprecated This API is ICU internal only.
1392          */
1393         @Deprecated
hasDateAndTimeFields()1394         public boolean hasDateAndTimeFields() {
1395             int foundMask = 0;
1396             for (Object item : items) {
1397                 if (item instanceof VariableField) {
1398                     int type = ((VariableField)item).getType();
1399                     foundMask |= 1 << type;
1400                 }
1401             }
1402             boolean isDate = (foundMask & DATE_MASK) != 0;
1403             boolean isTime = (foundMask & TIME_MASK) != 0;
1404             return isDate && isTime;
1405         }
1406 
1407         //        /**
1408         //         * Internal routine
1409         //         * @param value
1410         //         * @param result
1411         //         * @return list
1412         //         */
1413         //        public List getAutoPatterns(String value, List result) {
1414         //            if (result == null) result = new ArrayList();
1415         //            int fieldCount = 0;
1416         //            int minField = Integer.MAX_VALUE;
1417         //            int maxField = Integer.MIN_VALUE;
1418         //            for (Iterator it = items.iterator(); it.hasNext();) {
1419         //                Object item = it.next();
1420         //                if (item instanceof VariableField) {
1421         //                    try {
1422         //                        int type = ((VariableField)item).getType();
1423         //                        if (minField > type) minField = type;
1424         //                        if (maxField < type) maxField = type;
1425         //                        if (type == ZONE || type == DAYPERIOD || type == WEEKDAY) return result; // skip anything with zones
1426         //                        fieldCount++;
1427         //                    } catch (Exception e) {
1428         //                        return result; // if there are any funny fields, return
1429         //                    }
1430         //                }
1431         //            }
1432         //            if (fieldCount < 3) return result; // skip
1433         //            // trim from start
1434         //            // trim first field IF there are no letters around it
1435         //            // and it is either the min or the max field
1436         //            // first field is either 0 or 1
1437         //            for (int i = 0; i < items.size(); ++i) {
1438         //                Object item = items.get(i);
1439         //                if (item instanceof VariableField) {
1440         //                    int type = ((VariableField)item).getType();
1441         //                    if (type != minField && type != maxField) break;
1442         //
1443         //                    if (i > 0) {
1444         //                        Object previousItem = items.get(0);
1445         //                        if (alpha.containsSome(previousItem.toString())) break;
1446         //                    }
1447         //                    int start = i+1;
1448         //                    if (start < items.size()) {
1449         //                        Object nextItem = items.get(start);
1450         //                        if (nextItem instanceof String) {
1451         //                            if (alpha.containsSome(nextItem.toString())) break;
1452         //                            start++; // otherwise skip over string
1453         //                        }
1454         //                    }
1455         //                    result.add(toString(start, items.size()));
1456         //                    break;
1457         //                }
1458         //            }
1459         //            // now trim from end
1460         //            for (int i = items.size()-1; i >= 0; --i) {
1461         //                Object item = items.get(i);
1462         //                if (item instanceof VariableField) {
1463         //                    int type = ((VariableField)item).getType();
1464         //                    if (type != minField && type != maxField) break;
1465         //                    if (i < items.size() - 1) {
1466         //                        Object previousItem = items.get(items.size() - 1);
1467         //                        if (alpha.containsSome(previousItem.toString())) break;
1468         //                    }
1469         //                    int end = i-1;
1470         //                    if (end > 0) {
1471         //                        Object nextItem = items.get(end);
1472         //                        if (nextItem instanceof String) {
1473         //                            if (alpha.containsSome(nextItem.toString())) break;
1474         //                            end--; // otherwise skip over string
1475         //                        }
1476         //                    }
1477         //                    result.add(toString(0, end+1));
1478         //                    break;
1479         //                }
1480         //            }
1481         //
1482         //            return result;
1483         //        }
1484 
1485         //        private static UnicodeSet alpha = new UnicodeSet("[:alphabetic:]");
1486 
1487         //        private int getType(Object item) {
1488         //            String s = item.toString();
1489         //            int canonicalIndex = getCanonicalIndex(s);
1490         //            if (canonicalIndex < 0) {
1491         //                throw new IllegalArgumentException("Illegal field:\t"
1492         //                        + s);
1493         //            }
1494         //            int type = types[canonicalIndex][1];
1495         //            return type;
1496         //        }
1497 
1498         /**
1499          *  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' ".
1500          * @param string The string to check.
1501          * @return string with quoted literals
1502          * @internal
1503          * @deprecated This API is ICU internal only.
1504          */
1505         @Deprecated
quoteLiteral(String string)1506         public Object quoteLiteral(String string) {
1507             return tokenizer.quoteLiteral(string);
1508         }
1509 
1510     }
1511 
1512     /**
1513     * Used by CLDR tooling; not in ICU4C.
1514     * Note, this will not work correctly with normal skeletons, since fields
1515     * that should be related in the two skeletons being compared - like EEE and
1516     * ccc, or y and U - will not be sorted in the same relative place as each
1517     * other when iterating over both TreeSets being compare, using TreeSet's
1518     * "natural" code point ordering (this could be addressed by initializing
1519     * the TreeSet with a comparator that compares fields first by their index
1520     * from getCanonicalIndex()). However if comparing canonical skeletons from
1521     * getCanonicalSkeletonAllowingDuplicates it will be OK regardless, since
1522     * in these skeletons all fields are normalized to the canonical pattern
1523     * char for those fields - M or L to M, E or c to E, y or U to y, etc. -
1524     * so corresponding fields will sort in the same way for both TreeMaps.
1525     * @internal
1526     * @deprecated This API is ICU internal only.
1527     */
1528     @Deprecated
skeletonsAreSimilar(String id, String skeleton)1529     public boolean skeletonsAreSimilar(String id, String skeleton) {
1530         if (id.equals(skeleton)) {
1531             return true; // fast path
1532         }
1533         // must clone array, make sure items are in same order.
1534         TreeSet<String> parser1 = getSet(id);
1535         TreeSet<String> parser2 = getSet(skeleton);
1536         if (parser1.size() != parser2.size()) {
1537             return false;
1538         }
1539         Iterator<String> it2 = parser2.iterator();
1540         for (String item : parser1) {
1541             int index1 = getCanonicalIndex(item, false);
1542             String item2 = it2.next(); // same length so safe
1543             int index2 = getCanonicalIndex(item2, false);
1544             if (types[index1][1] != types[index2][1]) {
1545                 return false;
1546             }
1547         }
1548         return true;
1549     }
1550 
getSet(String id)1551     private TreeSet<String> getSet(String id) {
1552         final List<Object> items = fp.set(id).getItems();
1553         TreeSet<String> result = new TreeSet<String>();
1554         for (Object obj : items) {
1555             final String item = obj.toString();
1556             if (item.startsWith("G") || item.startsWith("a")) {
1557                 continue;
1558             }
1559             result.add(item);
1560         }
1561         return result;
1562     }
1563 
1564     // ========= PRIVATES ============
1565 
1566     private static class PatternWithMatcher {
1567         public String pattern;
1568         public DateTimeMatcher matcherWithSkeleton;
1569         // Simple constructor
PatternWithMatcher(String pat, DateTimeMatcher matcher)1570         public PatternWithMatcher(String pat, DateTimeMatcher matcher) {
1571             pattern = pat;
1572             matcherWithSkeleton = matcher;
1573         }
1574     }
1575     private static class PatternWithSkeletonFlag {
1576         public String pattern;
1577         public boolean skeletonWasSpecified;
1578         // Simple constructor
PatternWithSkeletonFlag(String pat, boolean skelSpecified)1579         public PatternWithSkeletonFlag(String pat, boolean skelSpecified) {
1580             pattern = pat;
1581             skeletonWasSpecified = skelSpecified;
1582         }
toString()1583         public String toString() {
1584             return pattern + "," + skeletonWasSpecified;
1585         }
1586     }
1587     private TreeMap<DateTimeMatcher, PatternWithSkeletonFlag> skeleton2pattern = new TreeMap<DateTimeMatcher, PatternWithSkeletonFlag>(); // items are in priority order
1588     private TreeMap<String, PatternWithSkeletonFlag> basePattern_pattern = new TreeMap<String, PatternWithSkeletonFlag>(); // items are in priority order
1589     private String decimal = "?";
1590     private String dateTimeFormat = "{1} {0}";
1591     private String[] appendItemFormats = new String[TYPE_LIMIT];
1592     private String[] appendItemNames = new String[TYPE_LIMIT];
1593     {
1594         for (int i = 0; i < TYPE_LIMIT; ++i) {
1595             appendItemFormats[i] = "{0} \u251C{2}: {1}\u2524";
1596             appendItemNames[i] = "F" + i;
1597         }
1598     }
1599     private char defaultHourFormatChar = 'H';
1600     //private boolean chineseMonthHack = false;
1601     //private boolean isComplete = false;
1602     private volatile boolean frozen = false;
1603 
1604     private transient DateTimeMatcher current = new DateTimeMatcher();
1605     private transient FormatParser fp = new FormatParser();
1606     private transient DistanceInfo _distanceInfo = new DistanceInfo();
1607 
1608     private static final int FRACTIONAL_MASK = 1<<FRACTIONAL_SECOND;
1609     private static final int SECOND_AND_FRACTIONAL_MASK = (1<<SECOND) | (1<<FRACTIONAL_SECOND);
1610 
1611     // Cache for DateTimePatternGenerator
1612     private static ICUCache<String, DateTimePatternGenerator> DTPNG_CACHE = new SimpleCache<String, DateTimePatternGenerator>();
1613 
checkFrozen()1614     private void checkFrozen() {
1615         if (isFrozen()) {
1616             throw new UnsupportedOperationException("Attempt to modify frozen object");
1617         }
1618     }
1619 
1620     /**
1621      * 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.
1622      * If we fail to find a complete skeleton, we compose in a loop until we have all the fields.
1623      */
getBestAppending(DateTimeMatcher source, int missingFields, DistanceInfo distInfo, DateTimeMatcher skipMatcher, EnumSet<DTPGflags> flags, int options)1624     private String getBestAppending(DateTimeMatcher source, int missingFields, DistanceInfo distInfo, DateTimeMatcher skipMatcher, EnumSet<DTPGflags> flags, int options) {
1625         String resultPattern = null;
1626         if (missingFields != 0) {
1627             PatternWithMatcher resultPatternWithMatcher = getBestRaw(source, missingFields, distInfo, skipMatcher);
1628             resultPattern = adjustFieldTypes(resultPatternWithMatcher, source, flags, options);
1629 
1630             while (distInfo.missingFieldMask != 0) { // precondition: EVERY single field must work!
1631 
1632                 // special hack for SSS. If we are missing SSS, and we had ss but found it, replace the s field according to the
1633                 // number separator
1634                 if ((distInfo.missingFieldMask & SECOND_AND_FRACTIONAL_MASK) == FRACTIONAL_MASK
1635                         && (missingFields & SECOND_AND_FRACTIONAL_MASK) == SECOND_AND_FRACTIONAL_MASK) {
1636                     resultPatternWithMatcher.pattern = resultPattern;
1637                     flags = EnumSet.copyOf(flags);
1638                     flags.add(DTPGflags.FIX_FRACTIONAL_SECONDS);
1639                     resultPattern = adjustFieldTypes(resultPatternWithMatcher, source, flags, options);
1640                     distInfo.missingFieldMask &= ~FRACTIONAL_MASK; // remove bit
1641                     continue;
1642                 }
1643 
1644                 int startingMask = distInfo.missingFieldMask;
1645                 PatternWithMatcher tempWithMatcher = getBestRaw(source, distInfo.missingFieldMask, distInfo, skipMatcher);
1646                 String temp = adjustFieldTypes(tempWithMatcher, source, flags, options);
1647                 int foundMask = startingMask & ~distInfo.missingFieldMask;
1648                 int topField = getTopBitNumber(foundMask);
1649                 resultPattern = MessageFormat.format(getAppendFormat(topField), new Object[]{resultPattern, temp, getAppendName(topField)});
1650             }
1651         }
1652         return resultPattern;
1653     }
1654 
getAppendName(int foundMask)1655     private String getAppendName(int foundMask) {
1656         return "'" + appendItemNames[foundMask] + "'";
1657     }
getAppendFormat(int foundMask)1658     private String getAppendFormat(int foundMask) {
1659         return appendItemFormats[foundMask];
1660     }
1661 
1662     //    /**
1663     //     * @param current2
1664     //     * @return
1665     //     */
1666     //    private String adjustSeconds(DateTimeMatcher current2) {
1667     //        // TODO Auto-generated method stub
1668     //        return null;
1669     //    }
1670 
1671     /**
1672      * @param foundMask
1673      */
getTopBitNumber(int foundMask)1674     private int getTopBitNumber(int foundMask) {
1675         int i = 0;
1676         while (foundMask != 0) {
1677             foundMask >>>= 1;
1678             ++i;
1679         }
1680         return i-1;
1681     }
1682 
1683     /**
1684      *
1685      */
complete()1686     private void complete() {
1687         PatternInfo patternInfo = new PatternInfo();
1688         // make sure that every valid field occurs once, with a "default" length
1689         for (int i = 0; i < CANONICAL_ITEMS.length; ++i) {
1690             //char c = (char)types[i][0];
1691             addPattern(String.valueOf(CANONICAL_ITEMS[i]), false, patternInfo);
1692         }
1693         //isComplete = true;
1694     }
1695     {
complete()1696         complete();
1697     }
1698 
1699     /**
1700      *
1701      */
getBestRaw(DateTimeMatcher source, int includeMask, DistanceInfo missingFields, DateTimeMatcher skipMatcher)1702     private PatternWithMatcher getBestRaw(DateTimeMatcher source, int includeMask, DistanceInfo missingFields, DateTimeMatcher skipMatcher) {
1703         //      if (SHOW_DISTANCE) System.out.println("Searching for: " + source.pattern
1704         //      + ", mask: " + showMask(includeMask));
1705         int bestDistance = Integer.MAX_VALUE;
1706         PatternWithMatcher bestPatternWithMatcher = new PatternWithMatcher("", null);
1707         DistanceInfo tempInfo = new DistanceInfo();
1708         for (DateTimeMatcher trial : skeleton2pattern.keySet()) {
1709             if (trial.equals(skipMatcher)) {
1710                 continue;
1711             }
1712             int distance = source.getDistance(trial, includeMask, tempInfo);
1713             //          if (SHOW_DISTANCE) System.out.println("\tDistance: " + trial.pattern + ":\t"
1714             //          + distance + ",\tmissing fields: " + tempInfo);
1715             if (distance < bestDistance) {
1716                 bestDistance = distance;
1717                 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(trial);
1718                 bestPatternWithMatcher.pattern = patternWithSkelFlag.pattern;
1719                 // If the best raw match had a specified skeleton then return it too.
1720                 // This can be passed through to adjustFieldTypes to help it do a better job.
1721                 if (patternWithSkelFlag.skeletonWasSpecified) {
1722                     bestPatternWithMatcher.matcherWithSkeleton = trial;
1723                 } else {
1724                     bestPatternWithMatcher.matcherWithSkeleton = null;
1725                 }
1726                 missingFields.setTo(tempInfo);
1727                 if (distance == 0) {
1728                     break;
1729                 }
1730             }
1731         }
1732         return bestPatternWithMatcher;
1733     }
1734 
1735     /*
1736      * @param fixFractionalSeconds TODO
1737      */
1738     // flags values
1739     private enum DTPGflags { FIX_FRACTIONAL_SECONDS, SKELETON_USES_CAP_J };
1740 
adjustFieldTypes(PatternWithMatcher patternWithMatcher, DateTimeMatcher inputRequest, EnumSet<DTPGflags> flags, int options)1741     private String adjustFieldTypes(PatternWithMatcher patternWithMatcher, DateTimeMatcher inputRequest, EnumSet<DTPGflags> flags, int options) {
1742         fp.set(patternWithMatcher.pattern);
1743         StringBuilder newPattern = new StringBuilder();
1744         for (Object item : fp.getItems()) {
1745             if (item instanceof String) {
1746                 newPattern.append(fp.quoteLiteral((String)item));
1747             } else {
1748                 final VariableField variableField = (VariableField) item;
1749                 StringBuilder fieldBuilder = new StringBuilder(variableField.toString());
1750                 //                int canonicalIndex = getCanonicalIndex(field, true);
1751                 //                if (canonicalIndex < 0) {
1752                 //                    continue; // don't adjust
1753                 //                }
1754                 //                int type = types[canonicalIndex][1];
1755                 int type = variableField.getType();
1756 
1757                 if (flags.contains(DTPGflags.FIX_FRACTIONAL_SECONDS) && type == SECOND) {
1758                     String newField = inputRequest.original[FRACTIONAL_SECOND];
1759                     fieldBuilder.append(decimal);
1760                     fieldBuilder.append(newField);
1761                 } else if (inputRequest.type[type] != 0) {
1762                     // Here:
1763                     // - "reqField" is the field from the originally requested skeleton, with length
1764                     // "reqFieldLen".
1765                     // - "field" is the field from the found pattern.
1766                     //
1767                     // The adjusted field should consist of characters from the originally requested
1768                     // skeleton, except in the case of HOUR or MONTH or WEEKDAY or YEAR, in which case it
1769                     // should consist of characters from the found pattern.
1770                     //
1771                     // The length of the adjusted field (adjFieldLen) should match that in the originally
1772                     // requested skeleton, except that in the following cases the length of the adjusted field
1773                     // should match that in the found pattern (i.e. the length of this pattern field should
1774                     // not be adjusted):
1775                     // 1. type is HOUR and the corresponding bit in options is not set (ticket #7180).
1776                     //    Note, we may want to implement a similar change for other numeric fields (MM, dd,
1777                     //    etc.) so the default behavior is to get locale preference for field length, but
1778                     //    options bits can be used to override this.
1779                     // 2. There is a specified skeleton for the found pattern and one of the following is true:
1780                     //    a) The length of the field in the skeleton (skelFieldLen) is equal to reqFieldLen.
1781                     //    b) The pattern field is numeric and the skeleton field is not, or vice versa.
1782                     //
1783                     // Old behavior was:
1784                     // normally we just replace the field. However HOUR is special; we only change the length
1785 
1786                     String reqField = inputRequest.original[type];
1787                     int reqFieldLen = reqField.length();
1788                     if ( reqField.charAt(0) == 'E' && reqFieldLen < 3 ) {
1789                         reqFieldLen = 3; // 1-3 for E are equivalent to 3 for c,e
1790                     }
1791                     int adjFieldLen = reqFieldLen;
1792                     DateTimeMatcher matcherWithSkeleton = patternWithMatcher.matcherWithSkeleton;
1793                     if ( (type == HOUR && (options & MATCH_HOUR_FIELD_LENGTH)==0) ||
1794                          (type == MINUTE && (options & MATCH_MINUTE_FIELD_LENGTH)==0) ||
1795                          (type == SECOND && (options & MATCH_SECOND_FIELD_LENGTH)==0) ) {
1796                         adjFieldLen = fieldBuilder.length();
1797                     } else if (matcherWithSkeleton != null) {
1798                         String skelField = matcherWithSkeleton.origStringForField(type);
1799                         int skelFieldLen = skelField.length();
1800                         boolean patFieldIsNumeric = variableField.isNumeric();
1801                         boolean skelFieldIsNumeric = matcherWithSkeleton.fieldIsNumeric(type);
1802                         if (skelFieldLen == reqFieldLen || (patFieldIsNumeric && !skelFieldIsNumeric) || (skelFieldIsNumeric && !patFieldIsNumeric)) {
1803                             // don't adjust the field length in the found pattern
1804                             adjFieldLen = fieldBuilder.length();
1805                         }
1806                     }
1807                     char c = (type != HOUR && type != MONTH && type != WEEKDAY && (type != YEAR || reqField.charAt(0)=='Y'))?
1808                                 reqField.charAt(0): fieldBuilder.charAt(0);
1809                     if (type == HOUR && flags.contains(DTPGflags.SKELETON_USES_CAP_J)) {
1810                         c = defaultHourFormatChar;
1811                     }
1812                     fieldBuilder = new StringBuilder();
1813                     for (int i = adjFieldLen; i > 0; --i) fieldBuilder.append(c);
1814                 }
1815                 newPattern.append(fieldBuilder);
1816             }
1817         }
1818         //if (SHOW_DISTANCE) System.out.println("\tRaw: " + pattern);
1819         return newPattern.toString();
1820     }
1821 
1822     //  public static String repeat(String s, int count) {
1823     //  StringBuffer result = new StringBuffer();
1824     //  for (int i = 0; i < count; ++i) {
1825     //  result.append(s);
1826     //  }
1827     //  return result.toString();
1828     //  }
1829 
1830     /**
1831      * internal routine
1832      * @param pattern The pattern that is passed.
1833      * @return field value
1834      * @internal
1835      * @deprecated This API is ICU internal only.
1836      */
1837     @Deprecated
getFields(String pattern)1838     public String getFields(String pattern) {
1839         fp.set(pattern);
1840         StringBuilder newPattern = new StringBuilder();
1841         for (Object item : fp.getItems()) {
1842             if (item instanceof String) {
1843                 newPattern.append(fp.quoteLiteral((String)item));
1844             } else {
1845                 newPattern.append("{" + getName(item.toString()) + "}");
1846             }
1847         }
1848         return newPattern.toString();
1849     }
1850 
showMask(int mask)1851     private static String showMask(int mask) {
1852         StringBuilder result = new StringBuilder();
1853         for (int i = 0; i < TYPE_LIMIT; ++i) {
1854             if ((mask & (1<<i)) == 0)
1855                 continue;
1856             if (result.length() != 0)
1857                 result.append(" | ");
1858             result.append(FIELD_NAME[i]);
1859             result.append(" ");
1860         }
1861         return result.toString();
1862     }
1863 
1864     private static final String[] CLDR_FIELD_APPEND = {
1865         "Era", "Year", "Quarter", "Month", "Week", "*", "Day-Of-Week",
1866         "Day", "*", "*", "*",
1867         "Hour", "Minute", "Second", "*", "Timezone"
1868     };
1869 
1870     private static final String[] CLDR_FIELD_NAME = {
1871         "era", "year", "*", "month", "week", "*", "weekday",
1872         "day", "*", "*", "dayperiod",
1873         "hour", "minute", "second", "*", "zone"
1874     };
1875 
1876     private static final String[] FIELD_NAME = {
1877         "Era", "Year", "Quarter", "Month", "Week_in_Year", "Week_in_Month", "Weekday",
1878         "Day", "Day_Of_Year", "Day_of_Week_in_Month", "Dayperiod",
1879         "Hour", "Minute", "Second", "Fractional_Second", "Zone"
1880     };
1881 
1882 
1883     private static final String[] CANONICAL_ITEMS = {
1884         "G", "y", "Q", "M", "w", "W", "E",
1885         "d", "D", "F",
1886         "H", "m", "s", "S", "v"
1887     };
1888 
1889     private static final Set<String> CANONICAL_SET = new HashSet<String>(Arrays.asList(CANONICAL_ITEMS));
1890     private Set<String> cldrAvailableFormatKeys = new HashSet<String>(20);
1891 
1892     private static final int
1893     DATE_MASK = (1<<DAYPERIOD) - 1,
1894     TIME_MASK = (1<<TYPE_LIMIT) - 1 - DATE_MASK;
1895 
1896     private static final int // numbers are chosen to express 'distance'
1897     DELTA = 0x10,
1898     NUMERIC = 0x100,
1899     NONE = 0,
1900     NARROW = -0x101,
1901     SHORT = -0x102,
1902     LONG = -0x103,
1903     EXTRA_FIELD =   0x10000,
1904     MISSING_FIELD = 0x1000;
1905 
1906 
getName(String s)1907     static private String getName(String s) {
1908         int i = getCanonicalIndex(s, true);
1909         String name = FIELD_NAME[types[i][1]];
1910         int subtype = types[i][2];
1911         boolean string = subtype < 0;
1912         if (string) subtype = -subtype;
1913         if (subtype < 0) name += ":S";
1914         else name += ":N";
1915         return name;
1916     }
1917 
1918     /**
1919      * Get the canonical index, or return -1 if illegal.
1920      * @param s
1921      * @param strict TODO
1922      */
1923     private static int getCanonicalIndex(String s, boolean strict) {
1924         int len = s.length();
1925         if (len == 0) {
1926             return -1;
1927         }
1928         int ch = s.charAt(0);
1929         //      verify that all are the same character
1930         for (int i = 1; i < len; ++i) {
1931             if (s.charAt(i) != ch) {
1932                 return -1;
1933             }
1934         }
1935         int bestRow = -1;
1936         for (int i = 0; i < types.length; ++i) {
1937             int[] row = types[i];
1938             if (row[0] != ch) continue;
1939             bestRow = i;
1940             if (row[3] > len) continue;
1941             if (row[row.length-1] < len) continue;
1942             return i;
1943         }
1944         return strict ? -1 : bestRow;
1945     }
1946 
1947     private static final int[][] types = {
1948         // the order here makes a difference only when searching for single field.
1949         // format is:
1950         // pattern character, main type, weight, min length, weight
1951         {'G', ERA, SHORT, 1, 3},
1952         {'G', ERA, LONG, 4},
1953 
1954         {'y', YEAR, NUMERIC, 1, 20},
1955         {'Y', YEAR, NUMERIC + DELTA, 1, 20},
1956         {'u', YEAR, NUMERIC + 2*DELTA, 1, 20},
1957         {'r', YEAR, NUMERIC + 3*DELTA, 1, 20},
1958         {'U', YEAR, SHORT, 1, 3},
1959         {'U', YEAR, LONG, 4},
1960         {'U', YEAR, NARROW, 5},
1961 
1962         {'Q', QUARTER, NUMERIC, 1, 2},
1963         {'Q', QUARTER, SHORT, 3},
1964         {'Q', QUARTER, LONG, 4},
1965 
1966         {'q', QUARTER, NUMERIC + DELTA, 1, 2},
1967         {'q', QUARTER, SHORT + DELTA, 3},
1968         {'q', QUARTER, LONG + DELTA, 4},
1969 
1970         {'M', MONTH, NUMERIC, 1, 2},
1971         {'M', MONTH, SHORT, 3},
1972         {'M', MONTH, LONG, 4},
1973         {'M', MONTH, NARROW, 5},
1974         {'L', MONTH, NUMERIC + DELTA, 1, 2},
1975         {'L', MONTH, SHORT - DELTA, 3},
1976         {'L', MONTH, LONG - DELTA, 4},
1977         {'L', MONTH, NARROW - DELTA, 5},
1978 
1979         {'l', MONTH, NUMERIC + DELTA, 1, 1},
1980 
1981         {'w', WEEK_OF_YEAR, NUMERIC, 1, 2},
1982         {'W', WEEK_OF_MONTH, NUMERIC + DELTA, 1},
1983 
1984         {'E', WEEKDAY, SHORT, 1, 3},
1985         {'E', WEEKDAY, LONG, 4},
1986         {'E', WEEKDAY, NARROW, 5},
1987         {'c', WEEKDAY, NUMERIC + 2*DELTA, 1, 2},
1988         {'c', WEEKDAY, SHORT - 2*DELTA, 3},
1989         {'c', WEEKDAY, LONG - 2*DELTA, 4},
1990         {'c', WEEKDAY, NARROW - 2*DELTA, 5},
1991         {'e', WEEKDAY, NUMERIC + DELTA, 1, 2}, // 'e' is currently not used in CLDR data, should not be canonical
1992         {'e', WEEKDAY, SHORT - DELTA, 3},
1993         {'e', WEEKDAY, LONG - DELTA, 4},
1994         {'e', WEEKDAY, NARROW - DELTA, 5},
1995 
1996         {'d', DAY, NUMERIC, 1, 2},
1997         {'D', DAY_OF_YEAR, NUMERIC + DELTA, 1, 3},
1998         {'F', DAY_OF_WEEK_IN_MONTH, NUMERIC + 2*DELTA, 1},
1999         {'g', DAY, NUMERIC + 3*DELTA, 1, 20}, // really internal use, so we don't care
2000 
2001         {'a', DAYPERIOD, SHORT, 1},
2002 
2003         {'H', HOUR, NUMERIC + 10*DELTA, 1, 2}, // 24 hour
2004         {'k', HOUR, NUMERIC + 11*DELTA, 1, 2},
2005         {'h', HOUR, NUMERIC, 1, 2}, // 12 hour
2006         {'K', HOUR, NUMERIC + DELTA, 1, 2},
2007 
2008         {'m', MINUTE, NUMERIC, 1, 2},
2009 
2010         {'s', SECOND, NUMERIC, 1, 2},
2011         {'S', FRACTIONAL_SECOND, NUMERIC + DELTA, 1, 1000},
2012         {'A', SECOND, NUMERIC + 2*DELTA, 1, 1000},
2013 
2014         {'v', ZONE, SHORT - 2*DELTA, 1},
2015         {'v', ZONE, LONG - 2*DELTA, 4},
2016         {'z', ZONE, SHORT, 1, 3},
2017         {'z', ZONE, LONG, 4},
2018         {'Z', ZONE, NARROW - DELTA, 1, 3},
2019         {'Z', ZONE, LONG - DELTA, 4},
2020         {'Z', ZONE, SHORT - DELTA, 5},
2021         {'O', ZONE, SHORT - DELTA, 1},
2022         {'O', ZONE, LONG - DELTA, 4},
2023         {'V', ZONE, SHORT - DELTA, 1},
2024         {'V', ZONE, LONG - DELTA, 2},
2025         {'X', ZONE, NARROW - DELTA, 1},
2026         {'X', ZONE, SHORT - DELTA, 2},
2027         {'X', ZONE, LONG - DELTA, 4},
2028         {'x', ZONE, NARROW - DELTA, 1},
2029         {'x', ZONE, SHORT - DELTA, 2},
2030         {'x', ZONE, LONG - DELTA, 4},
2031     };
2032 
2033     private static class DateTimeMatcher implements Comparable<DateTimeMatcher> {
2034         //private String pattern = null;
2035         private int[] type = new int[TYPE_LIMIT];
2036         private String[] original = new String[TYPE_LIMIT];
2037         private String[] baseOriginal = new String[TYPE_LIMIT];
2038 
2039         // just for testing; fix to make multi-threaded later
2040         // private static FormatParser fp = new FormatParser();
2041 
2042         public String origStringForField(int field) {
2043             return original[field];
2044         }
2045 
2046         public boolean fieldIsNumeric(int field) {
2047             return type[field] > 0;
2048         }
2049 
2050         public String toString() {
2051             StringBuilder result = new StringBuilder();
2052             for (int i = 0; i < TYPE_LIMIT; ++i) {
2053                 if (original[i].length() != 0) result.append(original[i]);
2054             }
2055             return result.toString();
2056         }
2057 
2058         // returns a string like toString but using the canonical character for most types,
2059         // e.g. M for M or L, E for E or c, y for y or U, etc. The hour field is canonicalized
2060         // to 'H' (for 24-hour types) or 'h' (for 12-hour types)
2061         public String toCanonicalString() {
2062             StringBuilder result = new StringBuilder();
2063             for (int i = 0; i < TYPE_LIMIT; ++i) {
2064                 if (original[i].length() != 0) {
2065                     // append a string of the same length using the canonical character
2066                     for (int j = 0; j < types.length; ++j) {
2067                         int[] row = types[j];
2068                         if (row[1] == i) {
2069                             char originalChar = original[i].charAt(0);
2070                             char repeatChar = (originalChar=='h' || originalChar=='K')? 'h': (char)row[0];
2071                             result.append(Utility.repeat(String.valueOf(repeatChar), original[i].length()));
2072                             break;
2073                         }
2074                     }
2075                 }
2076             }
2077             return result.toString();
2078         }
2079 
2080         String getBasePattern() {
2081             StringBuilder result = new StringBuilder();
2082             for (int i = 0; i < TYPE_LIMIT; ++i) {
2083                 if (baseOriginal[i].length() != 0) result.append(baseOriginal[i]);
2084             }
2085             return result.toString();
2086         }
2087 
2088         DateTimeMatcher set(String pattern, FormatParser fp, boolean allowDuplicateFields) {
2089             for (int i = 0; i < TYPE_LIMIT; ++i) {
2090                 type[i] = NONE;
2091                 original[i] = "";
2092                 baseOriginal[i] = "";
2093             }
2094             fp.set(pattern);
2095             for (Object obj : fp.getItems()) {
2096                 if (!(obj instanceof VariableField)) {
2097                     continue;
2098                 }
2099                 VariableField item = (VariableField)obj;
2100                 String field = item.toString();
2101                 if (field.charAt(0) == 'a') continue; // skip day period, special case
2102                 int canonicalIndex = item.getCanonicalIndex();
2103                 //                if (canonicalIndex < 0) {
2104                 //                    throw new IllegalArgumentException("Illegal field:\t"
2105                 //                            + field + "\t in " + pattern);
2106                 //                }
2107                 int[] row = types[canonicalIndex];
2108                 int typeValue = row[1];
2109                 if (original[typeValue].length() != 0) {
2110                     if ( allowDuplicateFields ||
2111                           (original[typeValue].charAt(0) == 'r' && field.charAt(0) == 'U') ||
2112                           (original[typeValue].charAt(0) == 'U' && field.charAt(0) == 'r') ) {
2113                         continue;
2114                     }
2115                     throw new IllegalArgumentException("Conflicting fields:\t"
2116                             + original[typeValue] + ", " + field + "\t in " + pattern);
2117                 }
2118                 original[typeValue] = field;
2119                 char repeatChar = (char)row[0];
2120                 int repeatCount = row[3];
2121                 // #7930 removes hack to cap repeatCount at 3
2122                 if ("GEzvQ".indexOf(repeatChar) >= 0) repeatCount = 1;
2123                 baseOriginal[typeValue] = Utility.repeat(String.valueOf(repeatChar),repeatCount);
2124                 int subTypeValue = row[2];
2125                 if (subTypeValue > 0) subTypeValue += field.length();
2126                 type[typeValue] = subTypeValue;
2127             }
2128             return this;
2129         }
2130 
2131         /**
2132          *
2133          */
2134         int getFieldMask() {
2135             int result = 0;
2136             for (int i = 0; i < type.length; ++i) {
2137                 if (type[i] != 0) result |= (1<<i);
2138             }
2139             return result;
2140         }
2141 
2142         /**
2143          *
2144          */
2145         @SuppressWarnings("unused")
2146         void extractFrom(DateTimeMatcher source, int fieldMask) {
2147             for (int i = 0; i < type.length; ++i) {
2148                 if ((fieldMask & (1<<i)) != 0) {
2149                     type[i] = source.type[i];
2150                     original[i] = source.original[i];
2151                 } else {
2152                     type[i] = NONE;
2153                     original[i] = "";
2154                 }
2155             }
2156         }
2157 
2158         int getDistance(DateTimeMatcher other, int includeMask, DistanceInfo distanceInfo) {
2159             int result = 0;
2160             distanceInfo.clear();
2161             for (int i = 0; i < type.length; ++i) {
2162                 int myType = (includeMask & (1<<i)) == 0 ? 0 : type[i];
2163                 int otherType = other.type[i];
2164                 if (myType == otherType) continue; // identical (maybe both zero) add 0
2165                 if (myType == 0) { // and other is not
2166                     result += EXTRA_FIELD;
2167                     distanceInfo.addExtra(i);
2168                 } else if (otherType == 0) { // and mine is not
2169                     result += MISSING_FIELD;
2170                     distanceInfo.addMissing(i);
2171                 } else {
2172                     result += Math.abs(myType - otherType); // square of mismatch
2173                 }
2174             }
2175             return result;
2176         }
2177 
2178         public int compareTo(DateTimeMatcher that) {
2179             for (int i = 0; i < original.length; ++i) {
2180                 int comp = original[i].compareTo(that.original[i]);
2181                 if (comp != 0) return -comp;
2182             }
2183             return 0;
2184         }
2185 
2186         public boolean equals(Object other) {
2187             if (!(other instanceof DateTimeMatcher)) {
2188                 return false;
2189             }
2190             DateTimeMatcher that = (DateTimeMatcher) other;
2191             for (int i = 0; i < original.length; ++i) {
2192                 if (!original[i].equals(that.original[i])) return false;
2193             }
2194             return true;
2195         }
2196         public int hashCode() {
2197             int result = 0;
2198             for (int i = 0; i < original.length; ++i) {
2199                 result ^= original[i].hashCode();
2200             }
2201             return result;
2202         }
2203     }
2204 
2205     private static class DistanceInfo {
2206         int missingFieldMask;
2207         int extraFieldMask;
2208         void clear() {
2209             missingFieldMask = extraFieldMask = 0;
2210         }
2211         /**
2212          *
2213          */
2214         void setTo(DistanceInfo other) {
2215             missingFieldMask = other.missingFieldMask;
2216             extraFieldMask = other.extraFieldMask;
2217         }
2218         void addMissing(int field) {
2219             missingFieldMask |= (1<<field);
2220         }
2221         void addExtra(int field) {
2222             extraFieldMask |= (1<<field);
2223         }
2224         public String toString() {
2225             return "missingFieldMask: " + DateTimePatternGenerator.showMask(missingFieldMask)
2226             + ", extraFieldMask: " + DateTimePatternGenerator.showMask(extraFieldMask);
2227         }
2228     }
2229 }
2230 //eof
2231