jdk/make/src/classes/build/tools/cldrconverter/Bundle.java
changeset 21805 c7d7946239de
parent 16852 60207b2b4b42
child 31263 a81a0af34ca0
equal deleted inserted replaced
21804:07b686da11c4 21805:c7d7946239de
       
     1 /*
       
     2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package build.tools.cldrconverter;
       
    27 
       
    28 import java.util.ArrayList;
       
    29 import java.util.Arrays;
       
    30 import java.util.EnumSet;
       
    31 import java.util.HashMap;
       
    32 import java.util.Iterator;
       
    33 import java.util.List;
       
    34 import java.util.Map;
       
    35 
       
    36 class Bundle {
       
    37     static enum Type {
       
    38         LOCALENAMES, CURRENCYNAMES, TIMEZONENAMES, CALENDARDATA, FORMATDATA;
       
    39 
       
    40         static EnumSet<Type> ALL_TYPES = EnumSet.of(LOCALENAMES,
       
    41                                                     CURRENCYNAMES,
       
    42                                                     TIMEZONENAMES,
       
    43                                                     CALENDARDATA,
       
    44                                                     FORMATDATA);
       
    45     }
       
    46 
       
    47     private final static Map<String, Bundle> bundles = new HashMap<>();
       
    48 
       
    49     private final static String[] NUMBER_PATTERN_KEYS = {
       
    50         "NumberPatterns/decimal",
       
    51         "NumberPatterns/currency",
       
    52         "NumberPatterns/percent"
       
    53     };
       
    54 
       
    55     private final static String[] NUMBER_ELEMENT_KEYS = {
       
    56         "NumberElements/decimal",
       
    57         "NumberElements/group",
       
    58         "NumberElements/list",
       
    59         "NumberElements/percent",
       
    60         "NumberElements/zero",
       
    61         "NumberElements/pattern",
       
    62         "NumberElements/minus",
       
    63         "NumberElements/exponential",
       
    64         "NumberElements/permille",
       
    65         "NumberElements/infinity",
       
    66         "NumberElements/nan"
       
    67     };
       
    68 
       
    69     private final static String[] TIME_PATTERN_KEYS = {
       
    70         "DateTimePatterns/full-time",
       
    71         "DateTimePatterns/long-time",
       
    72         "DateTimePatterns/medium-time",
       
    73         "DateTimePatterns/short-time",
       
    74     };
       
    75 
       
    76     private final static String[] DATE_PATTERN_KEYS = {
       
    77         "DateTimePatterns/full-date",
       
    78         "DateTimePatterns/long-date",
       
    79         "DateTimePatterns/medium-date",
       
    80         "DateTimePatterns/short-date",
       
    81     };
       
    82 
       
    83     private final static String[] DATETIME_PATTERN_KEYS = {
       
    84         "DateTimePatterns/date-time"
       
    85     };
       
    86 
       
    87     private final static String[] ERA_KEYS = {
       
    88         "long.Eras",
       
    89         "Eras",
       
    90         "narrow.Eras"
       
    91     };
       
    92 
       
    93     // Keys for individual time zone names
       
    94     private final static String TZ_GEN_LONG_KEY = "timezone.displayname.generic.long";
       
    95     private final static String TZ_GEN_SHORT_KEY = "timezone.displayname.generic.short";
       
    96     private final static String TZ_STD_LONG_KEY = "timezone.displayname.standard.long";
       
    97     private final static String TZ_STD_SHORT_KEY = "timezone.displayname.standard.short";
       
    98     private final static String TZ_DST_LONG_KEY = "timezone.displayname.daylight.long";
       
    99     private final static String TZ_DST_SHORT_KEY = "timezone.displayname.daylight.short";
       
   100     private final static String[] ZONE_NAME_KEYS = {
       
   101         TZ_STD_LONG_KEY,
       
   102         TZ_STD_SHORT_KEY,
       
   103         TZ_DST_LONG_KEY,
       
   104         TZ_DST_SHORT_KEY,
       
   105         TZ_GEN_LONG_KEY,
       
   106         TZ_GEN_SHORT_KEY
       
   107     };
       
   108 
       
   109     private final String id;
       
   110     private final String cldrPath;
       
   111     private final EnumSet<Type> bundleTypes;
       
   112     private final String currencies;
       
   113 
       
   114     static Bundle getBundle(String id) {
       
   115         return bundles.get(id);
       
   116     }
       
   117 
       
   118     @SuppressWarnings("ConvertToStringSwitch")
       
   119     Bundle(String id, String cldrPath, String bundles, String currencies) {
       
   120         this.id = id;
       
   121         this.cldrPath = cldrPath;
       
   122         if ("localenames".equals(bundles)) {
       
   123             bundleTypes = EnumSet.of(Type.LOCALENAMES);
       
   124         } else if ("currencynames".equals(bundles)) {
       
   125             bundleTypes = EnumSet.of(Type.CURRENCYNAMES);
       
   126         } else {
       
   127             bundleTypes = Type.ALL_TYPES;
       
   128         }
       
   129         if (currencies == null) {
       
   130             currencies = "local";
       
   131         }
       
   132         this.currencies = currencies;
       
   133         addBundle();
       
   134     }
       
   135 
       
   136     private void addBundle() {
       
   137         Bundle.bundles.put(id, this);
       
   138     }
       
   139 
       
   140     String getID() {
       
   141         return id;
       
   142     }
       
   143 
       
   144     boolean isRoot() {
       
   145         return "root".equals(id);
       
   146     }
       
   147 
       
   148     String getCLDRPath() {
       
   149         return cldrPath;
       
   150     }
       
   151 
       
   152     EnumSet<Type> getBundleTypes() {
       
   153         return bundleTypes;
       
   154     }
       
   155 
       
   156     String getCurrencies() {
       
   157         return currencies;
       
   158     }
       
   159 
       
   160     /**
       
   161      * Generate a map that contains all the data that should be
       
   162      * visible for the bundle's locale
       
   163      */
       
   164     Map<String, Object> getTargetMap() throws Exception {
       
   165         String[] cldrBundles = getCLDRPath().split(",");
       
   166 
       
   167         // myMap contains resources for id.
       
   168         Map<String, Object> myMap = new HashMap<>();
       
   169         int index;
       
   170         for (index = 0; index < cldrBundles.length; index++) {
       
   171             if (cldrBundles[index].equals(id)) {
       
   172                 myMap.putAll(CLDRConverter.getCLDRBundle(cldrBundles[index]));
       
   173                 break;
       
   174             }
       
   175         }
       
   176 
       
   177         // parentsMap contains resources from id's parents.
       
   178         Map<String, Object> parentsMap = new HashMap<>();
       
   179         for (int i = cldrBundles.length - 1; i > index; i--) {
       
   180             if (!("no".equals(cldrBundles[i]) || cldrBundles[i].startsWith("no_"))) {
       
   181                 parentsMap.putAll(CLDRConverter.getCLDRBundle(cldrBundles[i]));
       
   182             }
       
   183         }
       
   184         // Duplicate myMap as parentsMap for "root" so that the
       
   185         // fallback works. This is a huck, though.
       
   186         if ("root".equals(cldrBundles[0])) {
       
   187             assert parentsMap.isEmpty();
       
   188             parentsMap.putAll(myMap);
       
   189         }
       
   190 
       
   191         // merge individual strings into arrays
       
   192 
       
   193         // if myMap has any of the NumberPatterns members
       
   194         for (String k : NUMBER_PATTERN_KEYS) {
       
   195             if (myMap.containsKey(k)) {
       
   196                 String[] numberPatterns = new String[NUMBER_PATTERN_KEYS.length];
       
   197                 for (int i = 0; i < NUMBER_PATTERN_KEYS.length; i++) {
       
   198                     String key = NUMBER_PATTERN_KEYS[i];
       
   199                     String value = (String) myMap.remove(key);
       
   200                     if (value == null) {
       
   201                         value = (String) parentsMap.remove(key);
       
   202                     }
       
   203                     if (value.length() == 0) {
       
   204                         CLDRConverter.warning("empty pattern for " + key);
       
   205                     }
       
   206                     numberPatterns[i] = value;
       
   207                 }
       
   208                 myMap.put("NumberPatterns", numberPatterns);
       
   209                 break;
       
   210             }
       
   211         }
       
   212 
       
   213         // if myMap has any of NUMBER_ELEMENT_KEYS, create a complete NumberElements.
       
   214         String defaultScript = (String) myMap.get("DefaultNumberingSystem");
       
   215         @SuppressWarnings("unchecked")
       
   216         List<String> scripts = (List<String>) myMap.get("numberingScripts");
       
   217         if (defaultScript == null && scripts != null) {
       
   218             // Some locale data has no default script for numbering even with mutiple scripts.
       
   219             // Take the first one as default in that case.
       
   220             defaultScript = scripts.get(0);
       
   221             myMap.put("DefaultNumberingSystem", defaultScript);
       
   222         }
       
   223         if (scripts != null) {
       
   224             for (String script : scripts) {
       
   225                 for (String k : NUMBER_ELEMENT_KEYS) {
       
   226                     String[] numberElements = new String[NUMBER_ELEMENT_KEYS.length];
       
   227                     for (int i = 0; i < NUMBER_ELEMENT_KEYS.length; i++) {
       
   228                         String key = script + "." + NUMBER_ELEMENT_KEYS[i];
       
   229                         String value = (String) myMap.remove(key);
       
   230                         if (value == null) {
       
   231                             if (key.endsWith("/pattern")) {
       
   232                                 value = "#";
       
   233                             } else {
       
   234                                 value = (String) parentsMap.get(key);
       
   235                                 if (value == null) {
       
   236                                     // the last resort is "latn"
       
   237                                     key = "latn." + NUMBER_ELEMENT_KEYS[i];
       
   238                                     value = (String) parentsMap.get(key);
       
   239                                     if (value == null) {
       
   240                                         throw new InternalError("NumberElements: null for " + key);
       
   241                                     }
       
   242                                 }
       
   243                             }
       
   244                         }
       
   245                         numberElements[i] = value;
       
   246                     }
       
   247                     myMap.put(script + "." + "NumberElements", numberElements);
       
   248                     break;
       
   249                 }
       
   250             }
       
   251         }
       
   252 
       
   253         // another hack: parentsMap is not used for date-time resources.
       
   254         if ("root".equals(id)) {
       
   255             parentsMap = null;
       
   256         }
       
   257 
       
   258         for (CalendarType calendarType : CalendarType.values()) {
       
   259             String calendarPrefix = calendarType.keyElementName();
       
   260             // handle multiple inheritance for month and day names
       
   261             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "MonthNames");
       
   262             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "MonthAbbreviations");
       
   263             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "MonthNarrows");
       
   264             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "DayNames");
       
   265             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "DayAbbreviations");
       
   266             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "DayNarrows");
       
   267             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "AmPmMarkers");
       
   268             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "narrow.AmPmMarkers");
       
   269             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "QuarterNames");
       
   270             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "QuarterAbbreviations");
       
   271             handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "QuarterNarrows");
       
   272 
       
   273             adjustEraNames(myMap, calendarType);
       
   274 
       
   275             handleDateTimeFormatPatterns(TIME_PATTERN_KEYS, myMap, parentsMap, calendarType, "TimePatterns");
       
   276             handleDateTimeFormatPatterns(DATE_PATTERN_KEYS, myMap, parentsMap, calendarType, "DatePatterns");
       
   277             handleDateTimeFormatPatterns(DATETIME_PATTERN_KEYS, myMap, parentsMap, calendarType, "DateTimePatterns");
       
   278         }
       
   279 
       
   280         // First, weed out any empty timezone or metazone names from myMap.
       
   281         // Fill in any missing abbreviations if locale is "en".
       
   282         for (Iterator<String> it = myMap.keySet().iterator(); it.hasNext();) {
       
   283             String key = it.next();
       
   284             if (key.startsWith(CLDRConverter.TIMEZONE_ID_PREFIX)
       
   285                     || key.startsWith(CLDRConverter.METAZONE_ID_PREFIX)) {
       
   286                 @SuppressWarnings("unchecked")
       
   287                 Map<String, String> nameMap = (Map<String, String>) myMap.get(key);
       
   288                 if (nameMap.isEmpty()) {
       
   289                     // Some zones have only exemplarCity, which become empty.
       
   290                     // Remove those from the map.
       
   291                     it.remove();
       
   292                     continue;
       
   293                 }
       
   294 
       
   295                 if (id.startsWith("en")) {
       
   296                     fillInAbbrs(key, nameMap);
       
   297                 }
       
   298             }
       
   299         }
       
   300         for (Iterator<String> it = myMap.keySet().iterator(); it.hasNext();) {
       
   301             String key = it.next();
       
   302             if (key.startsWith(CLDRConverter.TIMEZONE_ID_PREFIX)
       
   303                     || key.startsWith(CLDRConverter.METAZONE_ID_PREFIX)) {
       
   304                 @SuppressWarnings("unchecked")
       
   305                 Map<String, String> nameMap = (Map<String, String>) myMap.get(key);
       
   306                 // Convert key/value pairs to an array.
       
   307                 String[] names = new String[ZONE_NAME_KEYS.length];
       
   308                 int ix = 0;
       
   309                 for (String nameKey : ZONE_NAME_KEYS) {
       
   310                     String name = nameMap.get(nameKey);
       
   311                     if (name == null) {
       
   312                         @SuppressWarnings("unchecked")
       
   313                         Map<String, String> parentNames = (Map<String, String>) parentsMap.get(key);
       
   314                         if (parentNames != null) {
       
   315                             name = parentNames.get(nameKey);
       
   316                         }
       
   317                     }
       
   318                     names[ix++] = name;
       
   319                 }
       
   320                 if (hasNulls(names)) {
       
   321                     String metaKey = toMetaZoneKey(key);
       
   322                     if (metaKey != null) {
       
   323                         Object obj = myMap.get(metaKey);
       
   324                         if (obj instanceof String[]) {
       
   325                             String[] metaNames = (String[]) obj;
       
   326                             for (int i = 0; i < names.length; i++) {
       
   327                                 if (names[i] == null) {
       
   328                                     names[i] = metaNames[i];
       
   329                                 }
       
   330                             }
       
   331                         } else if (obj instanceof Map) {
       
   332                             @SuppressWarnings("unchecked")
       
   333                             Map<String, String> m = (Map<String, String>) obj;
       
   334                             for (int i = 0; i < names.length; i++) {
       
   335                                 if (names[i] == null) {
       
   336                                     names[i] = m.get(ZONE_NAME_KEYS[i]);
       
   337                                 }
       
   338                             }
       
   339                         }
       
   340                     }
       
   341                     // If there are still any nulls, try filling in them from en data.
       
   342                     if (hasNulls(names) && !id.equals("en")) {
       
   343                         @SuppressWarnings("unchecked")
       
   344                         String[] enNames = (String[]) Bundle.getBundle("en").getTargetMap().get(key);
       
   345                         if (enNames == null) {
       
   346                             if (metaKey != null) {
       
   347                                 @SuppressWarnings("unchecked")
       
   348                                 String[] metaNames = (String[]) Bundle.getBundle("en").getTargetMap().get(metaKey);
       
   349                                 enNames = metaNames;
       
   350                             }
       
   351                         }
       
   352                         if (enNames != null) {
       
   353                             for (int i = 0; i < names.length; i++) {
       
   354                                 if (names[i] == null) {
       
   355                                     names[i] = enNames[i];
       
   356                                 }
       
   357                             }
       
   358                         }
       
   359                         // If there are still nulls, give up names.
       
   360                         if (hasNulls(names)) {
       
   361                             names = null;
       
   362                         }
       
   363                     }
       
   364                 }
       
   365                 // replace the Map with the array
       
   366                 if (names != null) {
       
   367                     myMap.put(key, names);
       
   368                 } else {
       
   369                     it.remove();
       
   370                 }
       
   371             }
       
   372         }
       
   373         return myMap;
       
   374     }
       
   375 
       
   376     private void handleMultipleInheritance(Map<String, Object> map, Map<String, Object> parents, String key) {
       
   377         String formatKey = key + "/format";
       
   378         Object format = map.get(formatKey);
       
   379         if (format != null) {
       
   380             map.remove(formatKey);
       
   381             map.put(key, format);
       
   382             if (fillInElements(parents, formatKey, format)) {
       
   383                 map.remove(key);
       
   384             }
       
   385         }
       
   386         String standaloneKey = key + "/stand-alone";
       
   387         Object standalone = map.get(standaloneKey);
       
   388         if (standalone != null) {
       
   389             map.remove(standaloneKey);
       
   390             String realKey = key;
       
   391             if (format != null) {
       
   392                 realKey = "standalone." + key;
       
   393             }
       
   394             map.put(realKey, standalone);
       
   395             if (fillInElements(parents, standaloneKey, standalone)) {
       
   396                 map.remove(realKey);
       
   397             }
       
   398         }
       
   399     }
       
   400 
       
   401     /**
       
   402      * Fills in any empty elements with its parent element. Returns true if the resulting array is
       
   403      * identical to its parent array.
       
   404      *
       
   405      * @param parents
       
   406      * @param key
       
   407      * @param value
       
   408      * @return true if the resulting array is identical to its parent array.
       
   409      */
       
   410     private boolean fillInElements(Map<String, Object> parents, String key, Object value) {
       
   411         if (parents == null) {
       
   412             return false;
       
   413         }
       
   414         if (value instanceof String[]) {
       
   415             Object pvalue = parents.get(key);
       
   416             if (pvalue != null && pvalue instanceof String[]) {
       
   417                 String[] strings = (String[]) value;
       
   418                 String[] pstrings = (String[]) pvalue;
       
   419                 for (int i = 0; i < strings.length; i++) {
       
   420                     if (strings[i] == null || strings[i].length() == 0) {
       
   421                         strings[i] = pstrings[i];
       
   422                     }
       
   423                 }
       
   424                 return Arrays.equals(strings, pstrings);
       
   425             }
       
   426         }
       
   427         return false;
       
   428     }
       
   429 
       
   430     /*
       
   431      * Adjusts String[] for era names because JRE's Calendars use different
       
   432      * ERA value indexes in the Buddhist, Japanese Imperial, and Islamic calendars.
       
   433      */
       
   434     private void adjustEraNames(Map<String, Object> map, CalendarType type) {
       
   435         String[][] eraNames = new String[ERA_KEYS.length][];
       
   436         String[] realKeys = new String[ERA_KEYS.length];
       
   437         int index = 0;
       
   438         for (String key : ERA_KEYS) {
       
   439             String realKey = type.keyElementName() + key;
       
   440             String[] value = (String[]) map.get(realKey);
       
   441             if (value != null) {
       
   442                 switch (type) {
       
   443                 case GREGORIAN:
       
   444                     break;
       
   445 
       
   446                 case JAPANESE:
       
   447                     {
       
   448                         String[] newValue = new String[value.length + 1];
       
   449                         String[] julianEras = (String[]) map.get(key);
       
   450                         if (julianEras != null && julianEras.length >= 2) {
       
   451                             newValue[0] = julianEras[1];
       
   452                         } else {
       
   453                             newValue[0] = "";
       
   454                         }
       
   455                         System.arraycopy(value, 0, newValue, 1, value.length);
       
   456                         value = newValue;
       
   457                     }
       
   458                     break;
       
   459 
       
   460                 case BUDDHIST:
       
   461                     // Replace the value
       
   462                     value = new String[] {"BC", value[0]};
       
   463                     break;
       
   464 
       
   465                 case ISLAMIC:
       
   466                     // Replace the value
       
   467                     value = new String[] {"", value[0]};
       
   468                     break;
       
   469                 }
       
   470                 if (!key.equals(realKey)) {
       
   471                     map.put(realKey, value);
       
   472                 }
       
   473             }
       
   474             realKeys[index] = realKey;
       
   475             eraNames[index++] = value;
       
   476         }
       
   477         for (int i = 0; i < eraNames.length; i++) {
       
   478             if (eraNames[i] == null) {
       
   479                 map.put(realKeys[i], null);
       
   480             }
       
   481         }
       
   482     }
       
   483 
       
   484     private void handleDateTimeFormatPatterns(String[] patternKeys, Map<String, Object> myMap, Map<String, Object> parentsMap,
       
   485                                               CalendarType calendarType, String name) {
       
   486         String calendarPrefix = calendarType.keyElementName();
       
   487         for (String k : patternKeys) {
       
   488             if (myMap.containsKey(calendarPrefix + k)) {
       
   489                 int len = patternKeys.length;
       
   490                 List<String> rawPatterns = new ArrayList<>(len);
       
   491                 List<String> patterns = new ArrayList<>(len);
       
   492                 for (int i = 0; i < len; i++) {
       
   493                     String key = calendarPrefix + patternKeys[i];
       
   494                     String pattern = (String) myMap.remove(key);
       
   495                     if (pattern == null) {
       
   496                         pattern = (String) parentsMap.remove(key);
       
   497                     }
       
   498                     rawPatterns.add(i, pattern);
       
   499                     if (pattern != null) {
       
   500                         patterns.add(i, translateDateFormatLetters(calendarType, pattern));
       
   501                     } else {
       
   502                         patterns.add(i, null);
       
   503                     }
       
   504                 }
       
   505                 // If patterns is empty or has any nulls, discard patterns.
       
   506                 if (patterns.isEmpty()) {
       
   507                     return;
       
   508                 }
       
   509                 for (String p : patterns) {
       
   510                     if (p == null) {
       
   511                         return;
       
   512                     }
       
   513                 }
       
   514                 String key = calendarPrefix + name;
       
   515                 if (!rawPatterns.equals(patterns)) {
       
   516                     myMap.put("java.time." + key, rawPatterns.toArray(new String[len]));
       
   517                 }
       
   518                 myMap.put(key, patterns.toArray(new String[len]));
       
   519                 break;
       
   520             }
       
   521         }
       
   522     }
       
   523 
       
   524     private String translateDateFormatLetters(CalendarType calendarType, String cldrFormat) {
       
   525         String pattern = cldrFormat;
       
   526         int length = pattern.length();
       
   527         boolean inQuote = false;
       
   528         StringBuilder jrePattern = new StringBuilder(length);
       
   529         int count = 0;
       
   530         char lastLetter = 0;
       
   531 
       
   532         for (int i = 0; i < length; i++) {
       
   533             char c = pattern.charAt(i);
       
   534 
       
   535             if (c == '\'') {
       
   536                 // '' is treated as a single quote regardless of being
       
   537                 // in a quoted section.
       
   538                 if ((i + 1) < length) {
       
   539                     char nextc = pattern.charAt(i + 1);
       
   540                     if (nextc == '\'') {
       
   541                         i++;
       
   542                         if (count != 0) {
       
   543                             convert(calendarType, lastLetter, count, jrePattern);
       
   544                             lastLetter = 0;
       
   545                             count = 0;
       
   546                         }
       
   547                         jrePattern.append("''");
       
   548                         continue;
       
   549                     }
       
   550                 }
       
   551                 if (!inQuote) {
       
   552                     if (count != 0) {
       
   553                         convert(calendarType, lastLetter, count, jrePattern);
       
   554                         lastLetter = 0;
       
   555                         count = 0;
       
   556                     }
       
   557                     inQuote = true;
       
   558                 } else {
       
   559                     inQuote = false;
       
   560                 }
       
   561                 jrePattern.append(c);
       
   562                 continue;
       
   563             }
       
   564             if (inQuote) {
       
   565                 jrePattern.append(c);
       
   566                 continue;
       
   567             }
       
   568             if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
       
   569                 if (count != 0) {
       
   570                     convert(calendarType, lastLetter, count, jrePattern);
       
   571                     lastLetter = 0;
       
   572                     count = 0;
       
   573                 }
       
   574                 jrePattern.append(c);
       
   575                 continue;
       
   576             }
       
   577 
       
   578             if (lastLetter == 0 || lastLetter == c) {
       
   579                 lastLetter = c;
       
   580                 count++;
       
   581                 continue;
       
   582             }
       
   583             convert(calendarType, lastLetter, count, jrePattern);
       
   584             lastLetter = c;
       
   585             count = 1;
       
   586         }
       
   587 
       
   588         if (inQuote) {
       
   589             throw new InternalError("Unterminated quote in date-time pattern: " + cldrFormat);
       
   590         }
       
   591 
       
   592         if (count != 0) {
       
   593             convert(calendarType, lastLetter, count, jrePattern);
       
   594         }
       
   595         if (cldrFormat.contentEquals(jrePattern)) {
       
   596             return cldrFormat;
       
   597         }
       
   598         return jrePattern.toString();
       
   599     }
       
   600 
       
   601     private String toMetaZoneKey(String tzKey) {
       
   602         if (tzKey.startsWith(CLDRConverter.TIMEZONE_ID_PREFIX)) {
       
   603             String tz = tzKey.substring(CLDRConverter.TIMEZONE_ID_PREFIX.length());
       
   604             String meta = CLDRConverter.handlerMetaZones.get(tz);
       
   605             if (meta != null) {
       
   606                 return CLDRConverter.METAZONE_ID_PREFIX + meta;
       
   607             }
       
   608         }
       
   609         return null;
       
   610     }
       
   611 
       
   612     private void fillInAbbrs(String key, Map<String, String> map) {
       
   613         fillInAbbrs(TZ_STD_LONG_KEY, TZ_STD_SHORT_KEY, map);
       
   614         fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);
       
   615         fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);
       
   616 
       
   617         // If the standard std is "Standard Time" and daylight std is "Summer Time",
       
   618         // replace the standard std with the generic std to avoid using
       
   619         // the same abbrivation except for Australia time zone names.
       
   620         String std = map.get(TZ_STD_SHORT_KEY);
       
   621         String dst = map.get(TZ_DST_SHORT_KEY);
       
   622         String gen = map.get(TZ_GEN_SHORT_KEY);
       
   623         if (std != null) {
       
   624             if (dst == null) {
       
   625                 // if dst is null, create long and short names from the standard
       
   626                 // std. ("Something Standard Time" to "Something Daylight Time",
       
   627                 // or "Something Time" to "Something Summer Time")
       
   628                 String name = map.get(TZ_STD_LONG_KEY);
       
   629                 if (name != null) {
       
   630                     if (name.contains("Standard Time")) {
       
   631                         name = name.replace("Standard Time", "Daylight Time");
       
   632                     } else if (name.endsWith("Mean Time")) {
       
   633                         name = name.replace("Mean Time", "Summer Time");
       
   634                     } else if (name.endsWith(" Time")) {
       
   635                         name = name.replace(" Time", " Summer Time");
       
   636                     }
       
   637                     map.put(TZ_DST_LONG_KEY, name);
       
   638                     fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);
       
   639                 }
       
   640             }
       
   641             if (gen  == null) {
       
   642                 String name = map.get(TZ_STD_LONG_KEY);
       
   643                 if (name != null) {
       
   644                     if (name.endsWith("Standard Time")) {
       
   645                         name = name.replace("Standard Time", "Time");
       
   646                     } else if (name.endsWith("Mean Time")) {
       
   647                         name = name.replace("Mean Time", "Time");
       
   648                     }
       
   649                     map.put(TZ_GEN_LONG_KEY, name);
       
   650                     fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);
       
   651                 }
       
   652             }
       
   653         }
       
   654     }
       
   655 
       
   656     private void fillInAbbrs(String longKey, String shortKey, Map<String, String> map) {
       
   657         String abbr = map.get(shortKey);
       
   658         if (abbr == null) {
       
   659             String name = map.get(longKey);
       
   660             if (name != null) {
       
   661                 abbr = toAbbr(name);
       
   662                 if (abbr != null) {
       
   663                     map.put(shortKey, abbr);
       
   664                 }
       
   665             }
       
   666         }
       
   667     }
       
   668 
       
   669     private String toAbbr(String name) {
       
   670         String[] substrs = name.split("\\s+");
       
   671         StringBuilder sb = new StringBuilder();
       
   672         for (String s : substrs) {
       
   673             char c = s.charAt(0);
       
   674             if (c >= 'A' && c <= 'Z') {
       
   675                 sb.append(c);
       
   676             }
       
   677         }
       
   678         return sb.length() > 0 ? sb.toString() : null;
       
   679     }
       
   680 
       
   681     private void convert(CalendarType calendarType, char cldrLetter, int count, StringBuilder sb) {
       
   682         switch (cldrLetter) {
       
   683         case 'G':
       
   684             if (calendarType != CalendarType.GREGORIAN) {
       
   685                 // Adjust the number of 'G's for JRE SimpleDateFormat
       
   686                 if (count == 5) {
       
   687                     // CLDR narrow -> JRE short
       
   688                     count = 1;
       
   689                 } else if (count == 1) {
       
   690                     // CLDR abbr -> JRE long
       
   691                     count = 4;
       
   692                 }
       
   693             }
       
   694             appendN(cldrLetter, count, sb);
       
   695             break;
       
   696 
       
   697         // TODO: support 'c' and 'e' in JRE SimpleDateFormat
       
   698         // Use 'u' and 'E' for now.
       
   699         case 'c':
       
   700         case 'e':
       
   701             switch (count) {
       
   702             case 1:
       
   703                 sb.append('u');
       
   704                 break;
       
   705             case 3:
       
   706             case 4:
       
   707                 appendN('E', count, sb);
       
   708                 break;
       
   709             case 5:
       
   710                 appendN('E', 3, sb);
       
   711                 break;
       
   712             }
       
   713             break;
       
   714 
       
   715         case 'v':
       
   716         case 'V':
       
   717             appendN('z', count, sb);
       
   718             break;
       
   719 
       
   720         case 'Z':
       
   721             if (count == 4 || count == 5) {
       
   722                 sb.append("XXX");
       
   723             }
       
   724             break;
       
   725 
       
   726         case 'u':
       
   727         case 'U':
       
   728         case 'q':
       
   729         case 'Q':
       
   730         case 'l':
       
   731         case 'g':
       
   732         case 'j':
       
   733         case 'A':
       
   734             throw new InternalError(String.format("Unsupported letter: '%c', count=%d%n",
       
   735                                                   cldrLetter, count));
       
   736         default:
       
   737             appendN(cldrLetter, count, sb);
       
   738             break;
       
   739         }
       
   740     }
       
   741 
       
   742     private void appendN(char c, int n, StringBuilder sb) {
       
   743         for (int i = 0; i < n; i++) {
       
   744             sb.append(c);
       
   745         }
       
   746     }
       
   747 
       
   748     private static boolean hasNulls(Object[] array) {
       
   749         for (int i = 0; i < array.length; i++) {
       
   750             if (array[i] == null) {
       
   751                 return true;
       
   752             }
       
   753         }
       
   754         return false;
       
   755     }
       
   756 }