1 /* 2 * Copyright (c) 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.security.AccessController; 29 import java.text.spi.BreakIteratorProvider; 30 import java.text.spi.CollatorProvider; 31 import java.text.spi.DateFormatProvider; 32 import java.text.spi.DateFormatSymbolsProvider; 33 import java.text.spi.DecimalFormatSymbolsProvider; 34 import java.text.spi.NumberFormatProvider; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.Locale; 39 import java.util.ResourceBundle; 40 import java.util.Set; 41 import java.util.concurrent.ConcurrentHashMap; 42 import java.util.concurrent.ConcurrentMap; 43 import java.util.spi.CalendarDataProvider; 44 import java.util.spi.CalendarNameProvider; 45 import java.util.spi.CurrencyNameProvider; 46 import java.util.spi.LocaleNameProvider; 47 import java.util.spi.LocaleServiceProvider; 48 import java.util.spi.TimeZoneNameProvider; 49 import sun.util.cldr.CLDRLocaleProviderAdapter; 50 51 /** 52 * The LocaleProviderAdapter abstract class. 53 * 54 * @author Naoto Sato 55 * @author Masayoshi Okutsu 56 */ 57 public abstract class LocaleProviderAdapter { 58 /** 59 * Adapter type. 60 */ 61 public static enum Type { 62 JRE("sun.util.resources", "sun.text.resources"), 63 CLDR("sun.util.resources.cldr", "sun.text.resources.cldr"), 64 SPI, 65 HOST, 66 FALLBACK("sun.util.resources", "sun.text.resources"); 67 68 private final String UTIL_RESOURCES_PACKAGE; 69 private final String TEXT_RESOURCES_PACKAGE; 70 71 private Type() { 72 this(null, null); 73 } 74 75 private Type(String util, String text) { 76 UTIL_RESOURCES_PACKAGE = util; 77 TEXT_RESOURCES_PACKAGE = text; 78 } 79 80 public String getUtilResourcesPackage() { 81 return UTIL_RESOURCES_PACKAGE; 82 } 83 84 public String getTextResourcesPackage() { 85 return TEXT_RESOURCES_PACKAGE; 86 } 87 } 88 89 /** 90 * LocaleProviderAdapter preference list. The default list is intended 91 * to behave the same manner in JDK7. 92 */ 93 private static final List<Type> adapterPreference; 94 95 /** 96 * JRE Locale Data Adapter instance 97 */ 98 private static LocaleProviderAdapter jreLocaleProviderAdapter = new JRELocaleProviderAdapter(); 99 100 /** 101 * SPI Locale Data Adapter instance 102 */ 103 private static LocaleProviderAdapter spiLocaleProviderAdapter = new SPILocaleProviderAdapter(); 104 105 /** 106 * CLDR Locale Data Adapter instance, if any. 107 */ 108 private static LocaleProviderAdapter cldrLocaleProviderAdapter = null; 109 110 /** 111 * HOST Locale Data Adapter instance, if any. 112 */ 113 private static LocaleProviderAdapter hostLocaleProviderAdapter = null; 114 115 /** 116 * FALLBACK Locale Data Adapter instance. It's basically the same with JRE, but only kicks 117 * in for the root locale. 118 */ 119 private static LocaleProviderAdapter fallbackLocaleProviderAdapter = null; 120 121 /** 122 * Adapter lookup cache. 123 */ 124 private static ConcurrentMap<Class<? extends LocaleServiceProvider>, ConcurrentMap<Locale, LocaleProviderAdapter>> 125 adapterCache = new ConcurrentHashMap<>(); 126 127 static { 128 String order = AccessController.doPrivileged( 129 new sun.security.action.GetPropertyAction("java.locale.providers")); 130 List<Type> typeList = new ArrayList<>(); 131 132 // Check user specified adapter preference 133 if (order != null && order.length() != 0) { 134 String[] types = order.split(","); 135 for (String type : types) { 136 try { 137 Type aType = Type.valueOf(type.trim().toUpperCase(Locale.ROOT)); 138 139 // load adapter if necessary 140 switch (aType) { 141 case CLDR: 142 cldrLocaleProviderAdapter = new CLDRLocaleProviderAdapter(); 143 break; 144 case HOST: 145 hostLocaleProviderAdapter = new HostLocaleProviderAdapter(); 146 break; 147 } 148 typeList.add(aType); 149 } catch (IllegalArgumentException | UnsupportedOperationException e) { 150 // could be caused by the user specifying wrong 151 // provider name or format in the system property 152 LocaleServiceProviderPool.config(LocaleProviderAdapter.class, e.toString()); 153 } 154 } 155 } 156 157 if (!typeList.isEmpty()) { 158 if (!typeList.contains(Type.JRE)) { 159 // Append FALLBACK as the last resort. 160 fallbackLocaleProviderAdapter = new FallbackLocaleProviderAdapter(); 161 typeList.add(Type.FALLBACK); 162 } 163 } else { 164 // Default preference list 165 typeList.add(Type.JRE); 166 typeList.add(Type.SPI); 167 } 168 169 adapterPreference = Collections.unmodifiableList(typeList); 170 } 171 172 /** 173 * Returns the singleton instance for each adapter type 174 */ 175 public static LocaleProviderAdapter forType(Type type) { 176 switch (type) { 177 case JRE: 178 return jreLocaleProviderAdapter; 179 case CLDR: 180 return cldrLocaleProviderAdapter; 181 case SPI: 182 return spiLocaleProviderAdapter; 183 case HOST: 184 return hostLocaleProviderAdapter; 185 case FALLBACK: 186 return fallbackLocaleProviderAdapter; 187 default: 188 throw new InternalError("unknown locale data adapter type"); 189 } 190 } 191 192 public static LocaleProviderAdapter forJRE() { 193 return jreLocaleProviderAdapter; 194 } 195 196 public static LocaleProviderAdapter getResourceBundleBased() { 197 for (Type type : getAdapterPreference()) { 198 if (type == Type.JRE || type == Type.CLDR || type == Type.FALLBACK) { 199 return forType(type); 200 } 201 } 202 // Shouldn't happen. 203 throw new InternalError(); 204 } 205 /** 206 * Returns the preference order of LocaleProviderAdapter.Type 207 */ 208 public static List<Type> getAdapterPreference() { 209 return adapterPreference; 210 } 211 212 /** 213 * Returns a LocaleProviderAdapter for the given locale service provider that 214 * best matches the given locale. This method returns the LocaleProviderAdapter 215 * for JRE if none is found for the given locale. 216 * 217 * @param providerClass the class for the locale service provider 218 * @param locale the desired locale. 219 * @return a LocaleProviderAdapter 220 */ 221 public static LocaleProviderAdapter getAdapter(Class<? extends LocaleServiceProvider> providerClass, 222 Locale locale) { 223 LocaleProviderAdapter adapter; 224 225 // cache lookup 226 ConcurrentMap<Locale, LocaleProviderAdapter> adapterMap = adapterCache.get(providerClass); 227 if (adapterMap != null) { 228 if ((adapter = adapterMap.get(locale)) != null) { 229 return adapter; 230 } 231 } else { 232 adapterMap = new ConcurrentHashMap<>(); 233 adapterCache.putIfAbsent(providerClass, adapterMap); 234 } 235 236 // Fast look-up for the given locale 237 adapter = findAdapter(providerClass, locale); 238 if (adapter != null) { 239 adapterMap.putIfAbsent(locale, adapter); 240 return adapter; 241 } 242 243 // Try finding an adapter in the normal candidate locales path of the given locale. 244 List<Locale> lookupLocales = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT) 245 .getCandidateLocales("", locale); 246 for (Locale loc : lookupLocales) { 247 if (loc.equals(locale)) { 248 // We've already done with this loc. 249 continue; 250 } 251 adapter = findAdapter(providerClass, loc); 252 if (adapter != null) { 253 adapterMap.putIfAbsent(locale, adapter); 254 return adapter; 255 } 256 } 257 258 // returns the adapter for FALLBACK as the last resort 259 adapterMap.putIfAbsent(locale, fallbackLocaleProviderAdapter); 260 return fallbackLocaleProviderAdapter; 261 } 262 263 private static LocaleProviderAdapter findAdapter(Class<? extends LocaleServiceProvider> providerClass, 264 Locale locale) { 265 for (Type type : getAdapterPreference()) { 266 LocaleProviderAdapter adapter = forType(type); 267 LocaleServiceProvider provider = adapter.getLocaleServiceProvider(providerClass); 268 if (provider != null) { 269 if (provider.isSupportedLocale(locale)) { 270 return adapter; 271 } 272 } 273 } 274 return null; 275 } 276 277 /** 278 * A utility method for implementing the default LocaleServiceProvider.isSupportedLocale 279 * for the JRE, CLDR, and FALLBACK adapters. 280 */ 281 static boolean isSupportedLocale(Locale locale, LocaleProviderAdapter.Type type, Set<String> langtags) { 282 assert type == Type.JRE || type == Type.CLDR || type == Type.FALLBACK; 283 if (Locale.ROOT.equals(locale)) { 284 return true; 285 } 286 287 if (type == Type.FALLBACK) { 288 // no other locales except ROOT are supported for FALLBACK 289 return false; 290 } 291 292 locale = locale.stripExtensions(); 293 if (langtags.contains(locale.toLanguageTag())) { 294 return true; 295 } 296 if (type == Type.JRE) { 297 String oldname = locale.toString().replace('_', '-'); 298 return langtags.contains(oldname); 299 } 300 return false; 301 } 302 303 public static Locale[] toLocaleArray(Set<String> tags) { 304 Locale[] locs = new Locale[tags.size() + 1]; 305 int index = 0; 306 locs[index++] = Locale.ROOT; 307 for (String tag : tags) { 308 switch (tag) { 309 case "ja-JP-JP": 310 locs[index++] = JRELocaleConstants.JA_JP_JP; 311 break; 312 case "th-TH-TH": 313 locs[index++] = JRELocaleConstants.TH_TH_TH; 314 break; 315 default: 316 locs[index++] = Locale.forLanguageTag(tag); 317 break; 318 } 319 } 320 return locs; 321 } 322 323 /** 324 * Returns the type of this LocaleProviderAdapter 325 */ 326 public abstract LocaleProviderAdapter.Type getAdapterType(); 327 328 /** 329 * Getter method for Locale Service Providers. 330 */ 331 public abstract <P extends LocaleServiceProvider> P getLocaleServiceProvider(Class<P> c); 332 333 /** 334 * Returns a BreakIteratorProvider for this LocaleProviderAdapter, or null if no 335 * BreakIteratorProvider is available. 336 * 337 * @return a BreakIteratorProvider 338 */ 339 public abstract BreakIteratorProvider getBreakIteratorProvider(); 340 341 /** 342 * Returns a ollatorProvider for this LocaleProviderAdapter, or null if no 343 * ollatorProvider is available. 344 * 345 * @return a ollatorProvider 346 */ 347 public abstract CollatorProvider getCollatorProvider(); 348 349 /** 350 * Returns a DateFormatProvider for this LocaleProviderAdapter, or null if no 351 * DateFormatProvider is available. 352 * 353 * @return a DateFormatProvider 354 */ 355 public abstract DateFormatProvider getDateFormatProvider(); 356 357 /** 358 * Returns a DateFormatSymbolsProvider for this LocaleProviderAdapter, or null if no 359 * DateFormatSymbolsProvider is available. 360 * 361 * @return a DateFormatSymbolsProvider 362 */ 363 public abstract DateFormatSymbolsProvider getDateFormatSymbolsProvider(); 364 365 /** 366 * Returns a DecimalFormatSymbolsProvider for this LocaleProviderAdapter, or null if no 367 * DecimalFormatSymbolsProvider is available. 368 * 369 * @return a DecimalFormatSymbolsProvider 370 */ 371 public abstract DecimalFormatSymbolsProvider getDecimalFormatSymbolsProvider(); 372 373 /** 374 * Returns a NumberFormatProvider for this LocaleProviderAdapter, or null if no 375 * NumberFormatProvider is available. 376 * 377 * @return a NumberFormatProvider 378 */ 379 public abstract NumberFormatProvider getNumberFormatProvider(); 380 381 /* 382 * Getter methods for java.util.spi.* providers 383 */ 384 385 /** 386 * Returns a CurrencyNameProvider for this LocaleProviderAdapter, or null if no 387 * CurrencyNameProvider is available. 388 * 389 * @return a CurrencyNameProvider 390 */ 391 public abstract CurrencyNameProvider getCurrencyNameProvider(); 392 393 /** 394 * Returns a LocaleNameProvider for this LocaleProviderAdapter, or null if no 395 * LocaleNameProvider is available. 396 * 397 * @return a LocaleNameProvider 398 */ 399 public abstract LocaleNameProvider getLocaleNameProvider(); 400 401 /** 402 * Returns a TimeZoneNameProvider for this LocaleProviderAdapter, or null if no 403 * TimeZoneNameProvider is available. 404 * 405 * @return a TimeZoneNameProvider 406 */ 407 public abstract TimeZoneNameProvider getTimeZoneNameProvider(); 408 409 /** 410 * Returns a CalendarDataProvider for this LocaleProviderAdapter, or null if no 411 * CalendarDataProvider is available. 412 * 413 * @return a CalendarDataProvider 414 */ 415 public abstract CalendarDataProvider getCalendarDataProvider(); 416 417 /** 418 * Returns a CalendarNameProvider for this LocaleProviderAdapter, or null if no 419 * CalendarNameProvider is available. 420 * 421 * @return a CalendarNameProvider 422 */ 423 public abstract CalendarNameProvider getCalendarNameProvider(); 424 425 public abstract LocaleResources getLocaleResources(Locale locale); 426 427 public abstract Locale[] getAvailableLocales(); 428 }