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 */
25package sun.util.locale.provider;
26
27import static java.util.Calendar.*;
28import java.util.Comparator;
29import java.util.Locale;
30import java.util.Map;
31import java.util.Set;
32import java.util.TreeMap;
33import java.util.spi.CalendarNameProvider;
34import sun.util.calendar.CalendarSystem;
35import sun.util.calendar.Era;
36
37/**
38 * Concrete implementation of the  {@link java.util.spi.CalendarDataProvider
39 * CalendarDataProvider} class for the JRE LocaleProviderAdapter.
40 *
41 * @author Masayoshi Okutsu
42 * @author Naoto Sato
43 */
44public class CalendarNameProviderImpl extends CalendarNameProvider implements AvailableLanguageTags {
45    private final LocaleProviderAdapter.Type type;
46    private final Set<String> langtags;
47
48    public CalendarNameProviderImpl(LocaleProviderAdapter.Type type, Set<String> langtags) {
49        this.type = type;
50        this.langtags = langtags;
51    }
52
53    @Override
54    public String getDisplayName(String calendarType, int field, int value, int style, Locale locale) {
55        return getDisplayNameImpl(calendarType, field, value, style, locale, false);
56    }
57
58    public String getJavaTimeDisplayName(String calendarType, int field, int value, int style, Locale locale) {
59        return getDisplayNameImpl(calendarType, field, value, style, locale, true);
60    }
61
62    public String getDisplayNameImpl(String calendarType, int field, int value, int style, Locale locale, boolean javatime) {
63        String name = null;
64        String key = getResourceKey(calendarType, field, style, javatime);
65        if (key != null) {
66            LocaleResources lr = LocaleProviderAdapter.forType(type).getLocaleResources(locale);
67            String[] strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
68
69            // If standalone names are requested and no "standalone." resources are found,
70            // try the default ones instead.
71            if (strings == null && key.indexOf("standalone.") != -1) {
72                key = key.replaceFirst("standalone.", "");
73                strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
74            }
75
76            if (strings != null && strings.length > 0) {
77                if (field == DAY_OF_WEEK || field == YEAR) {
78                    --value;
79                }
80                if (value < 0 || value > strings.length) {
81                    return null;
82                } else if (value == strings.length) {
83                    if (field == ERA && "japanese".equals(calendarType)) {
84                        // get the supplemental era, if any, specified through
85                        // the property "jdk.calendar.japanese.supplemental.era"
86                        // which is always the last element.
87                        Era[] jeras = CalendarSystem.forName("japanese").getEras();
88                        if (jeras.length == value) {
89                            Era supEra = jeras[value - 1]; // 0-based index
90                            if (javatime) {
91                                return getBaseStyle(style) == NARROW_FORMAT ?
92                                    supEra.getAbbreviation() :
93                                    supEra.getName();
94                            } else {
95                                return (style & LONG) != 0 ?
96                                    supEra.getName() :
97                                    supEra.getAbbreviation();
98                            }
99                        }
100                    }
101                    return null;
102                }
103                name = strings[value];
104                // If name is empty in standalone, try its `format' style.
105                if (name.length() == 0
106                        && (style == SHORT_STANDALONE || style == LONG_STANDALONE
107                            || style == NARROW_STANDALONE)) {
108                    name = getDisplayName(calendarType, field, value,
109                                          getBaseStyle(style),
110                                          locale);
111                }
112            }
113        }
114        return name;
115    }
116
117    private static int[] REST_OF_STYLES = {
118        SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE,
119        NARROW_FORMAT, NARROW_STANDALONE
120    };
121
122    @Override
123    public Map<String, Integer> getDisplayNames(String calendarType, int field, int style, Locale locale) {
124        Map<String, Integer> names;
125        if (style == ALL_STYLES) {
126            names = getDisplayNamesImpl(calendarType, field, SHORT_FORMAT, locale, false);
127            for (int st : REST_OF_STYLES) {
128                names.putAll(getDisplayNamesImpl(calendarType, field, st, locale, false));
129            }
130        } else {
131            // specific style
132            names = getDisplayNamesImpl(calendarType, field, style, locale, false);
133        }
134        return names.isEmpty() ? null : names;
135    }
136
137    // NOTE: This method should be used ONLY BY JSR 310 classes.
138    public Map<String, Integer> getJavaTimeDisplayNames(String calendarType, int field, int style, Locale locale) {
139        Map<String, Integer> names;
140        names = getDisplayNamesImpl(calendarType, field, style, locale, true);
141        return names.isEmpty() ? null : names;
142    }
143
144    private Map<String, Integer> getDisplayNamesImpl(String calendarType, int field,
145                                                     int style, Locale locale, boolean javatime) {
146        String key = getResourceKey(calendarType, field, style, javatime);
147        Map<String, Integer> map = new TreeMap<>(LengthBasedComparator.INSTANCE);
148        if (key != null) {
149            LocaleResources lr = LocaleProviderAdapter.forType(type).getLocaleResources(locale);
150            String[] strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
151
152            // If standalone names are requested and no "standalone." resources are found,
153            // try the default ones instead.
154            if (strings == null && key.indexOf("standalone.") != -1) {
155                key = key.replaceFirst("standalone.", "");
156                strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
157            }
158
159            if (strings != null) {
160                if (!hasDuplicates(strings)) {
161                    if (field == YEAR) {
162                        if (strings.length > 0) {
163                            map.put(strings[0], 1);
164                        }
165                    } else {
166                        int base = (field == DAY_OF_WEEK) ? 1 : 0;
167                        for (int i = 0; i < strings.length; i++) {
168                            String name = strings[i];
169                            // Ignore any empty string (some standalone month names
170                            // are not defined)
171                            if (name.length() == 0) {
172                                continue;
173                            }
174                            map.put(name, base + i);
175                        }
176                    }
177                }
178            }
179        }
180        return map;
181    }
182
183    private int getBaseStyle(int style) {
184        return style & ~(SHORT_STANDALONE - SHORT_FORMAT);
185    }
186
187    /**
188     * Comparator implementation for TreeMap which iterates keys from longest
189     * to shortest.
190     */
191    private static class LengthBasedComparator implements Comparator<String> {
192        private static final LengthBasedComparator INSTANCE = new LengthBasedComparator();
193
194        private LengthBasedComparator() {
195        }
196
197        @Override
198        public int compare(String o1, String o2) {
199            int n = o2.length() - o1.length();
200            return (n == 0) ? o1.compareTo(o2) : n;
201        }
202    }
203
204    @Override
205    public Locale[] getAvailableLocales() {
206        return LocaleProviderAdapter.toLocaleArray(langtags);
207    }
208
209    @Override
210    public boolean isSupportedLocale(Locale locale) {
211        if (Locale.ROOT.equals(locale)) {
212            return true;
213        }
214        String calendarType = null;
215        if (locale.hasExtensions()) {
216            calendarType = locale.getUnicodeLocaleType("ca");
217            locale = locale.stripExtensions();
218        }
219
220        if (calendarType != null) {
221            switch (calendarType) {
222            case "buddhist":
223            case "japanese":
224            case "gregory":
225            case "islamic":
226            case "roc":
227                break;
228            default:
229                // Unknown calendar type
230                return false;
231            }
232        }
233        if (langtags.contains(locale.toLanguageTag())) {
234            return true;
235        }
236        if (type == LocaleProviderAdapter.Type.JRE) {
237            String oldname = locale.toString().replace('_', '-');
238            return langtags.contains(oldname);
239        }
240        return false;
241    }
242
243    @Override
244    public Set<String> getAvailableLanguageTags() {
245        return langtags;
246    }
247
248    private boolean hasDuplicates(String[] strings) {
249        int len = strings.length;
250        for (int i = 0; i < len - 1; i++) {
251            String a = strings[i];
252            if (a != null) {
253                for (int j = i + 1; j < len; j++) {
254                    if (a.equals(strings[j]))  {
255                        return true;
256                    }
257                }
258            }
259        }
260        return false;
261    }
262
263    private String getResourceKey(String type, int field, int style, boolean javatime) {
264        int baseStyle = getBaseStyle(style);
265        boolean isStandalone = (style != baseStyle);
266
267        if ("gregory".equals(type)) {
268            type = null;
269        }
270        boolean isNarrow = (baseStyle == NARROW_FORMAT);
271        StringBuilder key = new StringBuilder();
272        // If javatime is true, use prefix "java.time.".
273        if (javatime) {
274            key.append("java.time.");
275        }
276        switch (field) {
277        case ERA:
278            if (type != null) {
279                key.append(type).append('.');
280            }
281            if (isNarrow) {
282                key.append("narrow.");
283            } else {
284                // JRE and CLDR use different resource key conventions
285                // due to historical reasons. (JRE DateFormatSymbols.getEras returns
286                // abbreviations while other getShort*() return abbreviations.)
287                if (this.type == LocaleProviderAdapter.Type.JRE) {
288                    if (javatime) {
289                        if (baseStyle == LONG) {
290                            key.append("long.");
291                        }
292                    }
293                    if (baseStyle == SHORT) {
294                        key.append("short.");
295                    }
296                } else { // this.type == LocaleProviderAdapter.Type.CLDR
297                    if (baseStyle == LONG) {
298                        key.append("long.");
299                    }
300                }
301            }
302            key.append("Eras");
303            break;
304
305        case YEAR:
306            if (!isNarrow) {
307                key.append(type).append(".FirstYear");
308            }
309            break;
310
311        case MONTH:
312            if ("islamic".equals(type)) {
313                key.append(type).append('.');
314            }
315            if (isStandalone) {
316                key.append("standalone.");
317            }
318            key.append("Month").append(toStyleName(baseStyle));
319            break;
320
321        case DAY_OF_WEEK:
322            // support standalone day names
323            if (isStandalone) {
324                key.append("standalone.");
325            }
326            key.append("Day").append(toStyleName(baseStyle));
327            break;
328
329        case AM_PM:
330            if (isNarrow) {
331                key.append("narrow.");
332            }
333            key.append("AmPmMarkers");
334            break;
335        }
336        return key.length() > 0 ? key.toString() : null;
337    }
338
339    private String toStyleName(int baseStyle) {
340        switch (baseStyle) {
341        case SHORT:
342            return "Abbreviations";
343        case NARROW_FORMAT:
344            return "Narrows";
345        }
346        return "Names";
347    }
348}
349