< prev index next >

make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java

Print this page
rev 47734 : imported patch 8190918


  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 static build.tools.cldrconverter.Bundle.jreTimeZoneNames;
  29 import build.tools.cldrconverter.BundleGenerator.BundleType;
  30 import java.io.File;
  31 import java.nio.file.DirectoryStream;
  32 import java.nio.file.FileSystems;
  33 import java.nio.file.Files;
  34 import java.nio.file.Path;
  35 import java.util.*;
  36 import java.util.ResourceBundle.Control;
  37 import java.util.logging.Level;
  38 import java.util.logging.Logger;
  39 import java.util.stream.Collectors;

  40 import javax.xml.parsers.SAXParser;
  41 import javax.xml.parsers.SAXParserFactory;
  42 import org.xml.sax.SAXNotRecognizedException;
  43 import org.xml.sax.SAXNotSupportedException;
  44 
  45 
  46 /**
  47  * Converts locale data from "Locale Data Markup Language" format to
  48  * JRE resource bundle format. LDML is the format used by the Common
  49  * Locale Data Repository maintained by the Unicode Consortium.
  50  */
  51 public class CLDRConverter {
  52 
  53     static final String LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldml.dtd";
  54     static final String SPPL_LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldmlSupplemental.dtd";


  55 
  56     private static String CLDR_BASE = "../CLDR/21.0.1/";
  57     static String LOCAL_LDML_DTD;
  58     static String LOCAL_SPPL_LDML_DTD;

  59     private static String SOURCE_FILE_DIR;
  60     private static String SPPL_SOURCE_FILE;
  61     private static String NUMBERING_SOURCE_FILE;
  62     private static String METAZONES_SOURCE_FILE;
  63     private static String LIKELYSUBTAGS_SOURCE_FILE;

  64     static String DESTINATION_DIR = "build/gensrc";
  65 
  66     static final String LOCALE_NAME_PREFIX = "locale.displayname.";





  67     static final String CURRENCY_SYMBOL_PREFIX = "currency.symbol.";
  68     static final String CURRENCY_NAME_PREFIX = "currency.displayname.";
  69     static final String CALENDAR_NAME_PREFIX = "calendarname.";


  70     static final String TIMEZONE_ID_PREFIX = "timezone.id.";
  71     static final String ZONE_NAME_PREFIX = "timezone.displayname.";
  72     static final String METAZONE_ID_PREFIX = "metazone.id.";
  73     static final String PARENT_LOCALE_PREFIX = "parentLocale.";
  74 
  75     private static SupplementDataParseHandler handlerSuppl;
  76     private static LikelySubtagsParseHandler handlerLikelySubtags;
  77     static NumberingSystemsParseHandler handlerNumbering;
  78     static MetaZonesParseHandler handlerMetaZones;

  79     private static BundleGenerator bundleGenerator;
  80 
  81     // java.base module related
  82     static boolean isBaseModule = false;
  83     static final Set<Locale> BASE_LOCALES = new HashSet<>();
  84 
  85     // "parentLocales" map
  86     private static final Map<String, SortedSet<String>> parentLocalesMap = new HashMap<>();
  87     private static final ResourceBundle.Control defCon =
  88         ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT);
  89 
  90     static enum DraftType {
  91         UNCONFIRMED,
  92         PROVISIONAL,
  93         CONTRIBUTED,
  94         APPROVED;
  95 
  96         private static final Map<String, DraftType> map = new HashMap<>();
  97         static {
  98             for (DraftType dt : values()) {


 184 
 185                     case "-help":
 186                         usage();
 187                         System.exit(0);
 188                         break;
 189 
 190                     default:
 191                         throw new RuntimeException();
 192                     }
 193                 }
 194             } catch (RuntimeException e) {
 195                 severe("unknown or imcomplete arg(s): " + currentArg);
 196                 usage();
 197                 System.exit(1);
 198             }
 199         }
 200 
 201         // Set up path names
 202         LOCAL_LDML_DTD = CLDR_BASE + "/dtd/ldml.dtd";
 203         LOCAL_SPPL_LDML_DTD = CLDR_BASE + "/dtd/ldmlSupplemental.dtd";

 204         SOURCE_FILE_DIR = CLDR_BASE + "/main";
 205         SPPL_SOURCE_FILE = CLDR_BASE + "/supplemental/supplementalData.xml";
 206         LIKELYSUBTAGS_SOURCE_FILE = CLDR_BASE + "/supplemental/likelySubtags.xml";
 207         NUMBERING_SOURCE_FILE = CLDR_BASE + "/supplemental/numberingSystems.xml";
 208         METAZONES_SOURCE_FILE = CLDR_BASE + "/supplemental/metaZones.xml";

 209 
 210         if (BASE_LOCALES.isEmpty()) {
 211             setupBaseLocales("en-US");
 212         }
 213 
 214         bundleGenerator = new ResourceBundleGenerator();
 215 
 216         // Parse data independent of locales
 217         parseSupplemental();

 218 
 219         List<Bundle> bundles = readBundleList();
 220         convertBundles(bundles);
 221         convertBundles(addedBundles);
 222     }
 223 
 224     private static void usage() {
 225         errout("Usage: java CLDRConverter [options]%n"
 226                 + "\t-help          output this usage message and exit%n"
 227                 + "\t-verbose       output information%n"
 228                 + "\t-draft [contributed | approved | provisional | unconfirmed]%n"
 229                 + "\t\t       draft level for using data (default: contributed)%n"
 230                 + "\t-base dir      base directory for CLDR input files%n"
 231                 + "\t-basemodule    generates bundles that go into java.base module%n"
 232                 + "\t-baselocales loc(,loc)*      locales that go into the base module%n"
 233                 + "\t-o dir         output directory (default: ./build/gensrc)%n"
 234                 + "\t-o dir         output directory (defaut: ./build/gensrc)%n"
 235                 + "\t-utf8          use UTF-8 rather than \\uxxxx (for debug)%n");
 236     }
 237 
 238     static void info(String fmt, Object... args) {
 239         if (verbose) {
 240             System.out.printf(fmt, args);
 241         }


 297                     Locale cldrLoc = Locale.forLanguageTag(toLanguageTag(id));
 298                     StringBuilder sb = getCandLocales(cldrLoc);
 299                     if (sb.indexOf("root") == -1) {
 300                         sb.append("root");
 301                     }
 302                     Bundle b = new Bundle(id, sb.toString(), null, null);
 303                     // Insert the bundle for root at the top so that it will get
 304                     // processed first.
 305                     if ("root".equals(id)) {
 306                         retList.add(0, b);
 307                     } else {
 308                         retList.add(b);
 309                     }
 310                 }
 311             }
 312         }
 313         return retList;
 314     }
 315 
 316     private static final Map<String, Map<String, Object>> cldrBundles = new HashMap<>();
 317     // this list will contain additional bundles to be generated for Region dependent Data.
 318     private static List<Bundle> addedBundles = new ArrayList<>();
 319 
 320     private static Map<String, SortedSet<String>> metaInfo = new HashMap<>();
 321 
 322     static {
 323         // For generating information on supported locales.
 324         metaInfo.put("LocaleNames", new TreeSet<>());
 325         metaInfo.put("CurrencyNames", new TreeSet<>());
 326         metaInfo.put("TimeZoneNames", new TreeSet<>());
 327         metaInfo.put("CalendarData", new TreeSet<>());
 328         metaInfo.put("FormatData", new TreeSet<>());
 329         metaInfo.put("AvailableLocales", new TreeSet<>());
 330     }
 331 
 332 
 333     private static Set<String> calendarDataFields = Set.of("firstDayOfWeek", "minimalDaysInFirstWeek");
 334 
 335     static Map<String, Object> getCLDRBundle(String id) throws Exception {
 336         Map<String, Object> bundle = cldrBundles.get(id);
 337         if (bundle != null) {
 338             return bundle;
 339         }
 340         SAXParserFactory factory = SAXParserFactory.newInstance();
 341         factory.setValidating(true);
 342         SAXParser parser = factory.newSAXParser();
 343         enableFileAccess(parser);
 344         LDMLParseHandler handler = new LDMLParseHandler(id);
 345         File file = new File(SOURCE_FILE_DIR + File.separator + id + ".xml");
 346         if (!file.exists()) {
 347             // Skip if the file doesn't exist.
 348             return Collections.emptyMap();
 349         }
 350 
 351         info("..... main directory .....");
 352         info("Reading file " + file);
 353         parser.parse(file, handler);
 354 
 355         bundle = handler.getData();
 356         cldrBundles.put(id, bundle);
 357         String country = getCountryCode(id);
 358         if (country != null) {
 359             bundle = handlerSuppl.getData(country);

 360             if (bundle != null) {
 361                 //merge two maps into one map
 362                 Map<String, Object> temp = cldrBundles.remove(id);
 363                 bundle.putAll(temp);
 364                 cldrBundles.put(id, bundle);
 365             }
 366         }
 367         return bundle;
 368     }
 369 
 370     // Parsers for data in "supplemental" directory
 371     //
 372     private static void parseSupplemental() throws Exception {
 373         // Parse SupplementalData file and store the information in the HashMap
 374         // Calendar information such as firstDay and minDay are stored in
 375         // supplementalData.xml as of CLDR1.4. Individual territory is listed
 376         // with its ISO 3166 country code while default is listed using UNM49
 377         // region and composition numerical code (001 for World.)
 378         //
 379         // SupplementalData file also provides the "parent" locales which
 380         // are othrwise not to be fallen back. Process them here as well.
 381         //
 382         info("..... Parsing supplementalData.xml .....");
 383         SAXParserFactory factorySuppl = SAXParserFactory.newInstance();
 384         factorySuppl.setValidating(true);
 385         SAXParser parserSuppl = factorySuppl.newSAXParser();
 386         enableFileAccess(parserSuppl);
 387         handlerSuppl = new SupplementDataParseHandler();
 388         File fileSupply = new File(SPPL_SOURCE_FILE);
 389         parserSuppl.parse(fileSupply, handlerSuppl);
 390         Map<String, Object> parentData = handlerSuppl.getData("root");
 391         parentData.keySet().forEach(key -> {


 392                 parentLocalesMap.put(key, new TreeSet(
 393                     Arrays.asList(((String)parentData.get(key)).split(" "))));
 394             });
 395 
 396         // Parse numberingSystems to get digit zero character information.
 397         SAXParserFactory numberingParser = SAXParserFactory.newInstance();
 398         numberingParser.setValidating(true);
 399         SAXParser parserNumbering = numberingParser.newSAXParser();
 400         enableFileAccess(parserNumbering);
 401         handlerNumbering = new NumberingSystemsParseHandler();
 402         File fileNumbering = new File(NUMBERING_SOURCE_FILE);
 403         parserNumbering.parse(fileNumbering, handlerNumbering);
 404 
 405         // Parse metaZones to create mappings between Olson tzids and CLDR meta zone names
 406         info("..... Parsing metaZones.xml .....");
 407         SAXParserFactory metazonesParser = SAXParserFactory.newInstance();
 408         metazonesParser.setValidating(true);
 409         SAXParser parserMetaZones = metazonesParser.newSAXParser();
 410         enableFileAccess(parserMetaZones);
 411         handlerMetaZones = new MetaZonesParseHandler();
 412         File fileMetaZones = new File(METAZONES_SOURCE_FILE);
 413         parserMetaZones.parse(fileMetaZones, handlerMetaZones);
 414 
 415         // Parse likelySubtags
 416         info("..... Parsing likelySubtags.xml .....");
 417         SAXParserFactory likelySubtagsParser = SAXParserFactory.newInstance();
 418         likelySubtagsParser.setValidating(true);
 419         SAXParser parserLikelySubtags = likelySubtagsParser.newSAXParser();
 420         enableFileAccess(parserLikelySubtags);
 421         handlerLikelySubtags = new LikelySubtagsParseHandler();
 422         File fileLikelySubtags = new File(LIKELYSUBTAGS_SOURCE_FILE);
 423         parserLikelySubtags.parse(fileLikelySubtags, handlerLikelySubtags);
 424     }
 425 
 426     /**
 427      * This method will check if a new region dependent Bundle needs to be
 428      * generated for this Locale id and targetMap. New Bundle will be generated
 429      * when Locale id has non empty script and country code and targetMap
 430      * contains region dependent data. This method will also remove region
 431      * dependent data from this targetMap after candidate locales check. E.g. It
 432      * will call genRegionDependentBundle() in case of az_Latn_AZ locale and
 433      * remove region dependent data from this targetMap so that az_Latn_AZ
 434      * bundle will not be created. For az_Cyrl_AZ, new Bundle will be generated
 435      * but region dependent data will not be removed from targetMap as its candidate
 436      * locales are [az_Cyrl_AZ, az_Cyrl, root], which does not include az_AZ for
 437      * fallback.
 438      *
 439      */
 440 
 441     private static void checkRegionDependentBundle(Map<String, Object> targetMap, String id) {
 442         if ((CLDRConverter.getScript(id) != "")
 443                 && (CLDRConverter.getCountryCode(id) != "")) {
 444             Map<String, Object> regionDepDataMap = targetMap
 445                     .keySet()
 446                     .stream()
 447                     .filter(calendarDataFields::contains)
 448                     .collect(Collectors.toMap(k -> k, targetMap::get));
 449             if (!regionDepDataMap.isEmpty()) {
 450                 Locale cldrLoc = new Locale(CLDRConverter.getLanguageCode(id),
 451                                             CLDRConverter.getCountryCode(id));
 452                 genRegionDependentBundle(regionDepDataMap, cldrLoc);
 453                 if (checkCandidateLocales(id, cldrLoc)) {
 454                     // Remove matchedKeys from this targetMap only if checkCandidateLocales() returns true.
 455                     regionDepDataMap.keySet().forEach(targetMap::remove);
 456                 }
 457             }
 458         }
 459     }
 460     /**
 461      * This method will generate a new Bundle for region dependent data,
 462      * minimalDaysInFirstWeek and firstDayOfWeek. Newly generated Bundle will be added
 463      * to addedBundles list.
 464      */
 465     private static void genRegionDependentBundle(Map<String, Object> targetMap, Locale cldrLoc) {
 466         String localeId = cldrLoc.toString();
 467         StringBuilder sb = getCandLocales(cldrLoc);
 468         if (sb.indexOf(localeId) == -1) {
 469             sb.append(localeId);
 470         }
 471         Bundle bundle = new Bundle(localeId, sb.toString(), null, null);
 472         cldrBundles.put(localeId, targetMap);
 473         addedBundles.add(bundle);





 474     }
 475 
 476     private static StringBuilder getCandLocales(Locale cldrLoc) {
 477         List<Locale> candList = getCandidateLocales(cldrLoc);
 478         StringBuilder sb = new StringBuilder();
 479         for (Locale loc : candList) {
 480             if (!loc.equals(Locale.ROOT)) {
 481                 sb.append(toLocaleName(loc.toLanguageTag()));
 482                 sb.append(",");
 483             }
 484         }
 485         return sb;
 486     }
 487 
 488     private static List<Locale> getCandidateLocales(Locale cldrLoc) {
 489         List<Locale> candList = new ArrayList<>();
 490         candList = applyParentLocales("", defCon.getCandidateLocales("",  cldrLoc));
 491         return candList;
 492     }
 493 
 494     /**
 495      * This method will return true, if for a given locale, its language and
 496      * country specific locale will exist in runtime lookup path. E.g. it will
 497      * return true for bs_Latn_BA.
 498      */
 499     private static boolean checkCandidateLocales(String id, Locale cldrLoc) {
 500         return(getCandidateLocales(Locale.forLanguageTag(id.replaceAll("_", "-")))
 501                 .contains(cldrLoc));
 502     }
 503 
 504     private static void convertBundles(List<Bundle> bundles) throws Exception {
 505         // parent locales map. The mappings are put in base metaInfo file
 506         // for now.
 507         if (isBaseModule) {
 508             metaInfo.putAll(parentLocalesMap);
 509         }
 510 
 511         for (Bundle bundle : bundles) {
 512             // Get the target map, which contains all the data that should be
 513             // visible for the bundle's locale
 514 
 515             Map<String, Object> targetMap = bundle.getTargetMap();
 516 
 517             // check if new region DependentBundle needs to be generated for this Locale.
 518             checkRegionDependentBundle(targetMap, bundle.getID());
 519             EnumSet<Bundle.Type> bundleTypes = bundle.getBundleTypes();
 520 
 521             if (bundle.isRoot()) {
 522                 // Add DateTimePatternChars because CLDR no longer supports localized patterns.
 523                 targetMap.put("DateTimePatternChars", "GyMdkHmsSEDFwWahKzZ");
 524             }
 525 
 526             // Now the map contains just the entries that need to be in the resources bundles.
 527             // Go ahead and generate them.
 528             if (bundleTypes.contains(Bundle.Type.LOCALENAMES)) {
 529                 Map<String, Object> localeNamesMap = extractLocaleNames(targetMap, bundle.getID());
 530                 if (!localeNamesMap.isEmpty() || bundle.isRoot()) {
 531                     metaInfo.get("LocaleNames").add(toLanguageTag(bundle.getID()));
 532                     addLikelySubtags(metaInfo, "LocaleNames", bundle.getID());
 533                     bundleGenerator.generateBundle("util", "LocaleNames", bundle.getJavaID(), true, localeNamesMap, BundleType.OPEN);
 534                 }
 535             }
 536             if (bundleTypes.contains(Bundle.Type.CURRENCYNAMES)) {
 537                 Map<String, Object> currencyNamesMap = extractCurrencyNames(targetMap, bundle.getID(), bundle.getCurrencies());
 538                 if (!currencyNamesMap.isEmpty() || bundle.isRoot()) {
 539                     metaInfo.get("CurrencyNames").add(toLanguageTag(bundle.getID()));
 540                     addLikelySubtags(metaInfo, "CurrencyNames", bundle.getID());
 541                     bundleGenerator.generateBundle("util", "CurrencyNames", bundle.getJavaID(), true, currencyNamesMap, BundleType.OPEN);
 542                 }
 543             }
 544             if (bundleTypes.contains(Bundle.Type.TIMEZONENAMES)) {
 545                 Map<String, Object> zoneNamesMap = extractZoneNames(targetMap, bundle.getID());
 546                 if (!zoneNamesMap.isEmpty() || bundle.isRoot()) {
 547                     metaInfo.get("TimeZoneNames").add(toLanguageTag(bundle.getID()));
 548                     addLikelySubtags(metaInfo, "TimeZoneNames", bundle.getID());
 549                     bundleGenerator.generateBundle("util", "TimeZoneNames", bundle.getJavaID(), true, zoneNamesMap, BundleType.TIMEZONE);
 550                 }
 551             }
 552             if (bundleTypes.contains(Bundle.Type.CALENDARDATA)) {
 553                 Map<String, Object> calendarDataMap = extractCalendarData(targetMap, bundle.getID());
 554                 if (!calendarDataMap.isEmpty() || bundle.isRoot()) {
 555                     metaInfo.get("CalendarData").add(toLanguageTag(bundle.getID()));
 556                     addLikelySubtags(metaInfo, "CalendarData", bundle.getID());
 557                     bundleGenerator.generateBundle("util", "CalendarData", bundle.getJavaID(), true, calendarDataMap, BundleType.PLAIN);
 558                 }
 559             }
 560             if (bundleTypes.contains(Bundle.Type.FORMATDATA)) {
 561                 Map<String, Object> formatDataMap = extractFormatData(targetMap, bundle.getID());
 562                 if (!formatDataMap.isEmpty() || bundle.isRoot()) {
 563                     metaInfo.get("FormatData").add(toLanguageTag(bundle.getID()));
 564                     addLikelySubtags(metaInfo, "FormatData", bundle.getID());
 565                     bundleGenerator.generateBundle("text", "FormatData", bundle.getJavaID(), true, formatDataMap, BundleType.PLAIN);
 566                 }
 567             }
 568 
 569             // For AvailableLocales
 570             metaInfo.get("AvailableLocales").add(toLanguageTag(bundle.getID()));
 571             addLikelySubtags(metaInfo, "AvailableLocales", bundle.getID());
 572         }
 573         addCldrImplicitLocales(metaInfo);
 574         bundleGenerator.generateMetaInfo(metaInfo);
 575     }
 576 
 577     /**
 578      * These are the Locales that are implicitly supported by CLDR.
 579      * Adding them explicitly as likelySubtags here, will ensure that
 580      * COMPAT locales do not precede them during ResourceBundle search path.
 581      */
 582     private static void addCldrImplicitLocales(Map<String, SortedSet<String>> metaInfo) {
 583         metaInfo.get("LocaleNames").add("zh-Hans-CN");
 584         metaInfo.get("LocaleNames").add("zh-Hans-SG");
 585         metaInfo.get("LocaleNames").add("zh-Hant-HK");
 586         metaInfo.get("LocaleNames").add("zh-Hant-MO");
 587         metaInfo.get("LocaleNames").add("zh-Hant-TW");
 588         metaInfo.get("CurrencyNames").add("zh-Hans-CN");
 589         metaInfo.get("CurrencyNames").add("zh-Hans-SG");
 590         metaInfo.get("CurrencyNames").add("zh-Hant-HK");
 591         metaInfo.get("CurrencyNames").add("zh-Hant-MO");
 592         metaInfo.get("CurrencyNames").add("zh-Hant-TW");
 593         metaInfo.get("TimeZoneNames").add("zh-Hans-CN");
 594         metaInfo.get("TimeZoneNames").add("zh-Hans-SG");
 595         metaInfo.get("TimeZoneNames").add("zh-Hant-HK");
 596         metaInfo.get("TimeZoneNames").add("zh-Hant-MO");
 597         metaInfo.get("TimeZoneNames").add("zh-Hant-TW");
 598         metaInfo.get("TimeZoneNames").add("zh-HK");
 599         metaInfo.get("CalendarData").add("zh-Hans-CN");
 600         metaInfo.get("CalendarData").add("zh-Hans-SG");
 601         metaInfo.get("CalendarData").add("zh-Hant-HK");
 602         metaInfo.get("CalendarData").add("zh-Hant-MO");
 603         metaInfo.get("CalendarData").add("zh-Hant-TW");
 604         metaInfo.get("FormatData").add("zh-Hans-CN");
 605         metaInfo.get("FormatData").add("zh-Hans-SG");
 606         metaInfo.get("FormatData").add("zh-Hant-HK");
 607         metaInfo.get("FormatData").add("zh-Hant-MO");
 608         metaInfo.get("FormatData").add("zh-Hant-TW");
 609     }
 610     static final Map<String, String> aliases = new HashMap<>();
 611 
 612     /**
 613      * Translate the aliases into the real entries in the bundle map.
 614      */
 615     static void handleAliases(Map<String, Object> bundleMap) {
 616         Set bundleKeys = bundleMap.keySet();
 617         try {
 618             for (String key : aliases.keySet()) {
 619                 String targetKey = aliases.get(key);
 620                 if (bundleKeys.contains(targetKey)) {
 621                     bundleMap.putIfAbsent(key, bundleMap.get(targetKey));
 622                 }
 623             }
 624         } catch (Exception ex) {
 625             Logger.getLogger(CLDRConverter.class.getName()).log(Level.SEVERE, null, ex);
 626         }
 627     }
 628 
 629     /*


 639      * the country code.
 640      * Otherwise, it returns null. eg. when the id is "zh_Hans_SG", it return "SG".
 641      * It does NOT return UN M.49 code, e.g., '001', as those three digit numbers cannot
 642      * be translated into package names.
 643      */
 644     static String getCountryCode(String id) {
 645         String rgn = getRegionCode(id);
 646         return rgn.length() == 2 ? rgn: null;
 647     }
 648 
 649     /**
 650      * Examine if the id includes the region code. If it does, it returns
 651      * the region code.
 652      * Otherwise, it returns null. eg. when the id is "zh_Hans_SG", it return "SG".
 653      * It DOES return UN M.49 code, e.g., '001', as well as ISO 3166 two letter country codes.
 654      */
 655     static String getRegionCode(String id) {
 656         return Locale.forLanguageTag(id.replaceAll("_", "-")).getCountry();
 657     }
 658 
 659     /*
 660      * Returns the script portion of the given id.
 661      * If id is "root", "" is returned.
 662      */
 663     static String getScript(String id) {
 664         return "root".equals(id) ? "" : Locale.forLanguageTag(id.replaceAll("_", "-")).getScript();
 665     }
 666 
 667     private static class KeyComparator implements Comparator<String> {
 668         static KeyComparator INSTANCE = new KeyComparator();
 669 
 670         private KeyComparator() {
 671         }
 672 
 673         @Override
 674         public int compare(String o1, String o2) {
 675             int len1 = o1.length();
 676             int len2 = o2.length();
 677             if (!isDigit(o1.charAt(0)) && !isDigit(o2.charAt(0))) {
 678                 // Shorter string comes first unless either starts with a digit.
 679                 if (len1 < len2) {
 680                     return -1;
 681                 }
 682                 if (len1 > len2) {
 683                     return 1;
 684                 }
 685             }
 686             return o1.compareTo(o2);
 687         }
 688 
 689         private boolean isDigit(char c) {
 690             return c >= '0' && c <= '9';
 691         }
 692     }
 693 
 694     private static Map<String, Object> extractLocaleNames(Map<String, Object> map, String id) {
 695         Map<String, Object> localeNames = new TreeMap<>(KeyComparator.INSTANCE);
 696         for (String key : map.keySet()) {
 697             if (key.startsWith(LOCALE_NAME_PREFIX)) {








 698                 localeNames.put(key.substring(LOCALE_NAME_PREFIX.length()), map.get(key));

 699             }
 700         }







 701         return localeNames;
 702     }
 703 
 704     @SuppressWarnings("AssignmentToForLoopParameter")
 705     private static Map<String, Object> extractCurrencyNames(Map<String, Object> map, String id, String names)
 706             throws Exception {
 707         Map<String, Object> currencyNames = new TreeMap<>(KeyComparator.INSTANCE);
 708         for (String key : map.keySet()) {
 709             if (key.startsWith(CURRENCY_NAME_PREFIX)) {
 710                 currencyNames.put(key.substring(CURRENCY_NAME_PREFIX.length()), map.get(key));
 711             } else if (key.startsWith(CURRENCY_SYMBOL_PREFIX)) {
 712                 currencyNames.put(key.substring(CURRENCY_SYMBOL_PREFIX.length()), map.get(key));
 713             }
 714         }
 715         return currencyNames;
 716     }
 717 
 718     private static Map<String, Object> extractZoneNames(Map<String, Object> map, String id) {
 719         Map<String, Object> names = new HashMap<>();
 720 


 763             if (data instanceof String[]) {
 764                 names.put(tzid, data);
 765             } else {
 766                 String meta = handlerMetaZones.get(tzid);
 767                 if (meta != null) {
 768                     String metaKey = METAZONE_ID_PREFIX + meta;
 769                     data = map.get(metaKey);
 770                     if (data instanceof String[]) {
 771                         // Keep the metazone prefix here.
 772                         names.put(metaKey, data);
 773                         names.put(tzid, meta);
 774                     }
 775                 }
 776             }
 777         }
 778         return names;
 779     }
 780 
 781     private static Map<String, Object> extractCalendarData(Map<String, Object> map, String id) {
 782         Map<String, Object> calendarData = new LinkedHashMap<>();
 783         copyIfPresent(map, "firstDayOfWeek", calendarData);
 784         copyIfPresent(map, "minimalDaysInFirstWeek", calendarData);












 785         return calendarData;
 786     }
 787 
 788     static final String[] FORMAT_DATA_ELEMENTS = {
 789         "MonthNames",
 790         "standalone.MonthNames",
 791         "MonthAbbreviations",
 792         "standalone.MonthAbbreviations",
 793         "MonthNarrows",
 794         "standalone.MonthNarrows",
 795         "DayNames",
 796         "standalone.DayNames",
 797         "DayAbbreviations",
 798         "standalone.DayAbbreviations",
 799         "DayNarrows",
 800         "standalone.DayNarrows",
 801         "QuarterNames",
 802         "standalone.QuarterNames",
 803         "QuarterAbbreviations",
 804         "standalone.QuarterAbbreviations",


 827         "DateTimePatterns",
 828         "DateTimePatternChars"
 829     };
 830 
 831     private static Map<String, Object> extractFormatData(Map<String, Object> map, String id) {
 832         Map<String, Object> formatData = new LinkedHashMap<>();
 833         for (CalendarType calendarType : CalendarType.values()) {
 834             if (calendarType == CalendarType.GENERIC) {
 835                 continue;
 836             }
 837             String prefix = calendarType.keyElementName();
 838             for (String element : FORMAT_DATA_ELEMENTS) {
 839                 String key = prefix + element;
 840                 copyIfPresent(map, "java.time." + key, formatData);
 841                 copyIfPresent(map, key, formatData);
 842             }
 843         }
 844 
 845         for (String key : map.keySet()) {
 846         // Copy available calendar names
 847             if (key.startsWith(CLDRConverter.CALENDAR_NAME_PREFIX)) {
 848                 String type = key.substring(CLDRConverter.CALENDAR_NAME_PREFIX.length());
 849                 for (CalendarType calendarType : CalendarType.values()) {
 850                     if (calendarType == CalendarType.GENERIC) {
 851                         continue;
 852                     }
 853                     if (type.equals(calendarType.lname())) {
 854                         Object value = map.get(key);
 855                         formatData.put(key, value);
 856                         String ukey = CLDRConverter.CALENDAR_NAME_PREFIX + calendarType.uname();
 857                         if (!key.equals(ukey)) {


 858                             formatData.put(ukey, value);
 859                         }
 860                     }
 861                 }
 862             }
 863         }
 864 
 865         copyIfPresent(map, "DefaultNumberingSystem", formatData);
 866 
 867         @SuppressWarnings("unchecked")
 868         List<String> numberingScripts = (List<String>) map.remove("numberingScripts");
 869         if (numberingScripts != null) {
 870             for (String script : numberingScripts) {
 871                 copyIfPresent(map, script + "." + "NumberElements", formatData);
 872             }
 873         } else {
 874             copyIfPresent(map, "NumberElements", formatData);
 875         }
 876         copyIfPresent(map, "NumberPatterns", formatData);












 877         return formatData;
 878     }
 879 
 880     private static void copyIfPresent(Map<String, Object> src, String key, Map<String, Object> dest) {
 881         Object value = src.get(key);
 882         if (value != null) {
 883             dest.put(key, value);
 884         }
 885     }
 886 
 887     // --- code below here is adapted from java.util.Properties ---
 888     private static final String specialSaveCharsJava = "\"";
 889     private static final String specialSaveCharsProperties = "=: \t\r\n\f#!";
 890 
 891     /*
 892      * Converts unicodes to encoded \uxxxx
 893      * and writes out any of the characters in specialSaveChars
 894      * with a preceding slash
 895      */
 896     static String saveConvert(String theString, boolean useJava) {




  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 static build.tools.cldrconverter.Bundle.jreTimeZoneNames;
  29 import build.tools.cldrconverter.BundleGenerator.BundleType;
  30 import java.io.File;
  31 import java.nio.file.DirectoryStream;
  32 import java.nio.file.FileSystems;
  33 import java.nio.file.Files;
  34 import java.nio.file.Path;
  35 import java.util.*;
  36 import java.util.ResourceBundle.Control;
  37 import java.util.logging.Level;
  38 import java.util.logging.Logger;
  39 import java.util.stream.Collectors;
  40 import java.util.stream.IntStream;
  41 import javax.xml.parsers.SAXParser;
  42 import javax.xml.parsers.SAXParserFactory;
  43 import org.xml.sax.SAXNotRecognizedException;
  44 import org.xml.sax.SAXNotSupportedException;
  45 
  46 
  47 /**
  48  * Converts locale data from "Locale Data Markup Language" format to
  49  * JRE resource bundle format. LDML is the format used by the Common
  50  * Locale Data Repository maintained by the Unicode Consortium.
  51  */
  52 public class CLDRConverter {
  53 
  54     static final String LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldml.dtd";
  55     static final String SPPL_LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldmlSupplemental.dtd";
  56     static final String BCP47_LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldmlBCP47.dtd";
  57   
  58 
  59     private static String CLDR_BASE = "../CLDR/21.0.1/";
  60     static String LOCAL_LDML_DTD;
  61     static String LOCAL_SPPL_LDML_DTD;
  62     static String LOCAL_BCP47_LDML_DTD;  
  63     private static String SOURCE_FILE_DIR;
  64     private static String SPPL_SOURCE_FILE;
  65     private static String NUMBERING_SOURCE_FILE;
  66     private static String METAZONES_SOURCE_FILE;
  67     private static String LIKELYSUBTAGS_SOURCE_FILE;
  68     private static String TIMEZONE_SOURCE_FILE;
  69     static String DESTINATION_DIR = "build/gensrc";
  70 
  71     static final String LOCALE_NAME_PREFIX = "locale.displayname.";
  72     static final String LOCALE_SEPARATOR = LOCALE_NAME_PREFIX + "separator";
  73     static final String LOCALE_KEYTYPE = LOCALE_NAME_PREFIX + "keytype";    
  74     static final String LOCALE_KEY_PREFIX = LOCALE_NAME_PREFIX + "key.";
  75     static final String LOCALE_TYPE_PREFIX = LOCALE_NAME_PREFIX + "type.";
  76     static final String LOCALE_TYPE_PREFIX_CA = LOCALE_TYPE_PREFIX + "ca.";
  77     static final String CURRENCY_SYMBOL_PREFIX = "currency.symbol.";
  78     static final String CURRENCY_NAME_PREFIX = "currency.displayname.";
  79     static final String CALENDAR_NAME_PREFIX = "calendarname.";
  80     static final String CALENDAR_FIRSTDAY_PREFIX = "firstDay.";
  81     static final String CALENDAR_MINDAYS_PREFIX = "minDays.";
  82     static final String TIMEZONE_ID_PREFIX = "timezone.id.";
  83     static final String ZONE_NAME_PREFIX = "timezone.displayname.";
  84     static final String METAZONE_ID_PREFIX = "metazone.id.";
  85     static final String PARENT_LOCALE_PREFIX = "parentLocale.";
  86 
  87     private static SupplementDataParseHandler handlerSuppl;
  88     private static LikelySubtagsParseHandler handlerLikelySubtags;
  89     static NumberingSystemsParseHandler handlerNumbering;
  90     static MetaZonesParseHandler handlerMetaZones;
  91     static TimeZoneParseHandler handlerTimeZone;
  92     private static BundleGenerator bundleGenerator;
  93 
  94     // java.base module related
  95     static boolean isBaseModule = false;
  96     static final Set<Locale> BASE_LOCALES = new HashSet<>();
  97 
  98     // "parentLocales" map
  99     private static final Map<String, SortedSet<String>> parentLocalesMap = new HashMap<>();
 100     private static final ResourceBundle.Control defCon =
 101         ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT);
 102 
 103     static enum DraftType {
 104         UNCONFIRMED,
 105         PROVISIONAL,
 106         CONTRIBUTED,
 107         APPROVED;
 108 
 109         private static final Map<String, DraftType> map = new HashMap<>();
 110         static {
 111             for (DraftType dt : values()) {


 197 
 198                     case "-help":
 199                         usage();
 200                         System.exit(0);
 201                         break;
 202 
 203                     default:
 204                         throw new RuntimeException();
 205                     }
 206                 }
 207             } catch (RuntimeException e) {
 208                 severe("unknown or imcomplete arg(s): " + currentArg);
 209                 usage();
 210                 System.exit(1);
 211             }
 212         }
 213 
 214         // Set up path names
 215         LOCAL_LDML_DTD = CLDR_BASE + "/dtd/ldml.dtd";
 216         LOCAL_SPPL_LDML_DTD = CLDR_BASE + "/dtd/ldmlSupplemental.dtd";
 217         LOCAL_BCP47_LDML_DTD = CLDR_BASE + "/dtd/ldmlBCP47.dtd";
 218         SOURCE_FILE_DIR = CLDR_BASE + "/main";
 219         SPPL_SOURCE_FILE = CLDR_BASE + "/supplemental/supplementalData.xml";
 220         LIKELYSUBTAGS_SOURCE_FILE = CLDR_BASE + "/supplemental/likelySubtags.xml";
 221         NUMBERING_SOURCE_FILE = CLDR_BASE + "/supplemental/numberingSystems.xml";
 222         METAZONES_SOURCE_FILE = CLDR_BASE + "/supplemental/metaZones.xml";
 223         TIMEZONE_SOURCE_FILE = CLDR_BASE + "/bcp47/timezone.xml";
 224 
 225         if (BASE_LOCALES.isEmpty()) {
 226             setupBaseLocales("en-US");
 227         }
 228 
 229         bundleGenerator = new ResourceBundleGenerator();
 230 
 231         // Parse data independent of locales
 232         parseSupplemental();
 233         parseBCP47();
 234 
 235         List<Bundle> bundles = readBundleList();
 236         convertBundles(bundles);

 237     }
 238 
 239     private static void usage() {
 240         errout("Usage: java CLDRConverter [options]%n"
 241                 + "\t-help          output this usage message and exit%n"
 242                 + "\t-verbose       output information%n"
 243                 + "\t-draft [contributed | approved | provisional | unconfirmed]%n"
 244                 + "\t\t       draft level for using data (default: contributed)%n"
 245                 + "\t-base dir      base directory for CLDR input files%n"
 246                 + "\t-basemodule    generates bundles that go into java.base module%n"
 247                 + "\t-baselocales loc(,loc)*      locales that go into the base module%n"
 248                 + "\t-o dir         output directory (default: ./build/gensrc)%n"
 249                 + "\t-o dir         output directory (defaut: ./build/gensrc)%n"
 250                 + "\t-utf8          use UTF-8 rather than \\uxxxx (for debug)%n");
 251     }
 252 
 253     static void info(String fmt, Object... args) {
 254         if (verbose) {
 255             System.out.printf(fmt, args);
 256         }


 312                     Locale cldrLoc = Locale.forLanguageTag(toLanguageTag(id));
 313                     StringBuilder sb = getCandLocales(cldrLoc);
 314                     if (sb.indexOf("root") == -1) {
 315                         sb.append("root");
 316                     }
 317                     Bundle b = new Bundle(id, sb.toString(), null, null);
 318                     // Insert the bundle for root at the top so that it will get
 319                     // processed first.
 320                     if ("root".equals(id)) {
 321                         retList.add(0, b);
 322                     } else {
 323                         retList.add(b);
 324                     }
 325                 }
 326             }
 327         }
 328         return retList;
 329     }
 330 
 331     private static final Map<String, Map<String, Object>> cldrBundles = new HashMap<>();


 332 
 333     private static Map<String, SortedSet<String>> metaInfo = new HashMap<>();
 334 
 335     static {
 336         // For generating information on supported locales.





 337         metaInfo.put("AvailableLocales", new TreeSet<>());
 338     }
 339 



 340     static Map<String, Object> getCLDRBundle(String id) throws Exception {
 341         Map<String, Object> bundle = cldrBundles.get(id);
 342         if (bundle != null) {
 343             return bundle;
 344         }





 345         File file = new File(SOURCE_FILE_DIR + File.separator + id + ".xml");
 346         if (!file.exists()) {
 347             // Skip if the file doesn't exist.
 348             return Collections.emptyMap();
 349         }
 350         
 351         info("..... main directory .....");
 352         LDMLParseHandler handler = new LDMLParseHandler(id);
 353         parseLDMLFile(file, handler);
 354 
 355         bundle = handler.getData();
 356         cldrBundles.put(id, bundle);
 357 
 358         if (id.equals("root")) {
 359             // Calendar data (firstDayOfWeek & minDaysInFirstWeek)
 360             bundle = handlerSuppl.getData("root");
 361             if (bundle != null) {
 362                 //merge two maps into one map
 363                 Map<String, Object> temp = cldrBundles.remove(id);
 364                 bundle.putAll(temp);
 365                 cldrBundles.put(id, bundle);
 366             }
 367         }
 368         return bundle;
 369     }
 370 
 371     // Parsers for data in "supplemental" directory
 372     //
 373     private static void parseSupplemental() throws Exception {
 374         // Parse SupplementalData file and store the information in the HashMap
 375         // Calendar information such as firstDay and minDay are stored in
 376         // supplementalData.xml as of CLDR1.4. Individual territory is listed
 377         // with its ISO 3166 country code while default is listed using UNM49
 378         // region and composition numerical code (001 for World.)
 379         //
 380         // SupplementalData file also provides the "parent" locales which
 381         // are othrwise not to be fallen back. Process them here as well.
 382         //





 383         handlerSuppl = new SupplementDataParseHandler();
 384         parseLDMLFile(new File(SPPL_SOURCE_FILE), handlerSuppl);

 385         Map<String, Object> parentData = handlerSuppl.getData("root");
 386         parentData.keySet().stream()
 387                 .filter(key -> key.startsWith(PARENT_LOCALE_PREFIX))
 388                 .forEach(key -> {
 389                 parentLocalesMap.put(key, new TreeSet(
 390                     Arrays.asList(((String)parentData.get(key)).split(" "))));
 391             });
 392 
 393         // Parse numberingSystems to get digit zero character information.




 394         handlerNumbering = new NumberingSystemsParseHandler();
 395         parseLDMLFile(new File(NUMBERING_SOURCE_FILE), handlerNumbering);

 396 
 397         // Parse metaZones to create mappings between Olson tzids and CLDR meta zone names





 398         handlerMetaZones = new MetaZonesParseHandler();
 399         parseLDMLFile(new File(METAZONES_SOURCE_FILE), handlerMetaZones);

 400 
 401         // Parse likelySubtags





 402         handlerLikelySubtags = new LikelySubtagsParseHandler();
 403         parseLDMLFile(new File(LIKELYSUBTAGS_SOURCE_FILE), handlerLikelySubtags);

 404     }
 405     
 406     // Parsers for data in "bcp47" directory
 407     //
 408     private static void parseBCP47() throws Exception {
 409         // Parse timezone
 410         handlerTimeZone = new TimeZoneParseHandler();
 411         parseLDMLFile(new File(TIMEZONE_SOURCE_FILE), handlerTimeZone);






































 412     }
 413     
 414     private static void parseLDMLFile(File srcfile, AbstractLDMLHandler handler) throws Exception {
 415         info("..... Parsing " + srcfile.getName() + " .....");
 416         SAXParserFactory pf = SAXParserFactory.newInstance();
 417         pf.setValidating(true);
 418         SAXParser parser = pf.newSAXParser();
 419         enableFileAccess(parser);
 420         parser.parse(srcfile, handler);       
 421     }
 422 
 423     private static StringBuilder getCandLocales(Locale cldrLoc) {
 424         List<Locale> candList = getCandidateLocales(cldrLoc);
 425         StringBuilder sb = new StringBuilder();
 426         for (Locale loc : candList) {
 427             if (!loc.equals(Locale.ROOT)) {
 428                 sb.append(toLocaleName(loc.toLanguageTag()));
 429                 sb.append(",");
 430             }
 431         }
 432         return sb;
 433     }
 434 
 435     private static List<Locale> getCandidateLocales(Locale cldrLoc) {
 436         List<Locale> candList = new ArrayList<>();
 437         candList = applyParentLocales("", defCon.getCandidateLocales("",  cldrLoc));
 438         return candList;
 439     }
 440 










 441     private static void convertBundles(List<Bundle> bundles) throws Exception {
 442         // parent locales map. The mappings are put in base metaInfo file
 443         // for now.
 444         if (isBaseModule) {
 445             metaInfo.putAll(parentLocalesMap);
 446         }
 447 
 448         for (Bundle bundle : bundles) {
 449             // Get the target map, which contains all the data that should be
 450             // visible for the bundle's locale
 451 
 452             Map<String, Object> targetMap = bundle.getTargetMap();
 453 


 454             EnumSet<Bundle.Type> bundleTypes = bundle.getBundleTypes();
 455 
 456             if (bundle.isRoot()) {
 457                 // Add DateTimePatternChars because CLDR no longer supports localized patterns.
 458                 targetMap.put("DateTimePatternChars", "GyMdkHmsSEDFwWahKzZ");
 459             }
 460 
 461             // Now the map contains just the entries that need to be in the resources bundles.
 462             // Go ahead and generate them.
 463             if (bundleTypes.contains(Bundle.Type.LOCALENAMES)) {
 464                 Map<String, Object> localeNamesMap = extractLocaleNames(targetMap, bundle.getID());
 465                 if (!localeNamesMap.isEmpty() || bundle.isRoot()) {


 466                     bundleGenerator.generateBundle("util", "LocaleNames", bundle.getJavaID(), true, localeNamesMap, BundleType.OPEN);
 467                 }
 468             }
 469             if (bundleTypes.contains(Bundle.Type.CURRENCYNAMES)) {
 470                 Map<String, Object> currencyNamesMap = extractCurrencyNames(targetMap, bundle.getID(), bundle.getCurrencies());
 471                 if (!currencyNamesMap.isEmpty() || bundle.isRoot()) {


 472                     bundleGenerator.generateBundle("util", "CurrencyNames", bundle.getJavaID(), true, currencyNamesMap, BundleType.OPEN);
 473                 }
 474             }
 475             if (bundleTypes.contains(Bundle.Type.TIMEZONENAMES)) {
 476                 Map<String, Object> zoneNamesMap = extractZoneNames(targetMap, bundle.getID());
 477                 if (!zoneNamesMap.isEmpty() || bundle.isRoot()) {


 478                     bundleGenerator.generateBundle("util", "TimeZoneNames", bundle.getJavaID(), true, zoneNamesMap, BundleType.TIMEZONE);
 479                 }
 480             }
 481             if (bundleTypes.contains(Bundle.Type.CALENDARDATA)) {
 482                 Map<String, Object> calendarDataMap = extractCalendarData(targetMap, bundle.getID());
 483                 if (!calendarDataMap.isEmpty() || bundle.isRoot()) {


 484                     bundleGenerator.generateBundle("util", "CalendarData", bundle.getJavaID(), true, calendarDataMap, BundleType.PLAIN);
 485                 }
 486             }
 487             if (bundleTypes.contains(Bundle.Type.FORMATDATA)) {
 488                 Map<String, Object> formatDataMap = extractFormatData(targetMap, bundle.getID());
 489                 if (!formatDataMap.isEmpty() || bundle.isRoot()) {


 490                     bundleGenerator.generateBundle("text", "FormatData", bundle.getJavaID(), true, formatDataMap, BundleType.PLAIN);
 491                 }
 492             }
 493 
 494             // For AvailableLocales
 495             metaInfo.get("AvailableLocales").add(toLanguageTag(bundle.getID()));
 496             addLikelySubtags(metaInfo, "AvailableLocales", bundle.getID());
 497         }

 498         bundleGenerator.generateMetaInfo(metaInfo);
 499     }
 500 

































 501     static final Map<String, String> aliases = new HashMap<>();
 502 
 503     /**
 504      * Translate the aliases into the real entries in the bundle map.
 505      */
 506     static void handleAliases(Map<String, Object> bundleMap) {
 507         Set bundleKeys = bundleMap.keySet();
 508         try {
 509             for (String key : aliases.keySet()) {
 510                 String targetKey = aliases.get(key);
 511                 if (bundleKeys.contains(targetKey)) {
 512                     bundleMap.putIfAbsent(key, bundleMap.get(targetKey));
 513                 }
 514             }
 515         } catch (Exception ex) {
 516             Logger.getLogger(CLDRConverter.class.getName()).log(Level.SEVERE, null, ex);
 517         }
 518     }
 519 
 520     /*


 530      * the country code.
 531      * Otherwise, it returns null. eg. when the id is "zh_Hans_SG", it return "SG".
 532      * It does NOT return UN M.49 code, e.g., '001', as those three digit numbers cannot
 533      * be translated into package names.
 534      */
 535     static String getCountryCode(String id) {
 536         String rgn = getRegionCode(id);
 537         return rgn.length() == 2 ? rgn: null;
 538     }
 539 
 540     /**
 541      * Examine if the id includes the region code. If it does, it returns
 542      * the region code.
 543      * Otherwise, it returns null. eg. when the id is "zh_Hans_SG", it return "SG".
 544      * It DOES return UN M.49 code, e.g., '001', as well as ISO 3166 two letter country codes.
 545      */
 546     static String getRegionCode(String id) {
 547         return Locale.forLanguageTag(id.replaceAll("_", "-")).getCountry();
 548     }
 549 








 550     private static class KeyComparator implements Comparator<String> {
 551         static KeyComparator INSTANCE = new KeyComparator();
 552 
 553         private KeyComparator() {
 554         }
 555 
 556         @Override
 557         public int compare(String o1, String o2) {
 558             int len1 = o1.length();
 559             int len2 = o2.length();
 560             if (!isDigit(o1.charAt(0)) && !isDigit(o2.charAt(0))) {
 561                 // Shorter string comes first unless either starts with a digit.
 562                 if (len1 < len2) {
 563                     return -1;
 564                 }
 565                 if (len1 > len2) {
 566                     return 1;
 567                 }
 568             }
 569             return o1.compareTo(o2);
 570         }
 571 
 572         private boolean isDigit(char c) {
 573             return c >= '0' && c <= '9';
 574         }
 575     }
 576 
 577     private static Map<String, Object> extractLocaleNames(Map<String, Object> map, String id) {
 578         Map<String, Object> localeNames = new TreeMap<>(KeyComparator.INSTANCE);
 579         for (String key : map.keySet()) {
 580             if (key.startsWith(LOCALE_NAME_PREFIX)) {
 581                 switch (key) {
 582                     case LOCALE_SEPARATOR:
 583                         localeNames.put("ListCompositionPattern", map.get(key));
 584                         break;
 585                     case LOCALE_KEYTYPE:
 586                         localeNames.put("ListKeyTypePattern", map.get(key));
 587                         break;
 588                     default:
 589                         localeNames.put(key.substring(LOCALE_NAME_PREFIX.length()), map.get(key));
 590                         break;
 591                 }
 592             }
 593         }
 594 
 595         if (id.equals("root")) {
 596             // Add display name pattern, which is not in CLDR
 597             localeNames.put("DisplayNamePattern", "{0,choice,0#|1#{1}|2#{1} ({2})}");
 598         }
 599 
 600         return localeNames;
 601     }
 602 
 603     @SuppressWarnings("AssignmentToForLoopParameter")
 604     private static Map<String, Object> extractCurrencyNames(Map<String, Object> map, String id, String names)
 605             throws Exception {
 606         Map<String, Object> currencyNames = new TreeMap<>(KeyComparator.INSTANCE);
 607         for (String key : map.keySet()) {
 608             if (key.startsWith(CURRENCY_NAME_PREFIX)) {
 609                 currencyNames.put(key.substring(CURRENCY_NAME_PREFIX.length()), map.get(key));
 610             } else if (key.startsWith(CURRENCY_SYMBOL_PREFIX)) {
 611                 currencyNames.put(key.substring(CURRENCY_SYMBOL_PREFIX.length()), map.get(key));
 612             }
 613         }
 614         return currencyNames;
 615     }
 616 
 617     private static Map<String, Object> extractZoneNames(Map<String, Object> map, String id) {
 618         Map<String, Object> names = new HashMap<>();
 619 


 662             if (data instanceof String[]) {
 663                 names.put(tzid, data);
 664             } else {
 665                 String meta = handlerMetaZones.get(tzid);
 666                 if (meta != null) {
 667                     String metaKey = METAZONE_ID_PREFIX + meta;
 668                     data = map.get(metaKey);
 669                     if (data instanceof String[]) {
 670                         // Keep the metazone prefix here.
 671                         names.put(metaKey, data);
 672                         names.put(tzid, meta);
 673                     }
 674                 }
 675             }
 676         }
 677         return names;
 678     }
 679 
 680     private static Map<String, Object> extractCalendarData(Map<String, Object> map, String id) {
 681         Map<String, Object> calendarData = new LinkedHashMap<>();
 682         if (id.equals("root")) {
 683             calendarData.put("firstDayOfWeek",
 684                 IntStream.range(1, 8)
 685                     .mapToObj(String::valueOf)
 686                     .filter(d -> map.keySet().contains(CALENDAR_FIRSTDAY_PREFIX + d))
 687                     .map(d -> d + ": " + map.get(CALENDAR_FIRSTDAY_PREFIX + d))
 688                     .collect(Collectors.joining(";")));
 689             calendarData.put("minimalDaysInFirstWeek",
 690                 IntStream.range(0, 7)
 691                     .mapToObj(String::valueOf)
 692                     .filter(d -> map.keySet().contains(CALENDAR_MINDAYS_PREFIX + d))
 693                     .map(d -> d + ": " + map.get(CALENDAR_MINDAYS_PREFIX + d))
 694                     .collect(Collectors.joining(";")));
 695         }
 696         return calendarData;
 697     }
 698 
 699     static final String[] FORMAT_DATA_ELEMENTS = {
 700         "MonthNames",
 701         "standalone.MonthNames",
 702         "MonthAbbreviations",
 703         "standalone.MonthAbbreviations",
 704         "MonthNarrows",
 705         "standalone.MonthNarrows",
 706         "DayNames",
 707         "standalone.DayNames",
 708         "DayAbbreviations",
 709         "standalone.DayAbbreviations",
 710         "DayNarrows",
 711         "standalone.DayNarrows",
 712         "QuarterNames",
 713         "standalone.QuarterNames",
 714         "QuarterAbbreviations",
 715         "standalone.QuarterAbbreviations",


 738         "DateTimePatterns",
 739         "DateTimePatternChars"
 740     };
 741 
 742     private static Map<String, Object> extractFormatData(Map<String, Object> map, String id) {
 743         Map<String, Object> formatData = new LinkedHashMap<>();
 744         for (CalendarType calendarType : CalendarType.values()) {
 745             if (calendarType == CalendarType.GENERIC) {
 746                 continue;
 747             }
 748             String prefix = calendarType.keyElementName();
 749             for (String element : FORMAT_DATA_ELEMENTS) {
 750                 String key = prefix + element;
 751                 copyIfPresent(map, "java.time." + key, formatData);
 752                 copyIfPresent(map, key, formatData);
 753             }
 754         }
 755 
 756         for (String key : map.keySet()) {
 757         // Copy available calendar names
 758             if (key.startsWith(CLDRConverter.LOCALE_TYPE_PREFIX_CA)) {
 759                 String type = key.substring(CLDRConverter.LOCALE_TYPE_PREFIX_CA.length());
 760                 for (CalendarType calendarType : CalendarType.values()) {
 761                     if (calendarType == CalendarType.GENERIC) {
 762                         continue;
 763                     }
 764                     if (type.equals(calendarType.lname())) {
 765                         Object value = map.get(key);
 766                         String dataKey = key.replace(LOCALE_TYPE_PREFIX_CA,
 767                                 CALENDAR_NAME_PREFIX);
 768                         formatData.put(dataKey, value);
 769                         String ukey = CALENDAR_NAME_PREFIX + calendarType.uname();
 770                         if (!dataKey.equals(ukey)) {
 771                             formatData.put(ukey, value);
 772                         }
 773                     }
 774                 }
 775             }
 776         }
 777 
 778         copyIfPresent(map, "DefaultNumberingSystem", formatData);
 779 
 780         @SuppressWarnings("unchecked")
 781         List<String> numberingScripts = (List<String>) map.remove("numberingScripts");
 782         if (numberingScripts != null) {
 783             for (String script : numberingScripts) {
 784                 copyIfPresent(map, script + "." + "NumberElements", formatData);
 785             }
 786         } else {
 787             copyIfPresent(map, "NumberElements", formatData);
 788         }
 789         copyIfPresent(map, "NumberPatterns", formatData);
 790 
 791         // put extra number elements for available scripts into formatData, if it is "root"
 792         if (id.equals("root")) {
 793             handlerNumbering.keySet().stream()
 794                 .filter(k -> !numberingScripts.contains(k))
 795                 .forEach(k -> {
 796                     String[] ne = (String[])map.get("latn.NumberElements");
 797                     String[] neNew = Arrays.copyOf(ne, ne.length);
 798                     neNew[4] = handlerNumbering.get(k).substring(0, 1);
 799                     formatData.put(k + ".NumberElements", neNew);
 800                 });
 801         }
 802         return formatData;
 803     }
 804 
 805     private static void copyIfPresent(Map<String, Object> src, String key, Map<String, Object> dest) {
 806         Object value = src.get(key);
 807         if (value != null) {
 808             dest.put(key, value);
 809         }
 810     }
 811 
 812     // --- code below here is adapted from java.util.Properties ---
 813     private static final String specialSaveCharsJava = "\"";
 814     private static final String specialSaveCharsProperties = "=: \t\r\n\f#!";
 815 
 816     /*
 817      * Converts unicodes to encoded \uxxxx
 818      * and writes out any of the characters in specialSaveChars
 819      * with a preceding slash
 820      */
 821     static String saveConvert(String theString, boolean useJava) {


< prev index next >