package sun.util.cldr;
import java.security.AccessController;
import java.security.AccessControlException;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.text.spi.BreakIteratorProvider;
import java.text.spi.CollatorProvider;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.ServiceConfigurationError;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.spi.CalendarDataProvider;
import java.util.spi.CalendarNameProvider;
import java.util.spi.TimeZoneNameProvider;
import sun.util.locale.provider.JRELocaleProviderAdapter;
import sun.util.locale.provider.LocaleDataMetaInfo;
import sun.util.locale.provider.LocaleProviderAdapter;
public class CLDRLocaleProviderAdapter extends JRELocaleProviderAdapter {
private static final CLDRBaseLocaleDataMetaInfo baseMetaInfo = new CLDRBaseLocaleDataMetaInfo();
private final LocaleDataMetaInfo nonBaseMetaInfo;
private static volatile Map<Locale, Locale> parentLocalesMap;
private static volatile Map<String,String> langAliasesMap;
private static final Map<Locale, Locale> langAliasesCache;
static {
parentLocalesMap = new ConcurrentHashMap<>();
langAliasesMap = new ConcurrentHashMap<>();
langAliasesCache = new ConcurrentHashMap<>();
parentLocalesMap.put(Locale.ROOT, Locale.ROOT);
parentLocalesMap.put(Locale.ENGLISH, Locale.ENGLISH);
parentLocalesMap.put(Locale.US, Locale.US);
}
public CLDRLocaleProviderAdapter() {
LocaleDataMetaInfo nbmi = null;
try {
nbmi = AccessController.doPrivileged(new PrivilegedExceptionAction<LocaleDataMetaInfo>() {
@Override
public LocaleDataMetaInfo run() {
for (LocaleDataMetaInfo ldmi : ServiceLoader.loadInstalled(LocaleDataMetaInfo.class)) {
if (ldmi.getType() == LocaleProviderAdapter.Type.CLDR) {
return ldmi;
}
}
return null;
}
});
} catch (PrivilegedActionException pae) {
throw new InternalError(pae.getCause());
}
nonBaseMetaInfo = nbmi;
}
@Override
public LocaleProviderAdapter.Type getAdapterType() {
return LocaleProviderAdapter.Type.CLDR;
}
@Override
public BreakIteratorProvider getBreakIteratorProvider() {
return null;
}
@Override
public CalendarDataProvider getCalendarDataProvider() {
if (calendarDataProvider == null) {
CalendarDataProvider provider = AccessController.doPrivileged(
(PrivilegedAction<CalendarDataProvider>) () ->
new CLDRCalendarDataProviderImpl(
getAdapterType(),
getLanguageTagSet("CalendarData")));
synchronized (this) {
if (calendarDataProvider == null) {
calendarDataProvider = provider;
}
}
}
return calendarDataProvider;
}
@Override
public CalendarNameProvider getCalendarNameProvider() {
if (calendarNameProvider == null) {
CalendarNameProvider provider = AccessController.doPrivileged(
(PrivilegedAction<CalendarNameProvider>) ()
-> new CLDRCalendarNameProviderImpl(
getAdapterType(),
getLanguageTagSet("FormatData")));
synchronized (this) {
if (calendarNameProvider == null) {
calendarNameProvider = provider;
}
}
}
return calendarNameProvider;
}
@Override
public CollatorProvider getCollatorProvider() {
return null;
}
@Override
public TimeZoneNameProvider getTimeZoneNameProvider() {
if (timeZoneNameProvider == null) {
TimeZoneNameProvider provider = AccessController.doPrivileged(
(PrivilegedAction<TimeZoneNameProvider>) () ->
new CLDRTimeZoneNameProviderImpl(
getAdapterType(),
getLanguageTagSet("TimeZoneNames")));
synchronized (this) {
if (timeZoneNameProvider == null) {
timeZoneNameProvider = provider;
}
}
}
return timeZoneNameProvider;
}
@Override
public Locale[] getAvailableLocales() {
Set<String> all = createLanguageTagSet("AvailableLocales");
Locale[] locs = new Locale[all.size()];
int index = 0;
for (String tag : all) {
locs[index++] = Locale.forLanguageTag(tag);
}
return locs;
}
private static Locale applyAliases(Locale loc) {
if (langAliasesMap.isEmpty()) {
langAliasesMap = baseMetaInfo.getLanguageAliasMap();
}
Locale locale = langAliasesCache.get(loc);
if (locale == null) {
String locTag = loc.toLanguageTag();
Locale aliasLocale = langAliasesMap.containsKey(locTag)
? Locale.forLanguageTag(langAliasesMap.get(locTag)) : loc;
langAliasesCache.putIfAbsent(loc, aliasLocale);
return aliasLocale;
} else {
return locale;
}
}
@Override
protected Set<String> createLanguageTagSet(String category) {
category = "AvailableLocales";
String supportedLocaleString = baseMetaInfo.availableLanguageTags(category);
String nonBaseTags = null;
if (nonBaseMetaInfo != null) {
nonBaseTags = nonBaseMetaInfo.availableLanguageTags(category);
}
if (nonBaseTags != null) {
if (supportedLocaleString != null) {
supportedLocaleString += " " + nonBaseTags;
} else {
supportedLocaleString = nonBaseTags;
}
}
if (supportedLocaleString == null) {
return Collections.emptySet();
}
Set<String> tagset = new HashSet<>();
StringTokenizer tokens = new StringTokenizer(supportedLocaleString);
while (tokens.hasMoreTokens()) {
tagset.add(tokens.nextToken());
}
return tagset;
}
@Override
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
List<Locale> candidates = super.getCandidateLocales(baseName, applyAliases(locale));
return applyParentLocales(baseName, candidates);
}
private List<Locale> applyParentLocales(String baseName, List<Locale> candidates) {
for (int i = 0; i < candidates.size(); i++) {
Locale l = candidates.get(i);
if (!l.equals(Locale.ROOT)) {
Locale p = getParentLocale(l);
if (p != null &&
!candidates.get(i+1).equals(p)) {
List<Locale> applied = candidates.subList(0, i+1);
applied.addAll(applyParentLocales(baseName, super.getCandidateLocales(baseName, p)));
return applied;
}
}
}
return candidates;
}
private static Locale getParentLocale(Locale locale) {
Locale parent = parentLocalesMap.get(locale);
if (parent == null) {
String tag = locale.toLanguageTag();
for (Map.Entry<Locale, String[]> entry : baseMetaInfo.parentLocales().entrySet()) {
if (Arrays.binarySearch(entry.getValue(), tag) >= 0) {
parent = entry.getKey();
break;
}
}
if (parent == null) {
parent = locale;
}
parentLocalesMap.putIfAbsent(locale, parent);
}
if (locale.equals(parent)) {
parent = null;
}
return parent;
}
private static Locale getEquivalentLoc(Locale locale) {
switch (locale.toString()) {
case "no":
case "no_NO":
return Locale.forLanguageTag("nb");
}
return applyAliases(locale);
}
@Override
public boolean isSupportedProviderLocale(Locale locale, Set<String> langtags) {
return Locale.ROOT.equals(locale)
|| langtags.contains(locale.stripExtensions().toLanguageTag())
|| langtags.contains(getEquivalentLoc(locale).toLanguageTag());
}
public Optional<String> canonicalTZID(String id) {
return Optional.ofNullable(baseMetaInfo.tzCanonicalIDs().get(id));
}
}