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 159 static void config(Class<? extends Object> caller, String message) { 160 PlatformLogger logger = PlatformLogger.getLogger(caller.getCanonicalName()); 161 logger.config(message); 162 } 163 164 /** 165 * Lazy loaded set of available locales. 166 * Loading all locales is a very long operation. 167 */ 168 private static class AllAvailableLocales { 169 /** 170 * Available locales for all locale sensitive services. 171 * This also contains JRE's available locales 172 */ 173 static final Locale[] allAvailableLocales; 174 175 static { 176 Set<Locale> all = new HashSet<>(); 177 for (Class<? extends LocaleServiceProvider> c : spiClasses) { 178 LocaleServiceProviderPool pool = 179 LocaleServiceProviderPool.getPool(c); 180 all.addAll(pool.getAvailableLocaleList()); 181 } 182 183 allAvailableLocales = all.toArray(new Locale[0]); 184 } 185 186 // No instantiation 187 private AllAvailableLocales() { 188 } 189 } 190 191 /** 192 * Returns an array of available locales for all the provider classes. 193 * This array is a merged array of all the locales that are provided by each 194 * provider, including the JRE. 195 * 196 * @return an array of the available locales for all provider classes 197 */ 198 public static Locale[] getAllAvailableLocales() { 199 return AllAvailableLocales.allAvailableLocales.clone(); 200 } 201 202 /** 203 * Returns an array of available locales. This array is a 204 * merged array of all the locales that are provided by each 205 * provider, including the JRE. 206 * 207 * @return an array of the available locales 208 */ 209 public Locale[] getAvailableLocales() { 210 Set<Locale> locList = getAvailableLocaleList(); 211 Locale[] tmp = new Locale[locList.size()]; 212 locList.toArray(tmp); 213 return tmp; 214 } 215 216 private synchronized Set<Locale> getAvailableLocaleList() { 217 if (availableLocales == null) { 218 availableLocales = new HashSet<>(); 219 for (LocaleServiceProvider lsp : providers.values()) { 220 Locale[] locales = lsp.getAvailableLocales(); 221 for (Locale locale: locales) { 222 availableLocales.add(getLookupLocale(locale)); 223 } 224 } 225 226 // Remove Locale.ROOT for the compatibility. 227 availableLocales.remove(Locale.ROOT); 228 } 229 230 return availableLocales; 231 } 232 233 /** 234 * Returns whether any provider for this locale sensitive 235 * service is available or not, excluding JRE's one. 236 * 237 * @return true if any provider (other than JRE) is available 238 */ 239 boolean hasProviders() { 240 return providers.size() != 1 || 241 providers.get(LocaleProviderAdapter.Type.JRE) == null; 242 } 243 244 /** 245 * Returns the provider's localized object for the specified 246 * locale. 247 * 248 * @param getter an object on which getObject() method 249 * is called to obtain the provider's instance. 250 * @param locale the given locale that is used as the starting one 251 * @param params provider specific parameters 252 * @return provider's instance, or null. 253 */ 254 public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter, 255 Locale locale, 256 Object... params) { 257 return getLocalizedObjectImpl(getter, locale, true, null, params); 258 } 259 260 /** 261 * Returns the provider's localized name for the specified 262 * locale. 263 * 264 * @param getter an object on which getObject() method 265 * is called to obtain the provider's instance. 266 * @param locale the given locale that is used as the starting one 267 * @param key the key string for name providers 268 * @param params provider specific parameters 269 * @return provider's instance, or null. 270 */ 271 public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter, 272 Locale locale, 273 String key, 274 Object... params) { 275 return getLocalizedObjectImpl(getter, locale, false, key, params); 276 } 277 278 @SuppressWarnings("unchecked") 279 private <P extends LocaleServiceProvider, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter, 280 Locale locale, 281 boolean isObjectProvider, 282 String key, 283 Object... params) { 284 if (locale == null) { 285 throw new NullPointerException(); 286 } 287 288 // Check whether JRE is the sole locale data provider or not, 289 // and directly call it if it is. 290 if (!hasProviders()) { 291 return getter.getObject( 292 (P)providers.get(LocaleProviderAdapter.Type.JRE), 293 locale, key, params); 294 } 295 296 List<Locale> lookupLocales = getLookupLocales(locale); 297 298 Set<Locale> available = getAvailableLocaleList(); 299 for (Locale current : lookupLocales) { 300 if (available.contains(current)) { 301 S providersObj; 302 303 for (LocaleProviderAdapter.Type type: findProviders(current)) { 304 LocaleServiceProvider lsp = providers.get(type); 305 providersObj = getter.getObject((P)lsp, current, key, params); 306 if (providersObj != null) { 307 return providersObj; 308 } else if (isObjectProvider) { 309 config(LocaleServiceProviderPool.class, 310 "A locale sensitive service provider returned null for a localized objects, which should not happen. provider: " 311 + lsp + " locale: " + locale); 312 } 313 } 314 } 315 } 316 317 // not found. 318 return null; 319 } 320 321 /** 322 * Returns the list of locale service provider instances that support 323 * the specified locale. 324 * 325 * @param locale the given locale 326 * @return the list of locale data adapter types 327 */ 328 private List<LocaleProviderAdapter.Type> findProviders(Locale locale) { 329 List<LocaleProviderAdapter.Type> providersList = providersCache.get(locale); 330 if (providersList == null) { 331 for (LocaleProviderAdapter.Type type : LocaleProviderAdapter.getAdapterPreference()) { 332 LocaleServiceProvider lsp = providers.get(type); 333 if (lsp != null) { 334 if (lsp.isSupportedLocale(locale)) { 335 if (providersList == null) { 336 providersList = new ArrayList<>(2); 337 } 338 providersList.add(type); 339 340 } 341 } 342 } 343 if (providersList == null) { 344 providersList = NULL_LIST; 345 } 346 List<LocaleProviderAdapter.Type> val = providersCache.putIfAbsent(locale, providersList); 347 if (val != null) { 348 providersList = val; 349 } 350 } 351 return providersList; 352 } 353 354 /** 355 * Returns a list of candidate locales for service look up. 356 * @param locale the input locale 357 * @return the list of candidate locales for the given locale 358 */ 359 private static List<Locale> getLookupLocales(Locale locale) { 360 // Note: We currently use the default implementation of 361 // ResourceBundle.Control.getCandidateLocales. The result 362 // returned by getCandidateLocales are already normalized 363 // (no extensions) for service look up. 364 List<Locale> lookupLocales = Control.getNoFallbackControl(Control.FORMAT_DEFAULT) 365 .getCandidateLocales("", locale); 366 return lookupLocales; 367 } 368 369 /** 370 * Returns an instance of Locale used for service look up. 371 * The result Locale has no extensions except for ja_JP_JP 372 * and th_TH_TH 373 * 374 * @param locale the locale 375 * @return the locale used for service look up 376 */ 377 static Locale getLookupLocale(Locale locale) { 378 Locale lookupLocale = locale; 379 if (locale.hasExtensions() 380 && !locale.equals(JRELocaleConstants.JA_JP_JP) 381 && !locale.equals(JRELocaleConstants.TH_TH_TH)) { 382 // remove extensions 383 Builder locbld = new Builder(); 384 try { 385 locbld.setLocale(locale); 386 locbld.clearExtensions(); 387 lookupLocale = locbld.build(); 388 } catch (IllformedLocaleException e) { 389 // A Locale with non-empty extensions 390 // should have well-formed fields except 391 // for ja_JP_JP and th_TH_TH. Therefore, 392 // it should never enter in this catch clause. 393 config(LocaleServiceProviderPool.class, 394 "A locale(" + locale + ") has non-empty extensions, but has illformed fields."); 395 396 // Fallback - script field will be lost. 397 lookupLocale = new Locale(locale.getLanguage(), locale.getCountry(), locale.getVariant()); 398 } 399 } 400 return lookupLocale; 401 } 402 403 /** 404 * A dummy locale service provider list that indicates there is no 405 * provider available 406 */ 407 private static List<LocaleProviderAdapter.Type> NULL_LIST = 408 Collections.emptyList(); 409 410 /** 411 * An interface to get a localized object for each locale sensitive 412 * service class. 413 */ 414 public interface LocalizedObjectGetter<P extends LocaleServiceProvider, S> { 415 /** 416 * Returns an object from the provider 417 * 418 * @param lsp the provider 419 * @param locale the locale 420 * @param key key string to localize, or null if the provider is not 421 * a name provider 422 * @param params provider specific params 423 * @return localized object from the provider 424 */ 425 public S getObject(P lsp, 426 Locale locale, 427 String key, 428 Object... params); 429 } 430 }