author | naoto |
Tue, 16 Oct 2012 10:59:21 -0700 | |
changeset 14185 | 916ec0a4d039 |
parent 13583 | dc0017b1a452 |
child 14331 | d3411e624053 |
permissions | -rw-r--r-- |
2 | 1 |
/* |
13583 | 2 |
* Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. |
2 | 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 |
|
5506 | 7 |
* published by the Free Software Foundation. Oracle designates this |
2 | 8 |
* particular file as subject to the "Classpath" exception as provided |
5506 | 9 |
* by Oracle in the LICENSE file that accompanied this code. |
2 | 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 |
* |
|
5506 | 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. |
|
2 | 24 |
*/ |
25 |
||
13583 | 26 |
package sun.util.locale.provider; |
2 | 27 |
|
6501 | 28 |
import java.util.ArrayList; |
13583 | 29 |
import java.util.Collections; |
2 | 30 |
import java.util.HashSet; |
6501 | 31 |
import java.util.IllformedLocaleException; |
2 | 32 |
import java.util.List; |
33 |
import java.util.Locale; |
|
6501 | 34 |
import java.util.Locale.Builder; |
35 |
import java.util.ResourceBundle.Control; |
|
2 | 36 |
import java.util.Set; |
37 |
import java.util.concurrent.ConcurrentHashMap; |
|
10710
b18db2d63e3b
7027061: Testcase failure: java/util/Locale/Bug6989440.java - java.util.ConcurrentModificationException
naoto
parents:
6835
diff
changeset
|
38 |
import java.util.concurrent.ConcurrentMap; |
2 | 39 |
import java.util.spi.LocaleServiceProvider; |
3861
a98a057ec335
6882376: Add internal support for JRE implementation to eliminate the dependency on logging
mchung
parents:
2
diff
changeset
|
40 |
import sun.util.logging.PlatformLogger; |
2 | 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 |
* |
|
13583 | 46 |
* @author Naoto Sato |
47 |
* @author Masayoshi Okutsu |
|
2 | 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 |
*/ |
|
11138
9121a1a92512
7117469: Warning cleanup for j.u.Currency and j.u.Locale related classes
naoto
parents:
10710
diff
changeset
|
55 |
private static ConcurrentMap<Class<? extends LocaleServiceProvider>, LocaleServiceProviderPool> poolOfPools = |
10710
b18db2d63e3b
7027061: Testcase failure: java/util/Locale/Bug6989440.java - java.util.ConcurrentModificationException
naoto
parents:
6835
diff
changeset
|
56 |
new ConcurrentHashMap<>(); |
2 | 57 |
|
58 |
/** |
|
13583 | 59 |
* A Map containing locale service providers that implement the |
60 |
* specified provider SPI, keyed by a LocaleProviderAdapter.Type |
|
2 | 61 |
*/ |
13583 | 62 |
private ConcurrentMap<LocaleProviderAdapter.Type, LocaleServiceProvider> providers = |
63 |
new ConcurrentHashMap<>(); |
|
2 | 64 |
|
65 |
/** |
|
66 |
* A Map that retains Locale->provider mapping |
|
67 |
*/ |
|
13583 | 68 |
private ConcurrentMap<Locale, List<LocaleProviderAdapter.Type>> providersCache = |
69 |
new ConcurrentHashMap<>(); |
|
2 | 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 |
/** |
|
13583 | 78 |
* Provider class |
2 | 79 |
*/ |
13583 | 80 |
private Class<? extends LocaleServiceProvider> providerClass; |
2 | 81 |
|
82 |
/** |
|
13583 | 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. |
|
6501 | 88 |
*/ |
13583 | 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 |
}; |
|
6501 | 103 |
|
104 |
/** |
|
2 | 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); |
|
10710
b18db2d63e3b
7027061: Testcase failure: java/util/Locale/Bug6989440.java - java.util.ConcurrentModificationException
naoto
parents:
6835
diff
changeset
|
112 |
pool = poolOfPools.putIfAbsent(providerClass, newPool); |
2 | 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) { |
|
13583 | 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 |
} |
|
2 | 156 |
} |
157 |
} |
|
158 |
||
13583 | 159 |
static void config(Class<? extends Object> caller, String message) { |
160 |
PlatformLogger logger = PlatformLogger.getLogger(caller.getCanonicalName()); |
|
3861
a98a057ec335
6882376: Add internal support for JRE implementation to eliminate the dependency on logging
mchung
parents:
2
diff
changeset
|
161 |
logger.config(message); |
a98a057ec335
6882376: Add internal support for JRE implementation to eliminate the dependency on logging
mchung
parents:
2
diff
changeset
|
162 |
} |
a98a057ec335
6882376: Add internal support for JRE implementation to eliminate the dependency on logging
mchung
parents:
2
diff
changeset
|
163 |
|
2 | 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 { |
|
13583 | 176 |
Set<Locale> all = new HashSet<>(); |
177 |
for (Class<? extends LocaleServiceProvider> c : spiClasses) { |
|
2 | 178 |
LocaleServiceProviderPool pool = |
13583 | 179 |
LocaleServiceProviderPool.getPool(c); |
180 |
all.addAll(pool.getAvailableLocaleList()); |
|
2 | 181 |
} |
6501 | 182 |
|
2 | 183 |
allAvailableLocales = all.toArray(new Locale[0]); |
184 |
} |
|
13583 | 185 |
|
186 |
// No instantiation |
|
187 |
private AllAvailableLocales() { |
|
188 |
} |
|
2 | 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 |
*/ |
|
13583 | 209 |
public Locale[] getAvailableLocales() { |
210 |
Set<Locale> locList = getAvailableLocaleList(); |
|
211 |
Locale[] tmp = new Locale[locList.size()]; |
|
212 |
locList.toArray(tmp); |
|
2 | 213 |
return tmp; |
214 |
} |
|
215 |
||
13583 | 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)); |
|
2 | 223 |
} |
224 |
} |
|
13583 | 225 |
|
226 |
// Remove Locale.ROOT for the compatibility. |
|
227 |
availableLocales.remove(Locale.ROOT); |
|
2 | 228 |
} |
13583 | 229 |
|
230 |
return availableLocales; |
|
2 | 231 |
} |
232 |
||
233 |
/** |
|
234 |
* Returns whether any provider for this locale sensitive |
|
13583 | 235 |
* service is available or not, excluding JRE's one. |
2 | 236 |
* |
13583 | 237 |
* @return true if any provider (other than JRE) is available |
2 | 238 |
*/ |
13583 | 239 |
boolean hasProviders() { |
240 |
return providers.size() != 1 || |
|
241 |
providers.get(LocaleProviderAdapter.Type.JRE) == null; |
|
2 | 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 |
*/ |
|
13583 | 254 |
public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter, |
2 | 255 |
Locale locale, |
256 |
Object... params) { |
|
13583 | 257 |
return getLocalizedObjectImpl(getter, locale, true, null, params); |
2 | 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 |
|
13583 | 267 |
* @param key the key string for name providers |
2 | 268 |
* @param params provider specific parameters |
269 |
* @return provider's instance, or null. |
|
270 |
*/ |
|
13583 | 271 |
public <P extends LocaleServiceProvider, S> S getLocalizedObject(LocalizedObjectGetter<P, S> getter, |
2 | 272 |
Locale locale, |
273 |
String key, |
|
274 |
Object... params) { |
|
13583 | 275 |
return getLocalizedObjectImpl(getter, locale, false, key, params); |
2 | 276 |
} |
277 |
||
13583 | 278 |
@SuppressWarnings("unchecked") |
279 |
private <P extends LocaleServiceProvider, S> S getLocalizedObjectImpl(LocalizedObjectGetter<P, S> getter, |
|
2 | 280 |
Locale locale, |
281 |
boolean isObjectProvider, |
|
282 |
String key, |
|
283 |
Object... params) { |
|
13583 | 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); |
|
2 | 297 |
|
13583 | 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); |
|
14185
916ec0a4d039
8000245: SimpleDateFormat.format(date, StringBuffer, FieldPosition) doesn't work as expected with custom extensions
naoto
parents:
13583
diff
changeset
|
305 |
providersObj = getter.getObject((P)lsp, locale, key, params); |
13583 | 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); |
|
2 | 312 |
} |
313 |
} |
|
314 |
} |
|
315 |
} |
|
316 |
||
317 |
// not found. |
|
318 |
return null; |
|
319 |
} |
|
320 |
||
321 |
/** |
|
13583 | 322 |
* Returns the list of locale service provider instances that support |
2 | 323 |
* the specified locale. |
324 |
* |
|
325 |
* @param locale the given locale |
|
13583 | 326 |
* @return the list of locale data adapter types |
2 | 327 |
*/ |
13583 | 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); |
|
2 | 339 |
|
340 |
} |
|
341 |
} |
|
342 |
} |
|
13583 | 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 |
} |
|
2 | 350 |
} |
13583 | 351 |
return providersList; |
352 |
} |
|
2 | 353 |
|
354 |
/** |
|
6501 | 355 |
* Returns a list of candidate locales for service look up. |
356 |
* @param locale the input locale |
|
13583 | 357 |
* @return the list of candidate locales for the given locale |
2 | 358 |
*/ |
6501 | 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. |
|
13583 | 364 |
List<Locale> lookupLocales = Control.getNoFallbackControl(Control.FORMAT_DEFAULT) |
365 |
.getCandidateLocales("", locale); |
|
6501 | 366 |
return lookupLocales; |
2 | 367 |
} |
368 |
||
369 |
/** |
|
6501 | 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 |
|
2 | 373 |
* |
374 |
* @param locale the locale |
|
6501 | 375 |
* @return the locale used for service look up |
2 | 376 |
*/ |
13583 | 377 |
static Locale getLookupLocale(Locale locale) { |
6501 | 378 |
Locale lookupLocale = locale; |
13583 | 379 |
if (locale.hasExtensions() |
380 |
&& !locale.equals(JRELocaleConstants.JA_JP_JP) |
|
381 |
&& !locale.equals(JRELocaleConstants.TH_TH_TH)) { |
|
6501 | 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. |
|
13583 | 393 |
config(LocaleServiceProviderPool.class, |
394 |
"A locale(" + locale + ") has non-empty extensions, but has illformed fields."); |
|
6501 | 395 |
|
396 |
// Fallback - script field will be lost. |
|
397 |
lookupLocale = new Locale(locale.getLanguage(), locale.getCountry(), locale.getVariant()); |
|
2 | 398 |
} |
399 |
} |
|
6501 | 400 |
return lookupLocale; |
2 | 401 |
} |
402 |
||
403 |
/** |
|
13583 | 404 |
* A dummy locale service provider list that indicates there is no |
2 | 405 |
* provider available |
406 |
*/ |
|
13583 | 407 |
private static List<LocaleProviderAdapter.Type> NULL_LIST = |
408 |
Collections.emptyList(); |
|
2 | 409 |
|
410 |
/** |
|
13583 | 411 |
* An interface to get a localized object for each locale sensitive |
2 | 412 |
* service class. |
413 |
*/ |
|
13583 | 414 |
public interface LocalizedObjectGetter<P extends LocaleServiceProvider, S> { |
2 | 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, |
|
13583 | 426 |
Locale locale, |
427 |
String key, |
|
428 |
Object... params); |
|
2 | 429 |
} |
430 |
} |