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