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 }