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