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