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