Home | History | Annotate | Download | only in provider
      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 
     47     // Android-note: This class has been rewritten from scratch, keeping its API the same.
     48     // Since Android gets its calendar related data from ICU, the implementation of this class is
     49     // effectively independent of the upstream class, with the only similarity being is API in
     50     // order to keep the necessary modifications outside of this class to a minimum.
     51 
     52     // Android-added: calendar name constants for use in retrievFieldValueName.
     53     private static final String ISLAMIC_CALENDAR = "islamic";
     54     private static final String GREGORIAN_CALENDAR = "gregorian";
     55     private static final String BUDDHIST_CALENDAR = "buddhist";
     56     private static final String JAPANESE_CALENDAR = "japanese";
     57 
     58     // Android-added: REST_OF_STYLES array for use in retrieveFieldValueNames.
     59     // ALL_STYLES implies SHORT_FORMAT and all of these values.
     60     private static int[] REST_OF_STYLES = {
     61             SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE,
     62             NARROW_FORMAT, NARROW_STANDALONE
     63     };
     64 
     65     // Android-removed: unused FIRST_DAY_OF_WEEK and MINIMAL_DAYS_IN_FIRST_WEEK constants.
     66 
     67     // No instantiation
     68     private CalendarDataUtility() {
     69     }
     70 
     71     // Android-removed: retrieveFirstDayOfWeek and retrieveMinimalDaysInFirstWeek.
     72     // Android-note: use libcore.icu.LocaleData or android.icu.util.Calendar.WeekData instead.
     73 
     74     public static String retrieveFieldValueName(String id, int field, int value, int style,
     75             Locale locale) {
     76         // Android-changed: delegate to ICU.
     77         if (field == Calendar.ERA) {
     78             // For era the field value does not always equal the index into the names array.
     79             switch (normalizeCalendarType(id)) {
     80                 // These calendars have only one era, but represented it by the value 1.
     81                 case BUDDHIST_CALENDAR:
     82                 case ISLAMIC_CALENDAR:
     83                     value -= 1;
     84                     break;
     85                 case JAPANESE_CALENDAR:
     86                     // CLDR contains full data for historical eras, java.time only supports the 4
     87                     // modern eras and numbers the modern eras starting with 1 (MEIJI). There are
     88                     // 232 historical eras in CLDR/ICU so to get the real offset, we add 231.
     89                     value += 231;
     90                     break;
     91                 default:
     92                     // Other eras use 0-based values (e.g. 0=BCE, 1=CE for gregorian).
     93                     break;
     94             }
     95         }
     96         if (value < 0) {
     97             return null;
     98         }
     99         String[] names = getNames(id, field, style, locale);
    100         if (value >= names.length) {
    101             return null;
    102         }
    103         return names[value];
    104     }
    105 
    106     public static String retrieveJavaTimeFieldValueName(String id, int field, int value, int style,
    107             Locale locale) {
    108         // Android-changed: don't distinguish between retrieve* and retrieveJavaTime* methods.
    109         return retrieveFieldValueName(id, field, value, style, locale);
    110     }
    111 
    112     public static Map<String, Integer> retrieveFieldValueNames(String id, int field, int style,
    113             Locale locale) {
    114         // Android-changed: delegate to ICU.
    115         Map<String, Integer> names;
    116         if (style == ALL_STYLES) {
    117             names = retrieveFieldValueNamesImpl(id, field, SHORT_FORMAT, locale);
    118             for (int st : REST_OF_STYLES) {
    119                 names.putAll(retrieveFieldValueNamesImpl(id, field, st, locale));
    120             }
    121         } else {
    122             // specific style
    123             names = retrieveFieldValueNamesImpl(id, field, style, locale);
    124         }
    125         return names.isEmpty() ? null : names;
    126     }
    127 
    128     public static Map<String, Integer> retrieveJavaTimeFieldValueNames(String id, int field,
    129             int style, Locale locale) {
    130         // Android-changed: don't distinguish between retrieve* and retrieveJavaTime* methods.
    131         return retrieveFieldValueNames(id, field, style, locale);
    132     }
    133 
    134     private static String normalizeCalendarType(String requestID) {
    135         String type;
    136         // Android-changed: normalize "gregory" to "gregorian", not the other way around.
    137         // See android.icu.text.DateFormatSymbols.CALENDAR_CLASSES for reference.
    138         if (requestID.equals("gregory") || requestID.equals("iso8601")) {
    139             type = GREGORIAN_CALENDAR;
    140         } else if (requestID.startsWith(ISLAMIC_CALENDAR)) {
    141             type = ISLAMIC_CALENDAR;
    142         } else {
    143             type = requestID;
    144         }
    145         return type;
    146     }
    147 
    148     // BEGIN Android-added: various private helper methods.
    149     private static Map<String, Integer> retrieveFieldValueNamesImpl(String id, int field, int style,
    150             Locale locale) {
    151         String[] names = getNames(id, field, style, locale);
    152         int skipped = 0;
    153         int offset = 0;
    154         if (field == Calendar.ERA) {
    155             // See retrieveFieldValueName() for explanation of this code and the values used.
    156             switch (normalizeCalendarType(id)) {
    157                 case BUDDHIST_CALENDAR:
    158                 case ISLAMIC_CALENDAR:
    159                     offset = 1;
    160                     break;
    161                 case JAPANESE_CALENDAR:
    162                     skipped = 232;
    163                     offset = -231;
    164                     break;
    165                 default:
    166                     break;
    167             }
    168         }
    169         Map<String, Integer> result = new LinkedHashMap<>();
    170         for (int i = skipped; i < names.length; i++) {
    171             if (names[i].isEmpty()) {
    172                 continue;
    173             }
    174 
    175             if (result.put(names[i], i + offset) != null) {
    176                 // Duplicate names indicate that the names would be ambiguous. Skip this style for
    177                 // ALL_STYLES. In other cases this results in null being returned in
    178                 // retrieveValueNames(), which is required by Calendar.getDisplayNames().
    179                 return new LinkedHashMap<>();
    180             }
    181         }
    182         return result;
    183     }
    184 
    185     private static String[] getNames(String id, int field, int style, Locale locale) {
    186         int context = toContext(style);
    187         int width = toWidth(style);
    188         DateFormatSymbols symbols = getDateFormatSymbols(id, locale);
    189         switch (field) {
    190             case Calendar.MONTH:
    191                 return symbols.getMonths(context, width);
    192             case Calendar.ERA:
    193                 switch (width) {
    194                     case DateFormatSymbols.NARROW:
    195                         return symbols.getNarrowEras();
    196                     case DateFormatSymbols.ABBREVIATED:
    197                         return symbols.getEras();
    198                     case DateFormatSymbols.WIDE:
    199                         return symbols.getEraNames();
    200                     default:
    201                         throw new UnsupportedOperationException("Unknown width: " + width);
    202                 }
    203             case Calendar.DAY_OF_WEEK:
    204                 return symbols.getWeekdays(context, width);
    205             case Calendar.AM_PM:
    206                 return symbols.getAmPmStrings();
    207             default:
    208                 throw new UnsupportedOperationException("Unknown field: " + field);
    209         }
    210     }
    211 
    212     private static DateFormatSymbols getDateFormatSymbols(String id, Locale locale) {
    213         String calendarType = normalizeCalendarType(id);
    214         return new DateFormatSymbols(ULocale.forLocale(locale), calendarType);
    215     }
    216 
    217     /**
    218      * Transform a {@link Calendar} style constant into an ICU width value.
    219      */
    220     private static int toWidth(int style) {
    221         switch (style) {
    222             case Calendar.SHORT_FORMAT:
    223             case Calendar.SHORT_STANDALONE:
    224                 return DateFormatSymbols.ABBREVIATED;
    225             case Calendar.NARROW_FORMAT:
    226             case Calendar.NARROW_STANDALONE:
    227                 return DateFormatSymbols.NARROW;
    228             case Calendar.LONG_FORMAT:
    229             case Calendar.LONG_STANDALONE:
    230                 return DateFormatSymbols.WIDE;
    231             default:
    232                 throw new IllegalArgumentException("Invalid style: " + style);
    233         }
    234     }
    235 
    236     /**
    237      * Transform a {@link Calendar} style constant into an ICU context value.
    238      */
    239     private static int toContext(int style) {
    240         switch (style) {
    241             case Calendar.SHORT_FORMAT:
    242             case Calendar.NARROW_FORMAT:
    243             case Calendar.LONG_FORMAT:
    244                 return DateFormatSymbols.FORMAT;
    245             case Calendar.SHORT_STANDALONE:
    246             case Calendar.NARROW_STANDALONE:
    247             case Calendar.LONG_STANDALONE:
    248                 return DateFormatSymbols.STANDALONE;
    249             default:
    250                 throw new IllegalArgumentException("Invalid style: " + style);
    251         }
    252     }
    253     // END Android-added: various private helper methods.
    254 
    255     // Android-removed: CalendarFieldValueNameGetter, CalendarFieldValueNamesMapGetter
    256     // Android-removed: CalendarWeekParameterGetter
    257 }
    258