1 /* 2 * Copyright (c) 2012, 2015, 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 build.tools.cldrconverter.BundleGenerator.BundleType; 29 import java.io.File; 30 import java.nio.file.DirectoryStream; 31 import java.nio.file.FileSystems; 32 import java.nio.file.Files; 33 import java.nio.file.Path; 34 import java.util.*; 35 import java.util.ResourceBundle.Control; 36 import java.util.logging.Level; 37 import java.util.logging.Logger; 38 import javax.xml.parsers.SAXParser; 39 import javax.xml.parsers.SAXParserFactory; 40 import org.xml.sax.SAXNotRecognizedException; 41 import org.xml.sax.SAXNotSupportedException; 42 43 44 /** 45 * Converts locale data from "Locale Data Markup Language" format to 46 * JRE resource bundle format. LDML is the format used by the Common 47 * Locale Data Repository maintained by the Unicode Consortium. 48 */ 49 public class CLDRConverter { 50 51 static final String LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldml.dtd"; 52 static final String SPPL_LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldmlSupplemental.dtd"; 53 54 private static String CLDR_BASE = "../CLDR/21.0.1/"; 55 static String LOCAL_LDML_DTD; 56 static String LOCAL_SPPL_LDML_DTD; 57 private static String SOURCE_FILE_DIR; 58 private static String SPPL_SOURCE_FILE; 59 private static String NUMBERING_SOURCE_FILE; 60 private static String METAZONES_SOURCE_FILE; 61 static String DESTINATION_DIR = "build/gensrc"; 62 63 static final String LOCALE_NAME_PREFIX = "locale.displayname."; 64 static final String CURRENCY_SYMBOL_PREFIX = "currency.symbol."; 65 static final String CURRENCY_NAME_PREFIX = "currency.displayname."; 66 static final String CALENDAR_NAME_PREFIX = "calendarname."; 67 static final String TIMEZONE_ID_PREFIX = "timezone.id."; 68 static final String ZONE_NAME_PREFIX = "timezone.displayname."; 69 static final String METAZONE_ID_PREFIX = "metazone.id."; 70 static final String PARENT_LOCALE_PREFIX = "parentLocale."; 71 72 private static SupplementDataParseHandler handlerSuppl; 73 static NumberingSystemsParseHandler handlerNumbering; 74 static MetaZonesParseHandler handlerMetaZones; 75 private static BundleGenerator bundleGenerator; 76 77 // java.base module related 78 static boolean isBaseModule = false; 79 static final Set<Locale> BASE_LOCALES = new HashSet<>(); 80 81 // "parentLocales" map 82 private static final Map<String, SortedSet<String>> parentLocalesMap = new HashMap<>(); 83 private static final ResourceBundle.Control defCon = 84 ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT); 85 86 static enum DraftType { 87 UNCONFIRMED, 88 PROVISIONAL, 89 CONTRIBUTED, 90 APPROVED; 91 92 private static final Map<String, DraftType> map = new HashMap<>(); 93 static { 94 for (DraftType dt : values()) { 95 map.put(dt.getKeyword(), dt); 96 } 97 } 98 static private DraftType defaultType = CONTRIBUTED; 99 100 private final String keyword; 101 102 private DraftType() { 103 keyword = this.name().toLowerCase(Locale.ROOT); 104 105 } 106 107 static DraftType forKeyword(String keyword) { 108 return map.get(keyword); 109 } 110 111 static DraftType getDefault() { 112 return defaultType; 113 } 114 115 static void setDefault(String keyword) { 116 defaultType = Objects.requireNonNull(forKeyword(keyword)); 117 } 118 119 String getKeyword() { 120 return keyword; 121 } 122 } 123 124 static boolean USE_UTF8 = false; 125 private static boolean verbose; 126 127 private CLDRConverter() { 128 // no instantiation 129 } 130 131 @SuppressWarnings("AssignmentToForLoopParameter") 132 public static void main(String[] args) throws Exception { 133 if (args.length != 0) { 134 String currentArg = null; 135 try { 136 for (int i = 0; i < args.length; i++) { 137 currentArg = args[i]; 138 switch (currentArg) { 139 case "-draft": 140 String draftDataType = args[++i]; 141 try { 142 DraftType.setDefault(draftDataType); 143 } catch (NullPointerException e) { 144 severe("Error: incorrect draft value: %s%n", draftDataType); 145 System.exit(1); 146 } 147 info("Using the specified data type: %s%n", draftDataType); 148 break; 149 150 case "-base": 151 // base directory for input files 152 CLDR_BASE = args[++i]; 153 if (!CLDR_BASE.endsWith("/")) { 154 CLDR_BASE += "/"; 155 } 156 break; 157 158 case "-baselocales": 159 // base locales 160 setupBaseLocales(args[++i]); 161 break; 162 163 case "-basemodule": 164 // indicates java.base module resource generation 165 isBaseModule = true; 166 break; 167 168 case "-o": 169 // output directory 170 DESTINATION_DIR = args[++i]; 171 break; 172 173 case "-utf8": 174 USE_UTF8 = true; 175 break; 176 177 case "-verbose": 178 verbose = true; 179 break; 180 181 case "-help": 182 usage(); 183 System.exit(0); 184 break; 185 186 default: 187 throw new RuntimeException(); 188 } 189 } 190 } catch (RuntimeException e) { 191 severe("unknown or imcomplete arg(s): " + currentArg); 192 usage(); 193 System.exit(1); 194 } 195 } 196 197 // Set up path names 198 LOCAL_LDML_DTD = CLDR_BASE + "common/dtd/ldml.dtd"; 199 LOCAL_SPPL_LDML_DTD = CLDR_BASE + "common/dtd/ldmlSupplemental.dtd"; 200 SOURCE_FILE_DIR = CLDR_BASE + "common/main"; 201 SPPL_SOURCE_FILE = CLDR_BASE + "common/supplemental/supplementalData.xml"; 202 NUMBERING_SOURCE_FILE = CLDR_BASE + "common/supplemental/numberingSystems.xml"; 203 METAZONES_SOURCE_FILE = CLDR_BASE + "common/supplemental/metaZones.xml"; 204 205 if (BASE_LOCALES.isEmpty()) { 206 setupBaseLocales("en-US"); 207 } 208 209 bundleGenerator = new ResourceBundleGenerator(); 210 211 // Parse data independent of locales 212 parseSupplemental(); 213 214 List<Bundle> bundles = readBundleList(); 215 convertBundles(bundles); 216 } 217 218 private static void usage() { 219 errout("Usage: java CLDRConverter [options]%n" 220 + "\t-help output this usage message and exit%n" 221 + "\t-verbose output information%n" 222 + "\t-draft [approved | provisional | unconfirmed]%n" 223 + "\t\t draft level for using data (default: approved)%n" 224 + "\t-base dir base directory for CLDR input files%n" 225 + "\t-basemodule generates bundles that go into java.base module%n" 226 + "\t-baselocales loc(,loc)* locales that go into the base module%n" 227 + "\t-o dir output directory (default: ./build/gensrc)%n" 228 + "\t-o dir output directory (defaut: ./build/gensrc)%n" 229 + "\t-utf8 use UTF-8 rather than \\uxxxx (for debug)%n"); 230 } 231 232 static void info(String fmt, Object... args) { 233 if (verbose) { 234 System.out.printf(fmt, args); 235 } 236 } 237 238 static void info(String msg) { 239 if (verbose) { 240 System.out.println(msg); 241 } 242 } 243 244 static void warning(String fmt, Object... args) { 245 System.err.print("Warning: "); 246 System.err.printf(fmt, args); 247 } 248 249 static void warning(String msg) { 250 System.err.print("Warning: "); 251 errout(msg); 252 } 253 254 static void severe(String fmt, Object... args) { 255 System.err.print("Error: "); 256 System.err.printf(fmt, args); 257 } 258 259 static void severe(String msg) { 260 System.err.print("Error: "); 261 errout(msg); 262 } 263 264 private static void errout(String msg) { 265 if (msg.contains("%n")) { 266 System.err.printf(msg); 267 } else { 268 System.err.println(msg); 269 } 270 } 271 272 /** 273 * Configure the parser to allow access to DTDs on the file system. 274 */ 275 private static void enableFileAccess(SAXParser parser) throws SAXNotSupportedException { 276 try { 277 parser.setProperty("http://javax.xml.XMLConstants/property/accessExternalDTD", "file"); 278 } catch (SAXNotRecognizedException ignore) { 279 // property requires >= JAXP 1.5 280 } 281 } 282 283 private static List<Bundle> readBundleList() throws Exception { 284 List<Bundle> retList = new ArrayList<>(); 285 Path path = FileSystems.getDefault().getPath(SOURCE_FILE_DIR); 286 try (DirectoryStream<Path> dirStr = Files.newDirectoryStream(path)) { 287 for (Path entry : dirStr) { 288 String fileName = entry.getFileName().toString(); 289 if (fileName.endsWith(".xml")) { 290 String id = fileName.substring(0, fileName.indexOf('.')); 291 Locale cldrLoc = Locale.forLanguageTag(toLanguageTag(id)); 292 List<Locale> candList = applyParentLocales("", defCon.getCandidateLocales("", cldrLoc)); 293 StringBuilder sb = new StringBuilder(); 294 for (Locale loc : candList) { 295 if (!loc.equals(Locale.ROOT)) { 296 sb.append(toLocaleName(loc.toLanguageTag())); 297 sb.append(","); 298 } 299 } 300 if (sb.indexOf("root") == -1) { 301 sb.append("root"); 302 } 303 Bundle b = new Bundle(id, sb.toString(), null, null); 304 // Insert the bundle for root at the top so that it will get 305 // processed first. 306 if ("root".equals(id)) { 307 retList.add(0, b); 308 } else { 309 retList.add(b); 310 } 311 } 312 } 313 } 314 return retList; 315 } 316 317 private static final Map<String, Map<String, Object>> cldrBundles = new HashMap<>(); 318 319 static Map<String, Object> getCLDRBundle(String id) throws Exception { 320 Map<String, Object> bundle = cldrBundles.get(id); 321 if (bundle != null) { 322 return bundle; 323 } 324 SAXParserFactory factory = SAXParserFactory.newInstance(); 325 factory.setValidating(true); 326 SAXParser parser = factory.newSAXParser(); 327 enableFileAccess(parser); 328 LDMLParseHandler handler = new LDMLParseHandler(id); 329 File file = new File(SOURCE_FILE_DIR + File.separator + id + ".xml"); 330 if (!file.exists()) { 331 // Skip if the file doesn't exist. 332 return Collections.emptyMap(); 333 } 334 335 info("..... main directory ....."); 336 info("Reading file " + file); 337 parser.parse(file, handler); 338 339 bundle = handler.getData(); 340 cldrBundles.put(id, bundle); 341 String country = getCountryCode(id); 342 if (country != null) { 343 bundle = handlerSuppl.getData(country); 344 if (bundle != null) { 345 //merge two maps into one map 346 Map<String, Object> temp = cldrBundles.remove(id); 347 bundle.putAll(temp); 348 cldrBundles.put(id, bundle); 349 } 350 } 351 return bundle; 352 } 353 354 // Parsers for data in "supplemental" directory 355 // 356 private static void parseSupplemental() throws Exception { 357 // Parse SupplementalData file and store the information in the HashMap 358 // Calendar information such as firstDay and minDay are stored in 359 // supplementalData.xml as of CLDR1.4. Individual territory is listed 360 // with its ISO 3166 country code while default is listed using UNM49 361 // region and composition numerical code (001 for World.) 362 // 363 // SupplementalData file also provides the "parent" locales which 364 // are othrwise not to be fallen back. Process them here as well. 365 // 366 info("..... Parsing supplementalData.xml ....."); 367 SAXParserFactory factorySuppl = SAXParserFactory.newInstance(); 368 factorySuppl.setValidating(true); 369 SAXParser parserSuppl = factorySuppl.newSAXParser(); 370 enableFileAccess(parserSuppl); 371 handlerSuppl = new SupplementDataParseHandler(); 372 File fileSupply = new File(SPPL_SOURCE_FILE); 373 parserSuppl.parse(fileSupply, handlerSuppl); 374 Map<String, Object> parentData = handlerSuppl.getData("root"); 375 parentData.keySet().forEach(key -> { 376 parentLocalesMap.put(key, new TreeSet( 377 Arrays.asList(((String)parentData.get(key)).split(" ")))); 378 }); 379 380 // Parse numberingSystems to get digit zero character information. 381 info("..... Parsing numberingSystem.xml ....."); 382 SAXParserFactory numberingParser = SAXParserFactory.newInstance(); 383 numberingParser.setValidating(true); 384 SAXParser parserNumbering = numberingParser.newSAXParser(); 385 enableFileAccess(parserNumbering); 386 handlerNumbering = new NumberingSystemsParseHandler(); 387 File fileNumbering = new File(NUMBERING_SOURCE_FILE); 388 parserNumbering.parse(fileNumbering, handlerNumbering); 389 390 // Parse metaZones to create mappings between Olson tzids and CLDR meta zone names 391 info("..... Parsing metaZones.xml ....."); 392 SAXParserFactory metazonesParser = SAXParserFactory.newInstance(); 393 metazonesParser.setValidating(true); 394 SAXParser parserMetaZones = metazonesParser.newSAXParser(); 395 enableFileAccess(parserMetaZones); 396 handlerMetaZones = new MetaZonesParseHandler(); 397 File fileMetaZones = new File(METAZONES_SOURCE_FILE); 398 parserNumbering.parse(fileMetaZones, handlerMetaZones); 399 } 400 401 private static void convertBundles(List<Bundle> bundles) throws Exception { 402 // For generating information on supported locales. 403 Map<String, SortedSet<String>> metaInfo = new HashMap<>(); 404 metaInfo.put("LocaleNames", new TreeSet<>()); 405 metaInfo.put("CurrencyNames", new TreeSet<>()); 406 metaInfo.put("TimeZoneNames", new TreeSet<>()); 407 metaInfo.put("CalendarData", new TreeSet<>()); 408 metaInfo.put("FormatData", new TreeSet<>()); 409 metaInfo.put("AvailableLocales", new TreeSet<>()); 410 411 // parent locales map. The mappings are put in base metaInfo file 412 // for now. 413 if (isBaseModule) { 414 metaInfo.putAll(parentLocalesMap); 415 } 416 417 for (Bundle bundle : bundles) { 418 // Get the target map, which contains all the data that should be 419 // visible for the bundle's locale 420 421 Map<String, Object> targetMap = bundle.getTargetMap(); 422 423 EnumSet<Bundle.Type> bundleTypes = bundle.getBundleTypes(); 424 425 if (bundle.isRoot()) { 426 // Add DateTimePatternChars because CLDR no longer supports localized patterns. 427 targetMap.put("DateTimePatternChars", "GyMdkHmsSEDFwWahKzZ"); 428 } 429 430 // Now the map contains just the entries that need to be in the resources bundles. 431 // Go ahead and generate them. 432 if (bundleTypes.contains(Bundle.Type.LOCALENAMES)) { 433 Map<String, Object> localeNamesMap = extractLocaleNames(targetMap, bundle.getID()); 434 if (!localeNamesMap.isEmpty() || bundle.isRoot()) { 435 metaInfo.get("LocaleNames").add(toLanguageTag(bundle.getID())); 436 bundleGenerator.generateBundle("util", "LocaleNames", bundle.getID(), true, localeNamesMap, BundleType.OPEN); 437 } 438 } 439 if (bundleTypes.contains(Bundle.Type.CURRENCYNAMES)) { 440 Map<String, Object> currencyNamesMap = extractCurrencyNames(targetMap, bundle.getID(), bundle.getCurrencies()); 441 if (!currencyNamesMap.isEmpty() || bundle.isRoot()) { 442 metaInfo.get("CurrencyNames").add(toLanguageTag(bundle.getID())); 443 bundleGenerator.generateBundle("util", "CurrencyNames", bundle.getID(), true, currencyNamesMap, BundleType.OPEN); 444 } 445 } 446 if (bundleTypes.contains(Bundle.Type.TIMEZONENAMES)) { 447 Map<String, Object> zoneNamesMap = extractZoneNames(targetMap, bundle.getID()); 448 if (!zoneNamesMap.isEmpty() || bundle.isRoot()) { 449 metaInfo.get("TimeZoneNames").add(toLanguageTag(bundle.getID())); 450 bundleGenerator.generateBundle("util", "TimeZoneNames", bundle.getID(), true, zoneNamesMap, BundleType.TIMEZONE); 451 } 452 } 453 if (bundleTypes.contains(Bundle.Type.CALENDARDATA)) { 454 Map<String, Object> calendarDataMap = extractCalendarData(targetMap, bundle.getID()); 455 if (!calendarDataMap.isEmpty() || bundle.isRoot()) { 456 metaInfo.get("CalendarData").add(toLanguageTag(bundle.getID())); 457 bundleGenerator.generateBundle("util", "CalendarData", bundle.getID(), true, calendarDataMap, BundleType.PLAIN); 458 } 459 } 460 if (bundleTypes.contains(Bundle.Type.FORMATDATA)) { 461 Map<String, Object> formatDataMap = extractFormatData(targetMap, bundle.getID()); 462 if (!formatDataMap.isEmpty() || bundle.isRoot()) { 463 metaInfo.get("FormatData").add(toLanguageTag(bundle.getID())); 464 bundleGenerator.generateBundle("text", "FormatData", bundle.getID(), true, formatDataMap, BundleType.PLAIN); 465 } 466 } 467 468 // For AvailableLocales 469 metaInfo.get("AvailableLocales").add(toLanguageTag(bundle.getID())); 470 } 471 472 bundleGenerator.generateMetaInfo(metaInfo); 473 } 474 475 static final Map<String, String> aliases = new HashMap<>(); 476 477 /** 478 * Translate the aliases into the real entries in the bundle map. 479 */ 480 static void handleAliases(Map<String, Object> bundleMap) { 481 Set bundleKeys = bundleMap.keySet(); 482 try { 483 for (String key : aliases.keySet()) { 484 String targetKey = aliases.get(key); 485 if (bundleKeys.contains(targetKey)) { 486 bundleMap.putIfAbsent(key, bundleMap.get(targetKey)); 487 } 488 } 489 } catch (Exception ex) { 490 Logger.getLogger(CLDRConverter.class.getName()).log(Level.SEVERE, null, ex); 491 } 492 } 493 494 /* 495 * Returns the language portion of the given id. 496 * If id is "root", "" is returned. 497 */ 498 static String getLanguageCode(String id) { 499 return "root".equals(id) ? "" : Locale.forLanguageTag(id.replaceAll("_", "-")).getLanguage(); 500 } 501 502 /** 503 * Examine if the id includes the country (territory) code. If it does, it returns 504 * the country code. 505 * Otherwise, it returns null. eg. when the id is "zh_Hans_SG", it return "SG". 506 * For now, it does not return US M.49 code, e.g., '001', as those three digit numbers cannot 507 * be translated into package names. 508 */ 509 static String getCountryCode(String id) { 510 String ctry = Locale.forLanguageTag(id.replaceAll("_", "-")).getCountry(); 511 return ctry.length() == 2 ? ctry : null; 512 } 513 514 private static class KeyComparator implements Comparator<String> { 515 static KeyComparator INSTANCE = new KeyComparator(); 516 517 private KeyComparator() { 518 } 519 520 @Override 521 public int compare(String o1, String o2) { 522 int len1 = o1.length(); 523 int len2 = o2.length(); 524 if (!isDigit(o1.charAt(0)) && !isDigit(o2.charAt(0))) { 525 // Shorter string comes first unless either starts with a digit. 526 if (len1 < len2) { 527 return -1; 528 } 529 if (len1 > len2) { 530 return 1; 531 } 532 } 533 return o1.compareTo(o2); 534 } 535 536 private boolean isDigit(char c) { 537 return c >= '0' && c <= '9'; 538 } 539 } 540 541 private static Map<String, Object> extractLocaleNames(Map<String, Object> map, String id) { 542 Map<String, Object> localeNames = new TreeMap<>(KeyComparator.INSTANCE); 543 for (String key : map.keySet()) { 544 if (key.startsWith(LOCALE_NAME_PREFIX)) { 545 localeNames.put(key.substring(LOCALE_NAME_PREFIX.length()), map.get(key)); 546 } 547 } 548 return localeNames; 549 } 550 551 @SuppressWarnings("AssignmentToForLoopParameter") 552 private static Map<String, Object> extractCurrencyNames(Map<String, Object> map, String id, String names) 553 throws Exception { 554 Map<String, Object> currencyNames = new TreeMap<>(KeyComparator.INSTANCE); 555 for (String key : map.keySet()) { 556 if (key.startsWith(CURRENCY_NAME_PREFIX)) { 557 currencyNames.put(key.substring(CURRENCY_NAME_PREFIX.length()), map.get(key)); 558 } else if (key.startsWith(CURRENCY_SYMBOL_PREFIX)) { 559 currencyNames.put(key.substring(CURRENCY_SYMBOL_PREFIX.length()), map.get(key)); 560 } 561 } 562 return currencyNames; 563 } 564 565 private static Map<String, Object> extractZoneNames(Map<String, Object> map, String id) { 566 Map<String, Object> names = new HashMap<>(); 567 for (String tzid : handlerMetaZones.keySet()) { 568 String tzKey = TIMEZONE_ID_PREFIX + tzid; 569 Object data = map.get(tzKey); 570 if (data instanceof String[]) { 571 names.put(tzid, data); 572 } else { 573 String meta = handlerMetaZones.get(tzid); 574 if (meta != null) { 575 String metaKey = METAZONE_ID_PREFIX + meta; 576 data = map.get(metaKey); 577 if (data instanceof String[]) { 578 // Keep the metazone prefix here. 579 names.put(metaKey, data); 580 names.put(tzid, meta); 581 } 582 } 583 } 584 } 585 return names; 586 } 587 588 private static Map<String, Object> extractCalendarData(Map<String, Object> map, String id) { 589 Map<String, Object> calendarData = new LinkedHashMap<>(); 590 copyIfPresent(map, "firstDayOfWeek", calendarData); 591 copyIfPresent(map, "minimalDaysInFirstWeek", calendarData); 592 return calendarData; 593 } 594 595 static final String[] FORMAT_DATA_ELEMENTS = { 596 "MonthNames", 597 "standalone.MonthNames", 598 "MonthAbbreviations", 599 "standalone.MonthAbbreviations", 600 "MonthNarrows", 601 "standalone.MonthNarrows", 602 "DayNames", 603 "standalone.DayNames", 604 "DayAbbreviations", 605 "standalone.DayAbbreviations", 606 "DayNarrows", 607 "standalone.DayNarrows", 608 "QuarterNames", 609 "standalone.QuarterNames", 610 "QuarterAbbreviations", 611 "standalone.QuarterAbbreviations", 612 "QuarterNarrows", 613 "standalone.QuarterNarrows", 614 "AmPmMarkers", 615 "narrow.AmPmMarkers", 616 "long.Eras", 617 "Eras", 618 "narrow.Eras", 619 "field.era", 620 "field.year", 621 "field.month", 622 "field.week", 623 "field.weekday", 624 "field.dayperiod", 625 "field.hour", 626 "field.minute", 627 "field.second", 628 "field.zone", 629 "TimePatterns", 630 "DatePatterns", 631 "DateTimePatterns", 632 "DateTimePatternChars" 633 }; 634 635 private static Map<String, Object> extractFormatData(Map<String, Object> map, String id) { 636 Map<String, Object> formatData = new LinkedHashMap<>(); 637 for (CalendarType calendarType : CalendarType.values()) { 638 if (calendarType == CalendarType.GENERIC) { 639 continue; 640 } 641 String prefix = calendarType.keyElementName(); 642 for (String element : FORMAT_DATA_ELEMENTS) { 643 String key = prefix + element; 644 copyIfPresent(map, "java.time." + key, formatData); 645 copyIfPresent(map, key, formatData); 646 } 647 } 648 649 for (String key : map.keySet()) { 650 // Copy available calendar names 651 if (key.startsWith(CLDRConverter.CALENDAR_NAME_PREFIX)) { 652 String type = key.substring(CLDRConverter.CALENDAR_NAME_PREFIX.length()); 653 for (CalendarType calendarType : CalendarType.values()) { 654 if (calendarType == CalendarType.GENERIC) { 655 continue; 656 } 657 if (type.equals(calendarType.lname())) { 658 Object value = map.get(key); 659 formatData.put(key, value); 660 String ukey = CLDRConverter.CALENDAR_NAME_PREFIX + calendarType.uname(); 661 if (!key.equals(ukey)) { 662 formatData.put(ukey, value); 663 } 664 } 665 } 666 } 667 } 668 669 copyIfPresent(map, "DefaultNumberingSystem", formatData); 670 671 @SuppressWarnings("unchecked") 672 List<String> numberingScripts = (List<String>) map.remove("numberingScripts"); 673 if (numberingScripts != null) { 674 for (String script : numberingScripts) { 675 copyIfPresent(map, script + "." + "NumberElements", formatData); 676 } 677 } else { 678 copyIfPresent(map, "NumberElements", formatData); 679 } 680 copyIfPresent(map, "NumberPatterns", formatData); 681 return formatData; 682 } 683 684 private static void copyIfPresent(Map<String, Object> src, String key, Map<String, Object> dest) { 685 Object value = src.get(key); 686 if (value != null) { 687 dest.put(key, value); 688 } 689 } 690 691 // --- code below here is adapted from java.util.Properties --- 692 private static final String specialSaveCharsJava = "\""; 693 private static final String specialSaveCharsProperties = "=: \t\r\n\f#!"; 694 695 /* 696 * Converts unicodes to encoded \uxxxx 697 * and writes out any of the characters in specialSaveChars 698 * with a preceding slash 699 */ 700 static String saveConvert(String theString, boolean useJava) { 701 if (theString == null) { 702 return ""; 703 } 704 705 String specialSaveChars; 706 if (useJava) { 707 specialSaveChars = specialSaveCharsJava; 708 } else { 709 specialSaveChars = specialSaveCharsProperties; 710 } 711 boolean escapeSpace = false; 712 713 int len = theString.length(); 714 StringBuilder outBuffer = new StringBuilder(len * 2); 715 Formatter formatter = new Formatter(outBuffer, Locale.ROOT); 716 717 for (int x = 0; x < len; x++) { 718 char aChar = theString.charAt(x); 719 switch (aChar) { 720 case ' ': 721 if (x == 0 || escapeSpace) { 722 outBuffer.append('\\'); 723 } 724 outBuffer.append(' '); 725 break; 726 case '\\': 727 outBuffer.append('\\'); 728 outBuffer.append('\\'); 729 break; 730 case '\t': 731 outBuffer.append('\\'); 732 outBuffer.append('t'); 733 break; 734 case '\n': 735 outBuffer.append('\\'); 736 outBuffer.append('n'); 737 break; 738 case '\r': 739 outBuffer.append('\\'); 740 outBuffer.append('r'); 741 break; 742 case '\f': 743 outBuffer.append('\\'); 744 outBuffer.append('f'); 745 break; 746 default: 747 if (aChar < 0x0020 || (!USE_UTF8 && aChar > 0x007e)) { 748 formatter.format("\\u%04x", (int)aChar); 749 } else { 750 if (specialSaveChars.indexOf(aChar) != -1) { 751 outBuffer.append('\\'); 752 } 753 outBuffer.append(aChar); 754 } 755 } 756 } 757 return outBuffer.toString(); 758 } 759 760 private static String toLanguageTag(String locName) { 761 if (locName.indexOf('_') == -1) { 762 return locName; 763 } 764 String tag = locName.replaceAll("_", "-"); 765 Locale loc = Locale.forLanguageTag(tag); 766 return loc.toLanguageTag(); 767 } 768 769 private static String toLocaleName(String tag) { 770 if (tag.indexOf('-') == -1) { 771 return tag; 772 } 773 return tag.replaceAll("-", "_"); 774 } 775 776 private static void setupBaseLocales(String localeList) { 777 Arrays.stream(localeList.split(",")) 778 .map(Locale::forLanguageTag) 779 .map(l -> Control.getControl(Control.FORMAT_DEFAULT) 780 .getCandidateLocales("", l)) 781 .forEach(BASE_LOCALES::addAll); 782 } 783 784 // applying parent locale rules to the passed candidates list 785 // This has to match with the one in sun.util.cldr.CLDRLocaleProviderAdapter 786 private static Map<Locale, Locale> childToParentLocaleMap = null; 787 private static List<Locale> applyParentLocales(String baseName, List<Locale> candidates) { 788 if (Objects.isNull(childToParentLocaleMap)) { 789 childToParentLocaleMap = new HashMap<>(); 790 parentLocalesMap.keySet().forEach(key -> { 791 String parent = key.substring(PARENT_LOCALE_PREFIX.length()).replaceAll("_", "-"); 792 parentLocalesMap.get(key).stream().forEach(child -> { 793 childToParentLocaleMap.put(Locale.forLanguageTag(child), 794 "root".equals(parent) ? Locale.ROOT : Locale.forLanguageTag(parent)); 795 }); 796 }); 797 } 798 799 // check irregular parents 800 for (int i = 0; i < candidates.size(); i++) { 801 Locale l = candidates.get(i); 802 Locale p = childToParentLocaleMap.get(l); 803 if (!l.equals(Locale.ROOT) && 804 Objects.nonNull(p) && 805 !candidates.get(i+1).equals(p)) { 806 List<Locale> applied = candidates.subList(0, i+1); 807 applied.addAll(applyParentLocales(baseName, defCon.getCandidateLocales(baseName, p))); 808 return applied; 809 } 810 } 811 812 return candidates; 813 } 814 }