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