8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package build.tools.cldrconverter;
27
28 import java.io.File;
29 import java.nio.file.DirectoryStream;
30 import java.nio.file.FileSystems;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.util.*;
34 import javax.xml.parsers.SAXParser;
35 import javax.xml.parsers.SAXParserFactory;
36
37
38 /**
39 * Converts locale data from "Locale Data Markup Language" format to
40 * JRE resource bundle format. LDML is the format used by the Common
41 * Locale Data Repository maintained by the Unicode Consortium.
42 */
43 public class CLDRConverter {
44
45 static final String LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldml.dtd";
46 static final String SPPL_LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldmlSupplemental.dtd";
47
48 private static String CLDR_BASE = "../CLDR/21.0.1/";
49 static String LOCAL_LDML_DTD;
50 static String LOCAL_SPPL_LDML_DTD;
51 private static String SOURCE_FILE_DIR;
52 private static String SPPL_SOURCE_FILE;
53 private static String NUMBERING_SOURCE_FILE;
54 private static String METAZONES_SOURCE_FILE;
55 static String DESTINATION_DIR = "build/gensrc";
56
57 static final String LOCALE_NAME_PREFIX = "locale.displayname.";
58 static final String CURRENCY_SYMBOL_PREFIX = "currency.symbol.";
59 static final String CURRENCY_NAME_PREFIX = "currency.displayname.";
60 static final String TIMEZONE_ID_PREFIX = "timezone.id.";
61 static final String TIMEZONE_NAME_PREFIX = "timezone.displayname.";
62 static final String METAZONE_ID_PREFIX = "metazone.id.";
63 static final String METAZONE_NAME_PREFIX = "metazone.displayname.";
64
65 private static SupplementDataParseHandler handlerSuppl;
66 static NumberingSystemsParseHandler handlerNumbering;
67 static MetaZonesParseHandler handlerMetaZones;
68 private static BundleGenerator bundleGenerator;
69
70 static int draftType;
71 private static final String DRAFT_UNCONFIRMED = "unconfirmed";
72 private static final String DRAFT_PROVISIONAL = "provisional";
73 private static final String DRAFT_CONTRIBUTED = "contributed";
74 private static final String DRAFT_APPROVED = "approved";
75 private static final String DRAFT_TRUE = "true";
76 private static final String DRAFT_FALSE = "false";
77 private static final String DRAFT_DEFAULT = DRAFT_APPROVED;
78 static final Map<String, Integer> DRAFT_MAP = new HashMap<>();
79
80 static {
81 DRAFT_MAP.put(DRAFT_UNCONFIRMED, 0);
82 DRAFT_MAP.put(DRAFT_PROVISIONAL, 1);
83 DRAFT_MAP.put(DRAFT_CONTRIBUTED, 2);
219 ResourceBundle.Control defCon = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT);
220 List<Bundle> retList = new ArrayList<>();
221 Path path = FileSystems.getDefault().getPath(SOURCE_FILE_DIR);
222 try (DirectoryStream<Path> dirStr = Files.newDirectoryStream(path)) {
223 for (Path entry : dirStr) {
224 String fileName = entry.getFileName().toString();
225 if (fileName.endsWith(".xml")) {
226 String id = fileName.substring(0, fileName.indexOf('.'));
227 Locale cldrLoc = Locale.forLanguageTag(toLanguageTag(id));
228 List<Locale> candList = defCon.getCandidateLocales("", cldrLoc);
229 StringBuilder sb = new StringBuilder();
230 for (Locale loc : candList) {
231 if (!loc.equals(Locale.ROOT)) {
232 sb.append(toLocaleName(loc.toLanguageTag()));
233 sb.append(",");
234 }
235 }
236 if (sb.indexOf("root") == -1) {
237 sb.append("root");
238 }
239 retList.add(new Bundle(id, sb.toString(), null, null));
240 }
241 }
242 }
243 return retList;
244 }
245
246 private static Map<String, Map<String, Object>> cldrBundles = new HashMap<>();
247
248 static Map<String, Object> getCLDRBundle(String id) throws Exception {
249 Map<String, Object> bundle = cldrBundles.get(id);
250 if (bundle != null) {
251 return bundle;
252 }
253 SAXParserFactory factory = SAXParserFactory.newInstance();
254 factory.setValidating(true);
255 SAXParser parser = factory.newSAXParser();
256 LDMLParseHandler handler = new LDMLParseHandler(id);
257 File file = new File(SOURCE_FILE_DIR + File.separator + id + ".xml");
258 if (!file.exists()) {
259 // Skip if the file doesn't exist.
295 // Parse numberingSystems to get digit zero character information.
296 SAXParserFactory numberingParser = SAXParserFactory.newInstance();
297 numberingParser.setValidating(true);
298 SAXParser parserNumbering = numberingParser.newSAXParser();
299 handlerNumbering = new NumberingSystemsParseHandler();
300 File fileNumbering = new File(NUMBERING_SOURCE_FILE);
301 parserNumbering.parse(fileNumbering, handlerNumbering);
302
303 // Parse metaZones to create mappings between Olson tzids and CLDR meta zone names
304 SAXParserFactory metazonesParser = SAXParserFactory.newInstance();
305 metazonesParser.setValidating(true);
306 SAXParser parserMetaZones = metazonesParser.newSAXParser();
307 handlerMetaZones = new MetaZonesParseHandler();
308 File fileMetaZones = new File(METAZONES_SOURCE_FILE);
309 parserNumbering.parse(fileMetaZones, handlerMetaZones);
310
311 // For generating information on supported locales.
312 Map<String, SortedSet<String>> metaInfo = new HashMap<>();
313 metaInfo.put("LocaleNames", new TreeSet<String>());
314 metaInfo.put("CurrencyNames", new TreeSet<String>());
315 metaInfo.put("CalendarData", new TreeSet<String>());
316 metaInfo.put("FormatData", new TreeSet<String>());
317
318 for (Bundle bundle : bundles) {
319 // Get the target map, which contains all the data that should be
320 // visible for the bundle's locale
321
322 Map<String, Object> targetMap = bundle.getTargetMap();
323
324 EnumSet<Bundle.Type> bundleTypes = bundle.getBundleTypes();
325
326 // Fill in any missing resources in the base bundle from en and en-US data.
327 // This is because CLDR root.xml is supposed to be language neutral and doesn't
328 // provide some resource data. Currently, the runtime assumes that there are all
329 // resources though the parent resource bundle chain.
330 if (bundle.isRoot()) {
331 Map<String, Object> enData = new HashMap<>();
332 // Create a superset of en-US and en bundles data in order to
333 // fill in any missing resources in the base bundle.
334 enData.putAll(Bundle.getBundle("en").getTargetMap());
335 enData.putAll(Bundle.getBundle("en_US").getTargetMap());
336 for (String key : enData.keySet()) {
337 if (!targetMap.containsKey(key)) {
338 targetMap.put(key, enData.get(key));
339 }
340 }
341 // Add DateTimePatternChars because CLDR no longer supports localized patterns.
342 targetMap.put("DateTimePatternChars", "GyMdkHmsSEDFwWahKzZ");
343 }
344
345 // Now the map contains just the entries that need to be in the resources bundles.
346 // Go ahead and generate them.
347 if (bundleTypes.contains(Bundle.Type.LOCALENAMES)) {
348 Map<String, Object> localeNamesMap = extractLocaleNames(targetMap, bundle.getID());
349 if (!localeNamesMap.isEmpty() || bundle.isRoot()) {
350 metaInfo.get("LocaleNames").add(toLanguageTag(bundle.getID()));
351 bundleGenerator.generateBundle("util", "LocaleNames", bundle.getID(), true, localeNamesMap, true);
352 }
353 }
354 if (bundleTypes.contains(Bundle.Type.CURRENCYNAMES)) {
355 Map<String, Object> currencyNamesMap = extractCurrencyNames(targetMap, bundle.getID(), bundle.getCurrencies());
356 if (!currencyNamesMap.isEmpty() || bundle.isRoot()) {
357 metaInfo.get("CurrencyNames").add(toLanguageTag(bundle.getID()));
358 bundleGenerator.generateBundle("util", "CurrencyNames", bundle.getID(), true, currencyNamesMap, true);
359 }
360 }
361 if (bundleTypes.contains(Bundle.Type.TIMEZONENAMES)) {
362 Map<String, Object> zoneNamesMap = extractZoneNames(targetMap, bundle.getID());
363 }
364 if (bundleTypes.contains(Bundle.Type.CALENDARDATA)) {
365 Map<String, Object> calendarDataMap = extractCalendarData(targetMap, bundle.getID());
366 if (!calendarDataMap.isEmpty() || bundle.isRoot()) {
367 metaInfo.get("CalendarData").add(toLanguageTag(bundle.getID()));
368 bundleGenerator.generateBundle("util", "CalendarData", bundle.getID(), true, calendarDataMap, false);
369 }
370 }
371 if (bundleTypes.contains(Bundle.Type.FORMATDATA)) {
372 Map<String, Object> formatDataMap = extractFormatData(targetMap, bundle.getID());
373 // LocaleData.getAvailableLocales depends on having FormatData bundles around
374 if (!formatDataMap.isEmpty() || bundle.isRoot()) {
375 metaInfo.get("FormatData").add(toLanguageTag(bundle.getID()));
376 bundleGenerator.generateBundle("text", "FormatData", bundle.getID(), true, formatDataMap, false);
377 }
378 }
379 // For testing
380 SortedSet<String> allLocales = new TreeSet<>();
381 allLocales.addAll(metaInfo.get("CurrencyNames"));
382 allLocales.addAll(metaInfo.get("LocaleNames"));
383 allLocales.addAll(metaInfo.get("CalendarData"));
384 allLocales.addAll(metaInfo.get("FormatData"));
385 metaInfo.put("All", allLocales);
386 }
387
388 bundleGenerator.generateMetaInfo(metaInfo);
389 }
390
391 /*
392 * Returns the language portion of the given id.
393 * If id is "root", "" is returned.
394 */
395 static String getLanguageCode(String id) {
396 int index = id.indexOf('_');
397 String lang = null;
398 if (index != -1) {
414 if (id.indexOf('@') != -1) {
415 id = id.substring(0, id.indexOf('@'));
416 }
417 String[] tokens = id.split("_");
418 for (int index = 1; index < tokens.length; ++index) {
419 if (tokens[index].length() == 2
420 && Character.isLetter(tokens[index].charAt(0))
421 && Character.isLetter(tokens[index].charAt(1))) {
422 return tokens[index];
423 }
424 }
425 return null;
426 }
427
428 private static class KeyComparator implements Comparator<String> {
429 static KeyComparator INSTANCE = new KeyComparator();
430
431 private KeyComparator() {
432 }
433
434 public int compare(String o1, String o2) {
435 int len1 = o1.length();
436 int len2 = o2.length();
437 if (!isDigit(o1.charAt(0)) && !isDigit(o2.charAt(0))) {
438 // Shorter string comes first unless either starts with a digit.
439 if (len1 < len2) {
440 return -1;
441 }
442 if (len1 > len2) {
443 return 1;
444 }
445 }
446 return o1.compareTo(o2);
447 }
448
449 private boolean isDigit(char c) {
450 return c >= '0' && c <= '9';
451 }
452 }
453
459 }
460 }
461 return localeNames;
462 }
463
464 @SuppressWarnings("AssignmentToForLoopParameter")
465 private static Map<String, Object> extractCurrencyNames(Map<String, Object> map, String id, String names)
466 throws Exception {
467 Map<String, Object> currencyNames = new TreeMap<>(KeyComparator.INSTANCE);
468 for (String key : map.keySet()) {
469 if (key.startsWith(CURRENCY_NAME_PREFIX)) {
470 currencyNames.put(key.substring(CURRENCY_NAME_PREFIX.length()), map.get(key));
471 } else if (key.startsWith(CURRENCY_SYMBOL_PREFIX)) {
472 currencyNames.put(key.substring(CURRENCY_SYMBOL_PREFIX.length()), map.get(key));
473 }
474 }
475 return currencyNames;
476 }
477
478 private static Map<String, Object> extractZoneNames(Map<String, Object> map, String id) {
479 return null;
480 }
481
482 private static Map<String, Object> extractCalendarData(Map<String, Object> map, String id) {
483 Map<String, Object> calendarData = new LinkedHashMap<>();
484 copyIfPresent(map, "firstDayOfWeek", calendarData);
485 copyIfPresent(map, "minimalDaysInFirstWeek", calendarData);
486 return calendarData;
487 }
488
489 private static Map<String, Object> extractFormatData(Map<String, Object> map, String id) {
490 Map<String, Object> formatData = new LinkedHashMap<>();
491 for (CalendarType calendarType : CalendarType.values()) {
492 String prefix = calendarType.keyElementName();
493 copyIfPresent(map, prefix + "MonthNames", formatData); // default FORMAT since JDK8
494 copyIfPresent(map, prefix + "standalone.MonthNames", formatData);
495 copyIfPresent(map, prefix + "MonthAbbreviations", formatData);
496 copyIfPresent(map, prefix + "standalone.MonthAbbreviations", formatData);
497 copyIfPresent(map, prefix + "DayNames", formatData);
498 copyIfPresent(map, prefix + "DayAbbreviations", formatData);
499 copyIfPresent(map, prefix + "AmPmMarkers", formatData);
500 copyIfPresent(map, prefix + "Eras", formatData);
501 copyIfPresent(map, prefix + "short.Eras", formatData);
502 copyIfPresent(map, prefix + "TimePatterns", formatData);
503 copyIfPresent(map, prefix + "DatePatterns", formatData);
504 copyIfPresent(map, prefix + "DateTimePatterns", formatData);
505 copyIfPresent(map, prefix + "DateTimePatternChars", formatData);
506 }
507
508 copyIfPresent(map, "DefaultNumberingSystem", formatData);
509 String defaultScript = (String) map.get("DefaultNumberingSystem");
510 @SuppressWarnings("unchecked")
511 List<String> numberingScripts = (List<String>) map.remove("numberingScripts");
512 if (numberingScripts != null) {
513 for (String script : numberingScripts) {
514 copyIfPresent(map, script + "." + "NumberElements", formatData);
515 }
516 } else {
517 copyIfPresent(map, "NumberElements", formatData);
518 }
519 copyIfPresent(map, "NumberPatterns", formatData);
520 return formatData;
521 }
543
544 String specialSaveChars;
545 if (useJava) {
546 specialSaveChars = specialSaveCharsJava;
547 } else {
548 specialSaveChars = specialSaveCharsProperties;
549 }
550 boolean escapeSpace = false;
551
552 int len = theString.length();
553 StringBuilder outBuffer = new StringBuilder(len * 2);
554 Formatter formatter = new Formatter(outBuffer, Locale.ROOT);
555
556 for (int x = 0; x < len; x++) {
557 char aChar = theString.charAt(x);
558 switch (aChar) {
559 case ' ':
560 if (x == 0 || escapeSpace) {
561 outBuffer.append('\\');
562 }
563
564 outBuffer.append(' ');
565 break;
566 case '\\':
567 outBuffer.append('\\');
568 outBuffer.append('\\');
569 break;
570 case '\t':
571 outBuffer.append('\\');
572 outBuffer.append('t');
573 break;
574 case '\n':
575 outBuffer.append('\\');
576 outBuffer.append('n');
577 break;
578 case '\r':
579 outBuffer.append('\\');
580 outBuffer.append('r');
581 break;
582 case '\f':
583 outBuffer.append('\\');
584 outBuffer.append('f');
585 break;
586 default:
587 if (!USE_UTF8 && ((aChar < 0x0020) || (aChar > 0x007e))) {
588 formatter.format("\\u%04x", (int)aChar);
589 } else {
590 if (specialSaveChars.indexOf(aChar) != -1) {
591 outBuffer.append('\\');
592 }
593 outBuffer.append(aChar);
594 }
595 }
596 }
597 return outBuffer.toString();
598 }
599
600 private static String toLanguageTag(String locName) {
601 if (locName.indexOf('_') == -1) {
602 return locName;
603 }
604 String tag = locName.replaceAll("_", "-");
605 Locale loc = Locale.forLanguageTag(tag);
606 return loc.toLanguageTag();
607 }
|
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
38
39 /**
40 * Converts locale data from "Locale Data Markup Language" format to
41 * JRE resource bundle format. LDML is the format used by the Common
42 * Locale Data Repository maintained by the Unicode Consortium.
43 */
44 public class CLDRConverter {
45
46 static final String LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldml.dtd";
47 static final String SPPL_LDML_DTD_SYSTEM_ID = "http://www.unicode.org/cldr/dtd/2.0/ldmlSupplemental.dtd";
48
49 private static String CLDR_BASE = "../CLDR/21.0.1/";
50 static String LOCAL_LDML_DTD;
51 static String LOCAL_SPPL_LDML_DTD;
52 private static String SOURCE_FILE_DIR;
53 private static String SPPL_SOURCE_FILE;
54 private static String NUMBERING_SOURCE_FILE;
55 private static String METAZONES_SOURCE_FILE;
56 static String DESTINATION_DIR = "build/gensrc";
57
58 static final String LOCALE_NAME_PREFIX = "locale.displayname.";
59 static final String CURRENCY_SYMBOL_PREFIX = "currency.symbol.";
60 static final String CURRENCY_NAME_PREFIX = "currency.displayname.";
61 static final String TIMEZONE_ID_PREFIX = "timezone.id.";
62 static final String ZONE_NAME_PREFIX = "timezone.displayname.";
63 static final String METAZONE_ID_PREFIX = "metazone.id.";
64
65 private static SupplementDataParseHandler handlerSuppl;
66 static NumberingSystemsParseHandler handlerNumbering;
67 static MetaZonesParseHandler handlerMetaZones;
68 private static BundleGenerator bundleGenerator;
69
70 static int draftType;
71 private static final String DRAFT_UNCONFIRMED = "unconfirmed";
72 private static final String DRAFT_PROVISIONAL = "provisional";
73 private static final String DRAFT_CONTRIBUTED = "contributed";
74 private static final String DRAFT_APPROVED = "approved";
75 private static final String DRAFT_TRUE = "true";
76 private static final String DRAFT_FALSE = "false";
77 private static final String DRAFT_DEFAULT = DRAFT_APPROVED;
78 static final Map<String, Integer> DRAFT_MAP = new HashMap<>();
79
80 static {
81 DRAFT_MAP.put(DRAFT_UNCONFIRMED, 0);
82 DRAFT_MAP.put(DRAFT_PROVISIONAL, 1);
83 DRAFT_MAP.put(DRAFT_CONTRIBUTED, 2);
219 ResourceBundle.Control defCon = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT);
220 List<Bundle> retList = new ArrayList<>();
221 Path path = FileSystems.getDefault().getPath(SOURCE_FILE_DIR);
222 try (DirectoryStream<Path> dirStr = Files.newDirectoryStream(path)) {
223 for (Path entry : dirStr) {
224 String fileName = entry.getFileName().toString();
225 if (fileName.endsWith(".xml")) {
226 String id = fileName.substring(0, fileName.indexOf('.'));
227 Locale cldrLoc = Locale.forLanguageTag(toLanguageTag(id));
228 List<Locale> candList = defCon.getCandidateLocales("", cldrLoc);
229 StringBuilder sb = new StringBuilder();
230 for (Locale loc : candList) {
231 if (!loc.equals(Locale.ROOT)) {
232 sb.append(toLocaleName(loc.toLanguageTag()));
233 sb.append(",");
234 }
235 }
236 if (sb.indexOf("root") == -1) {
237 sb.append("root");
238 }
239 Bundle b = new Bundle(id, sb.toString(), null, null);
240 // Insert the bundle for en at the top so that it will get
241 // processed first.
242 if ("en".equals(id)) {
243 retList.add(0, b);
244 } else {
245 retList.add(b);
246 }
247 }
248 }
249 }
250 return retList;
251 }
252
253 private static Map<String, Map<String, Object>> cldrBundles = new HashMap<>();
254
255 static Map<String, Object> getCLDRBundle(String id) throws Exception {
256 Map<String, Object> bundle = cldrBundles.get(id);
257 if (bundle != null) {
258 return bundle;
259 }
260 SAXParserFactory factory = SAXParserFactory.newInstance();
261 factory.setValidating(true);
262 SAXParser parser = factory.newSAXParser();
263 LDMLParseHandler handler = new LDMLParseHandler(id);
264 File file = new File(SOURCE_FILE_DIR + File.separator + id + ".xml");
265 if (!file.exists()) {
266 // Skip if the file doesn't exist.
302 // Parse numberingSystems to get digit zero character information.
303 SAXParserFactory numberingParser = SAXParserFactory.newInstance();
304 numberingParser.setValidating(true);
305 SAXParser parserNumbering = numberingParser.newSAXParser();
306 handlerNumbering = new NumberingSystemsParseHandler();
307 File fileNumbering = new File(NUMBERING_SOURCE_FILE);
308 parserNumbering.parse(fileNumbering, handlerNumbering);
309
310 // Parse metaZones to create mappings between Olson tzids and CLDR meta zone names
311 SAXParserFactory metazonesParser = SAXParserFactory.newInstance();
312 metazonesParser.setValidating(true);
313 SAXParser parserMetaZones = metazonesParser.newSAXParser();
314 handlerMetaZones = new MetaZonesParseHandler();
315 File fileMetaZones = new File(METAZONES_SOURCE_FILE);
316 parserNumbering.parse(fileMetaZones, handlerMetaZones);
317
318 // For generating information on supported locales.
319 Map<String, SortedSet<String>> metaInfo = new HashMap<>();
320 metaInfo.put("LocaleNames", new TreeSet<String>());
321 metaInfo.put("CurrencyNames", new TreeSet<String>());
322 metaInfo.put("TimeZoneNames", new TreeSet<String>());
323 metaInfo.put("CalendarData", new TreeSet<String>());
324 metaInfo.put("FormatData", new TreeSet<String>());
325
326 for (Bundle bundle : bundles) {
327 // Get the target map, which contains all the data that should be
328 // visible for the bundle's locale
329
330 Map<String, Object> targetMap = bundle.getTargetMap();
331
332 EnumSet<Bundle.Type> bundleTypes = bundle.getBundleTypes();
333
334 // Fill in any missing resources in the base bundle from en and en-US data.
335 // This is because CLDR root.xml is supposed to be language neutral and doesn't
336 // provide some resource data. Currently, the runtime assumes that there are all
337 // resources though the parent resource bundle chain.
338 if (bundle.isRoot()) {
339 Map<String, Object> enData = new HashMap<>();
340 // Create a superset of en-US and en bundles data in order to
341 // fill in any missing resources in the base bundle.
342 enData.putAll(Bundle.getBundle("en").getTargetMap());
343 enData.putAll(Bundle.getBundle("en_US").getTargetMap());
344 for (String key : enData.keySet()) {
345 if (!targetMap.containsKey(key)) {
346 targetMap.put(key, enData.get(key));
347 }
348 }
349 // Add DateTimePatternChars because CLDR no longer supports localized patterns.
350 targetMap.put("DateTimePatternChars", "GyMdkHmsSEDFwWahKzZ");
351 }
352
353 // Now the map contains just the entries that need to be in the resources bundles.
354 // Go ahead and generate them.
355 if (bundleTypes.contains(Bundle.Type.LOCALENAMES)) {
356 Map<String, Object> localeNamesMap = extractLocaleNames(targetMap, bundle.getID());
357 if (!localeNamesMap.isEmpty() || bundle.isRoot()) {
358 metaInfo.get("LocaleNames").add(toLanguageTag(bundle.getID()));
359 bundleGenerator.generateBundle("util", "LocaleNames", bundle.getID(), true, localeNamesMap, BundleType.OPEN);
360 }
361 }
362 if (bundleTypes.contains(Bundle.Type.CURRENCYNAMES)) {
363 Map<String, Object> currencyNamesMap = extractCurrencyNames(targetMap, bundle.getID(), bundle.getCurrencies());
364 if (!currencyNamesMap.isEmpty() || bundle.isRoot()) {
365 metaInfo.get("CurrencyNames").add(toLanguageTag(bundle.getID()));
366 bundleGenerator.generateBundle("util", "CurrencyNames", bundle.getID(), true, currencyNamesMap, BundleType.OPEN);
367 }
368 }
369 if (bundleTypes.contains(Bundle.Type.TIMEZONENAMES)) {
370 Map<String, Object> zoneNamesMap = extractZoneNames(targetMap, bundle.getID());
371 if (!zoneNamesMap.isEmpty() || bundle.isRoot()) {
372 metaInfo.get("TimeZoneNames").add(toLanguageTag(bundle.getID()));
373 bundleGenerator.generateBundle("util", "TimeZoneNames", bundle.getID(), true, zoneNamesMap, BundleType.TIMEZONE);
374 }
375 }
376 if (bundleTypes.contains(Bundle.Type.CALENDARDATA)) {
377 Map<String, Object> calendarDataMap = extractCalendarData(targetMap, bundle.getID());
378 if (!calendarDataMap.isEmpty() || bundle.isRoot()) {
379 metaInfo.get("CalendarData").add(toLanguageTag(bundle.getID()));
380 bundleGenerator.generateBundle("util", "CalendarData", bundle.getID(), true, calendarDataMap, BundleType.PLAIN);
381 }
382 }
383 if (bundleTypes.contains(Bundle.Type.FORMATDATA)) {
384 Map<String, Object> formatDataMap = extractFormatData(targetMap, bundle.getID());
385 // LocaleData.getAvailableLocales depends on having FormatData bundles around
386 if (!formatDataMap.isEmpty() || bundle.isRoot()) {
387 metaInfo.get("FormatData").add(toLanguageTag(bundle.getID()));
388 bundleGenerator.generateBundle("text", "FormatData", bundle.getID(), true, formatDataMap, BundleType.OPEN);
389 }
390 }
391
392 // For testing
393 SortedSet<String> allLocales = new TreeSet<>();
394 allLocales.addAll(metaInfo.get("CurrencyNames"));
395 allLocales.addAll(metaInfo.get("LocaleNames"));
396 allLocales.addAll(metaInfo.get("CalendarData"));
397 allLocales.addAll(metaInfo.get("FormatData"));
398 metaInfo.put("All", allLocales);
399 }
400
401 bundleGenerator.generateMetaInfo(metaInfo);
402 }
403
404 /*
405 * Returns the language portion of the given id.
406 * If id is "root", "" is returned.
407 */
408 static String getLanguageCode(String id) {
409 int index = id.indexOf('_');
410 String lang = null;
411 if (index != -1) {
427 if (id.indexOf('@') != -1) {
428 id = id.substring(0, id.indexOf('@'));
429 }
430 String[] tokens = id.split("_");
431 for (int index = 1; index < tokens.length; ++index) {
432 if (tokens[index].length() == 2
433 && Character.isLetter(tokens[index].charAt(0))
434 && Character.isLetter(tokens[index].charAt(1))) {
435 return tokens[index];
436 }
437 }
438 return null;
439 }
440
441 private static class KeyComparator implements Comparator<String> {
442 static KeyComparator INSTANCE = new KeyComparator();
443
444 private KeyComparator() {
445 }
446
447 @Override
448 public int compare(String o1, String o2) {
449 int len1 = o1.length();
450 int len2 = o2.length();
451 if (!isDigit(o1.charAt(0)) && !isDigit(o2.charAt(0))) {
452 // Shorter string comes first unless either starts with a digit.
453 if (len1 < len2) {
454 return -1;
455 }
456 if (len1 > len2) {
457 return 1;
458 }
459 }
460 return o1.compareTo(o2);
461 }
462
463 private boolean isDigit(char c) {
464 return c >= '0' && c <= '9';
465 }
466 }
467
473 }
474 }
475 return localeNames;
476 }
477
478 @SuppressWarnings("AssignmentToForLoopParameter")
479 private static Map<String, Object> extractCurrencyNames(Map<String, Object> map, String id, String names)
480 throws Exception {
481 Map<String, Object> currencyNames = new TreeMap<>(KeyComparator.INSTANCE);
482 for (String key : map.keySet()) {
483 if (key.startsWith(CURRENCY_NAME_PREFIX)) {
484 currencyNames.put(key.substring(CURRENCY_NAME_PREFIX.length()), map.get(key));
485 } else if (key.startsWith(CURRENCY_SYMBOL_PREFIX)) {
486 currencyNames.put(key.substring(CURRENCY_SYMBOL_PREFIX.length()), map.get(key));
487 }
488 }
489 return currencyNames;
490 }
491
492 private static Map<String, Object> extractZoneNames(Map<String, Object> map, String id) {
493 Map<String, Object> names = new HashMap<>();
494 for (String tzid : handlerMetaZones.keySet()) {
495 String tzKey = TIMEZONE_ID_PREFIX + tzid;
496 Object data = map.get(tzKey);
497 if (data instanceof String[]) {
498 names.put(tzid, data);
499 } else {
500 String meta = handlerMetaZones.get(tzid);
501 if (meta != null) {
502 String metaKey = METAZONE_ID_PREFIX + meta;
503 data = map.get(metaKey);
504 if (data instanceof String[]) {
505 // Keep the metazone prefix here.
506 names.put(metaKey, data);
507 names.put(tzid, meta);
508 }
509 }
510 }
511 }
512 return names;
513 }
514
515 private static Map<String, Object> extractCalendarData(Map<String, Object> map, String id) {
516 Map<String, Object> calendarData = new LinkedHashMap<>();
517 copyIfPresent(map, "firstDayOfWeek", calendarData);
518 copyIfPresent(map, "minimalDaysInFirstWeek", calendarData);
519 return calendarData;
520 }
521
522 private static Map<String, Object> extractFormatData(Map<String, Object> map, String id) {
523 Map<String, Object> formatData = new LinkedHashMap<>();
524 for (CalendarType calendarType : CalendarType.values()) {
525 String prefix = calendarType.keyElementName();
526 copyIfPresent(map, prefix + "MonthNames", formatData); // default FORMAT since JDK8
527 copyIfPresent(map, prefix + "standalone.MonthNames", formatData);
528 copyIfPresent(map, prefix + "MonthAbbreviations", formatData);
529 copyIfPresent(map, prefix + "standalone.MonthAbbreviations", formatData);
530 copyIfPresent(map, prefix + "MonthNarrow", formatData);
531 copyIfPresent(map, prefix + "standalone.MonthNarrows", formatData);
532 copyIfPresent(map, prefix + "DayNames", formatData);
533 copyIfPresent(map, prefix + "standalone.DayNames", formatData);
534 copyIfPresent(map, prefix + "DayAbbreviations", formatData);
535 copyIfPresent(map, prefix + "standalone.DayAbbreviations", formatData);
536 copyIfPresent(map, prefix + "DayNarrows", formatData);
537 copyIfPresent(map, prefix + "standalone.DayNarrows", formatData);
538 copyIfPresent(map, prefix + "AmPmMarkers", formatData);
539 copyIfPresent(map, prefix + "narrow.AmPmMarkers", formatData);
540 copyIfPresent(map, prefix + "long.Eras", formatData);
541 copyIfPresent(map, prefix + "Eras", formatData);
542 copyIfPresent(map, prefix + "narrow.Eras", formatData);
543 copyIfPresent(map, prefix + "TimePatterns", formatData);
544 copyIfPresent(map, prefix + "DatePatterns", formatData);
545 copyIfPresent(map, prefix + "DateTimePatterns", formatData);
546 copyIfPresent(map, prefix + "DateTimePatternChars", formatData);
547 }
548
549 copyIfPresent(map, "DefaultNumberingSystem", formatData);
550 String defaultScript = (String) map.get("DefaultNumberingSystem");
551 @SuppressWarnings("unchecked")
552 List<String> numberingScripts = (List<String>) map.remove("numberingScripts");
553 if (numberingScripts != null) {
554 for (String script : numberingScripts) {
555 copyIfPresent(map, script + "." + "NumberElements", formatData);
556 }
557 } else {
558 copyIfPresent(map, "NumberElements", formatData);
559 }
560 copyIfPresent(map, "NumberPatterns", formatData);
561 return formatData;
562 }
584
585 String specialSaveChars;
586 if (useJava) {
587 specialSaveChars = specialSaveCharsJava;
588 } else {
589 specialSaveChars = specialSaveCharsProperties;
590 }
591 boolean escapeSpace = false;
592
593 int len = theString.length();
594 StringBuilder outBuffer = new StringBuilder(len * 2);
595 Formatter formatter = new Formatter(outBuffer, Locale.ROOT);
596
597 for (int x = 0; x < len; x++) {
598 char aChar = theString.charAt(x);
599 switch (aChar) {
600 case ' ':
601 if (x == 0 || escapeSpace) {
602 outBuffer.append('\\');
603 }
604 outBuffer.append(' ');
605 break;
606 case '\\':
607 outBuffer.append('\\');
608 outBuffer.append('\\');
609 break;
610 case '\t':
611 outBuffer.append('\\');
612 outBuffer.append('t');
613 break;
614 case '\n':
615 outBuffer.append('\\');
616 outBuffer.append('n');
617 break;
618 case '\r':
619 outBuffer.append('\\');
620 outBuffer.append('r');
621 break;
622 case '\f':
623 outBuffer.append('\\');
624 outBuffer.append('f');
625 break;
626 default:
627 if (aChar < 0x0020 || (!USE_UTF8 && aChar > 0x007e)) {
628 formatter.format("\\u%04x", (int)aChar);
629 } else {
630 if (specialSaveChars.indexOf(aChar) != -1) {
631 outBuffer.append('\\');
632 }
633 outBuffer.append(aChar);
634 }
635 }
636 }
637 return outBuffer.toString();
638 }
639
640 private static String toLanguageTag(String locName) {
641 if (locName.indexOf('_') == -1) {
642 return locName;
643 }
644 String tag = locName.replaceAll("_", "-");
645 Locale loc = Locale.forLanguageTag(tag);
646 return loc.toLanguageTag();
647 }
|