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 hasDuplicates(String[] strings)260 private boolean hasDuplicates(String[] strings) { 261 int len = strings.length; 262 for (int i = 0; i < len - 1; i++) { 263 String a = strings[i]; 264 if (a != null) { 265 for (int j = i + 1; j < len; j++) { 266 if (a.equals(strings[j])) { 267 return true; 268 } 269 } 270 } 271 } 272 return false; 273 } 274 getResourceKey(String type, int field, int style, boolean javatime)275 private String getResourceKey(String type, int field, int style, boolean javatime) { 276 return getResourceKeyFor(this.type, type, field, style, javatime); 277 } 278 getResourceKeyFor(LocaleProviderAdapter.Type adapterType, String type, int field, int style, boolean javatime)279 private static String getResourceKeyFor(LocaleProviderAdapter.Type adapterType, 280 String type, int field, int style, boolean javatime) { 281 int baseStyle = getBaseStyle(style); 282 boolean isStandalone = (style != baseStyle); 283 284 if ("gregory".equals(type)) { 285 type = null; 286 } 287 boolean isNarrow = (baseStyle == NARROW_FORMAT); 288 StringBuilder key = new StringBuilder(); 289 // If javatime is true, use prefix "java.time.". 290 if (javatime) { 291 key.append("java.time."); 292 } 293 switch (field) { 294 case ERA: 295 if (type != null) { 296 key.append(type).append('.'); 297 } 298 if (isNarrow) { 299 key.append("narrow."); 300 } else { 301 // JRE and CLDR use different resource key conventions 302 // due to historical reasons. (JRE DateFormatSymbols.getEras returns 303 // abbreviations while other getShort*() return abbreviations.) 304 if (adapterType == LocaleProviderAdapter.Type.JRE) { 305 if (javatime) { 306 if (baseStyle == LONG) { 307 key.append("long."); 308 } 309 } 310 if (baseStyle == SHORT) { 311 key.append("short."); 312 } 313 } else { // this.type == LocaleProviderAdapter.Type.CLDR 314 if (baseStyle == LONG) { 315 key.append("long."); 316 } 317 } 318 } 319 key.append("Eras"); 320 break; 321 322 case YEAR: 323 if (!isNarrow) { 324 key.append(type).append(".FirstYear"); 325 } 326 break; 327 328 case MONTH: 329 if ("islamic".equals(type)) { 330 key.append(type).append('.'); 331 } 332 if (isStandalone) { 333 key.append("standalone."); 334 } 335 key.append("Month").append(toStyleName(baseStyle)); 336 break; 337 338 case DAY_OF_WEEK: 339 // support standalone day names 340 if (isStandalone) { 341 key.append("standalone."); 342 } 343 key.append("Day").append(toStyleName(baseStyle)); 344 break; 345 346 case AM_PM: 347 if (isNarrow) { 348 key.append("narrow."); 349 } 350 key.append("AmPmMarkers"); 351 break; 352 } 353 return key.length() > 0 ? key.toString() : null; 354 } 355 toStyleName(int baseStyle)356 private static String toStyleName(int baseStyle) { 357 switch (baseStyle) { 358 case SHORT: 359 return "Abbreviations"; 360 case NARROW_FORMAT: 361 return "Narrows"; 362 } 363 return "Names"; 364 } 365 } 366