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