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