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<>();
 491                 List<String> patterns = new ArrayList<>();
 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                     if (pattern != null) {
 499                         rawPatterns.add(i, pattern);
 500                         patterns.add(i, translateDateFormatLetters(calendarType, pattern));
 501                     }
 502                 }
 503                 if (patterns.isEmpty()) {
 504                     return;
 505                 }
 506                 String key = calendarPrefix + name;
 507                 if (!rawPatterns.equals(patterns)) {
 508                     myMap.put("cldr." + key, rawPatterns.toArray(new String[len]));
 509                 }
 510                 myMap.put(key, patterns.toArray(new String[len]));
 511                 break;
 512             }
 513         }
 514     }
 515 
 516     private String translateDateFormatLetters(CalendarType calendarType, String cldrFormat) {
 517         String pattern = cldrFormat;
 518         int length = pattern.length();
 519         boolean inQuote = false;
 520         StringBuilder jrePattern = new StringBuilder(length);
 521         int count = 0;
 522         char lastLetter = 0;
 523 
 524         for (int i = 0; i < length; i++) {
 525             char c = pattern.charAt(i);
 526 
 527             if (c == '\'') {
 528                 // '' is treated as a single quote regardless of being
 529                 // in a quoted section.
 530                 if ((i + 1) < length) {
 531                     char nextc = pattern.charAt(i + 1);
 532                     if (nextc == '\'') {
 533                         i++;
 534                         if (count != 0) {
 535                             convert(calendarType, lastLetter, count, jrePattern);
 536                             lastLetter = 0;
 537                             count = 0;
 538                         }
 539                         jrePattern.append("''");
 540                         continue;
 541                     }
 542                 }
 543                 if (!inQuote) {
 544                     if (count != 0) {
 545                         convert(calendarType, lastLetter, count, jrePattern);
 546                         lastLetter = 0;
 547                         count = 0;
 548                     }
 549                     inQuote = true;
 550                 } else {
 551                     inQuote = false;
 552                 }
 553                 jrePattern.append(c);
 554                 continue;
 555             }
 556             if (inQuote) {
 557                 jrePattern.append(c);
 558                 continue;
 559             }
 560             if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
 561                 if (count != 0) {
 562                     convert(calendarType, lastLetter, count, jrePattern);
 563                     lastLetter = 0;
 564                     count = 0;
 565                 }
 566                 jrePattern.append(c);
 567                 continue;
 568             }
 569 
 570             if (lastLetter == 0 || lastLetter == c) {
 571                 lastLetter = c;
 572                 count++;
 573                 continue;
 574             }
 575             convert(calendarType, lastLetter, count, jrePattern);
 576             lastLetter = c;
 577             count = 1;
 578         }
 579 
 580         if (inQuote) {
 581             throw new InternalError("Unterminated quote in date-time pattern: " + cldrFormat);
 582         }
 583 
 584         if (count != 0) {
 585             convert(calendarType, lastLetter, count, jrePattern);
 586         }
 587         if (cldrFormat.contentEquals(jrePattern)) {
 588             return cldrFormat;
 589         }
 590         return jrePattern.toString();
 591     }
 592 
 593     private String toMetaZoneKey(String tzKey) {
 594         if (tzKey.startsWith(CLDRConverter.TIMEZONE_ID_PREFIX)) {
 595             String tz = tzKey.substring(CLDRConverter.TIMEZONE_ID_PREFIX.length());
 596             String meta = CLDRConverter.handlerMetaZones.get(tz);
 597             if (meta != null) {
 598                 return CLDRConverter.METAZONE_ID_PREFIX + meta;
 599             }
 600         }
 601         return null;
 602     }
 603 
 604     private void fillInAbbrs(String key, Map<String, String> map) {
 605         fillInAbbrs(TZ_STD_LONG_KEY, TZ_STD_SHORT_KEY, map);
 606         fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);
 607         fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);
 608 
 609         // If the standard std is "Standard Time" and daylight std is "Summer Time",
 610         // replace the standard std with the generic std to avoid using
 611         // the same abbrivation except for Australia time zone names.
 612         String std = map.get(TZ_STD_SHORT_KEY);
 613         String dst = map.get(TZ_DST_SHORT_KEY);
 614         String gen = map.get(TZ_GEN_SHORT_KEY);
 615         if (std != null) {
 616             if (dst == null) {
 617                 // if dst is null, create long and short names from the standard
 618                 // std. ("Something Standard Time" to "Something Daylight Time",
 619                 // or "Something Time" to "Something Summer Time")
 620                 String name = map.get(TZ_STD_LONG_KEY);
 621                 if (name != null) {
 622                     if (name.contains("Standard Time")) {
 623                         name = name.replace("Standard Time", "Daylight Time");
 624                     } else if (name.endsWith("Mean Time")) {
 625                         name = name.replace("Mean Time", "Summer Time");
 626                     } else if (name.endsWith(" Time")) {
 627                         name = name.replace(" Time", " Summer Time");
 628                     }
 629                     map.put(TZ_DST_LONG_KEY, name);
 630                     fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);
 631                 }
 632             }
 633             if (gen  == null) {
 634                 String name = map.get(TZ_STD_LONG_KEY);
 635                 if (name != null) {
 636                     if (name.endsWith("Standard Time")) {
 637                         name = name.replace("Standard Time", "Time");
 638                     } else if (name.endsWith("Mean Time")) {
 639                         name = name.replace("Mean Time", "Time");
 640                     }
 641                     map.put(TZ_GEN_LONG_KEY, name);
 642                     fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);
 643                 }
 644             }
 645         }
 646     }
 647 
 648     private void fillInAbbrs(String longKey, String shortKey, Map<String, String> map) {
 649         String abbr = map.get(shortKey);
 650         if (abbr == null) {
 651             String name = map.get(longKey);
 652             if (name != null) {
 653                 abbr = toAbbr(name);
 654                 if (abbr != null) {
 655                     map.put(shortKey, abbr);
 656                 }
 657             }
 658         }
 659     }
 660 
 661     private String toAbbr(String name) {
 662         String[] substrs = name.split("\\s+");
 663         StringBuilder sb = new StringBuilder();
 664         for (String s : substrs) {
 665             char c = s.charAt(0);
 666             if (c >= 'A' && c <= 'Z') {
 667                 sb.append(c);
 668             }
 669         }
 670         return sb.length() > 0 ? sb.toString() : null;
 671     }
 672 
 673     private void convert(CalendarType calendarType, char cldrLetter, int count, StringBuilder sb) {
 674         switch (cldrLetter) {
 675         case 'G':
 676             if (calendarType != CalendarType.GREGORIAN) {
 677                 // Adjust the number of 'G's for JRE SimpleDateFormat
 678                 if (count == 5) {
 679                     // CLDR narrow -> JRE short
 680                     count = 1;
 681                 } else if (count == 1) {
 682                     // CLDR abbr -> JRE long
 683                     count = 4;
 684                 }
 685             }
 686             appendN(cldrLetter, count, sb);
 687             break;
 688 
 689         // TODO: support 'c' and 'e' in JRE SimpleDateFormat
 690         // Use 'u' and 'E' for now.
 691         case 'c':
 692         case 'e':
 693             switch (count) {
 694             case 1:
 695                 sb.append('u');
 696                 break;
 697             case 3:
 698             case 4:
 699                 appendN('E', count, sb);
 700                 break;
 701             case 5:
 702                 appendN('E', 3, sb);
 703                 break;
 704             }
 705             break;
 706 
 707         case 'v':
 708         case 'V':
 709             appendN('z', count, sb);
 710             break;
 711 
 712         case 'Z':
 713             if (count == 4 || count == 5) {
 714                 sb.append("XXX");
 715             }
 716             break;
 717 
 718         case 'u':
 719         case 'U':
 720         case 'q':
 721         case 'Q':
 722         case 'l':
 723         case 'g':
 724         case 'j':
 725         case 'A':
 726             throw new InternalError(String.format("Unsupported letter: '%c', count=%d%n",
 727                                                   cldrLetter, count));
 728         default:
 729             appendN(cldrLetter, count, sb);
 730             break;
 731         }
 732     }
 733 
 734     private void appendN(char c, int n, StringBuilder sb) {
 735         for (int i = 0; i < n; i++) {
 736             sb.append(c);
 737         }
 738     }
 739 
 740     private static boolean hasNulls(Object[] array) {
 741         for (int i = 0; i < array.length; i++) {
 742             if (array[i] == null) {
 743                 return true;
 744             }
 745         }
 746         return false;
 747     }
 748 }