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 }