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