1/*
2 * Copyright (c) 2012, 2015, 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
26package build.tools.cldrconverter;
27
28import java.io.File;
29import java.io.IOException;
30import java.io.PrintWriter;
31import java.util.Arrays;
32import java.util.Formatter;
33import java.util.HashSet;
34import java.util.HashMap;
35import java.util.LinkedHashMap;
36import java.util.Map;
37import java.util.Locale;
38import java.util.Objects;
39import java.util.Set;
40import java.util.SortedSet;
41
42class 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.println(CopyrightHeaders.getOpenJDKCopyright());
260
261            out.println((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.LocaleProviderAdapter;\n"
267                      + "import sun.util.locale.provider.LocaleDataMetaInfo;\n");
268            out.printf("public class %s implements LocaleDataMetaInfo {\n", className);
269            out.println("    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" : "\n") +
272                        "    static {\n");
273
274            for (String key : metaInfo.keySet()) {
275                if (key.startsWith(CLDRConverter.PARENT_LOCALE_PREFIX)) {
276                    String parentTag = key.substring(CLDRConverter.PARENT_LOCALE_PREFIX.length());
277                    if ("root".equals(parentTag)) {
278                        out.printf("        parentLocalesMap.put(Locale.ROOT,\n");
279                    } else {
280                        out.printf("        parentLocalesMap.put(Locale.forLanguageTag(\"%s\"),\n",
281                                   parentTag);
282                    }
283                    String[] children = toLocaleList(metaInfo.get(key), true).split(" ");
284                    Arrays.sort(children);
285                    out.printf("             new String[] {\n" +
286                               "                 ");
287                    int count = 0;
288                    for (int i = 0; i < children.length; i++) {
289                        String child = children[i];
290                        out.printf("\"%s\", ", child);
291                        count += child.length() + 4;
292                        if (i != children.length - 1 && count > 64) {
293                            out.printf("\n                 ");
294                            count = 0;
295                        }
296                    }
297                    out.printf("\n             });\n");
298                } else {
299                    out.printf("        resourceNameToLocales.put(\"%s\",\n", key);
300                    out.printf("              \"%s\");\n",
301                    toLocaleList(key.equals("FormatData") ? metaInfo.get("AvailableLocales") :
302                                            metaInfo.get(key), false));
303                }
304            }
305            out.println("    }\n\n");
306
307            out.println("    @Override\n" +
308                        "    public LocaleProviderAdapter.Type getType() {\n" +
309                        "        return LocaleProviderAdapter.Type.CLDR;\n" +
310                        "    }\n\n");
311
312            out.println("    @Override\n" +
313                        "    public String availableLanguageTags(String category) {\n" +
314                        "        return resourceNameToLocales.getOrDefault(category, \"\");\n" +
315                        "    }\n\n");
316
317            if (CLDRConverter.isBaseModule) {
318                out.printf("    public Map<Locale, String[]> parentLocales() {\n" +
319                           "        return parentLocalesMap;\n" +
320                           "    }\n}");
321            } else {
322                out.println("}");
323            }
324        }
325    }
326
327    private static final Locale.Builder LOCALE_BUILDER = new Locale.Builder();
328    private static boolean isBaseLocale(String localeID) {
329        localeID = localeID.replaceAll("-", "_");
330        // ignore script here
331        Locale locale = LOCALE_BUILDER
332                            .clear()
333                            .setLanguage(CLDRConverter.getLanguageCode(localeID))
334                            .setRegion(CLDRConverter.getRegionCode(localeID))
335                            .build();
336        return CLDRConverter.BASE_LOCALES.contains(locale);
337    }
338
339    private static String toLocaleList(SortedSet<String> set, boolean all) {
340        StringBuilder sb = new StringBuilder(set.size() * 6);
341        for (String id : set) {
342            if (!"root".equals(id)) {
343                if (!all && CLDRConverter.isBaseModule ^ isBaseLocale(id)) {
344                    continue;
345                }
346                sb.append(' ');
347                sb.append(id);
348            }
349        }
350        return sb.toString();
351    }
352}
353