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