1 /* 2 * Copyright (c) 2012, 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.io.File; 29 import java.security.AccessController; 30 import java.security.PrivilegedAction; 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.HashSet; 38 import java.util.Locale; 39 import java.util.Set; 40 import java.util.StringTokenizer; 41 import java.util.concurrent.ConcurrentHashMap; 42 import java.util.concurrent.ConcurrentMap; 43 import java.util.spi.CalendarDataProvider; 44 import java.util.spi.CurrencyNameProvider; 45 import java.util.spi.LocaleNameProvider; 46 import java.util.spi.LocaleServiceProvider; 47 import java.util.spi.TimeZoneNameProvider; 48 import sun.util.resources.LocaleData; 49 50 /** 51 * LocaleProviderAdapter implementation for the legacy JRE locale data. 52 * 53 * @author Naoto Sato 54 * @author Masayoshi Okutsu 55 */ 56 public class JRELocaleProviderAdapter extends LocaleProviderAdapter { 57 58 private static final String LOCALE_DATA_JAR_NAME = "localedata.jar"; 59 60 private final ConcurrentMap<String, Set<String>> langtagSets 61 = new ConcurrentHashMap<>(); 62 63 private final ConcurrentMap<Locale, LocaleResources> localeResourcesMap 64 = new ConcurrentHashMap<>(); 65 66 // LocaleData specific to this LocaleProviderAdapter. 67 private volatile LocaleData localeData; 68 69 /** 70 * Returns the type of this LocaleProviderAdapter 71 */ 72 @Override 73 public LocaleProviderAdapter.Type getAdapterType() { 74 return Type.JRE; 75 } 76 77 /** 78 * Getter method for Locale Service Providers 79 */ 80 @Override 81 @SuppressWarnings("unchecked") 82 public <P extends LocaleServiceProvider> P getLocaleServiceProvider(Class<P> c) { 83 switch (c.getSimpleName()) { 84 case "BreakIteratorProvider": 85 return (P) getBreakIteratorProvider(); 86 case "CollatorProvider": 87 return (P) getCollatorProvider(); 88 case "DateFormatProvider": 89 return (P) getDateFormatProvider(); 90 case "DateFormatSymbolsProvider": 91 return (P) getDateFormatSymbolsProvider(); 92 case "DecimalFormatSymbolsProvider": 93 return (P) getDecimalFormatSymbolsProvider(); 94 case "NumberFormatProvider": 95 return (P) getNumberFormatProvider(); 96 case "CurrencyNameProvider": 97 return (P) getCurrencyNameProvider(); 98 case "LocaleNameProvider": 99 return (P) getLocaleNameProvider(); 100 case "TimeZoneNameProvider": 101 return (P) getTimeZoneNameProvider(); 102 case "CalendarDataProvider": 103 return (P) getCalendarDataProvider(); 104 default: 105 throw new InternalError("should not come down here"); 106 } 107 } 108 109 private volatile BreakIteratorProvider breakIteratorProvider = null; 110 private volatile CollatorProvider collatorProvider = null; 111 private volatile DateFormatProvider dateFormatProvider = null; 112 private volatile DateFormatSymbolsProvider dateFormatSymbolsProvider = null; 113 private volatile DecimalFormatSymbolsProvider decimalFormatSymbolsProvider = null; 114 private volatile NumberFormatProvider numberFormatProvider = null; 115 116 private volatile CurrencyNameProvider currencyNameProvider = null; 117 private volatile LocaleNameProvider localeNameProvider = null; 118 private volatile TimeZoneNameProvider timeZoneNameProvider = null; 119 private volatile CalendarDataProvider calendarDataProvider = null; 120 121 /* 122 * Getter methods for java.text.spi.* providers 123 */ 124 @Override 125 public BreakIteratorProvider getBreakIteratorProvider() { 126 if (breakIteratorProvider == null) { 127 BreakIteratorProvider provider = new BreakIteratorProviderImpl(getAdapterType(), 128 getLanguageTagSet("FormatData")); 129 synchronized (this) { 130 if (breakIteratorProvider == null) { 131 breakIteratorProvider = provider; 132 } 133 } 134 } 135 return breakIteratorProvider; 136 } 137 138 @Override 139 public CollatorProvider getCollatorProvider() { 140 if (collatorProvider == null) { 141 CollatorProvider provider = new CollatorProviderImpl(getAdapterType(), 142 getLanguageTagSet("CollationData")); 143 synchronized (this) { 144 if (collatorProvider == null) { 145 collatorProvider = provider; 146 } 147 } 148 } 149 return collatorProvider; 150 } 151 152 @Override 153 public DateFormatProvider getDateFormatProvider() { 154 if (dateFormatProvider == null) { 155 DateFormatProvider provider = new DateFormatProviderImpl(getAdapterType(), 156 getLanguageTagSet("FormatData")); 157 synchronized (this) { 158 if (dateFormatProvider == null) { 159 dateFormatProvider = provider; 160 } 161 } 162 } 163 return dateFormatProvider; 164 } 165 166 @Override 167 public DateFormatSymbolsProvider getDateFormatSymbolsProvider() { 168 if (dateFormatSymbolsProvider == null) { 169 DateFormatSymbolsProvider provider = new DateFormatSymbolsProviderImpl(getAdapterType(), 170 getLanguageTagSet("FormatData")); 171 synchronized (this) { 172 if (dateFormatSymbolsProvider == null) { 173 dateFormatSymbolsProvider = provider; 174 } 175 } 176 } 177 return dateFormatSymbolsProvider; 178 } 179 180 @Override 181 public DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider() { 182 if (decimalFormatSymbolsProvider == null) { 183 DecimalFormatSymbolsProvider provider = new DecimalFormatSymbolsProviderImpl(getAdapterType(), getLanguageTagSet("FormatData")); 184 synchronized (this) { 185 if (decimalFormatSymbolsProvider == null) { 186 decimalFormatSymbolsProvider = provider; 187 } 188 } 189 } 190 return decimalFormatSymbolsProvider; 191 } 192 193 @Override 194 public NumberFormatProvider getNumberFormatProvider() { 195 if (numberFormatProvider == null) { 196 NumberFormatProvider provider = new NumberFormatProviderImpl(getAdapterType(), 197 getLanguageTagSet("FormatData")); 198 synchronized (this) { 199 if (numberFormatProvider == null) { 200 numberFormatProvider = provider; 201 } 202 } 203 } 204 return numberFormatProvider; 205 } 206 207 /** 208 * Getter methods for java.util.spi.* providers 209 */ 210 @Override 211 public CurrencyNameProvider getCurrencyNameProvider() { 212 if (currencyNameProvider == null) { 213 CurrencyNameProvider provider = new CurrencyNameProviderImpl(getAdapterType(), 214 getLanguageTagSet("CurrencyNames")); 215 synchronized (this) { 216 if (currencyNameProvider == null) { 217 currencyNameProvider = provider; 218 } 219 } 220 } 221 return currencyNameProvider; 222 } 223 224 @Override 225 public LocaleNameProvider getLocaleNameProvider() { 226 if (localeNameProvider == null) { 227 LocaleNameProvider provider = new LocaleNameProviderImpl(getAdapterType(), 228 getLanguageTagSet("LocaleNames")); 229 synchronized (this) { 230 if (localeNameProvider == null) { 231 localeNameProvider = provider; 232 } 233 } 234 } 235 return localeNameProvider; 236 } 237 238 @Override 239 public TimeZoneNameProvider getTimeZoneNameProvider() { 240 if (timeZoneNameProvider == null) { 241 TimeZoneNameProvider provider = new TimeZoneNameProviderImpl(getAdapterType(), 242 getLanguageTagSet("TimeZoneNames")); 243 synchronized (this) { 244 if (timeZoneNameProvider == null) { 245 timeZoneNameProvider = provider; 246 } 247 } 248 } 249 return timeZoneNameProvider; 250 } 251 252 @Override 253 public CalendarDataProvider getCalendarDataProvider() { 254 if (calendarDataProvider == null) { 255 Set<String> set = new HashSet<>(); 256 set.addAll(getLanguageTagSet("FormatData")); 257 set.addAll(getLanguageTagSet("CalendarData")); 258 CalendarDataProvider provider = new CalendarDataProviderImpl(getAdapterType(), 259 set); 260 synchronized (this) { 261 if (calendarDataProvider == null) { 262 calendarDataProvider = provider; 263 } 264 } 265 } 266 return calendarDataProvider; 267 } 268 269 @Override 270 public LocaleResources getLocaleResources(Locale locale) { 271 LocaleResources lr = localeResourcesMap.get(locale); 272 if (lr == null) { 273 lr = new LocaleResources(this, locale); 274 LocaleResources lrc = localeResourcesMap.putIfAbsent(locale, lr); 275 if (lrc != null) { 276 lr = lrc; 277 } 278 } 279 return lr; 280 } 281 282 @Override 283 public LocaleData getLocaleData() { 284 if (localeData == null) { 285 synchronized (this) { 286 if (localeData == null) { 287 localeData = new LocaleData(getAdapterType()); 288 } 289 } 290 } 291 return localeData; 292 } 293 294 /** 295 * Returns a list of the installed locales. Currently, this simply returns 296 * the list of locales for which a sun.text.resources.FormatData bundle 297 * exists. This bundle family happens to be the one with the broadest 298 * locale coverage in the JRE. 299 */ 300 @Override 301 public Locale[] getAvailableLocales() { 302 return AvailableJRELocales.localeList.clone(); 303 } 304 305 public Set<String> getLanguageTagSet(String category) { 306 Set<String> tagset = langtagSets.get(category); 307 if (tagset == null) { 308 tagset = createLanguageTagSet(category); 309 Set<String> ts = langtagSets.putIfAbsent(category, tagset); 310 if (ts != null) { 311 tagset = ts; 312 } 313 } 314 return tagset; 315 } 316 317 protected Set<String> createLanguageTagSet(String category) { 318 String supportedLocaleString = LocaleDataMetaInfo.getSupportedLocaleString(category); 319 Set<String> tagset = new HashSet<>(); 320 StringTokenizer tokens = new StringTokenizer(supportedLocaleString); 321 while (tokens.hasMoreTokens()) { 322 String token = tokens.nextToken(); 323 if (token.equals("|")) { 324 if (isNonUSLangSupported()) { 325 continue; 326 } 327 break; 328 } 329 tagset.add(token); 330 } 331 332 // ensure en-US is there (mandated by the spec, e.g. Collator.getAvailableLocales()) 333 tagset.add("en-US"); 334 335 return tagset; 336 } 337 338 /** 339 * Lazy load available locales. 340 */ 341 private static class AvailableJRELocales { 342 private static final Locale[] localeList = createAvailableLocales(); 343 private AvailableJRELocales() { 344 } 345 } 346 347 private static Locale[] createAvailableLocales() { 348 /* 349 * Gets the locale string list from LocaleDataMetaInfo class and then 350 * contructs the Locale array and a set of language tags based on the 351 * locale string returned above. 352 */ 353 String supportedLocaleString = LocaleDataMetaInfo.getSupportedLocaleString("AvailableLocales"); 354 355 if (supportedLocaleString.length() == 0) { 356 throw new InternalError("No available locales for JRE"); 357 } 358 359 /* 360 * Look for "|" and construct a new locale string list. 361 */ 362 int barIndex = supportedLocaleString.indexOf('|'); 363 StringTokenizer localeStringTokenizer; 364 if (isNonUSLangSupported()) { 365 localeStringTokenizer = new StringTokenizer(supportedLocaleString.substring(0, barIndex) 366 + supportedLocaleString.substring(barIndex + 1)); 367 } else { 368 localeStringTokenizer = new StringTokenizer(supportedLocaleString.substring(0, barIndex)); 369 } 370 371 int length = localeStringTokenizer.countTokens(); 372 Locale[] locales = new Locale[length + 1]; 373 locales[0] = Locale.ROOT; 374 for (int i = 1; i <= length; i++) { 375 String currentToken = localeStringTokenizer.nextToken(); 376 switch (currentToken) { 377 case "ja-JP-JP": 378 locales[i] = JRELocaleConstants.JA_JP_JP; 379 break; 380 case "no-NO-NY": 381 locales[i] = JRELocaleConstants.NO_NO_NY; 382 break; 383 case "th-TH-TH": 384 locales[i] = JRELocaleConstants.TH_TH_TH; 385 break; 386 default: 387 locales[i] = Locale.forLanguageTag(currentToken); 388 } 389 } 390 return locales; 391 } 392 393 private static volatile Boolean isNonUSSupported = null; 394 395 /* 396 * Returns true if the non US resources jar file exists in jre 397 * extension directory. @returns true if the jar file is there. Otherwise, 398 * returns false. 399 */ 400 private static boolean isNonUSLangSupported() { 401 if (isNonUSSupported == null) { 402 synchronized (JRELocaleProviderAdapter.class) { 403 if (isNonUSSupported == null) { 404 final String sep = File.separator; 405 String localeDataJar = 406 java.security.AccessController.doPrivileged( 407 new sun.security.action.GetPropertyAction("java.home")) 408 + sep + "lib" + sep + "ext" + sep + LOCALE_DATA_JAR_NAME; 409 410 /* 411 * Peek at the installed extension directory to see if 412 * localedata.jar is installed or not. 413 */ 414 final File f = new File(localeDataJar); 415 isNonUSSupported = 416 AccessController.doPrivileged(new PrivilegedAction<Boolean>() { 417 @Override 418 public Boolean run() { 419 return f.exists(); 420 } 421 }); 422 } 423 } 424 } 425 return isNonUSSupported; 426 } 427 }