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