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