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