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