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