1 /*
2  * Copyright (c) 2012, 2013, 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 sun.util.locale.provider;
27 
28 import android.icu.text.DateFormatSymbols;
29 import android.icu.util.ULocale;
30 
31 import static java.util.Calendar.*;
32 
33 import java.util.Calendar;
34 import java.util.LinkedHashMap;
35 import java.util.Locale;
36 import java.util.Map;
37 
38 // Android-changed: remove mention of CalendarDataProvider that's not used on Android.
39 /**
40  * {@code CalendarDataUtility} is a utility class for getting calendar field name values.
41  *
42  * @author Masayoshi Okutsu
43  * @author Naoto Sato
44  */
45 public class CalendarDataUtility {
46     // Android-note: This class has been rewritten from scratch and is effectively forked.
47     // The API (names of public constants, method signatures etc.) generally derives
48     // from OpenJDK so that other OpenJDK code that refers to this class doesn't need to
49     // be changed, but the implementation has been rewritten; logic / identifiers
50     // that weren't used from anywhere else have been dropped altogether.
51 
52     // Android-removed: Dead code, unused on Android.
53     // public final static String FIRST_DAY_OF_WEEK = "firstDayOfWeek";
54     // public final static String MINIMAL_DAYS_IN_FIRST_WEEK = "minimalDaysInFirstWeek";
55 
56     // Android-added: Calendar name constants for use in retrievFieldValueName.
57     private static final String ISLAMIC_CALENDAR = "islamic";
58     private static final String GREGORIAN_CALENDAR = "gregorian";
59     private static final String BUDDHIST_CALENDAR = "buddhist";
60     private static final String JAPANESE_CALENDAR = "japanese";
61 
62     // Android-added: REST_OF_STYLES array for use in retrieveFieldValueNames.
63     // ALL_STYLES implies SHORT_FORMAT and all of these values.
64     private static int[] REST_OF_STYLES = {
65             SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE,
66             NARROW_FORMAT, NARROW_STANDALONE
67     };
68 
69     // No instantiation
CalendarDataUtility()70     private CalendarDataUtility() {
71     }
72 
73     // BEGIN Android-removed: Dead code, unused on Android.
74     // Clients should use libcore.icu.LocaleData or android.icu.util.Calendar.WeekData instead.
75     /*
76     public static int retrieveFirstDayOfWeek(Locale locale) {
77         LocaleServiceProviderPool pool =
78                 LocaleServiceProviderPool.getPool(CalendarDataProvider.class);
79         Integer value = pool.getLocalizedObject(CalendarWeekParameterGetter.INSTANCE,
80                                                 locale, FIRST_DAY_OF_WEEK);
81         return (value != null && (value >= SUNDAY && value <= SATURDAY)) ? value : SUNDAY;
82     }
83 
84     public static int retrieveMinimalDaysInFirstWeek(Locale locale) {
85         LocaleServiceProviderPool pool =
86                 LocaleServiceProviderPool.getPool(CalendarDataProvider.class);
87         Integer value = pool.getLocalizedObject(CalendarWeekParameterGetter.INSTANCE,
88                                                 locale, MINIMAL_DAYS_IN_FIRST_WEEK);
89         return (value != null && (value >= 1 && value <= 7)) ? value : 1;
90     }
91     */
92     // END Android-removed: Dead code, unused on Android.
93 
94     // BEGIN Android-changed: Implement on top of ICU.
95     /*
96     public static String retrieveFieldValueName(String id, int field, int value, int style, Locale locale) {
97         LocaleServiceProviderPool pool =
98                 LocaleServiceProviderPool.getPool(CalendarNameProvider.class);
99         return pool.getLocalizedObject(CalendarFieldValueNameGetter.INSTANCE, locale, normalizeCalendarType(id),
100                                        field, value, style, false);
101     }
102     */
retrieveFieldValueName(String id, int field, int value, int style, Locale locale)103     public static String retrieveFieldValueName(String id, int field, int value, int style,
104             Locale locale) {
105         if (field == Calendar.ERA) {
106             // For era the field value does not always equal the index into the names array.
107             switch (normalizeCalendarType(id)) {
108                 // These calendars have only one era, but represented it by the value 1.
109                 case BUDDHIST_CALENDAR:
110                 case ISLAMIC_CALENDAR:
111                     value -= 1;
112                     break;
113                 case JAPANESE_CALENDAR:
114                     // CLDR contains full data for historical eras, java.time only supports the 4
115                     // modern eras and numbers the modern eras starting with 1 (MEIJI). There are
116                     // 232 historical eras in CLDR/ICU so to get the real offset, we add 231.
117                     value += 231;
118                     break;
119                 default:
120                     // Other eras use 0-based values (e.g. 0=BCE, 1=CE for gregorian).
121                     break;
122             }
123         }
124         if (value < 0) {
125             return null;
126         }
127         String[] names = getNames(id, field, style, locale);
128         if (value >= names.length) {
129             return null;
130         }
131         return names[value];
132     }
133     // END Android-changed: Implement on top of ICU.
134 
135     // BEGIN Android-changed: Implement on top of ICU.
136     /*
137     public static String retrieveJavaTimeFieldValueName(String id, int field, int value, int style, Locale locale) {
138         LocaleServiceProviderPool pool =
139                 LocaleServiceProviderPool.getPool(CalendarNameProvider.class);
140         String name;
141         name = pool.getLocalizedObject(CalendarFieldValueNameGetter.INSTANCE, locale, normalizeCalendarType(id),
142                                        field, value, style, true);
143         if (name == null) {
144             name = pool.getLocalizedObject(CalendarFieldValueNameGetter.INSTANCE, locale, normalizeCalendarType(id),
145                                            field, value, style, false);
146         }
147         return name;
148     }
149     */
retrieveJavaTimeFieldValueName(String id, int field, int value, int style, Locale locale)150     public static String retrieveJavaTimeFieldValueName(String id, int field, int value, int style,
151             Locale locale) {
152         // Don't distinguish between retrieve* and retrieveJavaTime* methods.
153         return retrieveFieldValueName(id, field, value, style, locale);
154     }
155     // END Android-changed: Implement on top of ICU.
156 
157     // BEGIN Android-changed: Implement on top of ICU.
158     /*
159     public static Map<String, Integer> retrieveFieldValueNames(String id, int field, int style, Locale locale) {
160         LocaleServiceProviderPool pool =
161             LocaleServiceProviderPool.getPool(CalendarNameProvider.class);
162         return pool.getLocalizedObject(CalendarFieldValueNamesMapGetter.INSTANCE, locale,
163                                        normalizeCalendarType(id), field, style, false);
164     }
165     */
retrieveFieldValueNames(String id, int field, int style, Locale locale)166     public static Map<String, Integer> retrieveFieldValueNames(String id, int field, int style,
167             Locale locale) {
168         Map<String, Integer> names;
169         if (style == ALL_STYLES) {
170             names = retrieveFieldValueNamesImpl(id, field, SHORT_FORMAT, locale);
171             for (int st : REST_OF_STYLES) {
172                 names.putAll(retrieveFieldValueNamesImpl(id, field, st, locale));
173             }
174         } else {
175             // specific style
176             names = retrieveFieldValueNamesImpl(id, field, style, locale);
177         }
178         return names.isEmpty() ? null : names;
179     }
180     // END Android-changed: Implement on top of ICU.
181 
182     // BEGIN Android-changed: Implement on top of ICU.
183     /*
184     public static Map<String, Integer> retrieveJavaTimeFieldValueNames(String id, int field, int style, Locale locale) {
185         LocaleServiceProviderPool pool =
186             LocaleServiceProviderPool.getPool(CalendarNameProvider.class);
187         Map<String, Integer> map;
188         map = pool.getLocalizedObject(CalendarFieldValueNamesMapGetter.INSTANCE, locale,
189                                        normalizeCalendarType(id), field, style, true);
190         if (map == null) {
191             map = pool.getLocalizedObject(CalendarFieldValueNamesMapGetter.INSTANCE, locale,
192                                            normalizeCalendarType(id), field, style, false);
193         }
194         return map;
195     }
196     */
retrieveJavaTimeFieldValueNames(String id, int field, int style, Locale locale)197     public static Map<String, Integer> retrieveJavaTimeFieldValueNames(String id, int field,
198             int style, Locale locale) {
199         // Don't distinguish between retrieve* and retrieveJavaTime* methods.
200         return retrieveFieldValueNames(id, field, style, locale);
201     }
202     // END Android-changed: Implement on top of ICU.
203 
204     // Android-changed: Added private modifier for normalizeCalendarType().
205     // static String normalizeCalendarType(String requestID) {
normalizeCalendarType(String requestID)206     private static String normalizeCalendarType(String requestID) {
207         String type;
208         // Android-changed: normalize "gregory" to "gregorian", not the other way around.
209         // See android.icu.text.DateFormatSymbols.CALENDAR_CLASSES for reference.
210         // if (requestID.equals("gregorian") || requestID.equals("iso8601")) {
211         //    type = "gregory";
212         // } else if (requestID.startsWith("islamic")) {
213         //    type = "islamic";
214         if (requestID.equals("gregory") || requestID.equals("iso8601")) {
215             type = GREGORIAN_CALENDAR;
216         } else if (requestID.startsWith(ISLAMIC_CALENDAR)) {
217             type = ISLAMIC_CALENDAR;
218         } else {
219             type = requestID;
220         }
221         return type;
222     }
223 
224     // BEGIN Android-added: Various private helper methods.
retrieveFieldValueNamesImpl(String id, int field, int style, Locale locale)225     private static Map<String, Integer> retrieveFieldValueNamesImpl(String id, int field, int style,
226             Locale locale) {
227         String[] names = getNames(id, field, style, locale);
228         int skipped = 0;
229         int offset = 0;
230         if (field == Calendar.ERA) {
231             // See retrieveFieldValueName() for explanation of this code and the values used.
232             switch (normalizeCalendarType(id)) {
233                 case BUDDHIST_CALENDAR:
234                 case ISLAMIC_CALENDAR:
235                     offset = 1;
236                     break;
237                 case JAPANESE_CALENDAR:
238                     skipped = 232;
239                     offset = -231;
240                     break;
241                 default:
242                     break;
243             }
244         }
245         Map<String, Integer> result = new LinkedHashMap<>();
246         for (int i = skipped; i < names.length; i++) {
247             if (names[i].isEmpty()) {
248                 continue;
249             }
250 
251             if (result.put(names[i], i + offset) != null) {
252                 // Duplicate names indicate that the names would be ambiguous. Skip this style for
253                 // ALL_STYLES. In other cases this results in null being returned in
254                 // retrieveValueNames(), which is required by Calendar.getDisplayNames().
255                 return new LinkedHashMap<>();
256             }
257         }
258         return result;
259     }
260 
getNames(String id, int field, int style, Locale locale)261     private static String[] getNames(String id, int field, int style, Locale locale) {
262         int context = toContext(style);
263         int width = toWidth(style);
264         DateFormatSymbols symbols = getDateFormatSymbols(id, locale);
265         switch (field) {
266             case Calendar.MONTH:
267                 return symbols.getMonths(context, width);
268             case Calendar.ERA:
269                 switch (width) {
270                     case DateFormatSymbols.NARROW:
271                         return symbols.getNarrowEras();
272                     case DateFormatSymbols.ABBREVIATED:
273                         return symbols.getEras();
274                     case DateFormatSymbols.WIDE:
275                         return symbols.getEraNames();
276                     default:
277                         throw new UnsupportedOperationException("Unknown width: " + width);
278                 }
279             case Calendar.DAY_OF_WEEK:
280                 return symbols.getWeekdays(context, width);
281             case Calendar.AM_PM:
282                 return symbols.getAmPmStrings();
283             default:
284                 throw new UnsupportedOperationException("Unknown field: " + field);
285         }
286     }
287 
getDateFormatSymbols(String id, Locale locale)288     private static DateFormatSymbols getDateFormatSymbols(String id, Locale locale) {
289         String calendarType = normalizeCalendarType(id);
290         return new DateFormatSymbols(ULocale.forLocale(locale), calendarType);
291     }
292 
293     /**
294      * Transform a {@link Calendar} style constant into an ICU width value.
295      */
toWidth(int style)296     private static int toWidth(int style) {
297         switch (style) {
298             case Calendar.SHORT_FORMAT:
299             case Calendar.SHORT_STANDALONE:
300                 return DateFormatSymbols.ABBREVIATED;
301             case Calendar.NARROW_FORMAT:
302             case Calendar.NARROW_STANDALONE:
303                 return DateFormatSymbols.NARROW;
304             case Calendar.LONG_FORMAT:
305             case Calendar.LONG_STANDALONE:
306                 return DateFormatSymbols.WIDE;
307             default:
308                 throw new IllegalArgumentException("Invalid style: " + style);
309         }
310     }
311 
312     /**
313      * Transform a {@link Calendar} style constant into an ICU context value.
314      */
toContext(int style)315     private static int toContext(int style) {
316         switch (style) {
317             case Calendar.SHORT_FORMAT:
318             case Calendar.NARROW_FORMAT:
319             case Calendar.LONG_FORMAT:
320                 return DateFormatSymbols.FORMAT;
321             case Calendar.SHORT_STANDALONE:
322             case Calendar.NARROW_STANDALONE:
323             case Calendar.LONG_STANDALONE:
324                 return DateFormatSymbols.STANDALONE;
325             default:
326                 throw new IllegalArgumentException("Invalid style: " + style);
327         }
328     }
329     // END Android-added: Various private helper methods.
330 
331     // BEGIN Android-removed: Dead code, unused on Android.
332     /*
333     /**
334      * Obtains a localized field value string from a CalendarDataProvider
335      * implementation.
336      *
337     private static class CalendarFieldValueNameGetter
338         implements LocaleServiceProviderPool.LocalizedObjectGetter<CalendarNameProvider,
339                                                                    String> {
340         private static final CalendarFieldValueNameGetter INSTANCE =
341             new CalendarFieldValueNameGetter();
342 
343         @Override
344         public String getObject(CalendarNameProvider calendarNameProvider,
345                                 Locale locale,
346                                 String requestID, // calendarType
347                                 Object... params) {
348             assert params.length == 4;
349             int field = (int) params[0];
350             int value = (int) params[1];
351             int style = (int) params[2];
352             boolean javatime = (boolean) params[3];
353 
354             // If javatime is true, resources from CLDR have precedence over JRE
355             // native resources.
356             if (javatime && calendarNameProvider instanceof CalendarNameProviderImpl) {
357                 String name;
358                 name = ((CalendarNameProviderImpl)calendarNameProvider)
359                         .getJavaTimeDisplayName(requestID, field, value, style, locale);
360                 return name;
361             }
362             return calendarNameProvider.getDisplayName(requestID, field, value, style, locale);
363         }
364     }
365 
366     /**
367      * Obtains a localized field-value pairs from a CalendarDataProvider
368      * implementation.
369      *
370     private static class CalendarFieldValueNamesMapGetter
371         implements LocaleServiceProviderPool.LocalizedObjectGetter<CalendarNameProvider,
372                                                                    Map<String, Integer>> {
373         private static final CalendarFieldValueNamesMapGetter INSTANCE =
374             new CalendarFieldValueNamesMapGetter();
375 
376         @Override
377         public Map<String, Integer> getObject(CalendarNameProvider calendarNameProvider,
378                                               Locale locale,
379                                               String requestID, // calendarType
380                                               Object... params) {
381             assert params.length == 3;
382             int field = (int) params[0];
383             int style = (int) params[1];
384             boolean javatime = (boolean) params[2];
385 
386             // If javatime is true, resources from CLDR have precedence over JRE
387             // native resources.
388             if (javatime && calendarNameProvider instanceof CalendarNameProviderImpl) {
389                 Map<String, Integer> map;
390                 map = ((CalendarNameProviderImpl)calendarNameProvider)
391                         .getJavaTimeDisplayNames(requestID, field, style, locale);
392                 return map;
393             }
394             return calendarNameProvider.getDisplayNames(requestID, field, style, locale);
395         }
396     }
397 
398      private static class CalendarWeekParameterGetter
399         implements LocaleServiceProviderPool.LocalizedObjectGetter<CalendarDataProvider,
400                                                                    Integer> {
401         private static final CalendarWeekParameterGetter INSTANCE =
402             new CalendarWeekParameterGetter();
403 
404         @Override
405         public Integer getObject(CalendarDataProvider calendarDataProvider,
406                                  Locale locale,
407                                  String requestID,    // resource key
408                                  Object... params) {
409             assert params.length == 0;
410             int value;
411             switch (requestID) {
412             case FIRST_DAY_OF_WEEK:
413                 value = calendarDataProvider.getFirstDayOfWeek(locale);
414                 break;
415             case MINIMAL_DAYS_IN_FIRST_WEEK:
416                 value = calendarDataProvider.getMinimalDaysInFirstWeek(locale);
417                 break;
418             default:
419                 throw new InternalError("invalid requestID: " + requestID);
420             }
421             return (value != 0) ? value : null;
422         }
423     }
424     */
425     // END Android-removed: Dead code, unused on Android.
426 }
427