Path: blob/master/src/java.base/share/classes/sun/util/cldr/CLDRLocaleProviderAdapter.java
41159 views
/*1* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.util.cldr;2627import java.security.AccessController;28import java.security.PrivilegedAction;29import java.security.PrivilegedActionException;30import java.security.PrivilegedExceptionAction;31import java.text.spi.BreakIteratorProvider;32import java.text.spi.CollatorProvider;33import java.util.Arrays;34import java.util.Collections;35import java.util.HashSet;36import java.util.List;37import java.util.Locale;38import java.util.Map;39import java.util.Optional;40import java.util.ServiceLoader;41import java.util.Set;42import java.util.StringTokenizer;43import java.util.concurrent.ConcurrentHashMap;44import java.util.spi.CalendarDataProvider;45import java.util.spi.CalendarNameProvider;46import java.util.spi.TimeZoneNameProvider;47import sun.util.locale.provider.JRELocaleProviderAdapter;48import sun.util.locale.provider.LocaleDataMetaInfo;49import sun.util.locale.provider.LocaleProviderAdapter;5051/**52* LocaleProviderAdapter implementation for the CLDR locale data.53*54* @author Masayoshi Okutsu55* @author Naoto Sato56*/57public class CLDRLocaleProviderAdapter extends JRELocaleProviderAdapter {5859private static final CLDRBaseLocaleDataMetaInfo baseMetaInfo = new CLDRBaseLocaleDataMetaInfo();60// Assumption: CLDR has only one non-Base module.61private final LocaleDataMetaInfo nonBaseMetaInfo;6263// parent locales map64private static volatile Map<Locale, Locale> parentLocalesMap;65// language aliases map66private static volatile Map<String,String> langAliasesMap;67// cache to hold locale to locale mapping for language aliases.68private static final Map<Locale, Locale> langAliasesCache;69static {70parentLocalesMap = new ConcurrentHashMap<>();71langAliasesMap = new ConcurrentHashMap<>();72langAliasesCache = new ConcurrentHashMap<>();73// Assuming these locales do NOT have irregular parent locales.74parentLocalesMap.put(Locale.ROOT, Locale.ROOT);75parentLocalesMap.put(Locale.ENGLISH, Locale.ENGLISH);76parentLocalesMap.put(Locale.US, Locale.US);77}7879@SuppressWarnings("removal")80public CLDRLocaleProviderAdapter() {81LocaleDataMetaInfo nbmi;8283try {84nbmi = AccessController.doPrivileged((PrivilegedExceptionAction<LocaleDataMetaInfo>) () -> {85for (LocaleDataMetaInfo ldmi : ServiceLoader.loadInstalled(LocaleDataMetaInfo.class)) {86if (ldmi.getType() == Type.CLDR) {87return ldmi;88}89}90return null;91});92} catch (PrivilegedActionException pae) {93throw new InternalError(pae.getCause());94}9596nonBaseMetaInfo = nbmi;97}9899/**100* Returns the type of this LocaleProviderAdapter101* @return the type of this102*/103@Override104public LocaleProviderAdapter.Type getAdapterType() {105return LocaleProviderAdapter.Type.CLDR;106}107108@Override109public BreakIteratorProvider getBreakIteratorProvider() {110return null;111}112113@Override114public CalendarDataProvider getCalendarDataProvider() {115if (calendarDataProvider == null) {116@SuppressWarnings("removal")117CalendarDataProvider provider = AccessController.doPrivileged(118(PrivilegedAction<CalendarDataProvider>) () ->119new CLDRCalendarDataProviderImpl(120getAdapterType(),121getLanguageTagSet("CalendarData")));122123synchronized (this) {124if (calendarDataProvider == null) {125calendarDataProvider = provider;126}127}128}129return calendarDataProvider;130}131132@Override133public CalendarNameProvider getCalendarNameProvider() {134if (calendarNameProvider == null) {135@SuppressWarnings("removal")136CalendarNameProvider provider = AccessController.doPrivileged(137(PrivilegedAction<CalendarNameProvider>) ()138-> new CLDRCalendarNameProviderImpl(139getAdapterType(),140getLanguageTagSet("FormatData")));141142synchronized (this) {143if (calendarNameProvider == null) {144calendarNameProvider = provider;145}146}147}148return calendarNameProvider;149}150151@Override152public CollatorProvider getCollatorProvider() {153return null;154}155156@Override157public TimeZoneNameProvider getTimeZoneNameProvider() {158if (timeZoneNameProvider == null) {159@SuppressWarnings("removal")160TimeZoneNameProvider provider = AccessController.doPrivileged(161(PrivilegedAction<TimeZoneNameProvider>) () ->162new CLDRTimeZoneNameProviderImpl(163getAdapterType(),164getLanguageTagSet("TimeZoneNames")));165166synchronized (this) {167if (timeZoneNameProvider == null) {168timeZoneNameProvider = provider;169}170}171}172return timeZoneNameProvider;173}174175@Override176public Locale[] getAvailableLocales() {177Set<String> all = createLanguageTagSet("AvailableLocales");178Locale[] locs = new Locale[all.size()];179int index = 0;180for (String tag : all) {181locs[index++] = Locale.forLanguageTag(tag);182}183return locs;184}185186private static Locale applyAliases(Locale loc) {187if (langAliasesMap.isEmpty()) {188langAliasesMap = baseMetaInfo.getLanguageAliasMap();189}190Locale locale = langAliasesCache.get(loc);191if (locale == null) {192String locTag = loc.toLanguageTag();193Locale aliasLocale = langAliasesMap.containsKey(locTag)194? Locale.forLanguageTag(langAliasesMap.get(locTag)) : loc;195langAliasesCache.putIfAbsent(loc, aliasLocale);196return aliasLocale;197} else {198return locale;199}200}201202@Override203protected Set<String> createLanguageTagSet(String category) {204// Assume all categories support the same set as AvailableLocales205// in CLDR adapter.206category = "AvailableLocales";207208// Directly call Base tags, as we know it's in the base module.209String supportedLocaleString = baseMetaInfo.availableLanguageTags(category);210String nonBaseTags = null;211212if (nonBaseMetaInfo != null) {213nonBaseTags = nonBaseMetaInfo.availableLanguageTags(category);214}215if (nonBaseTags != null) {216if (supportedLocaleString != null) {217supportedLocaleString += " " + nonBaseTags;218} else {219supportedLocaleString = nonBaseTags;220}221}222if (supportedLocaleString == null) {223return Collections.emptySet();224}225StringTokenizer tokens = new StringTokenizer(supportedLocaleString);226Set<String> tagset = new HashSet<>((tokens.countTokens() * 4 + 2) / 3);227while (tokens.hasMoreTokens()) {228tagset.add(tokens.nextToken());229}230return tagset;231}232233// Implementation of ResourceBundleBasedAdapter234@Override235public List<Locale> getCandidateLocales(String baseName, Locale locale) {236List<Locale> candidates = super.getCandidateLocales(baseName, applyAliases(locale));237return applyParentLocales(baseName, candidates);238}239240private List<Locale> applyParentLocales(String baseName, List<Locale> candidates) {241// check irregular parents242for (int i = 0; i < candidates.size(); i++) {243Locale l = candidates.get(i);244if (!l.equals(Locale.ROOT)) {245Locale p = getParentLocale(l);246if (p != null &&247!candidates.get(i+1).equals(p)) {248List<Locale> applied = candidates.subList(0, i+1);249if (applied.contains(p)) {250// avoid circular recursion (could happen with nb/no case)251continue;252}253applied.addAll(applyParentLocales(baseName, super.getCandidateLocales(baseName, p)));254return applied;255}256}257}258259return candidates;260}261262private static Locale getParentLocale(Locale locale) {263Locale parent = parentLocalesMap.get(locale);264265if (parent == null) {266String tag = locale.toLanguageTag();267for (Map.Entry<Locale, String[]> entry : baseMetaInfo.parentLocales().entrySet()) {268if (Arrays.binarySearch(entry.getValue(), tag) >= 0) {269parent = entry.getKey();270break;271}272}273if (parent == null) {274parent = locale; // non existent marker275}276parentLocalesMap.putIfAbsent(locale, parent);277}278279if (locale.equals(parent)) {280// means no irregular parent.281parent = null;282}283284return parent;285}286287/**288* This method returns equivalent CLDR supported locale289* for no, no-NO locales so that COMPAT locales do not precede290* those locales during ResourceBundle search path, also if an alias exists for a locale,291* it returns equivalent locale, e.g for zh_HK it returns zh_Hant-HK.292*/293private static Locale getEquivalentLoc(Locale locale) {294return switch (locale.toString()) {295case "no", "no_NO" -> Locale.forLanguageTag("nb");296default -> applyAliases(locale);297};298}299300@Override301public boolean isSupportedProviderLocale(Locale locale, Set<String> langtags) {302return Locale.ROOT.equals(locale)303|| langtags.contains(locale.stripExtensions().toLanguageTag())304|| langtags.contains(getEquivalentLoc(locale).toLanguageTag());305}306307/**308* Returns the canonical ID for the given ID309*/310public Optional<String> canonicalTZID(String id) {311return Optional.ofNullable(baseMetaInfo.tzCanonicalIDs().get(id));312}313}314315316