1 /* 2 * Copyright (c) 2005, 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.util.ArrayList; 29 import java.util.Collections; 30 import java.util.HashSet; 31 import java.util.IllformedLocaleException; 32 import java.util.List; 33 import java.util.Locale; 34 import java.util.Locale.Builder; 35 import java.util.ResourceBundle.Control; 36 import java.util.Set; 37 import java.util.concurrent.ConcurrentHashMap; 38 import java.util.concurrent.ConcurrentMap; 39 import java.util.spi.LocaleServiceProvider; 40 import sun.util.logging.PlatformLogger; 41 42 /** 43 * An instance of this class holds a set of the third party implementations of a particular 44 * locale sensitive service, such as {@link java.util.spi.LocaleNameProvider}. 45 * 46 * @author Naoto Sato 47 * @author Masayoshi Okutsu 48 */ 49 public final class LocaleServiceProviderPool { 50 51 /** 52 * A Map that holds singleton instances of this class. Each instance holds a 53 * set of provider implementations of a particular locale sensitive service. 54 */ 55 private static ConcurrentMap<Class<? extends LocaleServiceProvider>, LocaleServiceProviderPool> poolOfPools = 56 new ConcurrentHashMap<>(); 57 58 /** 59 * A Map containing locale service providers that implement the 60 * specified provider SPI, keyed by a LocaleProviderAdapter.Type 61 */ 62 private ConcurrentMap<LocaleProviderAdapter.Type, LocaleServiceProvider> providers = 63 new ConcurrentHashMap<>(); 64 65 /** 66 * A Map that retains Locale->provider mapping 67 */ 68 private ConcurrentMap<Locale, List<LocaleProviderAdapter.Type>> providersCache = 69 new ConcurrentHashMap<>(); 70 71 /** 72 * Available locales for this locale sensitive service. This also contains 73 * JRE's available locales 74 */ 75 private Set<Locale> availableLocales = null; 76 77 /** 78 * Provider class 79 */ 80 private Class<? extends LocaleServiceProvider> providerClass; 81 82 /** 83 * Array of all Locale Sensitive SPI classes. 84 * 85 * We know "spiClasses" contains classes that extends LocaleServiceProvider, 86 * but generic array creation is not allowed, thus the "unchecked" warning 87 * is suppressed here. 88 */ 89 @SuppressWarnings("unchecked") 90 static final Class<LocaleServiceProvider>[] spiClasses = 91 (Class<LocaleServiceProvider>[]) new Class<?>[] { 92 java.text.spi.BreakIteratorProvider.class, 93 java.text.spi.CollatorProvider.class, 94 java.text.spi.DateFormatProvider.class, 95 java.text.spi.DateFormatSymbolsProvider.class, 96 java.text.spi.DecimalFormatSymbolsProvider.class, 97 java.text.spi.NumberFormatProvider.class, 98 java.util.spi.CurrencyNameProvider.class, 99 java.util.spi.LocaleNameProvider.class, 100 java.util.spi.TimeZoneNameProvider.class, 101 java.util.spi.CalendarDataProvider.class 102 }; 103 104 /** 105 * A factory method that returns a singleton instance 106 */ 107 public static LocaleServiceProviderPool getPool(Class<? extends LocaleServiceProvider> providerClass) { 108 LocaleServiceProviderPool pool = poolOfPools.get(providerClass); 109 if (pool == null) { 110 LocaleServiceProviderPool newPool = 111 new LocaleServiceProviderPool(providerClass); 112 pool = poolOfPools.putIfAbsent(providerClass, newPool); 113 if (pool == null) { 114 pool = newPool; 115 } 116 } 117 118 return pool; 119 } 120 121 /** 122 * The sole constructor. 123 * 124 * @param c class of the locale sensitive service 125 */ 126 private LocaleServiceProviderPool (final Class<? extends LocaleServiceProvider> c) { 127 providerClass = c; 128 129 // Add the JRE Locale Data Adapter implementation. 130 providers.putIfAbsent(LocaleProviderAdapter.Type.JRE, 131 LocaleProviderAdapter.forJRE().getLocaleServiceProvider(c)); 132 133 // Add the SPI Locale Data Adapter implementation. 134 LocaleProviderAdapter lda = LocaleProviderAdapter.forType(LocaleProviderAdapter.Type.SPI); 135 LocaleServiceProvider provider = lda.getLocaleServiceProvider(c); 136 if (provider != null) { 137 providers.putIfAbsent(LocaleProviderAdapter.Type.SPI, provider); 138 } 139 140 // Add the CLDR Locale Data Adapter implementation, if needed. 141 lda = LocaleProviderAdapter.forType(LocaleProviderAdapter.Type.CLDR); 142 if (lda != null) { 143 provider = lda.getLocaleServiceProvider(c); 144 if (provider != null) { 145 providers.putIfAbsent(LocaleProviderAdapter.Type.CLDR, provider); 146 } 147 } 148 149 // Add the Host Locale Data Adapter implementation, if needed. 150 lda = LocaleProviderAdapter.forType(LocaleProviderAdapter.Type.HOST); 151 if (lda != null) { 152 provider = lda.getLocaleServiceProvider(c); 153 if (provider != null) { 154 providers.putIfAbsent(LocaleProviderAdapter.Type.HOST, provider); 155 } 156 } 157 158 // Add the Fallback Locale Data Adapter implementation, if needed. 159 lda = LocaleProviderAdapter.forType(LocaleProviderAdapter.Type.FALLBACK); 160 if (lda != null) { 161 provider = lda.getLocaleServiceProvider(c); 162 if (provider != null) { 163 providers.putIfAbsent(LocaleProviderAdapter.Type.FALLBACK, provider); 164 } 165 } 166 } 167 168 static void config(Class<? extends Object> caller, String message) { 169 PlatformLogger logger = PlatformLogger.getLogger(caller.getCanonicalName()); 170 logger.config(message); 171 } 172 173 /** 174 * Lazy loaded set of available locales. 175 * Loading all locales is a very long operation. 176 */ 177 private static class AllAvailableLocales { 178 /** 179 * Available locales for all locale sensitive services. 180 * This also contains JRE's available locales 181 */ 182 static final Locale[] allAvailableLocales; 183 184 static { 185 Set<Locale> all = new HashSet<>(); 186 for (Class<? extends LocaleServiceProvider> c : spiClasses) { 187 LocaleServiceProviderPool pool = 188 LocaleServiceProviderPool.getPool(c); 189 all.addAll(pool.getAvailableLocaleList()); 190 } 191 192 // Remove Locale.ROOT for the compatibility. 193 all.remove(Locale.ROOT); 194 195 allAvailableLocales = all.toArray(new Locale[0]); 196 } 197 198 // No instantiation 199 private AllAvailableLocales() { 200 } 201 } 202 203 /** 204 * Returns an array of available locales for all the provider classes. 205 * This array is a merged array of all the locales that are provided by each 206 * provider, including the JRE. 207 * 208 * @return an array of the available locales for all provider classes 209 */ 210 public static Locale[] getAllAvailableLocales() { 211 return AllAvailableLocales.allAvailableLocales.clone(); 212 } 213 214 /** 215 * Returns an array of available locales. This array is a 216 * merged array of all the locales that are provided by each 217 * provider, including the JRE. 218 * 219 * @return an array of the available locales 220 */ 221 public Locale[] getAvailableLocales() { 222 Set<Locale> locList = getAvailableLocaleList(); 223 // Remove Locale.ROOT for the compatibility. 224 locList.remove(Locale.ROOT); 225 Locale[] tmp = new Locale[locList.size()]; 226 locList.toArray(tmp); 227 return tmp; 228 } 229 230 private synchronized Set<Locale> getAvailableLocaleList() { 231 if (availableLocales == null) { 232 availableLocales = new HashSet<>(); 233 for (LocaleServiceProvider lsp : providers.values()) { 234 Locale[] locales = lsp.getAvailableLocales(); 235 for (Locale locale: locales) { 236 availableLocales.add(getLookupLocale(locale)); 237 } 238 } 239 } 240 241 return availableLocales; 242 } 243 244 /** 245 * Returns whether any provider for this locale sensitive 246 * service is available or not, excluding JRE's one. 247 * 248 * @return true if any provider (other than JRE) is available 249 */ 250 boolean hasProviders() { 251 return providers.size() != 1 || 252 providers.get(LocaleProviderAdapter.Type.JRE) == null; 253 } 254 255 /** 256 * Returns the provider's localized object for the specified 257 * locale. 258 * 259 * @param getter an object on which getObject() method 260 * is called to obtain the provider's instance. 261 * @param locale the given locale that is used as the starting one 262 * @param params provider specific parameters 263 * @return provider's instance, or null. 264 */ 265 public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter, 266 Locale locale, 267 Object... params) { 268 return getLocalizedObjectImpl(getter, locale, true, null, params); 269 } 270 271 /** 272 * Returns the provider's localized name for the specified 273 * locale. 274 * 275 * @param getter an object on which getObject() method 276 * is called to obtain the provider's instance. 277 * @param locale the given locale that is used as the starting one 278 * @param key the key string for name providers 279 * @param params provider specific parameters 280 * @return provider's instance, or null. 281 */ 282 public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter, 283 Locale locale, 284 String key, 285 Object... params) { 286 return getLocalizedObjectImpl(getter, locale, false, key, params); 287 } 288 289 @SuppressWarnings("unchecked") 290 private <P extends LocaleServiceProvider, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter, 291 Locale locale, 292 boolean isObjectProvider, 293 String key, 294 Object... params) { 295 if (locale == null) { 296 throw new NullPointerException(); 297 } 298 299 // Check whether JRE is the sole locale data provider or not, 300 // and directly call it if it is. 301 if (!hasProviders()) { 302 return getter.getObject( 303 (P)providers.get(LocaleProviderAdapter.Type.JRE), 304 locale, key, params); 305 } 306 307 List<Locale> lookupLocales = getLookupLocales(locale); 308 309 Set<Locale> available = getAvailableLocaleList(); 310 for (Locale current : lookupLocales) { 311 if (available.contains(current)) { 312 S providersObj; 313 314 for (LocaleProviderAdapter.Type type: findProviders(current)) { 315 LocaleServiceProvider lsp = providers.get(type); 316 providersObj = getter.getObject((P)lsp, locale, key, params); 317 if (providersObj != null) { 318 return providersObj; 319 } else if (isObjectProvider) { 320 config(LocaleServiceProviderPool.class, 321 "A locale sensitive service provider returned null for a localized objects, which should not happen. provider: " 322 + lsp + " locale: " + locale); 323 } 324 } 325 } 326 } 327 328 // not found. 329 return null; 330 } 331 332 /** 333 * Returns the list of locale service provider instances that support 334 * the specified locale. 335 * 336 * @param locale the given locale 337 * @return the list of locale data adapter types 338 */ 339 private List<LocaleProviderAdapter.Type> findProviders(Locale locale) { 340 List<LocaleProviderAdapter.Type> providersList = providersCache.get(locale); 341 if (providersList == null) { 342 for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) { 343 LocaleServiceProvider lsp = providers.get(type); 344 if (lsp != null) { 345 if (lsp.isSupportedLocale(locale)) { 346 if (providersList == null) { 347 providersList = new ArrayList<>(2); 348 } 349 providersList.add(type); 350 351 } 352 } 353 } 354 if (providersList == null) { 355 providersList = NULL_LIST; 356 } 357 List<LocaleProviderAdapter.Type> val = providersCache.putIfAbsent(locale, providersList); 358 if (val != null) { 359 providersList = val; 360 } 361 } 362 return providersList; 363 } 364 365 /** 366 * Returns a list of candidate locales for service look up. 367 * @param locale the input locale 368 * @return the list of candidate locales for the given locale 369 */ 370 static List<Locale> getLookupLocales(Locale locale) { 371 // Note: We currently use the default implementation of 372 // ResourceBundle.Control.getCandidateLocales. The result 373 // returned by getCandidateLocales are already normalized 374 // (no extensions) for service look up. 375 List<Locale> lookupLocales = Control.getNoFallbackControl(Control.FORMAT_DEFAULT) 376 .getCandidateLocales("", locale); 377 return lookupLocales; 378 } 379 380 /** 381 * Returns an instance of Locale used for service look up. 382 * The result Locale has no extensions except for ja_JP_JP 383 * and th_TH_TH 384 * 385 * @param locale the locale 386 * @return the locale used for service look up 387 */ 388 static Locale getLookupLocale(Locale locale) { 389 Locale lookupLocale = locale; 390 if (locale.hasExtensions() 391 && !locale.equals(JRELocaleConstants.JA_JP_JP) 392 && !locale.equals(JRELocaleConstants.TH_TH_TH)) { 393 // remove extensions 394 Builder locbld = new Builder(); 395 try { 396 locbld.setLocale(locale); 397 locbld.clearExtensions(); 398 lookupLocale = locbld.build(); 399 } catch (IllformedLocaleException e) { 400 // A Locale with non-empty extensions 401 // should have well-formed fields except 402 // for ja_JP_JP and th_TH_TH. Therefore, 403 // it should never enter in this catch clause. 404 config(LocaleServiceProviderPool.class, 405 "A locale(" + locale + ") has non-empty extensions, but has illformed fields."); 406 407 // Fallback - script field will be lost. 408 lookupLocale = new Locale(locale.getLanguage(), locale.getCountry(), locale.getVariant()); 409 } 410 } 411 return lookupLocale; 412 } 413 414 /** 415 * A dummy locale service provider list that indicates there is no 416 * provider available 417 */ 418 private static List<LocaleProviderAdapter.Type> NULL_LIST = 419 Collections.emptyList(); 420 421 /** 422 * An interface to get a localized object for each locale sensitive 423 * service class. 424 */ 425 public interface LocalizedObjectGetter<P extends LocaleServiceProvider, S> { 426 /** 427 * Returns an object from the provider 428 * 429 * @param lsp the provider 430 * @param locale the locale 431 * @param key key string to localize, or null if the provider is not 432 * a name provider 433 * @param params provider specific params 434 * @return localized object from the provider 435 */ 436 public S getObject(P lsp, 437 Locale locale, 438 String key, 439 Object... params); 440 } 441 }