• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  **********************************************************************
3  * Copyright (c) 2002-2011, International Business Machines
4  * Corporation and others.  All Rights Reserved.
5  **********************************************************************
6  * Author: Mark Davis
7  **********************************************************************
8  */
9 package org.unicode.cldr.util;
10 
11 import java.io.BufferedReader;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collections;
15 import java.util.Comparator;
16 import java.util.EnumMap;
17 import java.util.EnumSet;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.LinkedHashMap;
21 import java.util.LinkedHashSet;
22 import java.util.List;
23 import java.util.Locale;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 import java.util.TreeMap;
28 import java.util.TreeSet;
29 import java.util.regex.Pattern;
30 
31 import org.unicode.cldr.draft.ScriptMetadata;
32 import org.unicode.cldr.draft.ScriptMetadata.IdUsage;
33 import org.unicode.cldr.util.Iso639Data.Type;
34 import org.unicode.cldr.util.ZoneParser.ZoneLine;
35 
36 import com.ibm.icu.impl.Relation;
37 import com.ibm.icu.lang.UCharacter;
38 import com.ibm.icu.text.UnicodeSet;
39 import com.ibm.icu.util.Output;
40 
41 /**
42  * Provides access to various codes used by CLDR: RFC 3066, ISO 4217, Olson
43  * tzids
44  */
45 public class StandardCodes {
46 
47     public enum CodeType {
48         language, script, territory, extlang, legacy, redundant, variant, currency, tzid;
from(String name)49         public static CodeType from(String name) {
50             if ("region".equals(name)) {
51                 return territory;
52             }
53             return CodeType.valueOf(name);
54         }
55     }
56 
57     private static final Set<CodeType> TypeSet = Collections.unmodifiableSet(EnumSet.allOf(CodeType.class));
58 
59     private static final Set<String> TypeStringSet;
60     static {
61         LinkedHashSet<String> foo = new LinkedHashSet<>();
62         for (CodeType x : CodeType.values()) {
x.toString()63             foo.add(x.toString());
64         }
65         TypeStringSet = Collections.unmodifiableSet(foo);
66     }
67 
68     public static final String DESCRIPTION_SEPARATOR = "\u25AA";
69 
70     public static final String NO_COUNTRY = "001";
71 
72     private EnumMap<CodeType, Map<String, List<String>>> type_code_data = new EnumMap<>(
73         CodeType.class);
74 
75     private EnumMap<CodeType, Map<String, List<String>>> type_name_codes = new EnumMap<>(
76         CodeType.class);
77 
78     private EnumMap<CodeType, Map<String, String>> type_code_preferred = new EnumMap<>(
79         CodeType.class);
80 
81     private Map<String, Set<String>> country_modernCurrency = new TreeMap<>();
82 
83     private Map<CodeType, Set<String>> goodCodes = new TreeMap<>();
84 
85     private static final boolean DEBUG = false;
86 
87     private static final class StandardCodesHelper {
88         static final StandardCodes SINGLETON = new StandardCodes();
89     }
90     /**
91      * Get the singleton copy of the standard codes.
92      */
make()93     static public synchronized StandardCodes make() {
94         return StandardCodesHelper.SINGLETON;
95     }
96 
97     /**
98      * The data is the name in the case of RFC3066 codes, and the country code in
99      * the case of TZIDs and ISO currency codes. If the country code is missing,
100      * uses ZZ.
101      */
getData(String type, String code)102     public String getData(String type, String code) {
103         Map<String, List<String>> code_data = getCodeData(type);
104         if (code_data == null)
105             return null;
106         List<String> list = code_data.get(code);
107         if (list == null)
108             return null;
109         return list.get(0);
110     }
111 
112     /**
113      * @return the full data for the type and code For the data in lstreg, it is
114      *         description | date | canonical_value | recommended_prefix #
115      *         comments
116      */
getFullData(String type, String code)117     public List<String> getFullData(String type, String code) {
118         Map<String, List<String>> code_data = getCodeData(type);
119         if (code_data == null)
120             return null;
121         return code_data.get(code);
122     }
123 
124     /**
125      * @return the full data for the type and code For the data in lstreg, it is
126      *         description | date | canonical_value | recommended_prefix #
127      *         comments
128      */
getFullData(CodeType type, String code)129     public List<String> getFullData(CodeType type, String code) {
130         Map<String, List<String>> code_data = type_code_data.get(type);
131         if (code_data == null)
132             return null;
133         return code_data.get(code);
134     }
135 
getCodeData(String type)136     private Map<String, List<String>> getCodeData(String type) {
137         return getCodeData(CodeType.from(type));
138     }
139 
getCodeData(CodeType type)140     private Map<String, List<String>> getCodeData(CodeType type) {
141         return type_code_data.get(type);
142     }
143 
144     /**
145      * Get at the language registry values, as a Map from label to value.
146      *
147      * @param type
148      * @param code
149      * @return
150      */
getLangData(String type, String code)151     public Map<String, String> getLangData(String type, String code) {
152         try {
153             if (type.equals("territory"))
154                 type = "region";
155             else if (type.equals("variant")) code = code.toLowerCase(Locale.ENGLISH);
156             return (Map) ((Map) getLStreg().get(type)).get(code);
157         } catch (RuntimeException e) {
158             return null;
159         }
160     }
161 
162     /**
163      * Return a replacement code, if available. If not, return null.
164      *
165      */
getReplacement(String type, String code)166     public String getReplacement(String type, String code) {
167         if (type.equals("currency"))
168             return null; // no replacement codes for currencies
169         List<String> data = getFullData(type, code);
170         if (data == null)
171             return null;
172         // if available, the replacement is a non-empty value other than --, in
173         // position 2.
174         if (data.size() < 3)
175             return null;
176         String replacement = data.get(2);
177         if (!replacement.equals("") && !replacement.equals("--"))
178             return replacement;
179         return null;
180     }
181 
182     /**
183      * Return the list of codes that have the same data. For example, returns all
184      * currency codes for a country. If there is a preferred one, it is first.
185      *
186      * @param type
187      * @param data
188      * @return
189      */
190     @Deprecated
getCodes(String type, String data)191     public List<String> getCodes(String type, String data) {
192         return getCodes(CodeType.from(type), data);
193     }
194 
195     /**
196      * Return the list of codes that have the same data. For example, returns all
197      * currency codes for a country. If there is a preferred one, it is first.
198      */
getCodes(CodeType type, String data)199     public List<String> getCodes(CodeType type, String data) {
200         Map<String, List<String>> data_codes = type_name_codes.get(type);
201         if (data_codes == null)
202             return null;
203         return Collections.unmodifiableList(data_codes.get(data));
204     }
205 
206     /**
207      * Where there is a preferred code, return it.
208      */
209     @Deprecated
getPreferred(String type, String code)210     public String getPreferred(String type, String code) {
211         return getPreferred(CodeType.from(type), code);
212     }
213 
214     /**
215      * Where there is a preferred code, return it.
216      */
217 
getPreferred(CodeType type, String code)218     public String getPreferred(CodeType type, String code) {
219         Map<String, String> code_preferred = type_code_preferred.get(type);
220         if (code_preferred == null)
221             return code;
222         String newCode = code_preferred.get(code);
223         if (newCode == null)
224             return code;
225         return newCode;
226     }
227 
228     /**
229      * Get all the available types
230      */
getAvailableTypes()231     public Set<String> getAvailableTypes() {
232         return TypeStringSet;
233     }
234 
235     /**
236      * Get all the available types
237      */
getAvailableTypesEnum()238     public Set<CodeType> getAvailableTypesEnum() {
239         return TypeSet;
240     }
241 
242     /**
243      * Get all the available codes for a given type
244      *
245      * @param type
246      * @return
247      */
getAvailableCodes(String type)248     public Set<String> getAvailableCodes(String type) {
249         return getAvailableCodes(CodeType.from(type));
250     }
251 
252     /**
253      * Get all the available codes for a given type
254      *
255      * @param type
256      * @return
257      */
getAvailableCodes(CodeType type)258     public Set<String> getAvailableCodes(CodeType type) {
259         Map<String, List<String>> code_name = type_code_data.get(type);
260         return Collections.unmodifiableSet(code_name.keySet());
261     }
262 
getGoodAvailableCodes(String stringType)263     public Set<String> getGoodAvailableCodes(String stringType) {
264         return getGoodAvailableCodes(CodeType.from(stringType));
265     }
266 
267     /**
268      * Get all the available "real" codes for a given type, excluding private use,
269      * but including some deprecated codes. Use SupplementalDataInfo getLocaleAliases to
270      * exclude others.
271      *
272      * @param type
273      * @return
274      */
getGoodAvailableCodes(CodeType type)275     public Set<String> getGoodAvailableCodes(CodeType type) {
276         Set<String> result = goodCodes.get(type);
277         if (result == null) {
278             synchronized (goodCodes) {
279                 Map<String, List<String>> code_name = getCodeData(type);
280                 SupplementalDataInfo sd = SupplementalDataInfo.getInstance();
281                 if (code_name == null)
282                     return null;
283                 result = new TreeSet<>(code_name.keySet());
284                 switch (type) {
285                 case currency:
286                     break; // nothing special
287                 case language:
288                     return sd.getCLDRLanguageCodes();
289                 case script:
290                     return sd.getCLDRScriptCodes();
291                 case tzid:
292                     break; // nothing special
293                 default:
294                     for (Iterator<String> it = result.iterator(); it.hasNext();) {
295                         String code = it.next();
296                         if (code.equals("root") || code.equals("QO"))
297                             continue;
298                         List<String> data = getFullData(type, code);
299                         if (data.size() < 3) {
300                             if (DEBUG)
301                                 System.out.println(code + "\t" + data);
302                         }
303                         if ("PRIVATE USE".equalsIgnoreCase(data.get(0))
304                             || (!data.get(2).equals("") && !data.get(2).equals("--"))) {
305                             // System.out.println("Removing: " + code);
306                             it.remove();
307                         }
308                     }
309                 }
310                 result = Collections.unmodifiableSet(result);
311                 goodCodes.put(type, result);
312             }
313         }
314         return result;
315     }
316 
317     private static Set<String> GOOD_COUNTRIES;
318 
getGoodCountries()319     public Set<String> getGoodCountries() {
320         synchronized (goodCodes) {
321             if (GOOD_COUNTRIES == null) {
322                 Set<String> temp = new LinkedHashSet<>();
323                 for (String s : getGoodAvailableCodes(CodeType.territory)) {
324                     if (isCountry(s)) {
325                         temp.add(s);
326                     }
327                 }
328                 GOOD_COUNTRIES = Collections.unmodifiableSet(temp);
329             }
330         }
331         return GOOD_COUNTRIES;
332     }
333 
334     /**
335      * Gets the modern currency.
336      */
getMainCurrencies(String countryCode)337     public Set<String> getMainCurrencies(String countryCode) {
338         return country_modernCurrency.get(countryCode);
339     }
340 
341 //    /**
342 //     * Get rid of this
343 //     *
344 //     * @param type
345 //     * @return
346 //     * @throws IOException
347 //     * @deprecated
348 //     */
349 //    public String getEffectiveLocaleType(String type) throws IOException {
350 //        if ((type != null) && (getLocaleCoverageOrganizations().contains(Organization.valueOf(type)))) {
351 //            return type;
352 //        } else {
353 //            return null; // the default.. for now..
354 //        }
355 //    }
356 
357     static Comparator caseless = new Comparator() {
358 
359         @Override
360         public int compare(Object arg0, Object arg1) {
361             String s1 = (String) arg0;
362             String s2 = (String) arg1;
363             return s1.compareToIgnoreCase(s2);
364         }
365 
366     };
367 
368     /**
369      * Used for Locales.txt to mean "all"
370      */
371     public static final String ALL_LOCALES = "*";
372 
373     /**
374      * Returns locales according to status. It returns a Map of Maps, key 1 is
375      * either IBM or Java (perhaps more later), key 2 is the Level.
376      *
377      * @deprecated
378      */
379     @Deprecated
getLocaleTypes()380     public Map<Organization, Map<String, Level>> getLocaleTypes() {
381         synchronized (StandardCodes.class) {
382             return loadPlatformLocaleStatus().platform_locale_level;
383         }
384     }
385 
386     /**
387      * Return map of locales to levels
388      * @param org
389      * @return
390      */
getLocaleToLevel(Organization org)391     public Map<String, Level> getLocaleToLevel(Organization org) {
392         return getLocaleTypes().get(org);
393     }
394 
395     /**
396      * returns the highest level in the hierarchy, not including root.
397      */
getHighestLocaleCoverageLevel(String organization, String locale)398     public Level getHighestLocaleCoverageLevel(String organization, String locale) {
399         // first get parent
400         final String parentId = LocaleIDParser.getParent(locale);
401         Level parentLevel = Level.UNDETERMINED;
402         if (parentId != null && !parentId.equals("root")) {
403             parentLevel = getHighestLocaleCoverageLevel(organization, parentId); // recurse
404         }
405         final Level ourLevel = getLocaleCoverageLevel(organization, locale);
406         if (parentLevel.getLevel() > ourLevel.getLevel()) {
407             // if parentLevel is higher
408             return parentLevel;
409         } else {
410             return ourLevel;
411         }
412     }
413 
getLocaleCoverageLevel(String organization, String desiredLocale)414     public Level getLocaleCoverageLevel(String organization, String desiredLocale) {
415         return getLocaleCoverageLevel(Organization.fromString(organization), desiredLocale);
416     }
417 
getLocaleCoverageLevel(Organization organization, String desiredLocale)418     public Level getLocaleCoverageLevel(Organization organization, String desiredLocale) {
419         return getLocaleCoverageLevel(organization, desiredLocale, new Output<LocaleCoverageType>());
420     }
421 
422     public enum LocaleCoverageType {
423         explicit, parent, star, undetermined
424     }
425 
426     /**
427      * Returns coverage level of locale according to organization. Returns Level.UNDETERMINED if information is missing.
428      * A locale of "*" in the data means "everything else".
429      */
getLocaleCoverageLevel(Organization organization, String desiredLocale, Output<LocaleCoverageType> coverageType)430     public Level getLocaleCoverageLevel(Organization organization, String desiredLocale, Output<LocaleCoverageType> coverageType) {
431         coverageType.value = LocaleCoverageType.undetermined;
432         if (organization == null) {
433             return Level.UNDETERMINED;
434         }
435         Map<String, Level> locale_status = loadPlatformLocaleStatus().platform_locale_level.get(organization);
436         if (locale_status == null) {
437             return Level.UNDETERMINED;
438         }
439         // see if there is a parent
440         String originalLocale = desiredLocale;
441         while (desiredLocale != null) {
442             Level status = locale_status.get(desiredLocale);
443             if (status != null && status != Level.UNDETERMINED) {
444                 coverageType.value = originalLocale == desiredLocale ? LocaleCoverageType.explicit : LocaleCoverageType.parent;
445                 return status;
446             }
447             desiredLocale = LocaleIDParser.getParent(desiredLocale);
448         }
449         Level status = locale_status.get(ALL_LOCALES);
450         if (status != null && status != Level.UNDETERMINED) {
451             coverageType.value = LocaleCoverageType.star;
452             return status;
453         }
454         return Level.UNDETERMINED;
455     }
456 
457     /**
458      * Returns coverage level of locale according to organization. Returns Level.UNDETERMINED if information is missing.
459      */
getDefaultLocaleCoverageLevel(Organization organization)460     public Level getDefaultLocaleCoverageLevel(Organization organization) {
461         return getLocaleCoverageLevel(organization, ALL_LOCALES);
462     }
463 
getLocaleCoverageOrganizations()464     public Set<Organization> getLocaleCoverageOrganizations() {
465         return loadPlatformLocaleStatus().platform_locale_level.keySet();
466     }
467 
getLocaleCoverageOrganizationStrings()468     public Set<String> getLocaleCoverageOrganizationStrings() {
469         return loadPlatformLocaleStatus().platform_locale_levelString.keySet();
470     }
471 
getLocaleCoverageLocales(String organization)472     public Set<String> getLocaleCoverageLocales(String organization) {
473         return getLocaleCoverageLocales(Organization.fromString(organization));
474     }
475 
getLocaleCoverageLocales(Organization organization)476     public Set<String> getLocaleCoverageLocales(Organization organization) {
477         return loadPlatformLocaleStatus().platform_locale_level.get(organization).keySet();
478     }
479 
getLocalesToLevelsFor(Organization organization)480     public Map<String, Level> getLocalesToLevelsFor(Organization organization) {
481         return loadPlatformLocaleStatus().platform_locale_level.get(organization);
482     }
483 
getLevelsToLocalesFor(Organization organization)484     public Relation<Level, String> getLevelsToLocalesFor(Organization organization) {
485         return loadPlatformLocaleStatus().platform_level_locale.get(organization);
486     }
487 
getLocaleCoverageLocales(Organization organization, Set<Level> choice)488     public Set<String> getLocaleCoverageLocales(Organization organization, Set<Level> choice) {
489         Set<String> result = new LinkedHashSet<>();
490         for (String locale : getLocaleCoverageLocales(organization)) {
491             if (choice.contains(getLocaleCoverageLevel(organization, locale))) {
492                 result.add(locale);
493             }
494         }
495         return result;
496     }
497 
498     /**
499      * "The target coverage level is set to:
500      * - The CLDR Org coverage level if it exists,
501      * - Otherise, the maximum of all the coverage levels for that locale across all Organizations
502      *  (max Modern) in Locales.txt, if there is at least one.
503      * - Otherwise Basic.
504      * - That makes the number the same for all Organizations, which makes communicating the values less prone
505      * to misinterpretation, and gives all the vetters and managers a common metric for that locale.
506      */
getTargetCoverageLevel(String localeId)507     public Level getTargetCoverageLevel(String localeId) {
508         Level level;
509 
510         // First, try CLDR locale
511         level = getLocaleCoverageLevel(Organization.cldr, localeId);
512         if (level != Level.UNDETERMINED) {
513             return level;
514         }
515 
516         // Next, Find maximum coverage level
517         for (final Organization o : Organization.values()) {
518             if (o == Organization.cldr ||  // Already handled, above
519                 o == Organization.guest ||
520                 o == Organization.surveytool) {
521                 continue; // Skip some 'special' orgs
522             }
523             final Output<StandardCodes.LocaleCoverageType> outputType = new Output<>();
524             final Level orgLevel = getLocaleCoverageLevel(o, localeId, outputType);
525             if (outputType.value == StandardCodes.LocaleCoverageType.undetermined ||
526                 outputType.value == StandardCodes.LocaleCoverageType.star) {
527                 // Skip undetermined or star
528                 continue;
529             }
530             // Pin the level to MODERN
531             final Level pinnedOrgLevel = Level.min(Level.MODERN, orgLevel);
532             // Accumulate the maxiumum org level (up to MODERN)
533             level = Level.max(level, pinnedOrgLevel);
534         }
535         if (level != Level.UNDETERMINED) {
536             return level;
537         }
538 
539         // Otherwise, BASIC
540         level = Level.BASIC;
541         return level;
542     }
543 
544     private static final class LocalesTxtHelper {
545         static LocalesTxtHelper SINGLETON = new LocalesTxtHelper();
546 
547         public LocalesTxtReader reader;
548 
LocalesTxtHelper()549         LocalesTxtHelper() {
550             reader = new LocalesTxtReader()
551                 .read(StandardCodes.make()); // circular dependency
552         }
553     }
554 
555     /**
556      * Get the 'platform locale status' (aka Locales.txt)
557      * Note, do not call this from the StandardCodes constructor!
558      * @return
559      */
loadPlatformLocaleStatus()560     private LocalesTxtReader loadPlatformLocaleStatus() {
561         return LocalesTxtHelper.SINGLETON.reader;
562     }
563 
validate(LocaleIDParser parser)564     String validate(LocaleIDParser parser) {
565         String message = "";
566         String lang = parser.getLanguage();
567         if (lang.length() == 0) {
568             message += ", Missing language";
569         } else if (!getAvailableCodes("language").contains(lang)) {
570             message += ", Invalid language code: " + lang;
571         }
572         String script = parser.getScript();
573         if (script.length() != 0 && !getAvailableCodes("script").contains(script)) {
574             message += ", Invalid script code: " + script;
575         }
576         String territory = parser.getRegion();
577         if (territory.length() != 0 && !getAvailableCodes("territory").contains(territory)) {
578             message += ", Invalid territory code: " + lang;
579         }
580         return message.length() == 0 ? message : message.substring(2);
581     }
582 
583     /**
584      * Ascertain that the given locale in in the given group specified by the
585      * organization
586      *
587      * @param locale
588      * @param group
589      * @param org
590      * @return boolean
591      */
isLocaleInGroup(String locale, String group, Organization org)592     public boolean isLocaleInGroup(String locale, String group, Organization org) {
593         return group.equals(getGroup(locale, org));
594     }
595 
isLocaleInGroup(String locale, String group, String org)596     public boolean isLocaleInGroup(String locale, String group, String org) {
597         return isLocaleInGroup(locale, group, Organization.fromString(org));
598     }
599 
getGroup(String locale, String org)600     public String getGroup(String locale, String org) {
601         return getGroup(locale, Organization.fromString(org));
602     }
603 
604     /**
605      * Gets the coverage group given a locale and org
606      *
607      * @param locale
608      * @param org
609      * @return group if availble, null if not
610      */
getGroup(String locale, Organization org)611     private String getGroup(String locale, Organization org) {
612         Level l = getLocaleCoverageLevel(org, locale);
613         if (l.equals(Level.UNDETERMINED)) {
614             return null;
615         } else {
616             return l.toString();
617         }
618     }
619 
620     // ========== PRIVATES ==========
621 
StandardCodes()622     private StandardCodes() {
623         String[] files = { "ISO4217.txt" }; // , "TZID.txt"
624         type_code_preferred.put(CodeType.tzid, new TreeMap<String, String>());
625         add(CodeType.language, "root", "Root");
626         String originalLine = null;
627         for (int fileIndex = 0; fileIndex < files.length; ++fileIndex) {
628             try {
629                 BufferedReader lstreg = CldrUtility.getUTF8Data(files[fileIndex]);
630                 while (true) {
631                     String line = originalLine = lstreg.readLine();
632                     if (line == null)
633                         break;
634                     if (line.startsWith("\uFEFF")) {
635                         line = line.substring(1);
636                     }
637                     line = line.trim();
638                     int commentPos = line.indexOf('#');
639                     String comment = "";
640                     if (commentPos >= 0) {
641                         comment = line.substring(commentPos + 1).trim();
642                         line = line.substring(0, commentPos);
643                     }
644                     if (line.length() == 0)
645                         continue;
646                     List<String> pieces = CldrUtility.splitList(line, '|', true,
647                         new ArrayList<String>());
648                     CodeType type = CodeType.from(pieces.get(0));
649                     pieces.remove(0);
650 
651                     String code = pieces.get(0);
652                     pieces.remove(0);
653                     if (type.equals("date")) {
654                         continue;
655                     }
656 
657                     String oldName = pieces.get(0);
658                     int pos = oldName.indexOf(';');
659                     if (pos >= 0) {
660                         oldName = oldName.substring(0, pos).trim();
661                         pieces.set(0, oldName);
662                     }
663 
664                     List<String> data = pieces;
665                     if (comment.indexOf("deprecated") >= 0) {
666                         // System.out.println(originalLine);
667                         if (data.get(2).toString().length() == 0) {
668                             data.set(2, "--");
669                         }
670                     }
671                     if (oldName.equalsIgnoreCase("PRIVATE USE")) {
672                         int separatorPos = code.indexOf("..");
673                         if (separatorPos < 0) {
674                             add(type, code, data);
675                         } else {
676                             String current = code.substring(0, separatorPos);
677                             String end = code.substring(separatorPos + 2);
678                             // System.out.println(">>" + code + "\t" + current + "\t" + end);
679                             for (; current.compareTo(end) <= 0; current = nextAlpha(current)) {
680                                 // System.out.println(">" + current);
681                                 add(type, current, data);
682                             }
683                         }
684                         continue;
685                     }
686                     if (!type.equals("tzid")) {
687                         add(type, code, data);
688                         if (type.equals("currency")) {
689                             // currency | TPE | Timor Escudo | TP | EAST TIMOR | O
690                             if (data.get(3).equals("C")) {
691                                 String country = data.get(1);
692                                 Set<String> codes = country_modernCurrency.get(country);
693                                 if (codes == null) {
694                                     country_modernCurrency.put(country, codes = new TreeSet<>());
695                                 }
696                                 codes.add(code);
697                             }
698                         }
699                         continue;
700                     }
701                     // type = tzid
702                     // List codes = (List) Utility.splitList(code, ',', true, new
703                     // ArrayList());
704                     String preferred = null;
705                     for (int i = 0; i < pieces.size(); ++i) {
706                         code = pieces.get(i);
707                         add(type, code, data);
708                         if (preferred == null)
709                             preferred = code;
710                         else {
711                             Map<String, String> code_preferred = type_code_preferred.get(type);
712                             code_preferred.put(code, preferred);
713                         }
714                     }
715                 }
716                 lstreg.close();
717             } catch (Exception e) {
718                 System.err.println("WARNING: " + files[fileIndex]
719                     + " may be a corrupted UTF-8 file. Please check.");
720                 throw (IllegalArgumentException) new IllegalArgumentException(
721                     "Can't read " + files[fileIndex] + "\t" + originalLine)
722                         .initCause(e);
723             }
724             country_modernCurrency = CldrUtility.protectCollection(country_modernCurrency);
725         }
726 
727         // data is: description | date | canonical_value | recommended_prefix #
728         // comments
729         // HACK, just rework
730 
731         Map<String, Map<String, Map<String, String>>> languageRegistry = getLStreg();
732         // languageRegistry = CldrUtility.protectCollection(languageRegistry);
733 
734         for (String type : languageRegistry.keySet()) {
735             CodeType type2 = CodeType.from(type);
736             Map<String, Map<String, String>> m = languageRegistry.get(type);
737             for (String code : m.keySet()) {
738                 Map<String, String> mm = m.get(code);
739                 List<String> data = new ArrayList<>(0);
740                 data.add(mm.get("Description"));
741                 data.add(mm.get("Added"));
742                 String pref = mm.get("Preferred-Value");
743                 if (pref == null) {
744                     pref = mm.get("Deprecated");
745                     if (pref == null)
746                         pref = "";
747                     else
748                         pref = "deprecated";
749                 }
750                 data.add(pref);
751                 if (type.equals("variant")) {
752                     code = code.toUpperCase();
753                 }
754                 // data.add(mm.get("Recommended_Prefix"));
755                 // {"region", "BQ", "Description", "British Antarctic Territory",
756                 // "Preferred-Value", "AQ", "CLDR", "True", "Deprecated", "True"},
757                 add(type2, code, data);
758             }
759         }
760 
761         Map<String, List<String>> m = getZoneData();
762         for (Iterator<String> it = m.keySet().iterator(); it.hasNext();) {
763             String code = it.next();
764             add(CodeType.tzid, code, m.get(code).toString());
765         }
766     }
767 
768     /**
769      * @param current
770      * @return
771      */
nextAlpha(String current)772     private static String nextAlpha(String current) {
773         // Don't care that this is inefficient
774         int value = 0;
775         for (int i = 0; i < current.length(); ++i) {
776             char c = current.charAt(i);
777             c -= c < 'a' ? 'A' : 'a';
778             value = value * 26 + c;
779         }
780         value += 1;
781         String result = "";
782         for (int i = 0; i < current.length(); ++i) {
783             result = (char) ((value % 26) + 'A') + result;
784             value = value / 26;
785         }
786         if (UCharacter.toLowerCase(current).equals(current)) {
787             result = UCharacter.toLowerCase(result);
788         } else if (UCharacter.toUpperCase(current).equals(current)) {
789             // do nothing
790         } else {
791             result = UCharacter.toTitleCase(result, null);
792         }
793         return result;
794     }
795 
796     /**
797      * @param string
798      * @param string2
799      * @param string3
800      */
801     private void add(CodeType type, String string2, String string3) {
802         List<String> l = new ArrayList<>();
803         l.add(string3);
804         add(type, string2, l);
805     }
806 
807     private void add(CodeType type, String code, List<String> otherData) {
808         // hack
809         if (type == CodeType.script) {
810             if (code.equals("Qaai")) {
811                 otherData = new ArrayList<>(otherData);
812                 otherData.set(0, "Inherited");
813             } else if (code.equals("Zyyy")) {
814                 otherData = new ArrayList<>(otherData);
815                 otherData.set(0, "Common");
816             }
817         }
818 
819         // assume name is the first item
820 
821         String name = otherData.get(0);
822 
823         // add to main list
824         Map<String, List<String>> code_data = getCodeData(type);
825         if (code_data == null) {
826             code_data = new TreeMap<>();
827             type_code_data.put(type, code_data);
828         }
829         List<String> lastData = code_data.get(code);
830         if (lastData != null) {
831             lastData.addAll(otherData);
832         } else {
833             code_data.put(code, otherData);
834         }
835 
836         // now add mapping from name to codes
837         Map<String, List<String>> name_codes = type_name_codes.get(type);
838         if (name_codes == null) {
839             name_codes = new TreeMap<>();
840             type_name_codes.put(type, name_codes);
841         }
842         List<String> codes = name_codes.get(name);
843         if (codes == null) {
844             codes = new ArrayList<>();
845             name_codes.put(name, codes);
846         }
847         codes.add(code);
848     }
849 
850     private List<String> DELETED3166 = Collections.unmodifiableList(Arrays
851         .asList(new String[] { "BQ", "BU", "CT", "DD", "DY", "FQ", "FX", "HV",
852             "JT", "MI", "NH", "NQ", "NT", "PC", "PU", "PZ", "RH", "SU", "TP",
853             "VD", "WK", "YD", "YU", "ZR" }));
854 
855     public List<String> getOld3166() {
856         return DELETED3166;
857     }
858 
859     private Map<String, List<String>> WorldBankInfo;
860 
861     public Map<String, List<String>> getWorldBankInfo() {
862         if (WorldBankInfo == null) {
863             List<String> temp = fillFromCommaFile("WorldBankInfo.txt", false);
864             WorldBankInfo = new HashMap<>();
865             for (String line : temp) {
866                 List<String> row = CldrUtility.splitList(line, ';', true);
867                 String key = row.get(0);
868                 row.remove(0);
869                 WorldBankInfo.put(key, row);
870             }
871             WorldBankInfo = CldrUtility.protectCollection(WorldBankInfo);
872         }
873         return WorldBankInfo;
874     }
875 
876     Set<String> moribundLanguages;
877 
878     public Set<String> getMoribundLanguages() {
879         if (moribundLanguages == null) {
880             List<String> temp = fillFromCommaFile("moribund_languages.txt", true);
881             moribundLanguages = new TreeSet<>();
882             moribundLanguages.addAll(temp);
883             moribundLanguages = CldrUtility.protectCollection(moribundLanguages);
884         }
885         return moribundLanguages;
886     }
887 
888     // produces a list of the 'clean' lines
889     private List<String> fillFromCommaFile(String filename, boolean trim) {
890         try {
891             List<String> result = new ArrayList<>();
892             String line;
893             BufferedReader lstreg = CldrUtility.getUTF8Data(filename);
894             while (true) {
895                 line = lstreg.readLine();
896                 if (line == null)
897                     break;
898                 int commentPos = line.indexOf('#');
899                 if (commentPos >= 0) {
900                     line = line.substring(0, commentPos);
901                 }
902                 if (trim) {
903                     line = line.trim();
904                 }
905                 if (line.length() == 0)
906                     continue;
907                 result.add(line);
908             }
909             return result;
910         } catch (Exception e) {
911             throw (RuntimeException) new IllegalArgumentException(
912                 "Can't process file: data/" + filename).initCause(e);
913         }
914     }
915 
916     // return a complex map. language -> arn -> {"Comments" -> "x",
917     // "Description->y,...}
918     static String[][] extras = {
919         { "language", "root", "Description", "Root", "CLDR", "True" },
920         // { "language", "cch", "Description", "Atsam", "CLDR", "True" },
921         // { "language", "kaj", "Description", "Jju", "CLDR", "True" },
922         // { "language", "kcg", "Description", "Tyap", "CLDR", "True" },
923         // { "language", "kfo", "Description", "Koro", "CLDR", "True" },
924         // { "language", "mfe", "Description", "Morisyen", "CLDR", "True" },
925         // { "region", "172", "Description", "Commonwealth of Independent States", "CLDR", "True" },
926         // { "region", "062", "Description", "South-Central Asia", "CLDR", "True" },
927         // { "region", "003", "Description", "North America", "CLDR", "True" },
928         //        { "variant", "POLYTONI", "Description", "Polytonic Greek", "CLDR", "True", "Preferred-Value", "POLYTON" },
929         { "variant", "REVISED", "Description", "Revised Orthography", "CLDR", "True" },
930         { "variant", "SAAHO", "Description", "Dialect", "CLDR", "True" },
931         { "variant", "POSIX", "Description", "Computer-Style", "CLDR", "True" },
932         // {"region", "172", "Description", "Commonwealth of Independent States",
933         // "CLDR", "True"},
934         // { "region", "", "Description", "European Union", "CLDR", "True" },
935         { "region", "ZZ", "Description", "Unknown or Invalid Region", "CLDR", "True" },
936         { "region", "QO", "Description", "Outlying Oceania", "CLDR", "True" },
937         { "region", "XK", "Description", "Kosovo", "CLDR", "True" },
938         { "script", "Qaai", "Description", "Inherited", "CLDR", "True" },
939         // {"region", "003", "Description", "North America", "CLDR", "True"},
940         // {"region", "062", "Description", "South-central Asia", "CLDR", "True"},
941         // {"region", "200", "Description", "Czechoslovakia", "CLDR", "True"},
942         // {"region", "830", "Description", "Channel Islands", "CLDR", "True"},
943         // {"region", "833", "Description", "Isle of Man", "CLDR", "True"},
944 
945         // {"region", "NT", "Description", "Neutral Zone (formerly between Saudi
946         // Arabia & Iraq)", "CLDR", "True", "Deprecated", "True"},
947         // {"region", "SU", "Description", "Union of Soviet Socialist Republics",
948         // "CLDR", "True", "Deprecated", "True"},
949         // {"region", "BQ", "Description", "British Antarctic Territory",
950         // "Preferred-Value", "AQ", "CLDR", "True", "Deprecated", "True"},
951         // {"region", "CT", "Description", "Canton and Enderbury Islands",
952         // "Preferred-Value", "KI", "CLDR", "True", "Deprecated", "True"},
953         // {"region", "FQ", "Description", "French Southern and Antarctic Territories
954         // (now split between AQ and TF)", "CLDR", "True", "Deprecated", "True"},
955         // {"region", "JT", "Description", "Johnston Island", "Preferred-Value", "UM",
956         // "CLDR", "True", "Deprecated", "True"},
957         // {"region", "MI", "Description", "Midway Islands", "Preferred-Value", "UM",
958         // "CLDR", "True", "Deprecated", "True"},
959         // {"region", "NQ", "Description", "Dronning Maud Land", "Preferred-Value",
960         // "AQ", "CLDR", "True", "Deprecated", "True"},
961         // {"region", "PC", "Description", "Pacific Islands Trust Territory (divided
962         // into FM, MH, MP, and PW)", "Preferred-Value", "AQ", "CLDR", "True",
963         // "Deprecated", "True"},
964         // {"region", "PU", "Description", "U.S. Miscellaneous Pacific Islands",
965         // "Preferred-Value", "UM", "CLDR", "True", "Deprecated", "True"},
966         // {"region", "PZ", "Description", "Panama Canal Zone", "Preferred-Value",
967         // "PA", "CLDR", "True", "Deprecated", "True"},
968         // {"region", "VD", "Description", "North Vietnam", "Preferred-Value", "VN",
969         // "CLDR", "True", "Deprecated", "True"},
970         // {"region", "WK", "Description", "Wake Island", "Preferred-Value", "UM",
971         // "CLDR", "True", "Deprecated", "True"},
972     };
973 
974     static final String registryName = CldrUtility.getProperty("registry", "language-subtag-registry");
975 
976     public enum LstrType {
977         language("und", "zxx", "mul", "mis", "root"),
978         script("Zzzz", "Zsym", "Zxxx", "Zmth"),
979         region("ZZ"),
980         variant(),
981         extlang(true, false),
982         legacy(true, false),
983         redundant(true, false),
984         /** specialized codes for validity; TODO: rename LstrType **/
985         currency(false, true, "XXX"),
986         subdivision(false, true),
987         unit(false, true),
988         usage(false, true),
989         zone(false, true);
990 
991         public final Set<String> specials;
992         public final String unknown;
993         public final boolean isLstr;
994         public final boolean isUnicode;
995 
996         private LstrType(String... unknownValue) {
997             this(true, true, unknownValue);
998         }
999 
1000         private LstrType(boolean lstr, boolean unicode, String... unknownValue) {
1001             unknown = unknownValue.length == 0 ? null : unknownValue[0];
1002             LinkedHashSet<String> set = new LinkedHashSet<>(Arrays.asList(unknownValue));
1003             if (unknown != null) {
1004                 set.remove(unknown);
1005             }
1006             specials = Collections.unmodifiableSet(set);
1007             isLstr = lstr;
1008             isUnicode = unicode;
1009         }
1010 
1011         //
1012         static final Pattern WELLFORMED = Pattern.compile("([0-9]{3}|[a-zA-Z]{2})[a-zA-Z0-9]{1,4}");
1013 
1014         boolean isWellFormed(String candidate) {
1015             switch (this) {
1016             case subdivision:
1017                 return WELLFORMED.matcher(candidate).matches();
1018             default:
1019                 throw new UnsupportedOperationException();
1020             }
1021         }
1022 
1023         /**
1024          * Generate compatibility string, returning 'territory' instead of 'region', etc.
1025          */
1026         public String toCompatString() {
1027             switch (this) {
1028             case region: return "territory";
1029             case legacy: return "language";
1030             case redundant: return "language";
1031             default: return toString();
1032             }
1033         }
1034 
1035         /**
1036          * Create LstrType from string, allowing the compat string 'territory'.
1037          */
1038         public static LstrType fromString(String rawType) {
1039             try {
1040                 return valueOf(rawType);
1041             } catch (IllegalArgumentException e) {
1042                 if ("territory".equals(rawType)) {
1043                     return region;
1044                 }
1045                 throw e;
1046             }
1047         }
1048     }
1049 
1050     public enum LstrField {
1051         Type, Subtag, Description, Added, Scope, Tag, Suppress_Script, Macrolanguage, Deprecated, Preferred_Value, Comments, Prefix, CLDR;
1052         public static LstrField from(String s) {
1053             return LstrField.valueOf(s.trim().replace("-", "_"));
1054         }
1055     }
1056 
1057     static Map<String, Map<String, Map<String, String>>> LSTREG;
1058     static Map<LstrType, Map<String, Map<LstrField, String>>> LSTREG_ENUM;
1059     static Map<LstrType, Map<String, Map<LstrField, String>>> LSTREG_RAW;
1060 
1061     /**
1062      * Returns a map like {extlang={aao={Added=2009-07-29, Description=Algerian Saharan Arabic, ...<br>
1063      * That is, type => subtype => map<tag,value>. Descriptions are concatenated together, separated by
1064      * DESCRIPTION_SEPARATOR.
1065      *
1066      * @return
1067      */
1068     public static Map<String, Map<String, Map<String, String>>> getLStreg() {
1069         if (LSTREG == null) {
1070             initLstr();
1071         }
1072         return LSTREG;
1073     }
1074 
1075     /**
1076      * Returns a map like {extlang={aao={Added=2009-07-29, Description=Algerian Saharan Arabic, ...<br>
1077      * That is, type => subtype => map<tag,value>. Descriptions are concatenated together, separated by
1078      * DESCRIPTION_SEPARATOR.
1079      *
1080      * @return
1081      */
1082     public static Map<LstrType, Map<String, Map<LstrField, String>>> getEnumLstreg() {
1083         if (LSTREG_ENUM == null) {
1084             initLstr();
1085         }
1086         return LSTREG_ENUM;
1087     }
1088 
1089     public static Map<LstrType, Map<String, Map<LstrField, String>>> getLstregEnumRaw() {
1090         if (LSTREG_ENUM == null) {
1091             initLstr();
1092         }
1093         return LSTREG_RAW;
1094     }
1095 
1096     private static void initLstr() {
1097         Map<LstrType, Map<String, Map<LstrField, String>>> result2 = new TreeMap<>();
1098 
1099         int lineNumber = 1;
1100 
1101         Set<String> funnyTags = new TreeSet<>();
1102         String line;
1103         try {
1104             BufferedReader lstreg = CldrUtility.getUTF8Data(registryName);
1105             LstrType lastType = null;
1106             String lastTag = null;
1107             Map<String, Map<LstrField, String>> subtagData = null;
1108             Map<LstrField, String> currentData = null;
1109             LstrField lastLabel = null;
1110             String lastRest = null;
1111             boolean inRealContent = false;
1112 //            Map<String, String> translitCache = new HashMap<String, String>();
1113             for (;; ++lineNumber) {
1114                 line = lstreg.readLine();
1115                 if (line == null)
1116                     break;
1117                 if (line.length() == 0)
1118                     continue; // skip blanks
1119                 if (line.startsWith("File-Date: ")) {
1120                     if (DEBUG) System.out.println("Language Subtag Registry: " + line);
1121                     inRealContent = true;
1122                     continue;
1123                 }
1124                 if (!inRealContent) {
1125                     // skip until we get to real content
1126                     continue;
1127                 }
1128                 // skip cruft
1129                 if (line.startsWith("Internet-Draft")) {
1130                     continue;
1131                 }
1132                 if (line.startsWith("Ewell")) {
1133                     continue;
1134                 }
1135                 if (line.startsWith("\f")) {
1136                     continue;
1137                 }
1138                 if (line.startsWith("4.  Security Considerations")) {
1139                     break;
1140                 }
1141 
1142                 if (line.startsWith("%%"))
1143                     continue; // skip separators (ok, since data starts with Type:
1144                 if (line.startsWith(" ")) {
1145                     currentData.put(lastLabel, lastRest + " " + line.trim());
1146                     continue;
1147                 }
1148 
1149                 /*
1150                  * Type: language Subtag: aa Description: Afar Added: 2005-10-16
1151                  * Suppress-Script: Latn
1152                  */
1153                 int pos2 = line.indexOf(':');
1154                 LstrField label = LstrField.from(line.substring(0, pos2));
1155                 String rest = line.substring(pos2 + 1).trim();
1156                 if (label == LstrField.Type) {
1157                     lastType = rest.equals("grandfathered") ?
1158                         LstrType.legacy : LstrType.fromString(rest);
1159                     subtagData = CldrUtility.get(result2, lastType);
1160                     if (subtagData == null) {
1161                         result2.put(lastType, subtagData = new TreeMap<>());
1162                     }
1163                 } else if (label == LstrField.Subtag
1164                     || label == LstrField.Tag) {
1165                     lastTag = rest;
1166                     String endTag = null;
1167                     // Subtag: qaa..qtz
1168                     int pos = lastTag.indexOf("..");
1169                     if (pos >= 0) {
1170                         endTag = lastTag.substring(pos + 2);
1171                         lastTag = lastTag.substring(0, pos);
1172                     }
1173                     currentData = new TreeMap<>();
1174                     if (endTag == null) {
1175                         putSubtagData(lastTag, subtagData, currentData);
1176                         languageCount.add(lastType, 1);
1177                         // System.out.println(languageCount.getCount(lastType) + "\t" + lastType + "\t" + lastTag);
1178                     } else {
1179                         for (; lastTag.compareTo(endTag) <= 0; lastTag = nextAlpha(lastTag)) {
1180                             // System.out.println(">" + current);
1181                             putSubtagData(lastTag, subtagData, currentData);
1182                             languageCount.add(lastType, 1);
1183                             // System.out.println(languageCount.getCount(lastType) + "\t" + lastType + "\t" + lastTag);
1184                         }
1185 
1186                     }
1187                     // label.equalsIgnoreCase("Added") || label.equalsIgnoreCase("Suppress-Script")) {
1188                     // skip
1189                     // } else if (pieces.length < 2) {
1190                     // System.out.println("Odd Line: " + lastType + "\t" + lastTag + "\t" + line);
1191                 } else {
1192                     lastLabel = label;
1193                     // The following code was removed because in the standard tests (TestAll) both lastRest and rest were always equal.
1194                     //                    if(!translitCache.containsKey(rest)) {
1195                     //                        lastRest = TransliteratorUtilities.fromXML.transliterate(rest);
1196                     //                        translitCache.put(rest, lastRest);
1197                     //                        if (!lastRest.equals(rest)) {
1198                     //                            System.out.println(System.currentTimeMillis()+" initLStr: LastRest: '"+lastRest+"' Rest: '"+rest+"'");
1199                     //                        }
1200                     //                    } else {
1201                     //                        lastRest = translitCache.get(rest);
1202                     //                    }
1203                     lastRest = rest;
1204                     String oldValue = CldrUtility.get(currentData, lastLabel);
1205                     if (oldValue != null) {
1206                         lastRest = oldValue + DESCRIPTION_SEPARATOR + lastRest;
1207                     }
1208                     currentData.put(lastLabel, lastRest);
1209                 }
1210             }
1211         } catch (Exception e) {
1212             throw (RuntimeException) new IllegalArgumentException(
1213                 "Can't process file: data/"
1214                     + registryName + ";\t at line " + lineNumber).initCause(e);
1215         } finally {
1216             if (!funnyTags.isEmpty()) {
1217                 if (DEBUG)
1218                     System.out.println("Funny tags: " + funnyTags);
1219             }
1220         }
1221         // copy raw
1222         Map<LstrType, Map<String, Map<LstrField, String>>> rawLstreg = new TreeMap<>();
1223         for (Entry<LstrType, Map<String, Map<LstrField, String>>> entry1 : result2.entrySet()) {
1224             LstrType key1 = entry1.getKey();
1225             TreeMap<String, Map<LstrField, String>> raw1 = new TreeMap<>();
rawLstreg.put(key1, raw1)1226             rawLstreg.put(key1, raw1);
1227             for (Entry<String, Map<LstrField, String>> entry2 : entry1.getValue().entrySet()) {
1228                 String key2 = entry2.getKey();
1229                 final Map<LstrField, String> value2 = entry2.getValue();
1230                 TreeMap<LstrField, String> raw2 = new TreeMap<>();
1231                 raw2.putAll(value2);
raw1.put(key2, raw2)1232                 raw1.put(key2, raw2);
1233             }
1234         }
1235         LSTREG_RAW = CldrUtility.protectCollection(rawLstreg);
1236 
1237         // add extras
1238         for (int i = 0; i < extras.length; ++i) {
1239             Map<String, Map<LstrField, String>> subtagData = CldrUtility.get(result2, LstrType.fromString(extras[i][0]));
1240             if (subtagData == null) {
LstrType.fromString(extras[i][0])1241                 result2.put(LstrType.fromString(extras[i][0]), subtagData = new TreeMap<>());
1242             }
1243             Map<LstrField, String> labelData = new TreeMap<>();
1244             for (int j = 2; j < extras[i].length; j += 2) {
LstrField.from(extras[i][j])1245                 labelData.put(LstrField.from(extras[i][j]), extras[i][j + 1]);
1246             }
1247             Map<LstrField, String> old = CldrUtility.get(subtagData, extras[i][1]);
1248             if (old != null) {
1249                 if (!"Private use".equals(CldrUtility.get(old, LstrField.Description))) {
1250                     throw new IllegalArgumentException("REPLACING data for " + extras[i][1] + "\t" + old + "\twith"
1251                         + labelData);
1252                 }
1253             }
1254             if (false) {
1255                 System.out.println((old != null ? "REPLACING" + "\t" + old : "ADDING") +
1256                     " data for " + extras[i][1] + "\twith" + labelData);
1257             }
subtagData.put(extras[i][1], labelData)1258             subtagData.put(extras[i][1], labelData);
1259         }
1260         // build compatibility map
1261         Map<String, Map<String, Map<String, String>>> result = new LinkedHashMap<>();
1262         for (Entry<LstrType, Map<String, Map<LstrField, String>>> entry : result2.entrySet()) {
1263             Map<String, Map<String, String>> copy2 = new LinkedHashMap<>();
1264             result.put(entry.getKey().toString(), copy2);
1265             for (Entry<String, Map<LstrField, String>> entry2 : entry.getValue().entrySet()) {
1266                 Map<String, String> copy3 = new LinkedHashMap<>();
entry2.getKey()1267                 copy2.put(entry2.getKey(), copy3);
1268                 for (Entry<LstrField, String> entry3 : entry2.getValue().entrySet()) {
entry3.getValue()1269                     copy3.put(entry3.getKey().toString(), entry3.getValue());
1270                 }
1271             }
1272         }
1273         LSTREG = CldrUtility.protectCollection(result);
1274         LSTREG_ENUM = CldrUtility.protectCollection(result2);
1275     }
1276 
1277     private static <K, K2, V> Map<K2, V> putSubtagData(K lastTag, Map<K, Map<K2, V>> subtagData, Map<K2, V> currentData) {
1278         Map<K2, V> oldData = subtagData.get(lastTag);
1279         if (oldData != null) {
1280             if (oldData.get("CLDR") != null) {
1281                 System.out.println("overriding: " + lastTag + ", " + oldData);
1282             } else {
1283                 throw new IllegalArgumentException("Duplicate tag: " + lastTag);
1284             }
1285         }
1286         return subtagData.put(lastTag, currentData);
1287     }
1288 
1289     static Counter<LstrType> languageCount = new Counter<>();
1290 
1291     public static Counter<LstrType> getLanguageCount() {
1292         return languageCount;
1293     }
1294 
1295     ZoneParser zoneParser = new ZoneParser();
1296 
1297     // static public final Set<String> MODERN_SCRIPTS = Collections
1298     // .unmodifiableSet(new TreeSet(
1299     // // "Bali " +
1300     // // "Bugi " +
1301     // // "Copt " +
1302     // // "Hano " +
1303     // // "Osma " +
1304     // // "Qaai " +
1305     // // "Sylo " +
1306     // // "Syrc " +
1307     // // "Tagb " +
1308     // // "Tglg " +
1309     // Arrays
1310     // .asList("Hans Hant Jpan Hrkt Kore Arab Armn Bali Beng Bopo Cans Cham Cher Cyrl Deva Ethi Geor Grek Gujr Guru Hani Hang Hebr Hira Knda Kana Kali Khmr Laoo Latn Lepc Limb Mlym Mong Mymr Talu Nkoo Olck Orya Saur Sinh Tale Taml Telu Thaa Thai Tibt Tfng Vaii Yiii"
1311     // .split("\\s+"))));
1312 
1313     // updated to http://www.unicode.org/reports/tr31/tr31-9.html#Specific_Character_Adjustments
1314 
1315     /**
1316      * @deprecated
1317      */
1318     @Deprecated
1319     public Map<String, List<ZoneLine>> getZone_rules() {
1320         return zoneParser.getZone_rules();
1321     }
1322 
1323     /**
1324      * @deprecated
1325      */
1326     @Deprecated
1327     public Map<String, List<String>> getZoneData() {
1328         return zoneParser.getZoneData();
1329     }
1330 
1331     /**
1332      * @deprecated
1333      */
1334     @Deprecated
1335     public Set<String> getCanonicalTimeZones() {
1336         return zoneParser.getZoneData().keySet();
1337     }
1338 
1339     /**
1340      * @deprecated
1341      */
1342     @Deprecated
1343     public Map<String, Set<String>> getCountryToZoneSet() {
1344         return zoneParser.getCountryToZoneSet();
1345     }
1346 
1347     /**
1348      * @deprecated
1349      */
1350     @Deprecated
1351     public List<String> getDeprecatedZoneIDs() {
1352         return zoneParser.getDeprecatedZoneIDs();
1353     }
1354 
1355     /**
1356      * @deprecated
1357      */
1358     @Deprecated
1359     public Comparator<String> getTZIDComparator() {
1360         return zoneParser.getTZIDComparator();
1361     }
1362 
1363     /**
1364      * @deprecated
1365      */
1366     @Deprecated
1367     public Map<String, Set<String>> getZoneLinkNew_OldSet() {
1368         return zoneParser.getZoneLinkNew_OldSet();
1369     }
1370 
1371     /**
1372      * @deprecated
1373      */
1374     @Deprecated
1375     public Map<String, String> getZoneLinkold_new() {
1376         return zoneParser.getZoneLinkold_new();
1377     }
1378 
1379     /**
1380      * @deprecated
1381      */
1382     @Deprecated
1383     public Map getZoneRuleID_rules() {
1384         return zoneParser.getZoneRuleID_rules();
1385     }
1386 
1387     /**
1388      * @deprecated
1389      */
1390     @Deprecated
1391     public Map<String, String> getZoneToCounty() {
1392         return zoneParser.getZoneToCounty();
1393     }
1394 
1395     /**
1396      * @deprecated
1397      */
1398     @Deprecated
1399     public String getZoneVersion() {
1400         return zoneParser.getVersion();
1401     }
1402 
1403     public static String fixLanguageTag(String languageSubtag) {
1404         if (languageSubtag.equals("mo")) { // fix special cases
1405             return "ro";
1406         }
1407         return languageSubtag;
1408     }
1409 
1410     public boolean isModernLanguage(String languageCode) {
1411         if (getMoribundLanguages().contains(languageCode)) return false;
1412         Type type = Iso639Data.getType(languageCode);
1413         if (type == Type.Living) return true;
1414         if (languageCode.equals("eo")) return true; // exception for Esperanto
1415         // Scope scope = Iso639Data.getScope(languageCode);
1416         // if (scope == Scope.Collection) return false;
1417         return false;
1418     }
1419 
1420     public static boolean isScriptModern(String script) {
1421         ScriptMetadata.Info info = ScriptMetadata.getInfo(script);
1422         if (info == null) {
1423             if (false) throw new IllegalArgumentException("No script metadata for: " + script);
1424             return false;
1425         }
1426         IdUsage idUsage = info.idUsage;
1427         return idUsage != IdUsage.EXCLUSION && idUsage != IdUsage.UNKNOWN;
1428     }
1429 
1430     static final Pattern whitespace = PatternCache.get("\\s+");
1431     static Set<String> filteredCurrencies = null;
1432 
1433     public Set<String> getSurveyToolDisplayCodes(String type) {
1434         return getGoodAvailableCodes(type);
1435     }
1436 
1437     static UnicodeSet COUNTRY = new UnicodeSet("[a-zA-Z]").freeze();
1438 
1439     /**
1440      * Quick check for whether valid country. Not complete: should use Validity
1441      * @param territory
1442      * @return
1443      */
1444     public static boolean isCountry(String territory) {
1445         switch (territory) {
1446         case "ZZ":
1447         case "QO":
1448         case "EU":
1449         case "UN":
1450         case "EZ":
1451             return false;
1452         default:
1453             return territory.length() == 2 && COUNTRY.containsAll(territory);
1454         }
1455     }
1456 
1457     public boolean isLstregPrivateUse(String type, String code) {
1458         Map<String, String> lStregData = getLStreg().get(type).get(code);
1459         return lStregData.get("Description").equalsIgnoreCase("private use");
1460     }
1461 
1462     public boolean isLstregDeprecated(String type, String code) {
1463         Map<String, String> lStregData = getLStreg().get(type).get(code);
1464         return lStregData.get("Deprecated") != null;
1465     }
1466 }
1467