1 /* 2 * Copyright (c) 2012, 2018, 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 java.io.File; 29 import java.io.IOException; 30 import java.io.PrintWriter; 31 import java.util.Arrays; 32 import java.util.Formatter; 33 import java.util.HashSet; 34 import java.util.HashMap; 35 import java.util.LinkedHashMap; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Locale; 39 import java.util.Objects; 40 import java.util.Set; 41 import java.util.SortedSet; 42 43 class ResourceBundleGenerator implements BundleGenerator { 44 // preferred timezones - keeping compatibility with JDK1.1 3 letter abbreviations 45 private static final String[] preferredTZIDs = { 46 "America/Los_Angeles", 47 "America/Denver", 48 "America/Phoenix", 49 "America/Chicago", 50 "America/New_York", 51 "America/Indianapolis", 52 "Pacific/Honolulu", 53 "America/Anchorage", 54 "America/Halifax", 55 "America/Sitka", 56 "America/St_Johns", 57 "Europe/Paris", 58 // Although CLDR does not support abbreviated zones, handle "GMT" as a 59 // special case here, as it is specified in the javadoc. 60 "GMT", 61 "Africa/Casablanca", 62 "Asia/Jerusalem", 63 "Asia/Tokyo", 64 "Europe/Bucharest", 65 "Asia/Shanghai", 66 "UTC", 67 }; 68 69 // For duplicated values 70 private static final String META_VALUE_PREFIX = "metaValue_"; 71 72 @Override 73 public void generateBundle(String packageName, String baseName, String localeID, boolean useJava, 74 Map<String, ?> map, BundleType type) throws IOException { 75 String suffix = useJava ? ".java" : ".properties"; 76 String dirName = CLDRConverter.DESTINATION_DIR + File.separator + "sun" + File.separator 77 + packageName + File.separator + "resources" + File.separator + "cldr"; 78 packageName = packageName + ".resources.cldr"; 79 80 if (CLDRConverter.isBaseModule ^ isBaseLocale(localeID)) { 81 return; 82 } 83 84 // Assume that non-base resources go into jdk.localedata 85 if (!CLDRConverter.isBaseModule) { 86 dirName = dirName + File.separator + "ext"; 87 packageName = packageName + ".ext"; 88 } 89 90 File dir = new File(dirName); 91 if (!dir.exists()) { 92 dir.mkdirs(); 93 } 94 File file = new File(dir, baseName + ("root".equals(localeID) ? "" : "_" + localeID) + suffix); 95 if (!file.exists()) { 96 file.createNewFile(); 97 } 98 CLDRConverter.info("\tWriting file " + file); 99 100 String encoding; 101 if (useJava) { 102 if (CLDRConverter.USE_UTF8) { 103 encoding = "utf-8"; 104 } else { 105 encoding = "us-ascii"; 106 } 107 } else { 108 encoding = "iso-8859-1"; 109 } 110 boolean containsList = false; 111 Formatter fmt = null; 112 if (type == BundleType.TIMEZONE) { 113 fmt = new Formatter(); 114 Set<String> metaKeys = new HashSet<>(); 115 for (String key : map.keySet()) { 116 if (key.startsWith(CLDRConverter.METAZONE_ID_PREFIX)) { 117 String meta = key.substring(CLDRConverter.METAZONE_ID_PREFIX.length()); 118 String[] value; 119 value = (String[]) map.get(key); 120 fmt.format(" final String[] %s = new String[] {\n", meta); 121 for (String s : value) { 122 fmt.format(" \"%s\",\n", CLDRConverter.saveConvert(s, useJava)); 123 } 124 fmt.format(" };\n"); 125 metaKeys.add(key); 126 } 127 } 128 for (String key : metaKeys) { 129 map.remove(key); 130 } 131 132 // Make it preferred ordered 133 LinkedHashMap<String, Object> newMap = new LinkedHashMap<>(); 134 for (String preferred : preferredTZIDs) { 135 if (map.containsKey(preferred)) { 136 newMap.put(preferred, map.remove(preferred)); 137 } else if (("GMT".equals(preferred) || "UTC".equals(preferred)) && 138 metaKeys.contains(CLDRConverter.METAZONE_ID_PREFIX+preferred)) { 139 newMap.put(preferred, preferred); 140 } 141 } 142 newMap.putAll(map); 143 map = newMap; 144 } else { 145 // generic reduction of duplicated values 146 Map<String, Object> newMap = null; 147 for (String key : map.keySet()) { 148 Object val = map.get(key); 149 String metaVal = null; 150 151 for (Map.Entry<String, ?> entry : map.entrySet()) { 152 String k = entry.getKey(); 153 if (!k.equals(key) && 154 Objects.deepEquals(val, entry.getValue()) && 155 !(Objects.nonNull(newMap) && newMap.containsKey(k))) { 156 if (Objects.isNull(newMap)) { 157 newMap = new HashMap<>(); 158 fmt = new Formatter(); 159 } 160 161 if (Objects.isNull(metaVal)) { 162 metaVal = META_VALUE_PREFIX + key.replaceAll("\\.", "_"); 163 164 if (val instanceof String[]) { 165 fmt.format(" final String[] %s = new String[] {\n", metaVal); 166 for (String s : (String[])val) { 167 fmt.format(" \"%s\",\n", CLDRConverter.saveConvert(s, useJava)); 168 } 169 fmt.format(" };\n"); 170 } else if (val instanceof List) { 171 containsList = true; 172 List<String> values = (List) val; 173 fmt.format(" final List<String> %s = List.of (\n", metaVal); 174 for (int index = 0; index < values.size() - 1; index++) { 175 fmt.format(" \"%s\",\n", CLDRConverter.saveConvert(values.get(index), useJava)); 176 } 177 fmt.format(" \"%s\");\n", CLDRConverter.saveConvert(values.get(values.size() - 1), useJava)); 178 } else { 179 fmt.format(" final String %s = \"%s\";\n", metaVal, CLDRConverter.saveConvert((String)val, useJava)); 180 } 181 } 182 183 newMap.put(k, metaVal); 184 } 185 } 186 187 if (Objects.nonNull(metaVal)) { 188 newMap.put(key, metaVal); 189 } 190 } 191 192 if (Objects.nonNull(newMap)) { 193 for (String key : map.keySet()) { 194 newMap.putIfAbsent(key, map.get(key)); 195 } 196 map = newMap; 197 } 198 } 199 200 try (PrintWriter out = new PrintWriter(file, encoding)) { 201 // Output copyright headers 202 out.println(CopyrightHeaders.getOpenJDKCopyright()); 203 out.println(CopyrightHeaders.getUnicodeCopyright()); 204 205 if (useJava) { 206 out.println("package sun." + packageName + ";\n"); 207 // only in the FormatData resource bundles which contains 208 // list in map 209 if (baseName.equals("FormatData") && (containsList 210 || map.values().stream().anyMatch(v -> v instanceof List))) { 211 out.printf("import java.util.List;\n\n"); 212 } 213 out.printf("import %s;\n\n", type.getPathName()); 214 out.printf("public class %s%s extends %s {\n", baseName, "root".equals(localeID) ? "" : "_" + localeID, type.getClassName()); 215 216 out.println(" @Override\n" + 217 " protected final Object[][] getContents() {"); 218 if (fmt != null) { 219 out.print(fmt.toString()); 220 } 221 out.println(" final Object[][] data = new Object[][] {"); 222 } 223 for (String key : map.keySet()) { 224 if (useJava) { 225 Object value = map.get(key); 226 if (value == null) { 227 CLDRConverter.warning("null value for " + key); 228 } else if (value instanceof String) { 229 String valStr = (String)value; 230 if (type == BundleType.TIMEZONE && 231 !key.startsWith(CLDRConverter.EXEMPLAR_CITY_PREFIX) || 232 valStr.startsWith(META_VALUE_PREFIX)) { 233 out.printf(" { \"%s\", %s },\n", key, CLDRConverter.saveConvert(valStr, useJava)); 234 } else { 235 out.printf(" { \"%s\", \"%s\" },\n", key, CLDRConverter.saveConvert(valStr, useJava)); 236 } 237 } else if (value instanceof String[]) { 238 String[] values = (String[]) value; 239 out.println(" { \"" + key + "\",\n new String[] {"); 240 for (String s : values) { 241 out.println(" \"" + CLDRConverter.saveConvert(s, useJava) + "\","); 242 } 243 out.println(" }\n },"); 244 } else if (value instanceof List) { 245 List<String> values = (List) value; 246 out.println(" { \"" + key + "\",\n List.of("); 247 for (int index = 0; index < values.size() - 1; index++) { 248 out.println(" \"" + CLDRConverter.saveConvert(values.get(index), useJava) + "\","); 249 } 250 out.println(" \"" + CLDRConverter.saveConvert(values.get(values.size() - 1), useJava) + "\""); 251 out.println(" )\n },"); 252 } else { 253 throw new RuntimeException("unknown value type: " + value.getClass().getName()); 254 } 255 } else { 256 out.println(key + "=" + CLDRConverter.saveConvert((String) map.get(key), useJava)); 257 } 258 } 259 if (useJava) { 260 out.println(" };\n return data;\n }\n}"); 261 } 262 } 263 } 264 265 @Override 266 public void generateMetaInfo(Map<String, SortedSet<String>> metaInfo) throws IOException { 267 String dirName = CLDRConverter.DESTINATION_DIR + File.separator + "sun" + File.separator + "util" + 268 File.separator + 269 (CLDRConverter.isBaseModule ? "cldr" + File.separator + File.separator : 270 "resources" + File.separator + "cldr" + File.separator + "provider" + File.separator); 271 File dir = new File(dirName); 272 if (!dir.exists()) { 273 dir.mkdirs(); 274 } 275 String className = 276 (CLDRConverter.isBaseModule ? "CLDRBaseLocaleDataMetaInfo" : "CLDRLocaleDataMetaInfo"); 277 File file = new File(dir, className + ".java"); 278 if (!file.exists()) { 279 file.createNewFile(); 280 } 281 CLDRConverter.info("Generating file " + file); 282 283 try (PrintWriter out = new PrintWriter(file, "us-ascii")) { 284 out.printf(CopyrightHeaders.getOpenJDKCopyright()); 285 286 out.printf((CLDRConverter.isBaseModule ? "package sun.util.cldr;\n\n" : 287 "package sun.util.resources.cldr.provider;\n\n") 288 + "import java.util.HashMap;\n" 289 + "import java.util.Locale;\n" 290 + "import java.util.Map;\n" 291 + "import sun.util.locale.provider.LocaleDataMetaInfo;\n" 292 + "import sun.util.locale.provider.LocaleProviderAdapter;\n\n"); 293 out.printf("public class %s implements LocaleDataMetaInfo {\n", className); 294 out.printf(" private static final Map<String, String> resourceNameToLocales = new HashMap<>();\n" + 295 (CLDRConverter.isBaseModule ? 296 " private static final Map<Locale, String[]> parentLocalesMap = new HashMap<>();\n" + 297 " private static final Map<String, String> languageAliasMap = new HashMap<>();\n\n" : 298 "\n") + 299 " static {\n"); 300 301 for (String key : metaInfo.keySet()) { 302 if (key.startsWith(CLDRConverter.PARENT_LOCALE_PREFIX)) { 303 String parentTag = key.substring(CLDRConverter.PARENT_LOCALE_PREFIX.length()); 304 if ("root".equals(parentTag)) { 305 out.printf(" parentLocalesMap.put(Locale.ROOT,\n"); 306 } else { 307 out.printf(" parentLocalesMap.put(Locale.forLanguageTag(\"%s\"),\n", 308 parentTag); 309 } 310 String[] children = toLocaleList(metaInfo.get(key), true).split(" "); 311 Arrays.sort(children); 312 out.printf(" new String[] {\n" + 313 " "); 314 int count = 0; 315 for (int i = 0; i < children.length; i++) { 316 String child = children[i]; 317 out.printf("\"%s\", ", child); 318 count += child.length() + 4; 319 if (i != children.length - 1 && count > 64) { 320 out.printf("\n "); 321 count = 0; 322 } 323 } 324 out.printf("\n });\n"); 325 } else { 326 if ("AvailableLocales".equals(key)) { 327 out.printf(" resourceNameToLocales.put(\"%s\",\n", key); 328 out.printf(" \"%s\");\n", toLocaleList(applyLanguageAliases(metaInfo.get(key)), false)); 329 } 330 } 331 } 332 // for languageAliasMap 333 if (CLDRConverter.isBaseModule) { 334 CLDRConverter.handlerSupplMeta.getLanguageAliasData().forEach((key, value) -> { 335 out.printf(" languageAliasMap.put(\"%s\", \"%s\");\n", key, value); 336 }); 337 } 338 339 out.printf(" }\n\n"); 340 341 // end of static initializer block. 342 343 // Canonical TZ names for delayed initialization 344 if (CLDRConverter.isBaseModule) { 345 out.printf(" private static class TZCanonicalIDMapHolder {\n"); 346 out.printf(" static final Map<String, String> tzCanonicalIDMap = new HashMap<>(600);\n"); 347 out.printf(" static {\n"); 348 CLDRConverter.handlerTimeZone.getData().entrySet().stream() 349 .forEach(e -> { 350 String[] ids = ((String)e.getValue()).split("\\s"); 351 out.printf(" tzCanonicalIDMap.put(\"%s\", \"%s\");\n", e.getKey(), 352 ids[0]); 353 for (int i = 1; i < ids.length; i++) { 354 out.printf(" tzCanonicalIDMap.put(\"%s\", \"%s\");\n", ids[i], 355 ids[0]); 356 } 357 }); 358 out.printf(" }\n }\n\n"); 359 } 360 361 out.printf(" @Override\n" + 362 " public LocaleProviderAdapter.Type getType() {\n" + 363 " return LocaleProviderAdapter.Type.CLDR;\n" + 364 " }\n\n"); 365 366 out.printf(" @Override\n" + 367 " public String availableLanguageTags(String category) {\n" + 368 " return resourceNameToLocales.getOrDefault(category, \"\");\n" + 369 " }\n\n"); 370 371 if (CLDRConverter.isBaseModule) { 372 out.printf(" @Override\n" + 373 " public Map<String, String> getLanguageAliasMap() {\n" + 374 " return languageAliasMap;\n" + 375 " }\n\n"); 376 out.printf(" @Override\n" + 377 " public Map<String, String> tzCanonicalIDs() {\n" + 378 " return TZCanonicalIDMapHolder.tzCanonicalIDMap;\n" + 379 " }\n\n"); 380 out.printf(" public Map<Locale, String[]> parentLocales() {\n" + 381 " return parentLocalesMap;\n" + 382 " }\n}"); 383 } else { 384 out.printf("}"); 385 } 386 } 387 } 388 389 private static final Locale.Builder LOCALE_BUILDER = new Locale.Builder(); 390 private static boolean isBaseLocale(String localeID) { 391 localeID = localeID.replaceAll("-", "_"); 392 // ignore script here 393 Locale locale = LOCALE_BUILDER 394 .clear() 395 .setLanguage(CLDRConverter.getLanguageCode(localeID)) 396 .setRegion(CLDRConverter.getRegionCode(localeID)) 397 .build(); 398 return CLDRConverter.BASE_LOCALES.contains(locale); 399 } 400 401 private static String toLocaleList(SortedSet<String> set, boolean all) { 402 StringBuilder sb = new StringBuilder(set.size() * 6); 403 for (String id : set) { 404 if (!"root".equals(id)) { 405 if (!all && CLDRConverter.isBaseModule ^ isBaseLocale(id)) { 406 continue; 407 } 408 sb.append(' '); 409 sb.append(id); 410 } 411 } 412 return sb.toString(); 413 } 414 415 private static SortedSet<String> applyLanguageAliases(SortedSet<String> tags) { 416 CLDRConverter.handlerSupplMeta.getLanguageAliasData().forEach((key, value) -> { 417 if (tags.remove(key)) { 418 tags.add(value); 419 } 420 }); 421 return tags; 422 } 423 }