1 /*
   2  * Copyright (c) 2012, 2017, 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.cldr;
  27 
  28 import java.security.AccessController;
  29 import java.security.AccessControlException;
  30 import java.security.PrivilegedAction;
  31 import java.security.PrivilegedActionException;
  32 import java.security.PrivilegedExceptionAction;
  33 import java.text.spi.BreakIteratorProvider;
  34 import java.text.spi.CollatorProvider;
  35 import java.util.Arrays;
  36 import java.util.Collections;
  37 import java.util.HashSet;
  38 import java.util.List;
  39 import java.util.Locale;
  40 import java.util.Map;
  41 import java.util.Optional;
  42 import java.util.ServiceLoader;
  43 import java.util.ServiceConfigurationError;
  44 import java.util.Set;
  45 import java.util.StringTokenizer;
  46 import java.util.concurrent.ConcurrentHashMap;
  47 import java.util.spi.CalendarDataProvider;
  48 import sun.util.locale.provider.JRELocaleProviderAdapter;
  49 import sun.util.locale.provider.LocaleDataMetaInfo;
  50 import sun.util.locale.provider.LocaleProviderAdapter;
  51 
  52 /**
  53  * LocaleProviderAdapter implementation for the CLDR locale data.
  54  *
  55  * @author Masayoshi Okutsu
  56  * @author Naoto Sato
  57  */
  58 public class CLDRLocaleProviderAdapter extends JRELocaleProviderAdapter {
  59 
  60     private static final CLDRBaseLocaleDataMetaInfo baseMetaInfo = new CLDRBaseLocaleDataMetaInfo();
  61     // Assumption: CLDR has only one non-Base module.
  62     private final LocaleDataMetaInfo nonBaseMetaInfo;
  63 
  64     // parent locales map
  65     private static volatile Map<Locale, Locale> parentLocalesMap;
  66     static {
  67         parentLocalesMap = new ConcurrentHashMap<>();
  68         // Assuming these locales do NOT have irregular parent locales.
  69         parentLocalesMap.put(Locale.ROOT, Locale.ROOT);
  70         parentLocalesMap.put(Locale.ENGLISH, Locale.ENGLISH);
  71         parentLocalesMap.put(Locale.US, Locale.US);
  72     }
  73 
  74     public CLDRLocaleProviderAdapter() {
  75         LocaleDataMetaInfo nbmi = null;
  76 
  77         try {
  78             nbmi = AccessController.doPrivileged(new PrivilegedExceptionAction<LocaleDataMetaInfo>() {
  79                 @Override
  80                 public LocaleDataMetaInfo run() {
  81                     for (LocaleDataMetaInfo ldmi : ServiceLoader.loadInstalled(LocaleDataMetaInfo.class)) {
  82                         if (ldmi.getType() == LocaleProviderAdapter.Type.CLDR) {
  83                             return ldmi;
  84                         }
  85                     }
  86                     return null;
  87                 }
  88             });
  89         } catch (PrivilegedActionException pae) {
  90             throw new InternalError(pae.getCause());
  91         }
  92 
  93         nonBaseMetaInfo = nbmi;
  94     }
  95 
  96     /**
  97      * Returns the type of this LocaleProviderAdapter
  98      * @return the type of this
  99      */
 100     @Override
 101     public LocaleProviderAdapter.Type getAdapterType() {
 102         return LocaleProviderAdapter.Type.CLDR;
 103     }
 104 
 105     @Override
 106     public BreakIteratorProvider getBreakIteratorProvider() {
 107         return null;
 108     }
 109 
 110     @Override
 111     public CalendarDataProvider getCalendarDataProvider() {
 112         if (calendarDataProvider == null) {
 113             CalendarDataProvider provider = AccessController.doPrivileged(
 114                 (PrivilegedAction<CalendarDataProvider>) () ->
 115                     new CLDRCalendarDataProviderImpl(
 116                         getAdapterType(),
 117                         getLanguageTagSet("CalendarData")));
 118 
 119             synchronized (this) {
 120                 if (calendarDataProvider == null) {
 121                     calendarDataProvider = provider;
 122                 }
 123             }
 124         }
 125         return calendarDataProvider;
 126     }
 127 
 128     @Override
 129     public CollatorProvider getCollatorProvider() {
 130         return null;
 131     }
 132 
 133     @Override
 134     public Locale[] getAvailableLocales() {
 135         Set<String> all = createLanguageTagSet("AvailableLocales");
 136         Locale[] locs = new Locale[all.size()];
 137         int index = 0;
 138         for (String tag : all) {
 139             locs[index++] = Locale.forLanguageTag(tag);
 140         }
 141         return locs;
 142     }
 143 
 144     @Override
 145     protected Set<String> createLanguageTagSet(String category) {
 146         // Assume all categories support the same set as AvailableLocales
 147         // in CLDR adapter.
 148         category = "AvailableLocales";
 149 
 150         // Directly call Base tags, as we know it's in the base module.
 151         String supportedLocaleString = baseMetaInfo.availableLanguageTags(category);
 152         String nonBaseTags = null;
 153 
 154         if (nonBaseMetaInfo != null) {
 155             nonBaseTags = nonBaseMetaInfo.availableLanguageTags(category);
 156         }
 157         if (nonBaseTags != null) {
 158             if (supportedLocaleString != null) {
 159                 supportedLocaleString += " " + nonBaseTags;
 160             } else {
 161                 supportedLocaleString = nonBaseTags;
 162             }
 163         }
 164         if (supportedLocaleString == null) {
 165             return Collections.emptySet();
 166         }
 167         Set<String> tagset = new HashSet<>();
 168         StringTokenizer tokens = new StringTokenizer(supportedLocaleString);
 169         while (tokens.hasMoreTokens()) {
 170             tagset.add(tokens.nextToken());
 171         }
 172         return tagset;
 173     }
 174 
 175     // Implementation of ResourceBundleBasedAdapter
 176     @Override
 177     public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 178         List<Locale> candidates = super.getCandidateLocales(baseName, locale);
 179         return applyParentLocales(baseName, candidates);
 180     }
 181 
 182     private List<Locale> applyParentLocales(String baseName, List<Locale> candidates) {
 183         // check irregular parents
 184         for (int i = 0; i < candidates.size(); i++) {
 185             Locale l = candidates.get(i);
 186             if (!l.equals(Locale.ROOT)) {
 187                 Locale p = getParentLocale(l);
 188                 if (p != null &&
 189                     !candidates.get(i+1).equals(p)) {
 190                     List<Locale> applied = candidates.subList(0, i+1);
 191                     applied.addAll(applyParentLocales(baseName, super.getCandidateLocales(baseName, p)));
 192                     return applied;
 193                 }
 194             }
 195         }
 196 
 197         return candidates;
 198     }
 199 
 200     private static Locale getParentLocale(Locale locale) {
 201         Locale parent = parentLocalesMap.get(locale);
 202 
 203         if (parent == null) {
 204             String tag = locale.toLanguageTag();
 205             for (Map.Entry<Locale, String[]> entry : baseMetaInfo.parentLocales().entrySet()) {
 206                 if (Arrays.binarySearch(entry.getValue(), tag) >= 0) {
 207                     parent = entry.getKey();
 208                     break;
 209                 }
 210             }
 211             if (parent == null) {
 212                 parent = locale; // non existent marker
 213             }
 214             parentLocalesMap.putIfAbsent(locale, parent);
 215         }
 216 
 217         if (locale.equals(parent)) {
 218             // means no irregular parent.
 219             parent = null;
 220         }
 221 
 222         return parent;
 223     }
 224 
 225     /**
 226      * This method returns equivalent CLDR supported locale for zh-HK,
 227      * no, no-NO locales so that COMPAT locales do not precede
 228      * those locales during ResourceBundle search path.
 229      */
 230     private static Locale getEquivalentLoc(Locale locale) {
 231         switch (locale.toString()) {
 232             case "zh_HK":
 233                 return Locale.forLanguageTag("zh-Hant-HK");
 234             case "no":
 235             case "no_NO":
 236                 return Locale.forLanguageTag("nb");
 237         }
 238         return locale;
 239     }
 240 
 241     @Override
 242     public boolean isSupportedProviderLocale(Locale locale, Set<String> langtags) {
 243         return Locale.ROOT.equals(locale)
 244                 || langtags.contains(locale.stripExtensions().toLanguageTag())
 245                 || langtags.contains(getEquivalentLoc(locale).toLanguageTag());
 246     }
 247 
 248     /**
 249      * Returns the time zone ID from an LDML's short ID
 250      */
 251     public Optional<String> getTimeZoneID(String shortID) {
 252         return Optional.ofNullable(baseMetaInfo.tzShortIDs().get(shortID));
 253     }
 254 }