1 /* 2 * Copyright (c) 2012, 2019, 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 package sun.util.locale.provider; 26 27 import static java.util.Calendar.*; 28 import java.util.Comparator; 29 import java.util.Locale; 30 import java.util.Map; 31 import java.util.Set; 32 import java.util.TreeMap; 33 import java.util.spi.CalendarNameProvider; 34 import sun.util.calendar.CalendarSystem; 35 import sun.util.calendar.Era; 36 37 /** 38 * Concrete implementation of the {@link java.util.spi.CalendarNameProvider 39 * CalendarNameProvider} class for the JRE LocaleProviderAdapter. 40 * 41 * @author Masayoshi Okutsu 42 * @author Naoto Sato 43 */ 44 public class CalendarNameProviderImpl extends CalendarNameProvider implements AvailableLanguageTags { 45 protected final LocaleProviderAdapter.Type type; 46 protected final Set<String> langtags; 47 CalendarNameProviderImpl(LocaleProviderAdapter.Type type, Set<String> langtags)48 public CalendarNameProviderImpl(LocaleProviderAdapter.Type type, Set<String> langtags) { 49 this.type = type; 50 this.langtags = langtags; 51 } 52 53 @Override getDisplayName(String calendarType, int field, int value, int style, Locale locale)54 public String getDisplayName(String calendarType, int field, int value, int style, Locale locale) { 55 return getDisplayNameImpl(calendarType, field, value, style, locale, false); 56 } 57 getJavaTimeDisplayName(String calendarType, int field, int value, int style, Locale locale)58 public String getJavaTimeDisplayName(String calendarType, int field, int value, int style, Locale locale) { 59 return getDisplayNameImpl(calendarType, field, value, style, locale, true); 60 } 61 getDisplayNameImpl(String calendarType, int field, int value, int style, Locale locale, boolean javatime)62 public String getDisplayNameImpl(String calendarType, int field, int value, int style, Locale locale, boolean javatime) { 63 String name = null; 64 String key = getResourceKey(calendarType, field, style, javatime); 65 if (key != null) { 66 LocaleResources lr = LocaleProviderAdapter.forType(type).getLocaleResources(locale); 67 String[] strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key); 68 69 // If standalone names are requested and no "standalone." resources are found, 70 // try the default ones instead. 71 if (strings == null && key.indexOf("standalone.") != -1) { 72 key = key.replaceFirst("standalone.", ""); 73 strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key); 74 } 75 76 if (strings != null && strings.length > 0) { 77 if (field == DAY_OF_WEEK || field == YEAR) { 78 --value; 79 } 80 if (value < 0) { 81 return null; 82 } else if (value >= strings.length) { 83 if (field == ERA && "japanese".equals(calendarType)) { 84 Era[] jeras = CalendarSystem.forName("japanese").getEras(); 85 if (value <= jeras.length) { 86 // Localized era name could not be retrieved from this provider. 87 // This can occur either for Reiwa or SupEra. 88 // 89 // If it's CLDR provider, try COMPAT first, which is guaranteed to have 90 // the name for Reiwa. 91 if (type == LocaleProviderAdapter.Type.CLDR) { 92 lr = LocaleProviderAdapter.forJRE().getLocaleResources(locale); 93 key = getResourceKeyFor(LocaleProviderAdapter.Type.JRE, 94 calendarType, field, style, javatime); 95 strings = 96 javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key); 97 } 98 if (strings == null || value >= strings.length) { 99 // Get the default name for SupEra 100 Era supEra = jeras[value - 1]; // 0-based index 101 if (javatime) { 102 return getBaseStyle(style) == NARROW_FORMAT ? 103 supEra.getAbbreviation() : 104 supEra.getName(); 105 } else { 106 return (style & LONG) != 0 ? 107 supEra.getName() : 108 supEra.getAbbreviation(); 109 } 110 } 111 } else { 112 return null; 113 } 114 } else { 115 return null; 116 } 117 } 118 name = strings[value]; 119 // If name is empty in standalone, try its `format' style. 120 if (name.isEmpty() 121 && (style == SHORT_STANDALONE || style == LONG_STANDALONE 122 || style == NARROW_STANDALONE)) { 123 name = getDisplayName(calendarType, field, value, 124 getBaseStyle(style), 125 locale); 126 } 127 } 128 } 129 return name; 130 } 131 132 private static int[] REST_OF_STYLES = { 133 SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE, 134 NARROW_FORMAT, NARROW_STANDALONE 135 }; 136 137 @Override getDisplayNames(String calendarType, int field, int style, Locale locale)138 public Map<String, Integer> getDisplayNames(String calendarType, int field, int style, Locale locale) { 139 Map<String, Integer> names; 140 if (style == ALL_STYLES) { 141 names = getDisplayNamesImpl(calendarType, field, SHORT_FORMAT, locale, false); 142 for (int st : REST_OF_STYLES) { 143 names.putAll(getDisplayNamesImpl(calendarType, field, st, locale, false)); 144 } 145 } else { 146 // specific style 147 names = getDisplayNamesImpl(calendarType, field, style, locale, false); 148 } 149 return names.isEmpty() ? null : names; 150 } 151 152 // NOTE: This method should be used ONLY BY JSR 310 classes. getJavaTimeDisplayNames(String calendarType, int field, int style, Locale locale)153 public Map<String, Integer> getJavaTimeDisplayNames(String calendarType, int field, int style, Locale locale) { 154 Map<String, Integer> names; 155 names = getDisplayNamesImpl(calendarType, field, style, locale, true); 156 return names.isEmpty() ? null : names; 157 } 158 getDisplayNamesImpl(String calendarType, int field, int style, Locale locale, boolean javatime)159 private Map<String, Integer> getDisplayNamesImpl(String calendarType, int field, 160 int style, Locale locale, boolean javatime) { 161 String key = getResourceKey(calendarType, field, style, javatime); 162 Map<String, Integer> map = new TreeMap<>(LengthBasedComparator.INSTANCE); 163 if (key != null) { 164 LocaleResources lr = LocaleProviderAdapter.forType(type).getLocaleResources(locale); 165 String[] strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key); 166 167 // If standalone names are requested and no "standalone." resources are found, 168 // try the default ones instead. 169 if (strings == null && key.indexOf("standalone.") != -1) { 170 key = key.replaceFirst("standalone.", ""); 171 strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key); 172 } 173 174 if (strings != null) { 175 if (!hasDuplicates(strings)) { 176 if (field == YEAR) { 177 if (strings.length > 0) { 178 map.put(strings[0], 1); 179 } 180 } else { 181 int base = (field == DAY_OF_WEEK) ? 1 : 0; 182 for (int i = 0; i < strings.length; i++) { 183 String name = strings[i]; 184 // Ignore any empty string (some standalone month names 185 // are not defined) 186 if (name.isEmpty()) { 187 continue; 188 } 189 map.put(name, base + i); 190 } 191 } 192 } 193 } 194 } 195 return map; 196 } 197 getBaseStyle(int style)198 private static int getBaseStyle(int style) { 199 return style & ~(SHORT_STANDALONE - SHORT_FORMAT); 200 } 201 202 /** 203 * Comparator implementation for TreeMap which iterates keys from longest 204 * to shortest. 205 */ 206 private static class LengthBasedComparator implements Comparator<String> { 207 private static final LengthBasedComparator INSTANCE = new LengthBasedComparator(); 208 LengthBasedComparator()209 private LengthBasedComparator() { 210 } 211 212 @Override compare(String o1, String o2)213 public int compare(String o1, String o2) { 214 int n = o2.length() - o1.length(); 215 return (n == 0) ? o1.compareTo(o2) : n; 216 } 217 } 218 219 @Override getAvailableLocales()220 public Locale[] getAvailableLocales() { 221 return LocaleProviderAdapter.toLocaleArray(langtags); 222 } 223 224 @Override isSupportedLocale(Locale locale)225 public boolean isSupportedLocale(Locale locale) { 226 if (Locale.ROOT.equals(locale)) { 227 return true; 228 } 229 String calendarType = null; 230 if (locale.hasExtensions()) { 231 calendarType = locale.getUnicodeLocaleType("ca"); 232 locale = locale.stripExtensions(); 233 } 234 235 if (calendarType != null) { 236 switch (calendarType) { 237 case "buddhist": 238 case "japanese": 239 case "gregory": 240 case "islamic": 241 case "roc": 242 break; 243 default: 244 // Unknown calendar type 245 return false; 246 } 247 } 248 if (langtags.contains(locale.toLanguageTag())) { 249 return true; 250 } 251 String oldname = locale.toString().replace('_', '-'); 252 return langtags.contains(oldname); 253 } 254 255 @Override getAvailableLanguageTags()256 public Set<String> getAvailableLanguageTags() { 257 return langtags; 258 } 259 260 // Check if each string is unique, except null or empty strings, 261 // as these strings are used for keys in the name-to-value map. hasDuplicates(String[] strings)262 private boolean hasDuplicates(String[] strings) { 263 int len = strings.length; 264 for (int i = 0; i < len - 1; i++) { 265 String a = strings[i]; 266 if (a != null && !a.isEmpty()) { 267 for (int j = i + 1; j < len; j++) { 268 if (a.equals(strings[j])) { 269 return true; 270 } 271 } 272 } 273 } 274 return false; 275 } 276 getResourceKey(String type, int field, int style, boolean javatime)277 private String getResourceKey(String type, int field, int style, boolean javatime) { 278 return getResourceKeyFor(this.type, type, field, style, javatime); 279 } 280 getResourceKeyFor(LocaleProviderAdapter.Type adapterType, String type, int field, int style, boolean javatime)281 private static String getResourceKeyFor(LocaleProviderAdapter.Type adapterType, 282 String type, int field, int style, boolean javatime) { 283 int baseStyle = getBaseStyle(style); 284 boolean isStandalone = (style != baseStyle); 285 286 if ("gregory".equals(type)) { 287 type = null; 288 } 289 boolean isNarrow = (baseStyle == NARROW_FORMAT); 290 StringBuilder key = new StringBuilder(); 291 // If javatime is true, use prefix "java.time.". 292 if (javatime) { 293 key.append("java.time."); 294 } 295 switch (field) { 296 case ERA: 297 if (type != null) { 298 key.append(type).append('.'); 299 } 300 if (isNarrow) { 301 key.append("narrow."); 302 } else { 303 // JRE and CLDR use different resource key conventions 304 // due to historical reasons. (JRE DateFormatSymbols.getEras returns 305 // abbreviations while other getShort*() return abbreviations.) 306 if (adapterType == LocaleProviderAdapter.Type.JRE) { 307 if (javatime) { 308 if (baseStyle == LONG) { 309 key.append("long."); 310 } 311 } 312 if (baseStyle == SHORT) { 313 key.append("short."); 314 } 315 } else { // this.type == LocaleProviderAdapter.Type.CLDR 316 if (baseStyle == LONG) { 317 key.append("long."); 318 } 319 } 320 } 321 key.append("Eras"); 322 break; 323 324 case YEAR: 325 if (!isNarrow) { 326 key.append(type).append(".FirstYear"); 327 } 328 break; 329 330 case MONTH: 331 if ("islamic".equals(type)) { 332 key.append(type).append('.'); 333 } 334 if (isStandalone) { 335 key.append("standalone."); 336 } 337 key.append("Month").append(toStyleName(baseStyle)); 338 break; 339 340 case DAY_OF_WEEK: 341 // support standalone day names 342 if (isStandalone) { 343 key.append("standalone."); 344 } 345 key.append("Day").append(toStyleName(baseStyle)); 346 break; 347 348 case AM_PM: 349 if (isNarrow) { 350 key.append("narrow."); 351 } 352 key.append("AmPmMarkers"); 353 break; 354 } 355 return key.length() > 0 ? key.toString() : null; 356 } 357 toStyleName(int baseStyle)358 private static String toStyleName(int baseStyle) { 359 switch (baseStyle) { 360 case SHORT: 361 return "Abbreviations"; 362 case NARROW_FORMAT: 363 return "Narrows"; 364 } 365 return "Names"; 366 } 367 } 368