1 /* 2 * Copyright (c) 2012, 2018, 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 java.security.AccessController; 29 import java.security.AccessControlException; 30 import java.security.PrivilegedAction; 31 import java.security.PrivilegedActionException; 32 import java.security.PrivilegedExceptionAction; 33 import java.text.spi.BreakIteratorProvider; 34 import java.text.spi.CollatorProvider; 35 import java.text.spi.DateFormatProvider; 36 import java.text.spi.DateFormatSymbolsProvider; 37 import java.text.spi.DecimalFormatSymbolsProvider; 38 import java.text.spi.NumberFormatProvider; 39 import java.util.Collections; 40 import java.util.HashSet; 41 import java.util.List; 42 import java.util.Locale; 43 import java.util.ResourceBundle; 44 import java.util.ServiceLoader; 45 import java.util.ServiceConfigurationError; 46 import java.util.Set; 47 import java.util.StringTokenizer; 48 import java.util.concurrent.ConcurrentHashMap; 49 import java.util.concurrent.ConcurrentMap; 50 import java.util.spi.CalendarDataProvider; 51 import java.util.spi.CalendarNameProvider; 52 import java.util.spi.CurrencyNameProvider; 53 import java.util.spi.LocaleNameProvider; 54 import java.util.spi.LocaleServiceProvider; 55 import java.util.spi.TimeZoneNameProvider; 56 import sun.text.spi.JavaTimeDateTimePatternProvider; 57 import sun.util.resources.LocaleData; 58 import sun.util.spi.CalendarProvider; 59 60 /** 61 * LocaleProviderAdapter implementation for the legacy JRE locale data. 62 * 63 * @author Naoto Sato 64 * @author Masayoshi Okutsu 65 */ 66 public class JRELocaleProviderAdapter extends LocaleProviderAdapter implements ResourceBundleBasedAdapter { 67 68 private final ConcurrentMap<String, Set<String>> langtagSets 69 = new ConcurrentHashMap<>(); 70 71 private final ConcurrentMap<Locale, LocaleResources> localeResourcesMap 72 = new ConcurrentHashMap<>(); 73 74 // LocaleData specific to this LocaleProviderAdapter. 75 private volatile LocaleData localeData; 76 77 /** 78 * Returns the type of this LocaleProviderAdapter 79 */ 80 @Override 81 public LocaleProviderAdapter.Type getAdapterType() { 82 return Type.JRE; 83 } 84 85 /** 86 * Getter method for Locale Service Providers 87 */ 88 @Override 89 @SuppressWarnings("unchecked") 90 public <P extends LocaleServiceProvider> P getLocaleServiceProvider(Class<P> c) { 91 switch (c.getSimpleName()) { 92 case "BreakIteratorProvider": 93 return (P) getBreakIteratorProvider(); 94 case "CollatorProvider": 95 return (P) getCollatorProvider(); 96 case "DateFormatProvider": 97 return (P) getDateFormatProvider(); 98 case "DateFormatSymbolsProvider": 99 return (P) getDateFormatSymbolsProvider(); 100 case "DecimalFormatSymbolsProvider": 101 return (P) getDecimalFormatSymbolsProvider(); 102 case "NumberFormatProvider": 103 return (P) getNumberFormatProvider(); 104 case "CurrencyNameProvider": 105 return (P) getCurrencyNameProvider(); 106 case "LocaleNameProvider": 107 return (P) getLocaleNameProvider(); 108 case "TimeZoneNameProvider": 109 return (P) getTimeZoneNameProvider(); 110 case "CalendarDataProvider": 111 return (P) getCalendarDataProvider(); 112 case "CalendarNameProvider": 113 return (P) getCalendarNameProvider(); 114 case "CalendarProvider": 115 return (P) getCalendarProvider(); 116 case "JavaTimeDateTimePatternProvider": 117 return (P) getJavaTimeDateTimePatternProvider(); 118 default: 119 throw new InternalError("should not come down here"); 120 } 121 } 122 123 private volatile BreakIteratorProvider breakIteratorProvider; 124 private volatile CollatorProvider collatorProvider; 125 private volatile DateFormatProvider dateFormatProvider; 126 private volatile DateFormatSymbolsProvider dateFormatSymbolsProvider; 127 private volatile DecimalFormatSymbolsProvider decimalFormatSymbolsProvider; 128 private volatile NumberFormatProvider numberFormatProvider; 129 130 private volatile CurrencyNameProvider currencyNameProvider; 131 private volatile LocaleNameProvider localeNameProvider; 132 protected volatile TimeZoneNameProvider timeZoneNameProvider; 133 protected volatile CalendarDataProvider calendarDataProvider; 134 protected volatile CalendarNameProvider calendarNameProvider; 135 136 private volatile CalendarProvider calendarProvider; 137 private volatile JavaTimeDateTimePatternProvider javaTimeDateTimePatternProvider; 138 139 /* 140 * Getter methods for java.text.spi.* providers 141 */ 142 @Override 143 public BreakIteratorProvider getBreakIteratorProvider() { 144 if (breakIteratorProvider == null) { 145 BreakIteratorProvider provider = AccessController.doPrivileged( 146 (PrivilegedAction<BreakIteratorProvider>) () -> 147 new BreakIteratorProviderImpl( 148 getAdapterType(), 149 getLanguageTagSet("FormatData"))); 150 151 synchronized (this) { 152 if (breakIteratorProvider == null) { 153 breakIteratorProvider = provider; 154 } 155 } 156 } 157 return breakIteratorProvider; 158 } 159 160 @Override 161 public CollatorProvider getCollatorProvider() { 162 if (collatorProvider == null) { 163 CollatorProvider provider = AccessController.doPrivileged( 164 (PrivilegedAction<CollatorProvider>) () -> 165 new CollatorProviderImpl( 166 getAdapterType(), 167 getLanguageTagSet("CollationData"))); 168 169 synchronized (this) { 170 if (collatorProvider == null) { 171 collatorProvider = provider; 172 } 173 } 174 } 175 return collatorProvider; 176 } 177 178 @Override 179 public DateFormatProvider getDateFormatProvider() { 180 if (dateFormatProvider == null) { 181 DateFormatProvider provider = AccessController.doPrivileged( 182 (PrivilegedAction<DateFormatProvider>) () -> 183 new DateFormatProviderImpl( 184 getAdapterType(), 185 getLanguageTagSet("FormatData"))); 186 187 synchronized (this) { 188 if (dateFormatProvider == null) { 189 dateFormatProvider = provider; 190 } 191 } 192 } 193 return dateFormatProvider; 194 } 195 196 @Override 197 public DateFormatSymbolsProvider getDateFormatSymbolsProvider() { 198 if (dateFormatSymbolsProvider == null) { 199 DateFormatSymbolsProvider provider = AccessController.doPrivileged( 200 (PrivilegedAction<DateFormatSymbolsProvider>) () -> 201 new DateFormatSymbolsProviderImpl( 202 getAdapterType(), 203 getLanguageTagSet("FormatData"))); 204 205 synchronized (this) { 206 if (dateFormatSymbolsProvider == null) { 207 dateFormatSymbolsProvider = provider; 208 } 209 } 210 } 211 return dateFormatSymbolsProvider; 212 } 213 214 @Override 215 public DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() { 216 if (decimalFormatSymbolsProvider == null) { 217 DecimalFormatSymbolsProvider provider = AccessController.doPrivileged( 218 (PrivilegedAction<DecimalFormatSymbolsProvider>) () -> 219 new DecimalFormatSymbolsProviderImpl( 220 getAdapterType(), 221 getLanguageTagSet("FormatData"))); 222 223 synchronized (this) { 224 if (decimalFormatSymbolsProvider == null) { 225 decimalFormatSymbolsProvider = provider; 226 } 227 } 228 } 229 return decimalFormatSymbolsProvider; 230 } 231 232 @Override 233 public NumberFormatProvider getNumberFormatProvider() { 234 if (numberFormatProvider == null) { 235 NumberFormatProvider provider = AccessController.doPrivileged( 236 (PrivilegedAction<NumberFormatProvider>) () -> 237 new NumberFormatProviderImpl( 238 getAdapterType(), 239 getLanguageTagSet("FormatData"))); 240 241 synchronized (this) { 242 if (numberFormatProvider == null) { 243 numberFormatProvider = provider; 244 } 245 } 246 } 247 return numberFormatProvider; 248 } 249 250 /** 251 * Getter methods for java.util.spi.* providers 252 */ 253 @Override 254 public CurrencyNameProvider getCurrencyNameProvider() { 255 if (currencyNameProvider == null) { 256 CurrencyNameProvider provider = AccessController.doPrivileged( 257 (PrivilegedAction<CurrencyNameProvider>) () -> 258 new CurrencyNameProviderImpl( 259 getAdapterType(), 260 getLanguageTagSet("CurrencyNames"))); 261 262 synchronized (this) { 263 if (currencyNameProvider == null) { 264 currencyNameProvider = provider; 265 } 266 } 267 } 268 return currencyNameProvider; 269 } 270 271 @Override 272 public LocaleNameProvider getLocaleNameProvider() { 273 if (localeNameProvider == null) { 274 LocaleNameProvider provider = AccessController.doPrivileged( 275 (PrivilegedAction<LocaleNameProvider>) () -> 276 new LocaleNameProviderImpl( 277 getAdapterType(), 278 getLanguageTagSet("LocaleNames"))); 279 280 synchronized (this) { 281 if (localeNameProvider == null) { 282 localeNameProvider = provider; 283 } 284 } 285 } 286 return localeNameProvider; 287 } 288 289 @Override 290 public TimeZoneNameProvider getTimeZoneNameProvider() { 291 if (timeZoneNameProvider == null) { 292 TimeZoneNameProvider provider = AccessController.doPrivileged( 293 (PrivilegedAction<TimeZoneNameProvider>) () -> 294 new TimeZoneNameProviderImpl( 295 getAdapterType(), 296 getLanguageTagSet("TimeZoneNames"))); 297 298 synchronized (this) { 299 if (timeZoneNameProvider == null) { 300 timeZoneNameProvider = provider; 301 } 302 } 303 } 304 return timeZoneNameProvider; 305 } 306 307 @Override 308 public CalendarDataProvider getCalendarDataProvider() { 309 if (calendarDataProvider == null) { 310 CalendarDataProvider provider = AccessController.doPrivileged( 311 (PrivilegedAction<CalendarDataProvider>) () -> 312 new CalendarDataProviderImpl( 313 getAdapterType(), 314 getLanguageTagSet("CalendarData"))); 315 316 synchronized (this) { 317 if (calendarDataProvider == null) { 318 calendarDataProvider = provider; 319 } 320 } 321 } 322 return calendarDataProvider; 323 } 324 325 @Override 326 public CalendarNameProvider getCalendarNameProvider() { 327 if (calendarNameProvider == null) { 328 CalendarNameProvider provider = AccessController.doPrivileged( 329 (PrivilegedAction<CalendarNameProvider>) () -> 330 new CalendarNameProviderImpl( 331 getAdapterType(), 332 getLanguageTagSet("FormatData"))); 333 334 synchronized (this) { 335 if (calendarNameProvider == null) { 336 calendarNameProvider = provider; 337 } 338 } 339 } 340 return calendarNameProvider; 341 } 342 343 /** 344 * Getter methods for sun.util.spi.* providers 345 */ 346 @Override 347 public CalendarProvider getCalendarProvider() { 348 if (calendarProvider == null) { 349 CalendarProvider provider = AccessController.doPrivileged( 350 (PrivilegedAction<CalendarProvider>) () -> 351 new CalendarProviderImpl( 352 getAdapterType(), 353 getLanguageTagSet("CalendarData"))); 354 355 synchronized (this) { 356 if (calendarProvider == null) { 357 calendarProvider = provider; 358 } 359 } 360 } 361 return calendarProvider; 362 } 363 364 /** 365 * Getter methods for sun.text.spi.JavaTimeDateTimePatternProvider provider 366 */ 367 @Override 368 public JavaTimeDateTimePatternProvider getJavaTimeDateTimePatternProvider() { 369 if (javaTimeDateTimePatternProvider == null) { 370 JavaTimeDateTimePatternProvider provider = AccessController.doPrivileged( 371 (PrivilegedAction<JavaTimeDateTimePatternProvider>) () 372 -> new JavaTimeDateTimePatternImpl( 373 getAdapterType(), 374 getLanguageTagSet("FormatData"))); 375 376 synchronized (this) { 377 if (javaTimeDateTimePatternProvider == null) { 378 javaTimeDateTimePatternProvider = provider; 379 } 380 } 381 } 382 return javaTimeDateTimePatternProvider; 383 } 384 385 @Override 386 public LocaleResources getLocaleResources(Locale locale) { 387 LocaleResources lr = localeResourcesMap.get(locale); 388 if (lr == null) { 389 lr = new LocaleResources(this, locale); 390 LocaleResources lrc = localeResourcesMap.putIfAbsent(locale, lr); 391 if (lrc != null) { 392 lr = lrc; 393 } 394 } 395 return lr; 396 } 397 398 // ResourceBundleBasedAdapter method implementation 399 400 @Override 401 public LocaleData getLocaleData() { 402 if (localeData == null) { 403 synchronized (this) { 404 if (localeData == null) { 405 localeData = new LocaleData(getAdapterType()); 406 } 407 } 408 } 409 return localeData; 410 } 411 412 @Override 413 public List<Locale> getCandidateLocales(String baseName, Locale locale) { 414 return ResourceBundle.Control 415 .getNoFallbackControl(ResourceBundle.Control.FORMAT_DEFAULT) 416 .getCandidateLocales(baseName, locale); 417 } 418 419 /** 420 * Returns a list of the installed locales. Currently, this simply returns 421 * the list of locales for which a sun.text.resources.FormatData bundle 422 * exists. This bundle family happens to be the one with the broadest 423 * locale coverage in the JRE. 424 */ 425 @Override 426 public Locale[] getAvailableLocales() { 427 return AvailableJRELocales.localeList.clone(); 428 } 429 430 public Set<String> getLanguageTagSet(String category) { 431 Set<String> tagset = langtagSets.get(category); 432 if (tagset == null) { 433 tagset = createLanguageTagSet(category); 434 Set<String> ts = langtagSets.putIfAbsent(category, tagset); 435 if (ts != null) { 436 tagset = ts; 437 } 438 } 439 return tagset; 440 } 441 442 protected Set<String> createLanguageTagSet(String category) { 443 String supportedLocaleString = createSupportedLocaleString(category); 444 if (supportedLocaleString == null) { 445 return Collections.emptySet(); 446 } 447 Set<String> tagset = new HashSet<>(); 448 StringTokenizer tokens = new StringTokenizer(supportedLocaleString); 449 while (tokens.hasMoreTokens()) { 450 tagset.add(tokens.nextToken()); 451 } 452 453 return tagset; 454 } 455 456 private static String createSupportedLocaleString(String category) { 457 // Directly call Base tags, as we know it's in the base module. 458 String supportedLocaleString = BaseLocaleDataMetaInfo.getSupportedLocaleString(category); 459 460 // Use ServiceLoader to dynamically acquire installed locales' tags. 461 try { 462 String nonBaseTags = AccessController.doPrivileged((PrivilegedExceptionAction<String>) () -> { 463 StringBuilder tags = new StringBuilder(); 464 for (LocaleDataMetaInfo ldmi : 465 ServiceLoader.loadInstalled(LocaleDataMetaInfo.class)) { 466 if (ldmi.getType() == LocaleProviderAdapter.Type.JRE) { 467 String t = ldmi.availableLanguageTags(category); 468 if (t != null) { 469 if (tags.length() > 0) { 470 tags.append(' '); 471 } 472 tags.append(t); 473 } 474 } 475 } 476 return tags.toString(); 477 }); 478 479 if (nonBaseTags != null) { 480 supportedLocaleString += " " + nonBaseTags; 481 } 482 } catch (PrivilegedActionException pae) { 483 throw new InternalError(pae.getCause()); 484 } 485 486 return supportedLocaleString; 487 } 488 489 /** 490 * Lazy load available locales. 491 */ 492 private static class AvailableJRELocales { 493 private static final Locale[] localeList = createAvailableLocales(); 494 private AvailableJRELocales() { 495 } 496 } 497 498 private static Locale[] createAvailableLocales() { 499 /* 500 * Gets the locale string list from LocaleDataMetaInfo classes and then 501 * contructs the Locale array and a set of language tags based on the 502 * locale string returned above. 503 */ 504 String supportedLocaleString = createSupportedLocaleString("AvailableLocales"); 505 506 if (supportedLocaleString.isEmpty()) { 507 throw new InternalError("No available locales for JRE"); 508 } 509 510 StringTokenizer localeStringTokenizer = new StringTokenizer(supportedLocaleString); 511 512 int length = localeStringTokenizer.countTokens(); 513 Locale[] locales = new Locale[length + 1]; 514 locales[0] = Locale.ROOT; 515 for (int i = 1; i <= length; i++) { 516 String currentToken = localeStringTokenizer.nextToken(); 517 switch (currentToken) { 518 case "ja-JP-JP": 519 locales[i] = JRELocaleConstants.JA_JP_JP; 520 break; 521 case "no-NO-NY": 522 locales[i] = JRELocaleConstants.NO_NO_NY; 523 break; 524 case "th-TH-TH": 525 locales[i] = JRELocaleConstants.TH_TH_TH; 526 break; 527 default: 528 locales[i] = Locale.forLanguageTag(currentToken); 529 } 530 } 531 return locales; 532 } 533 534 @Override 535 public boolean isSupportedProviderLocale(Locale locale, Set<String> langtags) { 536 if (Locale.ROOT.equals(locale)) { 537 return true; 538 } 539 540 locale = locale.stripExtensions(); 541 if (langtags.contains(locale.toLanguageTag())) { 542 return true; 543 } 544 545 String oldname = locale.toString().replace('_', '-'); 546 return langtags.contains(oldname) || 547 "ja-JP-JP".equals(oldname) || 548 "th-TH-TH".equals(oldname) || 549 "no-NO-NY".equals(oldname); 550 } 551 }