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