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