xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/SupplementalDataInfo.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 package org.unicode.cldr.util;
2 
3 import static org.unicode.cldr.util.PathUtilities.getNormalizedPathString;
4 
5 import com.google.common.base.Joiner;
6 import com.google.common.base.Splitter;
7 import com.google.common.base.Supplier;
8 import com.google.common.base.Suppliers;
9 import com.google.common.collect.ImmutableList;
10 import com.google.common.collect.ImmutableSet;
11 import com.google.common.collect.ImmutableSetMultimap;
12 import com.google.common.collect.Multimap;
13 import com.google.common.collect.Sets;
14 import com.google.common.collect.TreeMultimap;
15 import com.ibm.icu.impl.IterableComparator;
16 import com.ibm.icu.impl.Relation;
17 import com.ibm.icu.impl.Row;
18 import com.ibm.icu.impl.Row.R2;
19 import com.ibm.icu.impl.Row.R4;
20 import com.ibm.icu.impl.number.DecimalQuantity;
21 import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
22 import com.ibm.icu.text.DateFormat;
23 import com.ibm.icu.text.MessageFormat;
24 import com.ibm.icu.text.NumberFormat;
25 import com.ibm.icu.text.PluralRules;
26 import com.ibm.icu.text.PluralRules.DecimalQuantitySamples;
27 import com.ibm.icu.text.PluralRules.DecimalQuantitySamplesRange;
28 import com.ibm.icu.text.PluralRules.FixedDecimal;
29 import com.ibm.icu.text.PluralRules.Operand;
30 import com.ibm.icu.text.PluralRules.SampleType;
31 import com.ibm.icu.text.SimpleDateFormat;
32 import com.ibm.icu.text.UnicodeSet;
33 import com.ibm.icu.util.Freezable;
34 import com.ibm.icu.util.ICUUncheckedIOException;
35 import com.ibm.icu.util.Output;
36 import com.ibm.icu.util.TimeZone;
37 import com.ibm.icu.util.ULocale;
38 import com.ibm.icu.util.VersionInfo;
39 import java.io.File;
40 import java.text.ParseException;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.Comparator;
46 import java.util.Date;
47 import java.util.Deque;
48 import java.util.EnumMap;
49 import java.util.EnumSet;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.Iterator;
53 import java.util.LinkedHashMap;
54 import java.util.LinkedHashSet;
55 import java.util.LinkedList;
56 import java.util.List;
57 import java.util.Locale;
58 import java.util.Map;
59 import java.util.Map.Entry;
60 import java.util.Objects;
61 import java.util.Set;
62 import java.util.TreeMap;
63 import java.util.TreeSet;
64 import java.util.concurrent.ConcurrentHashMap;
65 import java.util.regex.Matcher;
66 import java.util.regex.Pattern;
67 import java.util.stream.Collectors;
68 import org.unicode.cldr.test.CoverageLevel2;
69 import org.unicode.cldr.tool.LikelySubtags;
70 import org.unicode.cldr.tool.SubdivisionNames;
71 import org.unicode.cldr.util.Builder.CBuilder;
72 import org.unicode.cldr.util.CldrUtility.VariableReplacer;
73 import org.unicode.cldr.util.DayPeriodInfo.DayPeriod;
74 import org.unicode.cldr.util.DtdType.DtdStatus;
75 import org.unicode.cldr.util.GrammarInfo.GrammaticalFeature;
76 import org.unicode.cldr.util.GrammarInfo.GrammaticalScope;
77 import org.unicode.cldr.util.GrammarInfo.GrammaticalTarget;
78 import org.unicode.cldr.util.Rational.RationalParser;
79 import org.unicode.cldr.util.StandardCodes.CodeType;
80 import org.unicode.cldr.util.StandardCodes.LstrType;
81 import org.unicode.cldr.util.SupplementalDataInfo.BasicLanguageData.Type;
82 import org.unicode.cldr.util.SupplementalDataInfo.NumberingSystemInfo.NumberingSystemType;
83 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
84 import org.unicode.cldr.util.Validity.Status;
85 import org.unicode.cldr.util.personname.PersonNameFormatter;
86 import org.unicode.cldr.util.personname.PersonNameFormatter.Order;
87 
88 /**
89  * Singleton class to provide API access to supplemental data -- in all the supplemental data files.
90  *
91  * <p>To create, use SupplementalDataInfo.getInstance
92  *
93  * <p>To add API for new structure, you will generally:
94  *
95  * <ul>
96  *   <li>add a Map or Relation as a data member,
97  *   <li>put a check and handler in MyHandler for the paths that you consume,
98  *   <li>make the data member immutable in makeStuffSave, and
99  *   <li>add a getter for the data member
100  * </ul>
101  *
102  * @author markdavis
103  */
104 public class SupplementalDataInfo {
105     private static final boolean DEBUG = false;
106     private static final StandardCodes sc = StandardCodes.make();
107     private static final String UNKNOWN_SCRIPT = "Zzzz";
108 
109     public static final Splitter split_space = Splitter.on(' ').omitEmptyStrings();
110 
111     // TODO add structure for items shown by TestSupplementalData to be missing
112     /*
113      * [calendarData/calendar,
114      * characters/character-fallback,
115      * measurementData/measurementSystem, measurementData/paperSize,
116      * metadata/attributeOrder, metadata/blocking, metadata/deprecated,
117      * metadata/distinguishing, metadata/elementOrder, metadata/serialElements, metadata/skipDefaultLocale,
118      * metadata/suppress, metadata/validity, metazoneInfo/timezone,
119      * timezoneData/mapTimezones,
120      * weekData/firstDay, weekData/minDays, weekData/weekendEnd, weekData/weekendStart]
121      */
122     // TODO: verify that we get everything by writing the files solely from the API, and verifying
123     // identity.
124 
125     public enum UnitIdComponentType {
126         prefix,
127         base,
128         suffix,
129         per,
130         and,
131         power;
132 
toShortId()133         public String toShortId() {
134             return name().substring(0, 1).toUpperCase();
135         }
136     }
137 
138     public class UnitPrefixInfo {
139         final String abbreviation;
140         final int base;
141         final int power;
142 
UnitPrefixInfo(String abbreviation, int base, int power)143         public UnitPrefixInfo(String abbreviation, int base, int power) {
144             this.abbreviation = abbreviation;
145             this.base = base;
146             this.power = power;
147         }
148 
149         @Override
toString()150         public String toString() {
151             return String.format(
152                     "%s\t%s", abbreviation, String.valueOf(base) + "^" + String.valueOf(power));
153         }
154     }
155 
156     /** Official status of languages */
157     public enum OfficialStatus {
158         unknown("U", 1),
159         recognized("R", 1),
160         official_minority("OM", 2),
161         official_regional("OR", 3),
162         de_facto_official("OD", 10),
163         official("O", 10);
164 
165         private final String shortName;
166         private final int weight;
167 
OfficialStatus(String shortName, int weight)168         private OfficialStatus(String shortName, int weight) {
169             this.shortName = shortName;
170             this.weight = weight;
171         }
172 
toShortString()173         public String toShortString() {
174             return shortName;
175         }
176 
getWeight()177         public int getWeight() {
178             return weight;
179         }
180 
isMajor()181         public boolean isMajor() {
182             return compareTo(OfficialStatus.de_facto_official) >= 0;
183         }
184 
isOfficial()185         public boolean isOfficial() {
186             return compareTo(OfficialStatus.official_regional) >= 0;
187         }
188     }
189 
190     /** Population data for different languages. */
191     public static final class PopulationData implements Freezable<PopulationData> {
192         private double population = Double.NaN;
193 
194         private double literatePopulation = Double.NaN;
195 
196         private double writingPopulation = Double.NaN;
197 
198         private double gdp = Double.NaN;
199 
200         private OfficialStatus officialStatus = OfficialStatus.unknown;
201 
getGdp()202         public double getGdp() {
203             return gdp;
204         }
205 
getLiteratePopulation()206         public double getLiteratePopulation() {
207             return literatePopulation;
208         }
209 
getLiteratePopulationPercent()210         public double getLiteratePopulationPercent() {
211             return 100 * literatePopulation / population;
212         }
213 
getWritingPopulation()214         public double getWritingPopulation() {
215             return writingPopulation;
216         }
217 
getWritingPercent()218         public double getWritingPercent() {
219             return 100 * writingPopulation / population;
220         }
221 
getPopulation()222         public double getPopulation() {
223             return population;
224         }
225 
setGdp(double gdp)226         public PopulationData setGdp(double gdp) {
227             if (frozen) {
228                 throw new UnsupportedOperationException("Attempt to modify frozen object");
229             }
230             this.gdp = gdp;
231             return this;
232         }
233 
setLiteratePopulation(double literatePopulation)234         public PopulationData setLiteratePopulation(double literatePopulation) {
235             if (frozen) {
236                 throw new UnsupportedOperationException("Attempt to modify frozen object");
237             }
238             this.literatePopulation = literatePopulation;
239             return this;
240         }
241 
setPopulation(double population)242         public PopulationData setPopulation(double population) {
243             if (frozen) {
244                 throw new UnsupportedOperationException("Attempt to modify frozen object");
245             }
246             this.population = population;
247             return this;
248         }
249 
set(PopulationData other)250         public PopulationData set(PopulationData other) {
251             if (frozen) {
252                 throw new UnsupportedOperationException("Attempt to modify frozen object");
253             }
254             if (other == null) {
255                 population = literatePopulation = gdp = Double.NaN;
256             } else {
257                 population = other.population;
258                 literatePopulation = other.literatePopulation;
259                 writingPopulation = other.writingPopulation;
260                 gdp = other.gdp;
261             }
262             return this;
263         }
264 
add(PopulationData other)265         public void add(PopulationData other) {
266             if (frozen) {
267                 throw new UnsupportedOperationException("Attempt to modify frozen object");
268             }
269             population += other.population;
270             literatePopulation += other.literatePopulation;
271             writingPopulation += other.writingPopulation;
272             gdp += other.gdp;
273         }
274 
275         @Override
toString()276         public String toString() {
277             return MessageFormat.format(
278                     "[pop: {0,number,#,##0},\t lit: {1,number,#,##0.00},\t gdp: {2,number,#,##0},\t status: {3}]",
279                     new Object[] {population, literatePopulation, gdp, officialStatus});
280         }
281 
282         private boolean frozen;
283 
284         @Override
isFrozen()285         public boolean isFrozen() {
286             return frozen;
287         }
288 
289         @Override
freeze()290         public PopulationData freeze() {
291             frozen = true;
292             return this;
293         }
294 
295         @Override
cloneAsThawed()296         public PopulationData cloneAsThawed() {
297             throw new UnsupportedOperationException("not yet implemented");
298         }
299 
getOfficialStatus()300         public OfficialStatus getOfficialStatus() {
301             return officialStatus;
302         }
303 
setOfficialStatus(OfficialStatus officialStatus)304         public PopulationData setOfficialStatus(OfficialStatus officialStatus) {
305             if (frozen) {
306                 throw new UnsupportedOperationException("Attempt to modify frozen object");
307             }
308             this.officialStatus = officialStatus;
309             return this;
310         }
311 
setWritingPopulation(double writingPopulation)312         public PopulationData setWritingPopulation(double writingPopulation) {
313             if (frozen) {
314                 throw new UnsupportedOperationException("Attempt to modify frozen object");
315             }
316             this.writingPopulation = writingPopulation;
317             return this;
318         }
319     }
320 
321     static final Pattern WHITESPACE_PATTERN = PatternCache.get("\\s+");
322 
323     /** Simple language/script/region information */
324     public static class BasicLanguageData
325             implements Comparable<BasicLanguageData>,
326                     com.ibm.icu.util.Freezable<BasicLanguageData> {
327         public enum Type {
328             primary,
329             secondary
330         }
331 
332         private Type type = Type.primary;
333 
334         private Set<String> scripts = Collections.emptySet();
335 
336         private Set<String> territories = Collections.emptySet();
337 
getType()338         public Type getType() {
339             return type;
340         }
341 
setType(Type type)342         public BasicLanguageData setType(Type type) {
343             this.type = type;
344             return this;
345         }
346 
setScripts(String scriptTokens)347         public BasicLanguageData setScripts(String scriptTokens) {
348             return setScripts(
349                     scriptTokens == null
350                             ? null
351                             : Arrays.asList(WHITESPACE_PATTERN.split(scriptTokens)));
352         }
353 
setTerritories(String territoryTokens)354         public BasicLanguageData setTerritories(String territoryTokens) {
355             return setTerritories(
356                     territoryTokens == null
357                             ? null
358                             : Arrays.asList(WHITESPACE_PATTERN.split(territoryTokens)));
359         }
360 
setScripts(Collection<String> scriptTokens)361         public BasicLanguageData setScripts(Collection<String> scriptTokens) {
362             if (frozen) {
363                 throw new UnsupportedOperationException();
364             }
365             // TODO add error checking
366             scripts = Collections.emptySet();
367             if (scriptTokens != null) {
368                 for (String script : scriptTokens) {
369                     addScript(script);
370                 }
371             }
372             return this;
373         }
374 
setTerritories(Collection<String> territoryTokens)375         public BasicLanguageData setTerritories(Collection<String> territoryTokens) {
376             if (frozen) {
377                 throw new UnsupportedOperationException();
378             }
379             territories = Collections.emptySet();
380             if (territoryTokens != null) {
381                 for (String territory : territoryTokens) {
382                     addTerritory(territory);
383                 }
384             }
385             return this;
386         }
387 
set(BasicLanguageData other)388         public BasicLanguageData set(BasicLanguageData other) {
389             scripts = other.scripts;
390             territories = other.territories;
391             return this;
392         }
393 
getScripts()394         public Set<String> getScripts() {
395             return scripts;
396         }
397 
getTerritories()398         public Set<String> getTerritories() {
399             return territories;
400         }
401 
toString(String languageSubtag)402         public String toString(String languageSubtag) {
403             if (scripts.size() == 0 && territories.size() == 0) return "";
404             return "\t\t<language type=\""
405                     + languageSubtag
406                     + "\""
407                     + (scripts.size() == 0
408                             ? ""
409                             : " scripts=\"" + CldrUtility.join(scripts, " ") + "\"")
410                     + (territories.size() == 0
411                             ? ""
412                             : " territories=\"" + CldrUtility.join(territories, " ") + "\"")
413                     + (type == Type.primary ? "" : " alt=\"" + type + "\"")
414                     + "/>";
415         }
416 
417         @Override
toString()418         public String toString() {
419             return "["
420                     + type
421                     + (scripts.isEmpty() ? "" : "; scripts=" + Joiner.on(" ").join(scripts))
422                     + (scripts.isEmpty() ? "" : "; territories=" + Joiner.on(" ").join(territories))
423                     + "]";
424         }
425 
426         @Override
compareTo(BasicLanguageData o)427         public int compareTo(BasicLanguageData o) {
428             int result;
429             if (0 != (result = type.compareTo(o.type))) return result;
430             if (0 != (result = IterableComparator.compareIterables(scripts, o.scripts)))
431                 return result;
432             if (0 != (result = IterableComparator.compareIterables(territories, o.territories)))
433                 return result;
434             return 0;
435         }
436 
437         @Override
equals(Object input)438         public boolean equals(Object input) {
439             return compareTo((BasicLanguageData) input) == 0;
440         }
441 
442         @Override
hashCode()443         public int hashCode() {
444             // TODO Auto-generated method stub
445             return ((type.ordinal() * 37 + scripts.hashCode()) * 37) + territories.hashCode();
446         }
447 
addScript(String script)448         public BasicLanguageData addScript(String script) {
449             // simple error checking
450             if (script.length() != 4) {
451                 throw new IllegalArgumentException("Illegal Script: " + script);
452             }
453             if (scripts == Collections.EMPTY_SET) {
454                 scripts = new TreeSet<>();
455             }
456             scripts.add(script);
457             return this;
458         }
459 
addTerritory(String territory)460         public BasicLanguageData addTerritory(String territory) {
461             // simple error checking
462             if (territory.length() != 2) {
463                 throw new IllegalArgumentException("Illegal Territory: " + territory);
464             }
465             if (territories == Collections.EMPTY_SET) {
466                 territories = new TreeSet<>();
467             }
468             territories.add(territory);
469             return this;
470         }
471 
472         boolean frozen = false;
473 
474         @Override
isFrozen()475         public boolean isFrozen() {
476             return frozen;
477         }
478 
479         @Override
freeze()480         public BasicLanguageData freeze() {
481             frozen = true;
482             if (scripts != Collections.EMPTY_SET) {
483                 scripts = Collections.unmodifiableSet(scripts);
484             }
485             if (territories != Collections.EMPTY_SET) {
486                 territories = Collections.unmodifiableSet(territories);
487             }
488             return this;
489         }
490 
491         @Override
cloneAsThawed()492         public BasicLanguageData cloneAsThawed() {
493             BasicLanguageData result = new BasicLanguageData();
494             result.scripts = new TreeSet<>(scripts);
495             result.territories = new TreeSet<>(territories);
496             return this;
497         }
498 
addScripts(Set<String> scripts2)499         public void addScripts(Set<String> scripts2) {
500             for (String script : scripts2) {
501                 addScript(script);
502             }
503         }
504     }
505 
506     /** Information about currency digits and rounding. */
507     public static class CurrencyNumberInfo {
508         public final int digits;
509         public final int rounding;
510         public final double roundingIncrement;
511         public final int cashDigits;
512         public final int cashRounding;
513         public final double cashRoundingIncrement;
514 
getDigits()515         public int getDigits() {
516             return digits;
517         }
518 
getRounding()519         public int getRounding() {
520             return rounding;
521         }
522 
getRoundingIncrement()523         public double getRoundingIncrement() {
524             return roundingIncrement;
525         }
526 
CurrencyNumberInfo(int _digits, int _rounding, int _cashDigits, int _cashRounding)527         public CurrencyNumberInfo(int _digits, int _rounding, int _cashDigits, int _cashRounding) {
528             digits = _digits;
529             rounding = _rounding < 0 ? 0 : _rounding;
530             roundingIncrement = rounding * Math.pow(10.0, -digits);
531             // if the values are not set, use the above values
532             cashDigits = _cashDigits < 0 ? digits : _cashDigits;
533             cashRounding = _cashRounding < 0 ? rounding : _cashRounding;
534             cashRoundingIncrement = this.cashRounding * Math.pow(10.0, -digits);
535         }
536     }
537 
538     public static class NumberingSystemInfo {
539         public enum NumberingSystemType {
540             algorithmic,
541             numeric,
542             unknown
543         }
544 
545         public final String name;
546         public final NumberingSystemType type;
547         public final String digits;
548         public final String rules;
549 
550         public NumberingSystemInfo(XPathParts parts) {
551             name = parts.getAttributeValue(-1, "id");
552             digits = parts.getAttributeValue(-1, "digits");
553             rules = parts.getAttributeValue(-1, "rules");
554             type = NumberingSystemType.valueOf(parts.getAttributeValue(-1, "type"));
555         }
556     }
557 
558     /**
559      * Class for a range of two dates, refactored to share code.
560      *
561      * @author markdavis
562      */
563     public static final class DateRange implements Comparable<DateRange> {
564         public static final long START_OF_TIME = Long.MIN_VALUE;
565         public static final long END_OF_TIME = Long.MAX_VALUE;
566         public final long from;
567         public final long to;
568 
569         public DateRange(String fromString, String toString) {
570             from = parseDate(fromString, START_OF_TIME);
571             to = parseDate(toString, END_OF_TIME);
572         }
573 
574         public long getFrom() {
575             return from;
576         }
577 
578         public long getTo() {
579             return to;
580         }
581 
582         static final DateFormat[] simpleFormats = {
583             new SimpleDateFormat("yyyy-MM-dd HH:mm"),
584             new SimpleDateFormat("yyyy-MM-dd"),
585             new SimpleDateFormat("yyyy-MM"),
586             new SimpleDateFormat("yyyy"),
587         };
588 
589         static {
590             TimeZone gmt = TimeZone.getTimeZone("GMT");
591             for (DateFormat format : simpleFormats) {
592                 format.setTimeZone(gmt);
593             }
594         }
595 
596         long parseDate(String dateString, long defaultDate) {
597             if (dateString == null) {
598                 return defaultDate;
599             }
600             ParseException e2 = null;
601             for (int i = 0; i < simpleFormats.length; ++i) {
602                 try {
603                     synchronized (simpleFormats[i]) {
604                         Date result = simpleFormats[i].parse(dateString);
605                         return result.getTime();
606                     }
607                 } catch (ParseException e) {
608                     if (e2 == null) {
609                         e2 = e;
610                     }
611                 }
612             }
613             throw new IllegalArgumentException(e2);
614         }
615 
616         @Override
617         public String toString() {
618             return "{" + formatDate(from) + ", " + formatDate(to) + "}";
619         }
620 
621         public static String formatDate(long date) {
622             if (date == START_OF_TIME) {
623                 return "-∞";
624             }
625             if (date == END_OF_TIME) {
626                 return "∞";
627             }
628             synchronized (simpleFormats[0]) {
629                 return simpleFormats[0].format(date);
630             }
631         }
632 
633         @Override
634         public int compareTo(DateRange arg0) {
635             return to > arg0.to
636                     ? 1
637                     : to < arg0.to ? -1 : from > arg0.from ? 1 : from < arg0.from ? -1 : 0;
638         }
639     }
640 
641     /** Information about when currencies are in use in territories */
642     public static class CurrencyDateInfo implements Comparable<CurrencyDateInfo> {
643 
644         public static final Date END_OF_TIME = new Date(DateRange.END_OF_TIME);
645         public static final Date START_OF_TIME = new Date(DateRange.START_OF_TIME);
646 
647         private String currency;
648         private DateRange dateRange;
649         private boolean isLegalTender;
650         private String errors = "";
651 
652         public CurrencyDateInfo(String currency, String startDate, String endDate, String tender) {
653             this.currency = currency;
654             this.dateRange = new DateRange(startDate, endDate);
655             this.isLegalTender = (tender == null || !tender.equals("false"));
656         }
657 
658         public String getCurrency() {
659             return currency;
660         }
661 
662         public Date getStart() {
663             return new Date(dateRange.getFrom());
664         }
665 
666         public Date getEnd() {
667             return new Date(dateRange.getTo());
668         }
669 
670         public String getErrors() {
671             return errors;
672         }
673 
674         public boolean isLegalTender() {
675             return isLegalTender;
676         }
677 
678         @Override
679         public int compareTo(CurrencyDateInfo o) {
680             int result = dateRange.compareTo(o.dateRange);
681             if (result != 0) return result;
682             return currency.compareTo(o.currency);
683         }
684 
685         @Override
686         public String toString() {
687             return "{" + dateRange + ", " + currency + "}";
688         }
689 
690         public static String formatDate(Date date) {
691             return DateRange.formatDate(date.getTime());
692         }
693     }
694 
695     public static final class MetaZoneRange implements Comparable<MetaZoneRange> {
696         public final DateRange dateRange;
697         public final String metazone;
698 
699         /**
700          * @param metazone
701          * @param fromString
702          * @param toString
703          */
704         public MetaZoneRange(String metazone, String fromString, String toString) {
705             super();
706             this.metazone = metazone;
707             dateRange = new DateRange(fromString, toString);
708         }
709 
710         @Override
711         public int compareTo(MetaZoneRange arg0) {
712             int result;
713             if (0 != (result = dateRange.compareTo(arg0.dateRange))) {
714                 return result;
715             }
716             return metazone.compareTo(arg0.metazone);
717         }
718 
719         @Override
720         public String toString() {
721             return "{" + dateRange + ", " + metazone + "}";
722         }
723     }
724 
725     /** Information about telephone code(s) for a given territory */
726     public static class TelephoneCodeInfo implements Comparable<TelephoneCodeInfo> {
727         public static final Date END_OF_TIME = new Date(Long.MAX_VALUE);
728         public static final Date START_OF_TIME = new Date(Long.MIN_VALUE);
729         private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
730 
731         private String code;
732         private Date start;
733         private Date end;
734         private String alt;
735         private String errors = "";
736 
737         // code must not be null, the others can be
738         public TelephoneCodeInfo(String code, String startDate, String endDate, String alt) {
739             if (code == null) throw new NullPointerException();
740             this.code = code; // code will not be null
741             this.start = parseDate(startDate, START_OF_TIME); // start will not be null
742             this.end = parseDate(endDate, END_OF_TIME); // end willl not be null
743             this.alt = (alt == null) ? "" : alt; // alt will not be null
744         }
745 
746         static DateFormat[] simpleFormats = {
747             new SimpleDateFormat("yyyy-MM-dd"),
748             new SimpleDateFormat("yyyy-MM"),
749             new SimpleDateFormat("yyyy"),
750         };
751 
752         Date parseDate(String dateString, Date defaultDate) {
753             if (dateString == null) {
754                 return defaultDate;
755             }
756             ParseException e2 = null;
757             for (int i = 0; i < simpleFormats.length; ++i) {
758                 try {
759                     Date result = simpleFormats[i].parse(dateString);
760                     return result;
761                 } catch (ParseException e) {
762                     if (i == 0) {
763                         errors += dateString + " ";
764                     }
765                     if (e2 == null) {
766                         e2 = e;
767                     }
768                 }
769             }
770             throw (IllegalArgumentException) new IllegalArgumentException().initCause(e2);
771         }
772 
773         public String getCode() {
774             return code;
775         }
776 
777         public Date getStart() {
778             return start;
779         }
780 
781         public Date getEnd() {
782             return end;
783         }
784 
785         public String getAlt() {
786             return alt; // may return null
787         }
788 
789         public String getErrors() {
790             return errors;
791         }
792 
793         @Override
794         public boolean equals(Object o) {
795             if (!(o instanceof TelephoneCodeInfo)) return false;
796             TelephoneCodeInfo tc = (TelephoneCodeInfo) o;
797             return tc.code.equals(code)
798                     && tc.start.equals(start)
799                     && tc.end.equals(end)
800                     && tc.alt.equals(alt);
801         }
802 
803         @Override
804         public int hashCode() {
805             return 31 * code.hashCode() + start.hashCode() + end.hashCode() + alt.hashCode();
806         }
807 
808         @Override
809         public int compareTo(TelephoneCodeInfo o) {
810             int result = code.compareTo(o.code);
811             if (result != 0) return result;
812             result = start.compareTo(o.start);
813             if (result != 0) return result;
814             result = end.compareTo(o.end);
815             if (result != 0) return result;
816             return alt.compareTo(o.alt);
817         }
818 
819         @Override
820         public String toString() {
821             return "{"
822                     + code
823                     + ", "
824                     + formatDate(start)
825                     + ", "
826                     + formatDate(end)
827                     + ", "
828                     + alt
829                     + "}";
830         }
831 
832         public static String formatDate(Date date) {
833             if (date.equals(START_OF_TIME)) return "-∞";
834             if (date.equals(END_OF_TIME)) return "∞";
835             return dateFormat.format(date);
836         }
837     }
838 
839     public static class CoverageLevelInfo {
840         public final String match;
841         public final Level value;
842         public final Pattern inLanguage;
843         public final String inScript;
844         public final Set<String> inScriptSet;
845         public final String inTerritory;
846         public final Set<String> inTerritorySet;
847         private Set<String> inTerritorySetInternal;
848 
849         public CoverageLevelInfo(
850                 String match, int value, String language, String script, String territory) {
851             this.inLanguage = language != null ? PatternCache.get(language) : null;
852             this.inScript = script;
853             this.inTerritory = territory;
854             this.inScriptSet = toSet(script);
855             this.inTerritorySet = toSet(territory); // MUST BE LAST, sets inTerritorySetInternal
856             this.match = match;
857             this.value = Level.fromLevel(value);
858         }
859 
860         public static final Pattern NON_ASCII_LETTER = PatternCache.get("[^A-Za-z]+");
861 
862         private Set<String> toSet(String source) {
863             if (source == null) {
864                 return null;
865             }
866             Set<String> result = new HashSet<>(Arrays.asList(NON_ASCII_LETTER.split(source)));
867             result.remove("");
868             inTerritorySetInternal = result;
869             return Collections.unmodifiableSet(result);
870         }
871 
872         public static void fixEU(Collection<CoverageLevelInfo> targets, SupplementalDataInfo info) {
873             Set<String> euCountries = info.getContained("EU");
874             for (CoverageLevelInfo item : targets) {
875                 if (item.inTerritorySet != null && item.inTerritorySet.contains("EU")) {
876                     item.inTerritorySetInternal.addAll(euCountries);
877                 }
878             }
879         }
880     }
881 
882     public enum RBNFGroup {
883         SpelloutRules,
884         OrdinalRules,
885         NumberingSystemRules
886     }
887 
888     public static final String STAR = "*";
889     public static final Set<String> STAR_SET =
890             Builder.with(new HashSet<String>()).add("*").freeze();
891 
892     private VersionInfo cldrVersion;
893 
894     private String cldrVersionString = null;
895     private String unicodeVersion = null;
896 
897     private Map<String, PopulationData> territoryToPopulationData = new TreeMap<>();
898 
899     private Map<String, Map<String, PopulationData>> territoryToLanguageToPopulationData =
900             new TreeMap<>();
901 
902     private Map<String, PopulationData> languageToPopulation = new TreeMap<>();
903 
904     private Map<String, PopulationData> baseLanguageToPopulation = new TreeMap<>();
905 
906     private Relation<String, String> languageToScriptVariants =
907             Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class);
908 
909     private Relation<String, String> languageToTerritories =
910             Relation.of(new TreeMap<String, Set<String>>(), LinkedHashSet.class);
911 
912     private transient Relation<String, Pair<Boolean, Pair<Double, String>>> languageToTerritories2 =
913             Relation.of(
914                     new TreeMap<String, Set<Pair<Boolean, Pair<Double, String>>>>(), TreeSet.class);
915 
916     private Map<String, Map<BasicLanguageData.Type, BasicLanguageData>>
917             languageToBasicLanguageData = new TreeMap<>();
918 
919     private Set<String> allLanguages = new TreeSet<>();
920     private final List<String> approvalRequirements = new LinkedList<>(); // xpath array
921 
922     private Relation<String, String> containment =
923             Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class);
924     private Relation<String, String> containmentCore =
925             Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class);
926     private Relation<String, String> containmentGrouping =
927             Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class);
928     private Relation<String, String> containmentDeprecated =
929             Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class);
930     private Relation<String, String> containerToSubdivision =
931             Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class);
932 
933     private Map<String, CurrencyNumberInfo> currencyToCurrencyNumberInfo = new TreeMap<>();
934 
935     private Relation<String, CurrencyDateInfo> territoryToCurrencyDateInfo =
936             Relation.of(new TreeMap<String, Set<CurrencyDateInfo>>(), LinkedHashSet.class);
937 
938     private Map<String, Set<TelephoneCodeInfo>> territoryToTelephoneCodeInfo = new TreeMap<>();
939 
940     private Map<String, String> zone_territory = new TreeMap<>();
941 
942     private Relation<String, String> zone_aliases =
943             Relation.of(new TreeMap<String, Set<String>>(), LinkedHashSet.class);
944 
945     private Map<String, Map<String, Map<String, String>>> typeToZoneToRegionToZone =
946             new TreeMap<>();
947     private Relation<String, MetaZoneRange> zoneToMetaZoneRanges =
948             Relation.of(new TreeMap<String, Set<MetaZoneRange>>(), TreeSet.class);
949 
950     private Map<String, String> metazoneContinentMap = new HashMap<>();
951     private Set<String> allMetazones = new TreeSet<>();
952 
953     private Map<String, String> alias_zone = new TreeMap<>();
954 
955     public Relation<String, Integer> numericTerritoryMapping =
956             Relation.of(new HashMap<String, Set<Integer>>(), HashSet.class);
957 
958     public Relation<String, String> alpha3TerritoryMapping =
959             Relation.of(new HashMap<String, Set<String>>(), HashSet.class);
960 
961     public Relation<String, Integer> numericCurrencyCodeMapping =
962             Relation.of(new HashMap<String, Set<Integer>>(), HashSet.class);
963 
964     static Map<String, SupplementalDataInfo> directory_instance = new HashMap<>();
965 
966     public Map<String, Map<String, Row.R2<List<String>, String>>> typeToTagToReplacement =
967             new TreeMap<>();
968 
969     Map<String, List<Row.R4<String, String, Integer, Boolean>>> languageMatch = new HashMap<>();
970 
971     public Relation<String, String> bcp47Key2Subtypes =
972             Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class);
973     public Relation<String, String> bcp47Extension2Keys =
974             Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class);
975     public Relation<Row.R2<String, String>, String> bcp47Aliases =
976             Relation.of(new TreeMap<Row.R2<String, String>, Set<String>>(), LinkedHashSet.class);
977     public Map<Row.R2<String, String>, String> bcp47Descriptions = new TreeMap<>();
978     public Map<Row.R2<String, String>, String> bcp47Since = new TreeMap<>();
979     public Map<Row.R2<String, String>, String> bcp47Preferred = new TreeMap<>();
980     public Map<Row.R2<String, String>, String> bcp47Deprecated = new TreeMap<>();
981 
982     Map<String, Map<String, Bcp47KeyInfo>> bcp47KeyToSubtypeToInfo = new TreeMap<>();
983     Map<String, Map<String, String>> bcp47KeyToAliasToSubtype = new TreeMap<>();
984 
985     public Map<String, String> bcp47ValueType = new TreeMap<>();
986 
987     public Map<String, Row.R2<String, String>> validityInfo = new LinkedHashMap<>();
988     public Map<AttributeValidityInfo, String> attributeValidityInfo = new LinkedHashMap<>();
989 
990     public Multimap<String, String> languageGroups = TreeMultimap.create();
991 
992     public RationalParser rationalParser = new RationalParser();
993 
994     private UnitConverter unitConverter = null;
995 
996     private final UnitPreferences unitPreferences = new UnitPreferences();
997 
998     private Map<String, UnitIdComponentType> unitIdComponentType = new TreeMap<>();
999 
1000     private Map<String, UnitPrefixInfo> unitPrefixInfo = new TreeMap<>();
1001 
1002     public Map<String, GrammarInfo> grammarLocaleToTargetToFeatureToValues = new TreeMap<>();
1003     public Map<String, GrammarDerivation> localeToGrammarDerivation = new TreeMap<>();
1004 
1005     public Multimap<PersonNameFormatter.Order, String> personNameOrder = TreeMultimap.create();
1006 
1007     public enum MeasurementType {
1008         measurementSystem,
1009         paperSize
1010     }
1011 
1012     Map<MeasurementType, Map<String, String>> measurementData = new HashMap<>();
1013     Map<String, PreferredAndAllowedHour> timeData = new HashMap<>();
1014 
1015     public Relation<String, String> getAlpha3TerritoryMapping() {
1016         return alpha3TerritoryMapping;
1017     }
1018 
1019     public Relation<String, Integer> getNumericTerritoryMapping() {
1020         return numericTerritoryMapping;
1021     }
1022 
1023     public Relation<String, Integer> getNumericCurrencyCodeMapping() {
1024         return numericCurrencyCodeMapping;
1025     }
1026 
1027     /**
1028      * Returns type -> tag -> <replacementList, reason>, like "language" -> "sh" -> <{"sr_Latn"},
1029      * reason>
1030      *
1031      * @return
1032      */
1033     public Map<String, Map<String, R2<List<String>, String>>> getLocaleAliasInfo() {
1034         return typeToTagToReplacement;
1035     }
1036 
1037     public R2<List<String>, String> getDeprecatedInfo(String type, String code) {
1038         return typeToTagToReplacement.get(type).get(code);
1039     }
1040 
1041     public static SupplementalDataInfo getInstance(File supplementalDirectory) {
1042         return getInstance(getNormalizedPathString(supplementalDirectory));
1043     }
1044 
1045     /** Which directory did we come from? */
1046     private final File directory;
1047 
1048     private Validity validity;
1049 
1050     /**
1051      * Get an instance chosen using setAsDefaultInstance(), otherwise return an instance using the
1052      * default directory CldrUtility.SUPPLEMENTAL_DIRECTORY
1053      *
1054      * @return
1055      */
1056     public static SupplementalDataInfo getInstance() {
1057         return SupplementalDataInfoHelper.SINGLETON;
1058     }
1059 
1060     /** Mark this as the default instance to be returned by getInstance() */
1061     public void setAsDefaultInstance() {
1062         SupplementalDataInfoHelper.SINGLETON = this;
1063     }
1064 
1065     public static final class SupplementalDataInfoHelper {
1066         // Note: not final, because setAsDefaultInstance can modify it.
1067         static SupplementalDataInfo SINGLETON = CLDRConfig.getInstance().getSupplementalDataInfo();
1068     }
1069 
1070     public static SupplementalDataInfo getInstance(String supplementalDirectory) {
1071         synchronized (SupplementalDataInfo.class) {
1072             // Sanity checks - not null, not empty
1073             if (supplementalDirectory == null) {
1074                 throw new IllegalArgumentException("Error: null supplemental directory.");
1075             }
1076             if (supplementalDirectory.isEmpty()) {
1077                 throw new IllegalArgumentException(
1078                         "Error: The string passed as a parameter resolves to the empty string.");
1079             }
1080             // canonicalize path
1081             String normalizedPath = getNormalizedPathString(supplementalDirectory);
1082             SupplementalDataInfo instance = directory_instance.get(normalizedPath);
1083             if (instance != null) {
1084                 return instance;
1085             }
1086             // reaching here means we have not cached the entry
1087             File directory = new File(normalizedPath);
1088             instance = new SupplementalDataInfo(directory);
1089             MyHandler myHandler = instance.new MyHandler();
1090             XMLFileReader xfr = new XMLFileReader().setHandler(myHandler);
1091             File files1[] = directory.listFiles();
1092             if (files1 == null || files1.length == 0) {
1093                 throw new ICUUncheckedIOException(
1094                         "Error: Supplemental files missing from " + directory.getAbsolutePath());
1095             }
1096             // get bcp47 files also
1097             File bcp47dir = instance.getBcp47Directory();
1098             if (!bcp47dir.isDirectory()) {
1099                 throw new ICUUncheckedIOException(
1100                         "Error: BCP47 dir is not a directory: " + bcp47dir.getAbsolutePath());
1101             }
1102             File files2[] = bcp47dir.listFiles();
1103             if (files2 == null || files2.length == 0) {
1104                 throw new ICUUncheckedIOException(
1105                         "Error: BCP47 files missing from " + bcp47dir.getAbsolutePath());
1106             }
1107 
1108             CBuilder<File, ArrayList<File>> builder = Builder.with(new ArrayList<File>());
1109             builder.addAll(files1);
1110             builder.addAll(files2);
1111             for (File file : builder.get()) {
1112                 if (DEBUG) {
1113                     System.out.println(getNormalizedPathString(file));
1114                 }
1115                 String name = file.toString();
1116                 String shortName = file.getName();
1117                 if (!shortName.endsWith(".xml")
1118                         || // skip non-XML
1119                         shortName.startsWith("#")
1120                         || // skip other junk files
1121                         shortName.startsWith(".")) continue; // skip dot files (backups, etc)
1122                 xfr.read(name, -1, true);
1123                 myHandler.cleanup();
1124             }
1125 
1126             // xfr = new XMLFileReader().setHandler(instance.new MyHandler());
1127             // .xfr.read(normalizedPath + "/supplementalMetadata.xml", -1, true);
1128 
1129             instance.makeStuffSafe();
1130             // cache
1131             //            directory_instance.put(supplementalDirectory, instance);
1132             directory_instance.put(normalizedPath, instance);
1133             //            if (!normalizedPath.equals(supplementalDirectory)) {
1134             //                directory_instance.put(normalizedPath, instance);
1135             //            }
1136             return instance;
1137         }
1138     }
1139 
1140     private File getBcp47Directory() {
1141         return new File(getDirectory().getParent(), "bcp47");
1142     }
1143 
1144     private SupplementalDataInfo(File directory) {
1145         this.directory = directory;
1146         this.validity = Validity.getInstance(directory.toString() + "/../validity/");
1147     } // hide
1148 
1149     public static class Bcp47KeyInfo {
1150         public Bcp47KeyInfo(
1151                 Set<String> aliases,
1152                 String description,
1153                 String since,
1154                 String preferred,
1155                 String deprecated) {
1156             this.description = description;
1157             this.deprecated = !(deprecated == null || deprecated.equals("false"));
1158             this.preferred = preferred;
1159             this.since = since == null ? null : VersionInfo.getInstance(since);
1160             this.aliases = aliases;
1161         }
1162 
1163         final String description;
1164         final VersionInfo since;
1165         final String preferred;
1166         final boolean deprecated;
1167         final Set<String> aliases;
1168 
1169         @Override
1170         public String toString() {
1171             return String.format(
1172                     "{description=«%s» since=%s preferred=%s deprecated=%s aliases=%s}",
1173                     description, since, preferred, deprecated, aliases);
1174         }
1175     }
1176 
1177     private void makeStuffSafe() {
1178         // now make stuff safe
1179         allLanguages.addAll(languageToPopulation.keySet());
1180         allLanguages.addAll(baseLanguageToPopulation.keySet());
1181         allLanguages = Collections.unmodifiableSet(allLanguages);
1182         skippedElements = Collections.unmodifiableSet(skippedElements);
1183         zone_territory = Collections.unmodifiableMap(zone_territory);
1184         alias_zone = Collections.unmodifiableMap(alias_zone);
1185         references = Collections.unmodifiableMap(references);
1186         likelySubtags = Collections.unmodifiableMap(likelySubtags);
1187         likelyOrigins = Collections.unmodifiableMap(likelyOrigins);
1188         currencyToCurrencyNumberInfo = Collections.unmodifiableMap(currencyToCurrencyNumberInfo);
1189         territoryToCurrencyDateInfo.freeze();
1190         // territoryToTelephoneCodeInfo.freeze();
1191         territoryToTelephoneCodeInfo = Collections.unmodifiableMap(territoryToTelephoneCodeInfo);
1192 
1193         typeToZoneToRegionToZone = CldrUtility.protectCollection(typeToZoneToRegionToZone);
1194         typeToTagToReplacement = CldrUtility.protectCollection(typeToTagToReplacement);
1195 
1196         zoneToMetaZoneRanges.freeze();
1197 
1198         containment.freeze();
1199         containmentCore.freeze();
1200         //        containmentNonDeprecated.freeze();
1201         containmentGrouping.freeze();
1202         containmentDeprecated.freeze();
1203 
1204         containerToSubdivision.freeze();
1205 
1206         CldrUtility.protectCollection(languageToBasicLanguageData);
1207         for (String language : languageToTerritories2.keySet()) {
1208             for (Pair<Boolean, Pair<Double, String>> pair :
1209                     languageToTerritories2.getAll(language)) {
1210                 languageToTerritories.put(language, pair.getSecond().getSecond());
1211             }
1212         }
1213         languageToTerritories2 = null; // free up the memory.
1214         languageToTerritories.freeze();
1215         zone_aliases.freeze();
1216         languageToScriptVariants.freeze();
1217 
1218         numericTerritoryMapping.freeze();
1219         alpha3TerritoryMapping.freeze();
1220         numericCurrencyCodeMapping.freeze();
1221 
1222         // freeze contents
1223         for (String language : languageToPopulation.keySet()) {
1224             languageToPopulation.get(language).freeze();
1225         }
1226         for (String language : baseLanguageToPopulation.keySet()) {
1227             baseLanguageToPopulation.get(language).freeze();
1228         }
1229         for (String territory : territoryToPopulationData.keySet()) {
1230             territoryToPopulationData.get(territory).freeze();
1231         }
1232         for (String territory : territoryToLanguageToPopulationData.keySet()) {
1233             Map<String, PopulationData> languageToPopulationDataTemp =
1234                     territoryToLanguageToPopulationData.get(territory);
1235             for (String language : languageToPopulationDataTemp.keySet()) {
1236                 languageToPopulationDataTemp.get(language).freeze();
1237             }
1238         }
1239         localeToPluralInfo2.put(
1240                 PluralType.cardinal,
1241                 Collections.unmodifiableMap(localeToPluralInfo2.get(PluralType.cardinal)));
1242         localeToPluralInfo2.put(
1243                 PluralType.ordinal,
1244                 Collections.unmodifiableMap(localeToPluralInfo2.get(PluralType.ordinal)));
1245 
1246         localeToPluralRanges = Collections.unmodifiableMap(localeToPluralRanges);
1247         for (PluralRanges pluralRanges : localeToPluralRanges.values()) {
1248             pluralRanges.freeze();
1249         }
1250 
1251         if (lastDayPeriodLocales != null) {
1252             addDayPeriodInfo();
1253         }
1254         typeToLocaleToDayPeriodInfo = CldrUtility.protectCollection(typeToLocaleToDayPeriodInfo);
1255         languageMatch = CldrUtility.protectCollection(languageMatch);
1256 
1257         bcp47Extension2Keys.freeze();
1258         bcp47Key2Subtypes.freeze();
1259         CldrUtility.protectCollection(bcp47ValueType);
1260         if (bcp47Key2Subtypes.isEmpty()) {
1261             throw new InternalError(
1262                     "No BCP47 key 2 subtype data was loaded from bcp47 dir "
1263                             + getBcp47Directory().getAbsolutePath());
1264         }
1265 
1266         bcp47Aliases.freeze();
1267         CldrUtility.protectCollection(bcp47Descriptions);
1268         CldrUtility.protectCollection(bcp47Since);
1269         CldrUtility.protectCollection(bcp47Preferred);
1270         CldrUtility.protectCollection(bcp47Deprecated);
1271 
1272         // create clean structure
1273 
1274         for (Entry<String, Set<String>> entry : bcp47Extension2Keys.keyValuesSet()) {
1275             for (String key : entry.getValue()) {
1276                 Map<String, Bcp47KeyInfo> subtypeToInfo = bcp47KeyToSubtypeToInfo.get(key);
1277                 if (subtypeToInfo == null) {
1278                     bcp47KeyToSubtypeToInfo.put(key, subtypeToInfo = new TreeMap<>());
1279                 }
1280                 Map<String, String> aliasToRegular = bcp47KeyToAliasToSubtype.get(key);
1281                 if (aliasToRegular == null) {
1282                     bcp47KeyToAliasToSubtype.put(key, aliasToRegular = new TreeMap<>());
1283                 }
1284                 for (String subtype : bcp47Key2Subtypes.get(key)) {
1285                     final R2<String, String> pair = R2.of(key, subtype);
1286                     final Set<String> aliases = bcp47Aliases.get(pair);
1287                     final Bcp47KeyInfo info =
1288                             new Bcp47KeyInfo(
1289                                     aliases,
1290                                     bcp47Descriptions.get(pair),
1291                                     bcp47Since.get(pair),
1292                                     bcp47Preferred.get(pair),
1293                                     bcp47Deprecated.get(pair));
1294                     subtypeToInfo.put(subtype, info);
1295                     final Map<String, String> aliasToRegularFinal = aliasToRegular;
1296                     if (aliases != null) {
1297                         aliases.forEach(x -> aliasToRegularFinal.put(x, subtype));
1298                     }
1299                 }
1300             }
1301         }
1302         bcp47KeyToSubtypeToInfo = CldrUtility.protectCollection(bcp47KeyToSubtypeToInfo);
1303         bcp47KeyToAliasToSubtype = CldrUtility.protectCollection(bcp47KeyToAliasToSubtype);
1304 
1305         CoverageLevelInfo.fixEU(coverageLevels, this);
1306         coverageLevels = CldrUtility.protectCollection(coverageLevels);
1307 
1308         measurementData = CldrUtility.protectCollection(measurementData);
1309 
1310         final Map<String, R2<List<String>, String>> unitAliases =
1311                 typeToTagToReplacement.get("unit");
1312         if (unitAliases != null) { // don't load unless the information is there (for old releases);
1313             unitConverter.addAliases(unitAliases);
1314         }
1315         unitConverter.freeze();
1316         rationalParser.freeze();
1317         unitPreferences.freeze();
1318 
1319         unitIdComponentType = CldrUtility.protectCollection(unitIdComponentType);
1320 
1321         unitPrefixInfo = CldrUtility.protectCollection(unitPrefixInfo);
1322 
1323         timeData = CldrUtility.protectCollection(timeData);
1324 
1325         validityInfo = CldrUtility.protectCollection(validityInfo);
1326         attributeValidityInfo = CldrUtility.protectCollection(attributeValidityInfo);
1327         parentLocales = CldrUtility.protectCollection(parentLocales);
1328         parentLocalesSkipNonLikely = ImmutableSet.copyOf(parentLocalesSkipNonLikely);
1329         languageGroups = ImmutableSetMultimap.copyOf(languageGroups);
1330 
1331         grammarLocaleToTargetToFeatureToValues =
1332                 CldrUtility.protectCollection(grammarLocaleToTargetToFeatureToValues);
1333         localeToGrammarDerivation = CldrUtility.protectCollection(localeToGrammarDerivation);
1334         personNameOrder = CldrUtility.protectCollection(personNameOrder);
1335 
1336         ImmutableSet.Builder<String> newScripts = ImmutableSet.<String>builder();
1337         Map<Validity.Status, Set<String>> scripts =
1338                 Validity.getInstance().getStatusToCodes(LstrType.script);
1339         for (Entry<Status, Set<String>> e : scripts.entrySet()) {
1340             switch (e.getKey()) {
1341                 case regular:
1342                 case special:
1343                 case unknown:
1344                     newScripts.addAll(e.getValue());
1345                     break;
1346                 default:
1347                     break; // do nothing
1348             }
1349         }
1350         CLDRScriptCodes = newScripts.build();
1351     }
1352 
1353     /**
1354      * Core function used to process each of the paths, and add the data to the appropriate data
1355      * member.
1356      */
1357     class MyHandler extends XMLFileReader.SimpleHandler {
1358         private static final double MAX_POPULATION = 3000000000.0;
1359 
1360         LanguageTagParser languageTagParser =
1361                 null; // postpone assignment until needed, to avoid re-entrance of
1362         // SupplementalDataInfo.getInstance
1363 
1364         /** Finish processing anything left hanging in the file. */
1365         public void cleanup() {
1366             if (lastPluralMap.size() > 0) {
1367                 addPluralInfo(lastPluralWasOrdinal);
1368             }
1369             lastPluralLocales = "";
1370         }
1371 
1372         @Override
1373         public void handlePathValue(String path, String value) {
1374             try {
1375                 XPathParts parts = XPathParts.getFrozenInstance(path);
1376                 String level0 = parts.getElement(0);
1377                 String level1 = parts.size() < 2 ? null : parts.getElement(1);
1378                 String level2 = parts.size() < 3 ? null : parts.getElement(2);
1379                 String level3 = parts.size() < 4 ? null : parts.getElement(3);
1380                 // String level4 = parts.size() < 5 ? null : parts.getElement(4);
1381                 if (level1.equals("generation")) {
1382                     // skip
1383                     return;
1384                 }
1385                 if (level1.equals("version")) {
1386                     if (cldrVersion == null) {
1387                         String version = parts.getAttributeValue(1, "cldrVersion");
1388                         if (version == null) {
1389                             // old format
1390                             version = parts.getAttributeValue(0, "version");
1391                         }
1392                         cldrVersionString = version;
1393                         cldrVersion = VersionInfo.getInstance(version);
1394                         unicodeVersion = parts.getAttributeValue(1, "unicodeVersion");
1395                     }
1396                     return;
1397                 }
1398 
1399                 // copy the rest from ShowLanguages later
1400                 if (level0.equals("ldmlBCP47")) {
1401                     if (handleBcp47(level1, parts)) {
1402                         return;
1403                     }
1404                 } else if (level1.equals("territoryInfo")) {
1405                     if (handleTerritoryInfo(parts)) {
1406                         return;
1407                     }
1408                 } else if (level1.equals("calendarPreferenceData")) {
1409                     handleCalendarPreferenceData(parts);
1410                     return;
1411                 } else if (level1.equals("languageData")) {
1412                     handleLanguageData(parts);
1413                     return;
1414                 } else if (level1.equals("territoryContainment")) {
1415                     handleTerritoryContainment(parts);
1416                     return;
1417                 } else if (level1.equals("subdivisionContainment")) {
1418                     handleSubdivisionContainment(parts);
1419                     return;
1420                 } else if (level1.equals("currencyData")) {
1421                     if (handleCurrencyData(level2, parts)) {
1422                         return;
1423                     }
1424                 } else if ("metazoneInfo".equals(level2)) {
1425                     if (handleMetazoneInfo(level3, parts)) {
1426                         return;
1427                     }
1428                 } else if ("mapTimezones".equals(level2)) {
1429                     if (handleMetazoneData(level3, parts)) {
1430                         return;
1431                     }
1432                 } else if (level1.equals("plurals")) {
1433                     if (addPluralPath(parts, value)) {
1434                         return;
1435                     }
1436                 } else if (level1.equals("dayPeriodRuleSet")) {
1437                     addDayPeriodPath(parts);
1438                     return;
1439                 } else if (level1.equals("telephoneCodeData")) {
1440                     handleTelephoneCodeData(parts);
1441                     return;
1442                 } else if (level1.equals("references")) {
1443                     String type = parts.getAttributeValue(-1, "type");
1444                     String uri = parts.getAttributeValue(-1, "uri");
1445                     references.put(type, new Pair<>(uri, value).freeze());
1446                     return;
1447                 } else if (level1.equals("likelySubtags")) {
1448                     handleLikelySubtags(parts);
1449                     return;
1450                 } else if (level1.equals("numberingSystems")) {
1451                     handleNumberingSystems(parts);
1452                     return;
1453                 } else if (level1.equals("coverageLevels")) {
1454                     handleCoverageLevels(parts);
1455                     return;
1456                 } else if (level1.equals("parentLocales")) {
1457                     handleParentLocales(parts);
1458                     return;
1459                 } else if (level1.equals("metadata")) {
1460                     if (handleMetadata(level2, value, parts)) {
1461                         return;
1462                     }
1463                 } else if (level1.equals("codeMappings")) {
1464                     if (handleCodeMappings(level2, parts)) {
1465                         return;
1466                     }
1467                 } else if (level1.equals("languageMatching")) {
1468                     if (handleLanguageMatcher(parts)) {
1469                         return;
1470                     }
1471                 } else if (level1.equals("measurementData")) {
1472                     if (handleMeasurementData(level2, parts)) {
1473                         return;
1474                     }
1475                 } else if (level1.equals("unitIdComponents")) {
1476                     if (handleUnitUnitIdComponents(parts)) {
1477                         return;
1478                     }
1479                 } else if (level1.equals("unitPrefixes")) {
1480                     if (handleUnitPrefix(parts)) {
1481                         return;
1482                     }
1483                 } else if (level1.equals("unitConstants")) {
1484                     if (handleUnitConstants(parts)) {
1485                         return;
1486                     }
1487                 } else if (level1.equals("unitQuantities")) {
1488                     if (handleUnitQuantities(parts)) {
1489                         return;
1490                     }
1491                 } else if (level1.equals("convertUnits")) {
1492                     if (handleUnitConversion(parts)) {
1493                         return;
1494                     }
1495                 } else if (level1.equals("unitPreferenceData")) {
1496                     if (handleUnitPreferences(parts, value)) {
1497                         return;
1498                     }
1499                 } else if (level1.equals("timeData")) {
1500                     if (handleTimeData(parts)) {
1501                         return;
1502                     }
1503                 } else if (level1.equals("languageGroups")) {
1504                     if (handleLanguageGroups(value, parts)) {
1505                         return;
1506                     }
1507                 } else if (level1.contentEquals("grammaticalData")) {
1508                     if (handleGrammaticalData(value, parts)) {
1509                         return;
1510                     }
1511                 } else if (level1.contentEquals("personNamesDefaults")) {
1512                     if (handlePersonNamesDefaults(value, parts)) {
1513                         return;
1514                     }
1515                 }
1516 
1517                 // capture elements we didn't look at, since we should cover everything.
1518                 // this helps for updates
1519 
1520                 final String skipKey = level1 + (level2 == null ? "" : "/" + level2);
1521                 if (!skippedElements.contains(skipKey)) {
1522                     skippedElements.add(skipKey);
1523                 }
1524                 // System.out.println("Skipped Element: " + path);
1525             } catch (Exception e) {
1526                 throw (IllegalArgumentException)
1527                         new IllegalArgumentException(
1528                                         "Exception while processing path: "
1529                                                 + path
1530                                                 + ",\tvalue: "
1531                                                 + value)
1532                                 .initCause(e);
1533             }
1534         }
1535 
1536         private boolean handleUnitPrefix(XPathParts parts) {
1537             //      <unitPrefix type='quecto' symbol='q' power10='-30'/>
1538             String power10 = parts.getAttributeValue(-1, "power10");
1539             String power2 = parts.getAttributeValue(-1, "power2");
1540             if ((power10 != null) == (power2 != null)) {
1541                 throw new IllegalArgumentException("Must have exactly one @power2 or @power10");
1542             }
1543             unitPrefixInfo.put(
1544                     parts.getAttributeValue(-1, "type"),
1545                     new UnitPrefixInfo(
1546                             parts.getAttributeValue(-1, "symbol"),
1547                             power10 != null ? 10 : 2,
1548                             Integer.parseInt(power10 != null ? power10 : power2)));
1549             return true;
1550         }
1551 
1552         private boolean handlePersonNamesDefaults(String value, XPathParts parts) {
1553             personNameOrder.putAll(
1554                     Order.from(parts.getAttributeValue(-1, "order")), split_space.split(value));
1555             return true;
1556         }
1557 
1558         private boolean handleUnitUnitIdComponents(XPathParts parts) {
1559             //      <unitIdComponent type="prefix" values="arc british dessert fluid light
1560             // nautical"/>
1561             UnitIdComponentType type =
1562                     UnitIdComponentType.valueOf(parts.getAttributeValue(-1, "type"));
1563             for (String value : split_space.split(parts.getAttributeValue(-1, "values"))) {
1564                 UnitIdComponentType old = unitIdComponentType.put(value, type);
1565                 if (old != null) {
1566                     throw new IllegalArgumentException("Duplicate component: " + value);
1567                 }
1568             }
1569             return true;
1570         }
1571 
1572         private boolean handleGrammaticalData(String value, XPathParts parts) {
1573             /*
1574             <!ATTLIST grammaticalFeatures targets NMTOKENS #REQUIRED >
1575             <!ATTLIST grammaticalFeatures locales NMTOKENS #REQUIRED >
1576             OR
1577             <!ATTLIST grammaticalDerivations locales NMTOKENS #REQUIRED >
1578              */
1579 
1580             for (String locale : split_space.split(parts.getAttributeValue(2, "locales"))) {
1581                 switch (parts.getElement(2)) {
1582                     case "grammaticalFeatures":
1583                         GrammarInfo targetToFeatureToValues =
1584                                 grammarLocaleToTargetToFeatureToValues.get(locale);
1585                         if (targetToFeatureToValues == null) {
1586                             grammarLocaleToTargetToFeatureToValues.put(
1587                                     locale, targetToFeatureToValues = new GrammarInfo());
1588                         }
1589                         final String targets = parts.getAttributeValue(2, "targets");
1590                         if (parts.size() < 4) {
1591                             targetToFeatureToValues.add(
1592                                     targets, null, null, null); // special case "known no features"
1593                         } else {
1594                             targetToFeatureToValues.add(
1595                                     targets,
1596                                     parts.getElement(3),
1597                                     parts.getAttributeValue(3, "scope"),
1598                                     parts.getAttributeValue(3, "values"));
1599                         }
1600                         break;
1601                     case "grammaticalDerivations":
1602                         String feature = parts.getAttributeValue(3, "feature");
1603                         String structure = parts.getAttributeValue(3, "structure");
1604                         GrammarDerivation grammarCompoundDerivation =
1605                                 localeToGrammarDerivation.get(locale);
1606                         if (grammarCompoundDerivation == null) {
1607                             localeToGrammarDerivation.put(
1608                                     locale, grammarCompoundDerivation = new GrammarDerivation());
1609                         }
1610 
1611                         switch (parts.getElement(3)) {
1612                             case "deriveCompound":
1613                                 grammarCompoundDerivation.add(
1614                                         feature, structure, parts.getAttributeValue(3, "value"));
1615                                 break;
1616                             case "deriveComponent":
1617                                 grammarCompoundDerivation.add(
1618                                         feature,
1619                                         structure,
1620                                         parts.getAttributeValue(3, "value0"),
1621                                         parts.getAttributeValue(3, "value1"));
1622                                 break;
1623                             default:
1624                                 throw new IllegalArgumentException(
1625                                         "Structure not handled: " + parts);
1626                         }
1627                         break;
1628                     default:
1629                         throw new IllegalArgumentException("Structure not handled: " + parts);
1630                 }
1631             }
1632             return true;
1633         }
1634 
1635         /*
1636          * Handles
1637          * <unitPreferences category="area" usage="_default">
1638          *<unitPreference regions="001" draft="unconfirmed">square-centimeter</unitPreference>
1639          */
1640 
1641         private boolean handleUnitPreferences(XPathParts parts, String value) {
1642             String geq = parts.getAttributeValue(-1, "geq");
1643             String small = parts.getAttributeValue(-2, "scope");
1644             if (small != null) {
1645                 geq = "0.1234";
1646             }
1647             unitPreferences.add(
1648                     parts.getAttributeValue(-2, "category"),
1649                     parts.getAttributeValue(-2, "usage"),
1650                     parts.getAttributeValue(-1, "regions"),
1651                     geq,
1652                     parts.getAttributeValue(-1, "skeleton"),
1653                     value);
1654             return true;
1655         }
1656 
1657         private boolean handleLanguageGroups(String value, XPathParts parts) {
1658             String parent = parts.getAttributeValue(-1, "parent");
1659             List<String> children = WHITESPACE_SPLTTER.splitToList(value);
1660             languageGroups.putAll(parent, children);
1661             return true;
1662         }
1663 
1664         private boolean handleMeasurementData(String level2, XPathParts parts) {
1665             /**
1666              * <measurementSystem type="US" territories="LR MM US"/> <paperSize type="A4"
1667              * territories="001"/>
1668              */
1669             MeasurementType measurementType = MeasurementType.valueOf(level2);
1670             String type = parts.getAttributeValue(-1, "type");
1671             String territories = parts.getAttributeValue(-1, "territories");
1672             Map<String, String> data = measurementData.get(measurementType);
1673             if (data == null) {
1674                 measurementData.put(measurementType, data = new HashMap<>());
1675             }
1676             for (String territory : territories.trim().split("\\s+")) {
1677                 data.put(territory, type);
1678             }
1679             return true;
1680         }
1681 
1682         private boolean handleUnitConstants(XPathParts parts) {
1683             //      <unitConstant constant="ft2m" value="0.3048"/>
1684 
1685             final String constant = parts.getAttributeValue(-1, "constant");
1686             final String value = parts.getAttributeValue(-1, "value");
1687             final String status = parts.getAttributeValue(-1, "status");
1688             rationalParser.addConstant(constant, value, status);
1689             return true;
1690         }
1691 
1692         private boolean handleUnitQuantities(XPathParts parts) {
1693             //      <unitQuantity quantity='wave-number' baseUnit='reciprocal-meter'/>
1694 
1695             final String baseUnit = parts.getAttributeValue(-1, "baseUnit");
1696             final String quantity = parts.getAttributeValue(-1, "quantity");
1697             final String status = parts.getAttributeValue(-1, "status");
1698             if (unitConverter == null) {
1699                 unitConverter = new UnitConverter(rationalParser, validity);
1700             }
1701             unitConverter.addQuantityInfo(baseUnit, quantity, status);
1702             return true;
1703         }
1704 
1705         private boolean handleUnitConversion(XPathParts parts) {
1706             // <convertUnit source='acre' target='square-meter' factor='ft2m^2 * 43560'/>
1707 
1708             final String source = parts.getAttributeValue(-1, "source");
1709             final String target = parts.getAttributeValue(-1, "baseUnit");
1710             //            if (source.contentEquals(target)) {
1711             //                throw new IllegalArgumentException("Cannot convert from something to
1712             // itself " + parts);
1713             //            }
1714             String factor = parts.getAttributeValue(-1, "factor");
1715             String offset = parts.getAttributeValue(-1, "offset");
1716             String special = parts.getAttributeValue(-1, "special");
1717             String systems = parts.getAttributeValue(-1, "systems");
1718             unitConverter.addRaw(source, target, factor, offset, special, systems);
1719             return true;
1720         }
1721 
1722         private boolean handleTimeData(XPathParts parts) {
1723             /** <hours preferred="H" allowed="H" regions="IL RU"/> */
1724             String preferred = parts.getAttributeValue(-1, "preferred");
1725             PreferredAndAllowedHour preferredAndAllowedHour =
1726                     new PreferredAndAllowedHour(preferred, parts.getAttributeValue(-1, "allowed"));
1727             for (String region : parts.getAttributeValue(-1, "regions").trim().split("\\s+")) {
1728                 PreferredAndAllowedHour oldValue = timeData.put(region, preferredAndAllowedHour);
1729                 if (oldValue != null) {
1730                     throw new IllegalArgumentException(
1731                             "timeData/hours must not have duplicate regions: " + region);
1732                 }
1733             }
1734             return true;
1735         }
1736 
1737         private boolean handleBcp47(String level1, XPathParts parts) {
1738             if (level1.equals("version")
1739                     || level1.equals("generation")
1740                     || level1.equals("cldrVersion")) {
1741                 return true; // skip
1742             }
1743             if (!level1.equals("keyword")) {
1744                 throw new IllegalArgumentException("Unexpected level1 element: " + level1);
1745             }
1746 
1747             String finalElement = parts.getElement(-1);
1748             String key = parts.getAttributeValue(2, "name");
1749             String extension = parts.getAttributeValue(2, "extension");
1750             if (extension == null) {
1751                 extension = "u";
1752             }
1753             bcp47Extension2Keys.put(extension, key);
1754 
1755             String keyAlias = parts.getAttributeValue(2, "alias");
1756             String keyDescription = parts.getAttributeValue(2, "description");
1757             String deprecated = parts.getAttributeValue(2, "deprecated");
1758             // TODO add preferred, valueType, since
1759 
1760             final R2<String, String> key_empty = (R2<String, String>) Row.of(key, "").freeze();
1761 
1762             if (keyAlias != null) {
1763                 bcp47Aliases.putAll(key_empty, Arrays.asList(keyAlias.trim().split("\\s+")));
1764             }
1765 
1766             if (keyDescription != null) {
1767                 bcp47Descriptions.put(key_empty, keyDescription);
1768             }
1769             if (deprecated != null && deprecated.equals("true")) {
1770                 bcp47Deprecated.put(key_empty, deprecated);
1771             }
1772 
1773             switch (finalElement) {
1774                 case "key":
1775                     break; // all actions taken above
1776 
1777                 case "type":
1778                     String subtype = parts.getAttributeValue(3, "name");
1779                     String subtypeAlias = parts.getAttributeValue(3, "alias");
1780                     String desc = parts.getAttributeValue(3, "description");
1781                     String subtypeDescription = desc == null ? null : desc.replaceAll("\\s+", " ");
1782                     String subtypeSince = parts.getAttributeValue(3, "since");
1783                     String subtypePreferred = parts.getAttributeValue(3, "preferred");
1784                     String subtypeDeprecated = parts.getAttributeValue(3, "deprecated");
1785                     String valueType = parts.getAttributeValue(3, "deprecated");
1786 
1787                     Set<String> set = bcp47Key2Subtypes.get(key);
1788                     if (set != null && set.contains(key)) {
1789                         throw new IllegalArgumentException(
1790                                 "Collision with bcp47 key-value: " + key + "," + subtype);
1791                     }
1792                     bcp47Key2Subtypes.put(key, subtype);
1793 
1794                     final R2<String, String> key_subtype =
1795                             (R2<String, String>) Row.of(key, subtype).freeze();
1796 
1797                     if (subtypeAlias != null) {
1798                         bcp47Aliases.putAll(
1799                                 key_subtype, Arrays.asList(subtypeAlias.trim().split("\\s+")));
1800                     }
1801                     if (subtypeDescription != null) {
1802                         bcp47Descriptions.put(
1803                                 key_subtype, subtypeDescription.replaceAll("\\s+", " "));
1804                     }
1805                     if (subtypeSince != null) {
1806                         bcp47Since.put(key_subtype, subtypeSince);
1807                     }
1808                     if (subtypePreferred != null) {
1809                         bcp47Preferred.put(key_subtype, subtypePreferred);
1810                     }
1811                     if (subtypeDeprecated != null) {
1812                         bcp47Deprecated.put(key_subtype, subtypeDeprecated);
1813                     }
1814                     if (valueType != null) {
1815                         bcp47ValueType.put(subtype, valueType);
1816                     }
1817                     break;
1818                 default:
1819                     throw new IllegalArgumentException("Unexpected element: " + finalElement);
1820             }
1821 
1822             return true;
1823         }
1824 
1825         private boolean handleLanguageMatcher(XPathParts parts) {
1826             String type = parts.getAttributeValue(2, "type");
1827             String alt = parts.getAttributeValue(2, "alt");
1828             if (alt != null) {
1829                 type += "_" + alt;
1830             }
1831             switch (parts.getElement(3)) {
1832                 case "paradigmLocales":
1833                     List<String> locales =
1834                             WHITESPACE_SPLTTER.splitToList(parts.getAttributeValue(3, "locales"));
1835                     // TODO
1836                     //                LanguageMatchData languageMatchData =
1837                     // languageMatchData.get(type);
1838                     //                if (languageMatchData == null) {
1839                     //                    languageMatch.put(type, languageMatchData = new
1840                     // LanguageMatchData());
1841                     //                }
1842                     break;
1843                 case "matchVariable":
1844                     // String id = parts.getAttributeValue(3, "id");
1845                     // String value = parts.getAttributeValue(3, "value");
1846                     // TODO
1847                     break;
1848                 case "languageMatch":
1849                     List<R4<String, String, Integer, Boolean>> matches = languageMatch.get(type);
1850                     if (matches == null) {
1851                         languageMatch.put(type, matches = new ArrayList<>());
1852                     }
1853                     String percent = parts.getAttributeValue(3, "percent");
1854                     String distance = parts.getAttributeValue(3, "distance");
1855                     matches.add(
1856                             Row.of(
1857                                     parts.getAttributeValue(3, "desired"),
1858                                     parts.getAttributeValue(3, "supported"),
1859                                     percent != null
1860                                             ? Integer.parseInt(percent)
1861                                             : 100 - Integer.parseInt(distance),
1862                                     "true".equals(parts.getAttributeValue(3, "oneway"))));
1863                     break;
1864                 default:
1865                     throw new IllegalArgumentException("Unknown element");
1866             }
1867             return true;
1868         }
1869 
1870         private boolean handleCodeMappings(String level2, XPathParts parts) {
1871             if (level2.equals("territoryCodes")) {
1872                 // <territoryCodes type="VU" numeric="548" alpha3="VUT"/>
1873                 String type = parts.getAttributeValue(-1, "type");
1874                 final String numeric = parts.getAttributeValue(-1, "numeric");
1875                 if (numeric != null) {
1876                     numericTerritoryMapping.put(type, Integer.parseInt(numeric));
1877                 }
1878                 final String alpha3 = parts.getAttributeValue(-1, "alpha3");
1879                 if (alpha3 != null) {
1880                     alpha3TerritoryMapping.put(type, alpha3);
1881                 }
1882                 return true;
1883             } else if (level2.equals("currencyCodes")) {
1884                 // <currencyCodes type="BBD" numeric="52"/>
1885                 String type = parts.getAttributeValue(-1, "type");
1886                 final String numeric = parts.getAttributeValue(-1, "numeric");
1887                 if (numeric != null) {
1888                     numericCurrencyCodeMapping.put(type, Integer.parseInt(numeric));
1889                 }
1890                 return true;
1891             }
1892             return false;
1893         }
1894 
1895         private void handleNumberingSystems(XPathParts parts) {
1896             NumberingSystemInfo ns = new NumberingSystemInfo(parts);
1897             numberingSystems.put(ns.name, ns);
1898             if (ns.type == NumberingSystemType.numeric) {
1899                 numericSystems.add(ns.name);
1900             }
1901         }
1902 
1903         private void handleCoverageLevels(XPathParts parts) {
1904             if (parts.containsElement("approvalRequirement")) {
1905                 approvalRequirements.add(parts.toString());
1906             } else if (parts.containsElement("coverageLevel")) {
1907                 String match =
1908                         parts.containsAttribute("match")
1909                                 ? coverageVariables.replace(parts.getAttributeValue(-1, "match"))
1910                                 : null;
1911                 String valueStr = parts.getAttributeValue(-1, "value");
1912                 // Ticket 7125: map the number to English. So switch from English to number for
1913                 // construction
1914                 valueStr = Integer.toString(Level.get(valueStr).getLevel());
1915 
1916                 String inLanguage =
1917                         parts.containsAttribute("inLanguage")
1918                                 ? coverageVariables.replace(
1919                                         parts.getAttributeValue(-1, "inLanguage"))
1920                                 : null;
1921                 String inScript =
1922                         parts.containsAttribute("inScript")
1923                                 ? coverageVariables.replace(parts.getAttributeValue(-1, "inScript"))
1924                                 : null;
1925                 String inTerritory =
1926                         parts.containsAttribute("inTerritory")
1927                                 ? coverageVariables.replace(
1928                                         parts.getAttributeValue(-1, "inTerritory"))
1929                                 : null;
1930                 Integer value =
1931                         (valueStr != null) ? Integer.valueOf(valueStr) : Integer.valueOf("101");
1932                 if (cldrVersion.getMajor() < 2) {
1933                     value = 40;
1934                 }
1935                 CoverageLevelInfo ci =
1936                         new CoverageLevelInfo(match, value, inLanguage, inScript, inTerritory);
1937                 coverageLevels.add(ci);
1938             } else if (parts.containsElement("coverageVariable")) {
1939                 String key = parts.getAttributeValue(-1, "key");
1940                 String value = parts.getAttributeValue(-1, "value");
1941                 coverageVariables.add(key, value);
1942             }
1943         }
1944 
1945         public static final String NONLIKELYSCRIPT = "nonlikelyScript";
1946 
1947         private void handleParentLocales(XPathParts parts) {
1948             // CLDR-16253 added component-specific parents, which we ignore for now.
1949             // TODO(CLDR-16361): Figure out how to handle these in CLDR itself.
1950             String componentsString = parts.getAttributeValue(1, "component");
1951             Set<ParentLocaleComponent> components;
1952             if (componentsString == null) {
1953                 components = ImmutableSet.of(ParentLocaleComponent.main);
1954             } else {
1955                 components =
1956                         split_space
1957                                 .splitToStream(componentsString)
1958                                 .map(x -> ParentLocaleComponent.fromString(x))
1959                                 .collect(Collectors.toSet());
1960             }
1961             if (!parts.getElement(-1).equals("parentLocale")) {
1962                 // If there is no parentLocale element , that means we have nothing to add
1963                 // Since we have pre-populated the parentLocales with component -> empty map,
1964                 // there is nothing more to do, and we can exit.
1965                 // We have parsed the components, however, so they are valid
1966                 return;
1967             }
1968             String parent = parts.getAttributeValue(-1, "parent");
1969             String locales = parts.getAttributeValue(-1, "locales");
1970             String localeRules = parts.getAttributeValue(-1, "localeRules");
1971             Set<String> localeRuleSet =
1972                     localeRules == null
1973                             ? Set.of()
1974                             : Set.copyOf(split_space.splitToList(localeRules));
1975 
1976             for (ParentLocaleComponent component : components) {
1977                 Map<String, String> componentParentLocales = parentLocales.get(component);
1978                 if (localeRuleSet.contains(NONLIKELYSCRIPT)) {
1979                     // This will need to be modified if we add any other rules,
1980                     // particularly if any rules are based on the particular parent
1981                     parentLocalesSkipNonLikely.add(component);
1982                     continue;
1983                 }
1984                 for (String childLocale : split_space.split(locales)) {
1985                     String old = componentParentLocales.put(childLocale, parent);
1986                     if (old != null) {
1987                         throw new IllegalArgumentException(
1988                                 "Locale "
1989                                         + childLocale
1990                                         + " cannot have two parents: "
1991                                         + old
1992                                         + " and "
1993                                         + parent);
1994                     }
1995                 }
1996             }
1997         }
1998 
1999         private void handleCalendarPreferenceData(XPathParts parts) {
2000             String territoryString = parts.getAttributeValue(-1, "territories");
2001             String orderingString = parts.getAttributeValue(-1, "ordering");
2002             String[] calendars = orderingString.split(" ");
2003             String[] territories = territoryString.split(" ");
2004             List<String> calendarList = Arrays.asList(calendars);
2005             for (int i = 0; i < territories.length; i++) {
2006                 calendarPreferences.put(territories[i], calendarList);
2007             }
2008         }
2009 
2010         private void handleLikelySubtags(XPathParts parts) {
2011             String from = parts.getAttributeValue(-1, "from");
2012             String to = parts.getAttributeValue(-1, "to");
2013             String origin = parts.getAttributeValue(-1, "origin");
2014             String toOld = likelySubtags.get(from);
2015             if (toOld != null) {
2016                 if (to.equals(toOld)) {
2017                     System.err.println("Likely subtags repeats from=" + from + " to= " + to);
2018                 } else {
2019                     throw new IllegalArgumentException(
2020                             "Likely subtags duplicate from="
2021                                     + from
2022                                     + ", overrides values: "
2023                                     + toOld
2024                                     + " with "
2025                                     + to);
2026                 }
2027             } else {
2028                 likelySubtags.put(from, to);
2029                 if (origin != null) {
2030                     likelyOrigins.put(from, origin);
2031                 }
2032             }
2033         }
2034 
2035         /**
2036          * Only called if level2 = mapTimezones. Level 1 might be metaZones or might be windowsZones
2037          */
2038         private boolean handleMetazoneData(String level3, XPathParts parts) {
2039             if (level3.equals("mapZone")) {
2040                 String maintype = parts.getAttributeValue(2, "type");
2041                 if (maintype == null) {
2042                     maintype = "windows";
2043                 }
2044                 String mzone = parts.getAttributeValue(3, "other");
2045                 String region = parts.getAttributeValue(3, "territory");
2046                 String zone = parts.getAttributeValue(3, "type");
2047 
2048                 Map<String, Map<String, String>> zoneToRegionToZone =
2049                         typeToZoneToRegionToZone.get(maintype);
2050                 if (zoneToRegionToZone == null) {
2051                     typeToZoneToRegionToZone.put(maintype, zoneToRegionToZone = new TreeMap<>());
2052                 }
2053                 Map<String, String> regionToZone = zoneToRegionToZone.get(mzone);
2054                 if (regionToZone == null) {
2055                     zoneToRegionToZone.put(mzone, regionToZone = new TreeMap<>());
2056                 }
2057                 if (region != null) {
2058                     regionToZone.put(region, zone);
2059                 }
2060                 if (maintype.equals("metazones")) {
2061                     if (mzone != null && region.equals("001")) {
2062                         metazoneContinentMap.put(mzone, zone.substring(0, zone.indexOf("/")));
2063                     }
2064                     allMetazones.add(mzone);
2065                 }
2066                 return true;
2067             }
2068             return false;
2069         }
2070 
2071         private Collection<String> getSpaceDelimited(
2072                 int index, String attribute, Collection<String> defaultValue, XPathParts parts) {
2073             String temp = parts.getAttributeValue(index, attribute);
2074             Collection<String> elements =
2075                     temp == null ? defaultValue : Arrays.asList(temp.split("\\s+"));
2076             return elements;
2077         }
2078 
2079         /*
2080          *
2081          * <supplementalData>
2082          * <metaZones>
2083          * <metazoneInfo>
2084          * <timezone type="Asia/Yerevan">
2085          * <usesMetazone to="1991-09-22 20:00" mzone="Yerevan"/>
2086          * <usesMetazone from="1991-09-22 20:00" mzone="Armenia"/>
2087          */
2088 
2089         private boolean handleMetazoneInfo(String level3, XPathParts parts) {
2090             if (level3.equals("timezone")) {
2091                 String zone = parts.getAttributeValue(3, "type");
2092                 String mzone = parts.getAttributeValue(4, "mzone");
2093                 String from = parts.getAttributeValue(4, "from");
2094                 String to = parts.getAttributeValue(4, "to");
2095                 MetaZoneRange mzoneRange = new MetaZoneRange(mzone, from, to);
2096                 zoneToMetaZoneRanges.put(zone, mzoneRange);
2097                 return true;
2098             }
2099             return false;
2100         }
2101 
2102         private boolean handleMetadata(String level2, String value, XPathParts parts) {
2103             if (parts.contains("defaultContent")) {
2104                 String defContent = parts.getAttributeValue(-1, "locales").trim();
2105                 String[] defLocales = defContent.split("\\s+");
2106                 defaultContentLocales =
2107                         Collections.unmodifiableSet(new TreeSet<>(Arrays.asList(defLocales)));
2108                 return true;
2109             }
2110             if (level2.equals("alias")) {
2111                 // <alias>
2112                 // <languageAlias type="art-lojban" replacement="jbo"/> <!-- Lojban -->
2113                 String level3 = parts.getElement(3);
2114                 if (!level3.endsWith("Alias")) {
2115                     throw new IllegalArgumentException();
2116                 }
2117                 level3 = level3.substring(0, level3.length() - "Alias".length());
2118                 Map<String, R2<List<String>, String>> tagToReplacement =
2119                         typeToTagToReplacement.get(level3);
2120                 if (tagToReplacement == null) {
2121                     typeToTagToReplacement.put(level3, tagToReplacement = new TreeMap<>());
2122                 }
2123                 final String replacement = parts.getAttributeValue(3, "replacement");
2124                 List<String> replacementList = null;
2125                 if (replacement != null) {
2126                     Set<String> builder = new LinkedHashSet<>();
2127                     for (String item : replacement.split("\\s+")) {
2128                         String cleaned =
2129                                 SubdivisionNames.isOldSubdivisionCode(item)
2130                                         ? replacement.replace("-", "").toLowerCase(Locale.ROOT)
2131                                         : item;
2132                         builder.add(cleaned);
2133                     }
2134                     replacementList = ImmutableList.copyOf(builder);
2135                 }
2136                 final String reason = parts.getAttributeValue(3, "reason");
2137                 String cleanTag = parts.getAttributeValue(3, "type");
2138                 tagToReplacement.put(
2139                         cleanTag,
2140                         (R2<List<String>, String>) Row.of(replacementList, reason).freeze());
2141                 return true;
2142             } else if (level2.equals("validity")) {
2143                 // <variable id="$grandfathered" type="choice">
2144                 String level3 = parts.getElement(3);
2145                 if (level3.equals("variable")) {
2146                     Map<String, String> attributes = parts.getAttributes(-1);
2147                     validityInfo.put(attributes.get("id"), Row.of(attributes.get("type"), value));
2148                     String idString = attributes.get("id");
2149                     if (("$language".equals(idString)
2150                                     || "$languageExceptions".equals(attributes.get("id")))
2151                             && "choice".equals(attributes.get("type"))) {
2152                         String[] validCodeArray = value.trim().split("\\s+");
2153                         CLDRLanguageCodes.addAll(Arrays.asList(validCodeArray));
2154                     }
2155                     return true;
2156                 } else if (level3.equals("attributeValues")) {
2157                     AttributeValidityInfo.add(
2158                             parts.getAttributes(-1), value, attributeValidityInfo);
2159                     return true;
2160                 }
2161             } else if (level2.equals("serialElements")) {
2162                 serialElements = Arrays.asList(value.trim().split("\\s+"));
2163                 return true;
2164             } else if (level2.equals("distinguishing")) {
2165                 String level3 = parts.getElement(3);
2166                 if (level3.equals("distinguishingItems")) {
2167                     Map<String, String> attributes = parts.getAttributes(-1);
2168                     // <distinguishingItems
2169                     // attributes="key request id _q registry alt iso4217 iso3166 mzone from to type
2170                     // numberSystem"/>
2171                     // <distinguishingItems exclude="true"
2172                     // elements="default measurementSystem mapping abbreviationFallback
2173                     // preferenceOrdering"
2174                     // attributes="type"/>
2175 
2176                     if (attributes.containsKey("exclude")
2177                             && "true".equals(attributes.get("exclude"))) {
2178                         return false; // don't handle the excludes -yet.
2179                     } else {
2180                         distinguishingAttributes =
2181                                 Collections.unmodifiableCollection(
2182                                         getSpaceDelimited(-1, "attributes", STAR_SET, parts));
2183                         return true;
2184                     }
2185                 }
2186             }
2187             return false;
2188         }
2189 
2190         private boolean handleTerritoryInfo(XPathParts parts) {
2191 
2192             // <territoryInfo>
2193             // <territory type="AD" gdp="1840000000" literacyPercent="100"
2194             // population="66000"> <!--Andorra-->
2195             // <languagePopulation type="ca" populationPercent="50"/>
2196             // <!--Catalan-->
2197 
2198             Map<String, String> territoryAttributes = parts.getAttributes(2);
2199             String territory = territoryAttributes.get("type");
2200             double territoryPopulation = parseDouble(territoryAttributes.get("population"));
2201             if (failsRangeCheck("population", territoryPopulation, 0, MAX_POPULATION)) {
2202                 return true;
2203             }
2204 
2205             double territoryLiteracyPercent =
2206                     parseDouble(territoryAttributes.get("literacyPercent"));
2207             double territoryGdp = parseDouble(territoryAttributes.get("gdp"));
2208             if (territoryToPopulationData.get(territory) == null) {
2209                 territoryToPopulationData.put(
2210                         territory,
2211                         new PopulationData()
2212                                 .setPopulation(territoryPopulation)
2213                                 .setLiteratePopulation(
2214                                         territoryLiteracyPercent * territoryPopulation / 100)
2215                                 .setGdp(territoryGdp));
2216             }
2217             if (parts.size() > 3) {
2218 
2219                 Map<String, String> languageInTerritoryAttributes = parts.getAttributes(3);
2220                 String language = languageInTerritoryAttributes.get("type");
2221                 double languageLiteracyPercent =
2222                         parseDouble(languageInTerritoryAttributes.get("literacyPercent"));
2223                 if (Double.isNaN(languageLiteracyPercent)) {
2224                     languageLiteracyPercent = territoryLiteracyPercent;
2225                 }
2226                 double writingPercent =
2227                         parseDouble(languageInTerritoryAttributes.get("writingPercent"));
2228                 if (Double.isNaN(writingPercent)) {
2229                     writingPercent = languageLiteracyPercent;
2230                 }
2231                 // else {
2232                 // System.out.println("writingPercent\t" + languageLiteracyPercent
2233                 // + "\tterritory\t" + territory
2234                 // + "\tlanguage\t" + language);
2235                 // }
2236                 double languagePopulationPercent =
2237                         parseDouble(languageInTerritoryAttributes.get("populationPercent"));
2238                 double languagePopulation = languagePopulationPercent * territoryPopulation / 100;
2239                 // double languageGdp = languagePopulationPercent * territoryGdp;
2240 
2241                 // store
2242                 Map<String, PopulationData> territoryLanguageToPopulation =
2243                         territoryToLanguageToPopulationData.get(territory);
2244                 if (territoryLanguageToPopulation == null) {
2245                     territoryToLanguageToPopulationData.put(
2246                             territory, territoryLanguageToPopulation = new TreeMap<>());
2247                 }
2248                 OfficialStatus officialStatus = OfficialStatus.unknown;
2249                 String officialStatusString = languageInTerritoryAttributes.get("officialStatus");
2250                 if (officialStatusString != null)
2251                     officialStatus = OfficialStatus.valueOf(officialStatusString);
2252 
2253                 PopulationData newData =
2254                         new PopulationData()
2255                                 .setPopulation(languagePopulation)
2256                                 .setLiteratePopulation(
2257                                         languageLiteracyPercent * languagePopulation / 100)
2258                                 .setWritingPopulation(writingPercent * languagePopulation / 100)
2259                                 .setOfficialStatus(officialStatus)
2260                         // .setGdp(languageGdp)
2261                         ;
2262                 newData.freeze();
2263                 if (territoryLanguageToPopulation.get(language) != null) {
2264                     System.out.println(
2265                             "Internal Problem in supplementalData: multiple data items for "
2266                                     + language
2267                                     + ", "
2268                                     + territory
2269                                     + "\tSkipping "
2270                                     + newData);
2271                     return true;
2272                 }
2273 
2274                 territoryLanguageToPopulation.put(language, newData);
2275                 // add the language, using the Pair fields to get the ordering right
2276                 languageToTerritories2.put(
2277                         language,
2278                         Pair.of(
2279                                 newData.getOfficialStatus().isMajor() ? false : true,
2280                                 Pair.of(-newData.getLiteratePopulation(), territory)));
2281 
2282                 // now collect data for languages globally
2283                 PopulationData data = languageToPopulation.get(language);
2284                 if (data == null) {
2285                     languageToPopulation.put(language, data = new PopulationData().set(newData));
2286                 } else {
2287                     data.add(newData);
2288                 }
2289                 // if (language.equals("en")) {
2290                 // System.out.println(territory + "\tnewData:\t" + newData + "\tdata:\t" + data);
2291                 // }
2292 
2293                 if (languageTagParser == null) {
2294                     languageTagParser = new LanguageTagParser();
2295                 }
2296                 String baseLanguage = languageTagParser.set(language).getLanguage();
2297                 data = baseLanguageToPopulation.get(baseLanguage);
2298                 if (data == null) {
2299                     baseLanguageToPopulation.put(
2300                             baseLanguage, data = new PopulationData().set(newData));
2301                 } else {
2302                     data.add(newData);
2303                 }
2304                 if (!baseLanguage.equals(language)) {
2305                     languageToScriptVariants.put(baseLanguage, language);
2306                 }
2307             }
2308             return true;
2309         }
2310 
2311         private boolean handleCurrencyData(String level2, XPathParts parts) {
2312             if (level2.equals("fractions")) {
2313                 // <info iso4217="ADP" digits="0" rounding="0" cashRounding="5"/>
2314                 currencyToCurrencyNumberInfo.put(
2315                         parts.getAttributeValue(3, "iso4217"),
2316                         new CurrencyNumberInfo(
2317                                 parseIntegerOrNull(parts.getAttributeValue(3, "digits")),
2318                                 parseIntegerOrNull(parts.getAttributeValue(3, "rounding")),
2319                                 parseIntegerOrNull(parts.getAttributeValue(3, "cashDigits")),
2320                                 parseIntegerOrNull(parts.getAttributeValue(3, "cashRounding"))));
2321                 return true;
2322             }
2323             /*
2324              * <region iso3166="AD">
2325              * <currency iso4217="EUR" from="1999-01-01"/>
2326              * <currency iso4217="ESP" from="1873" to="2002-02-28"/>
2327              */
2328             if (level2.equals("region")) {
2329                 territoryToCurrencyDateInfo.put(
2330                         parts.getAttributeValue(2, "iso3166"),
2331                         new CurrencyDateInfo(
2332                                 parts.getAttributeValue(3, "iso4217"),
2333                                 parts.getAttributeValue(3, "from"),
2334                                 parts.getAttributeValue(3, "to"),
2335                                 parts.getAttributeValue(3, "tender")));
2336                 return true;
2337             }
2338 
2339             return false;
2340         }
2341 
2342         private void handleTelephoneCodeData(XPathParts parts) {
2343             // element 2: codesByTerritory territory [draft] [references]
2344             String terr = parts.getAttributeValue(2, "territory");
2345             // element 3: telephoneCountryCode code [from] [to] [draft] [references] [alt]
2346             TelephoneCodeInfo tcInfo =
2347                     new TelephoneCodeInfo(
2348                             parts.getAttributeValue(3, "code"),
2349                             parts.getAttributeValue(3, "from"),
2350                             parts.getAttributeValue(3, "to"),
2351                             parts.getAttributeValue(3, "alt"));
2352 
2353             Set<TelephoneCodeInfo> tcSet = territoryToTelephoneCodeInfo.get(terr);
2354             if (tcSet == null) {
2355                 tcSet = new LinkedHashSet<>();
2356                 territoryToTelephoneCodeInfo.put(terr, tcSet);
2357             }
2358             tcSet.add(tcInfo);
2359         }
2360 
2361         private void handleTerritoryContainment(XPathParts parts) {
2362             // <group type="001" contains="002 009 019 142 150"/>
2363             final String container = parts.getAttributeValue(-1, "type");
2364             final List<String> contained =
2365                     Arrays.asList(parts.getAttributeValue(-1, "contains").split("\\s+"));
2366             // everything!
2367             containment.putAll(container, contained);
2368 
2369             String status = parts.getAttributeValue(-1, "status");
2370             String grouping = parts.getAttributeValue(-1, "grouping");
2371             if (status == null && grouping == null) {
2372                 containmentCore.putAll(container, contained);
2373             }
2374             if (status != null && status.equals("deprecated")) {
2375                 containmentDeprecated.putAll(container, contained);
2376             }
2377             if (grouping != null) {
2378                 containmentGrouping.putAll(container, contained);
2379             }
2380         }
2381 
2382         private void handleSubdivisionContainment(XPathParts parts) {
2383             //      <subgroup type="AL" subtype="04" contains="FR MK LU"/>
2384             final String country = parts.getAttributeValue(-1, "type");
2385             final String subtype = parts.getAttributeValue(-1, "subtype");
2386             final String container =
2387                     subtype == null ? country : (country + subtype).toLowerCase(Locale.ROOT);
2388             for (String contained : parts.getAttributeValue(-1, "contains").split("\\s+")) {
2389                 String newContained =
2390                         contained.charAt(0) >= 'a'
2391                                 ? contained
2392                                 : (country + contained).toLowerCase(Locale.ROOT);
2393                 containerToSubdivision.put(container, newContained);
2394             }
2395         }
2396 
2397         private void handleLanguageData(XPathParts parts) {
2398             // <languageData>
2399             // <language type="aa" scripts="Latn" territories="DJ ER ET"/> <!--
2400             // Reflecting submitted data, cldrbug #1013 -->
2401             // <language type="ab" scripts="Cyrl" territories="GE"
2402             // alt="secondary"/>
2403             String language = parts.getAttributeValue(2, "type");
2404             BasicLanguageData languageData = new BasicLanguageData();
2405             languageData.setType(
2406                     parts.getAttributeValue(2, "alt") == null
2407                             ? BasicLanguageData.Type.primary
2408                             : BasicLanguageData.Type.secondary);
2409             languageData
2410                     .setScripts(parts.getAttributeValue(2, "scripts"))
2411                     .setTerritories(parts.getAttributeValue(2, "territories"));
2412             Map<Type, BasicLanguageData> map = languageToBasicLanguageData.get(language);
2413             if (map == null) {
2414                 languageToBasicLanguageData.put(
2415                         language, map = new EnumMap<>(BasicLanguageData.Type.class));
2416             }
2417             if (map.containsKey(languageData.type)) {
2418                 throw new IllegalArgumentException("Duplicate value:\t" + parts);
2419             }
2420             map.put(languageData.type, languageData);
2421         }
2422 
2423         private boolean failsRangeCheck(String path, double input, double min, double max) {
2424             if (input >= min && input <= max) {
2425                 return false;
2426             }
2427             System.out.println(
2428                     "Internal Problem in supplementalData: range check fails for "
2429                             + input
2430                             + ", min: "
2431                             + min
2432                             + ", max:"
2433                             + max
2434                             + "\t"
2435                             + path);
2436 
2437             return false;
2438         }
2439 
2440         private double parseDouble(String literacyString) {
2441             return literacyString == null ? Double.NaN : Double.parseDouble(literacyString);
2442         }
2443     }
2444 
2445     public class CoverageVariableInfo {
2446         public Set<String> targetScripts;
2447         public Set<String> targetTerritories;
2448         public Set<String> calendars;
2449         public Set<String> targetCurrencies;
2450         public Set<String> targetTimeZones;
2451         public Set<String> targetPlurals;
2452     }
2453 
2454     public static String toRegexString(Set<String> s) {
2455         Iterator<String> it = s.iterator();
2456         StringBuilder sb = new StringBuilder("(");
2457         int count = 0;
2458         while (it.hasNext()) {
2459             if (count > 0) {
2460                 sb.append("|");
2461             }
2462             sb.append(it.next());
2463             count++;
2464         }
2465         sb.append(")");
2466         return sb.toString();
2467     }
2468 
2469     public int parseIntegerOrNull(String attributeValue) {
2470         return attributeValue == null ? -1 : Integer.parseInt(attributeValue);
2471     }
2472 
2473     Set<String> skippedElements = new TreeSet<>();
2474 
2475     private Map<String, Pair<String, String>> references = new TreeMap<>();
2476     private Map<String, String> likelySubtags = new TreeMap<>();
2477     private Map<String, String> likelyOrigins = new TreeMap<>();
2478     // make public temporarily until we resolve.
2479     private Set<CoverageLevelInfo> coverageLevels = new LinkedHashSet<>();
2480     private Map<ParentLocaleComponent, Map<String, String>> parentLocales = new HashMap<>();
2481 
2482     { // Prefill, since we know we will need these
2483         Arrays.stream(ParentLocaleComponent.values())
2484                 .forEach(x -> parentLocales.put(x, new HashMap<>()));
2485     }
2486 
2487     private Set<ParentLocaleComponent> parentLocalesSkipNonLikely =
2488             EnumSet.noneOf(ParentLocaleComponent.class);
2489     private Map<String, List<String>> calendarPreferences = new HashMap<>();
2490     private Map<String, CoverageVariableInfo> localeSpecificVariables = new TreeMap<>();
2491     private VariableReplacer coverageVariables = new VariableReplacer();
2492     private Map<String, NumberingSystemInfo> numberingSystems = new HashMap<>();
2493     private Set<String> numericSystems = new TreeSet<>();
2494     private Set<String> defaultContentLocales;
2495     public Map<CLDRLocale, CLDRLocale> baseToDefaultContent; // wo -> wo_Arab_SN
2496     public Map<CLDRLocale, CLDRLocale> defaultContentToBase; // wo_Arab_SN -> wo
2497     private Set<String> CLDRLanguageCodes = new TreeSet<>();
2498     private Set<String> CLDRScriptCodes;
2499 
2500     /**
2501      * Get the population data for a language. Warning: if the language has script variants, cycle
2502      * on those variants.
2503      *
2504      * @param language
2505      * @return
2506      */
2507     public PopulationData getLanguagePopulationData(String language) {
2508         return languageToPopulation.get(language);
2509     }
2510 
2511     public PopulationData getBaseLanguagePopulationData(String language) {
2512         return baseLanguageToPopulation.get(language);
2513     }
2514 
2515     public Set<String> getLanguages() {
2516         return allLanguages;
2517     }
2518 
2519     public Set<String> getTerritoryToLanguages(String territory) {
2520         Map<String, PopulationData> result = territoryToLanguageToPopulationData.get(territory);
2521         if (result == null) {
2522             return Collections.emptySet();
2523         }
2524         return result.keySet();
2525     }
2526 
2527     public PopulationData getLanguageAndTerritoryPopulationData(String language, String territory) {
2528         Map<String, PopulationData> result = territoryToLanguageToPopulationData.get(territory);
2529         if (result == null) {
2530             return null;
2531         }
2532         return result.get(language);
2533     }
2534 
2535     public Set<String> getTerritoriesWithPopulationData() {
2536         return territoryToLanguageToPopulationData.keySet();
2537     }
2538 
2539     public Set<String> getLanguagesForTerritoryWithPopulationData(String territory) {
2540         Map<String, PopulationData> languageToPopulationMap =
2541                 territoryToLanguageToPopulationData.get(territory);
2542         return languageToPopulationMap == null
2543                 ? Collections.emptySet()
2544                 : languageToPopulationMap.keySet();
2545     }
2546 
2547     public Set<BasicLanguageData> getBasicLanguageData(String language) {
2548         Map<Type, BasicLanguageData> map = languageToBasicLanguageData.get(language);
2549         if (map == null) {
2550             throw new IllegalArgumentException("Bad language code: " + language);
2551         }
2552         return new LinkedHashSet<>(map.values());
2553     }
2554 
2555     public Map<Type, BasicLanguageData> getBasicLanguageDataMap(String language) {
2556         return languageToBasicLanguageData.get(language);
2557     }
2558 
2559     public Set<String> getBasicLanguageDataLanguages() {
2560         return languageToBasicLanguageData.keySet();
2561     }
2562 
2563     public Relation<String, String> getContainmentCore() {
2564         return containmentCore;
2565     }
2566 
2567     public Set<String> getContained(String territoryCode) {
2568         return containment.getAll(territoryCode);
2569     }
2570 
2571     public Set<String> getContainers() {
2572         return containment.keySet();
2573     }
2574 
2575     public Set<String> getContainedSubdivisions(String territoryOrSubdivisionCode) {
2576         return containerToSubdivision.getAll(territoryOrSubdivisionCode);
2577     }
2578 
2579     public Set<String> getContainersForSubdivisions() {
2580         return containerToSubdivision.keySet();
2581     }
2582 
2583     public Relation<String, String> getTerritoryToContained() {
2584         return getTerritoryToContained(ContainmentStyle.all); // true
2585     }
2586 
2587     //    public Relation<String, String> getTerritoryToContained(boolean allowDeprecated) {
2588     //        return allowDeprecated ? containment : containmentNonDeprecated;
2589     //    }
2590     //
2591     public enum ContainmentStyle {
2592         all,
2593         core,
2594         grouping,
2595         deprecated
2596     }
2597 
2598     public Relation<String, String> getTerritoryToContained(ContainmentStyle containmentStyle) {
2599         switch (containmentStyle) {
2600             case all:
2601                 return containment;
2602             case core:
2603                 return containmentCore;
2604             case grouping:
2605                 return containmentGrouping;
2606             case deprecated:
2607                 return containmentDeprecated;
2608         }
2609         throw new IllegalArgumentException("internal error");
2610     }
2611 
2612     public Set<String> getSkippedElements() {
2613         return skippedElements;
2614     }
2615 
2616     public Set<String> getZone_aliases(String zone) {
2617         Set<String> result = zone_aliases.getAll(zone);
2618         if (result == null) {
2619             return Collections.emptySet();
2620         }
2621         return result;
2622     }
2623 
2624     public String getZone_territory(String zone) {
2625         return zone_territory.get(zone);
2626     }
2627 
2628     public Set<String> getCanonicalZones() {
2629         return zone_territory.keySet();
2630     }
2631 
2632     public Set<String> getTerritoriesForPopulationData(String language) {
2633         return languageToTerritories.getAll(language);
2634     }
2635 
2636     public Set<String> getLanguagesForTerritoriesPopulationData() {
2637         return languageToTerritories.keySet();
2638     }
2639 
2640     /**
2641      * Return the list of default content locales.
2642      *
2643      * @return
2644      */
2645     public Set<String> getDefaultContentLocales() {
2646         return defaultContentLocales;
2647     }
2648 
2649     public static Map<String, String> makeLocaleToDefaultContents(
2650             Set<String> defaultContents, Map<String, String> result, Set<String> errors) {
2651         for (String s : defaultContents) {
2652             String simpleParent = LanguageTagParser.getSimpleParent(s);
2653             String oldValue = result.get(simpleParent);
2654             if (oldValue != null) {
2655                 errors.add(
2656                         "*** Error: Default contents cannot contain two children for the same parent:\t"
2657                                 + oldValue
2658                                 + ", "
2659                                 + s
2660                                 + "; keeping "
2661                                 + oldValue);
2662                 continue;
2663             }
2664             result.put(simpleParent, s);
2665         }
2666         return result;
2667     }
2668 
2669     /**
2670      * Return the list of default content locales.
2671      *
2672      * @return
2673      */
2674     public Set<CLDRLocale> getDefaultContentCLDRLocales() {
2675         initCLDRLocaleBasedData();
2676         return defaultContentToBase.keySet();
2677     }
2678 
2679     /**
2680      * Get the default content locale for a specified language
2681      *
2682      * @param language language to search
2683      * @return default content, or null if none
2684      */
2685     public String getDefaultContentLocale(String locale) {
2686         CLDRLocale cLocale = CLDRLocale.getInstance(locale);
2687         for (String dc : defaultContentLocales) {
2688             if (CLDRLocale.getInstance(dc).getParent() == cLocale) {
2689                 return dc;
2690             }
2691         }
2692         return null;
2693     }
2694 
2695     /**
2696      * Get the default content locale for a specified language and script. If script is null,
2697      * delegates to {@link #getDefaultContentLocale(String)}
2698      *
2699      * @param language
2700      * @param script if null, delegates to {@link #getDefaultContentLocale(String)}
2701      * @return default content, or null if none
2702      */
2703     public String getDefaultContentLocale(String language, String script) {
2704         if (script == null) return getDefaultContentLocale(language);
2705         for (String dc : defaultContentLocales) {
2706             if (dc.startsWith(language + "_" + script + "_")) {
2707                 return dc;
2708             }
2709         }
2710         return null;
2711     }
2712 
2713     /**
2714      * Given a default locale (such as 'wo_Arab_SN') return the base locale (such as 'wo'), or null
2715      * if the input wasn't a default conetnt locale.
2716      *
2717      * @param dcLocale
2718      * @return
2719      */
2720     public CLDRLocale getBaseFromDefaultContent(CLDRLocale dcLocale) {
2721         initCLDRLocaleBasedData();
2722         return defaultContentToBase.get(dcLocale);
2723     }
2724 
2725     /**
2726      * Given a base locale (such as 'wo') return the default content locale (such as 'wo_Arab_SN'),
2727      * or null.
2728      *
2729      * @param baseLocale
2730      * @return
2731      */
2732     public CLDRLocale getDefaultContentFromBase(CLDRLocale baseLocale) {
2733         initCLDRLocaleBasedData();
2734         return baseToDefaultContent.get(baseLocale);
2735     }
2736 
2737     /**
2738      * Is this a default content locale?
2739      *
2740      * @param dcLocale
2741      * @return
2742      */
2743     public boolean isDefaultContent(CLDRLocale dcLocale) {
2744         initCLDRLocaleBasedData();
2745         if (dcLocale == null) throw new NullPointerException("null locale");
2746         return (defaultContentToBase.get(dcLocale) != null);
2747     }
2748 
2749     public Set<String> getNumberingSystems() {
2750         return numberingSystems.keySet();
2751     }
2752 
2753     public Set<String> getNumericNumberingSystems() {
2754         return Collections.unmodifiableSet(numericSystems);
2755     }
2756 
2757     public String getDigits(String numberingSystem) {
2758         try {
2759             return numberingSystems.get(numberingSystem).digits;
2760         } catch (Exception e) {
2761             throw new IllegalArgumentException("Can't get digits for:" + numberingSystem);
2762         }
2763     }
2764 
2765     public NumberingSystemType getNumberingSystemType(String numberingSystem) {
2766         return numberingSystems.get(numberingSystem).type;
2767     }
2768 
2769     public Set<CoverageLevelInfo> getCoverageLevelInfo() {
2770         return coverageLevels;
2771     }
2772 
2773     /**
2774      * Used to get the coverage value for a path. This is generally the most efficient way for tools
2775      * to get coverage.
2776      *
2777      * @param xpath
2778      * @param loc
2779      * @return
2780      */
2781     public Level getCoverageLevel(String xpath, String loc) {
2782         Level result = null;
2783         result = coverageCache.get(xpath, loc);
2784         if (result == null) {
2785             CoverageLevel2 cov = localeToCoverageLevelInfo.get(loc);
2786             if (cov == null) {
2787                 cov = CoverageLevel2.getInstance(this, loc);
2788                 localeToCoverageLevelInfo.put(loc, cov);
2789             }
2790 
2791             result = cov.getLevel(xpath);
2792             coverageCache.put(xpath, loc, result);
2793         }
2794         return result;
2795     }
2796 
2797     /**
2798      * Cache Data structure with object expiry, List that can hold up to MAX_LOCALES caches of
2799      * locales, when one locale hasn't been used for a while it will removed and GC'd
2800      */
2801     private class CoverageCache {
2802         private final Deque<Node> localeList = new LinkedList<>();
2803         private final int MAX_LOCALES = 10;
2804 
2805         /** Object to sync on for modifying the locale list */
2806         private final Object LOCALE_LIST_ITER_SYNC = new Object();
2807 
2808         /*
2809          * constructor
2810          */
2811         public CoverageCache() {
2812             //            localeList = new LinkedList<Node>();
2813         }
2814 
2815         /*
2816          * retrieves coverage level associated with two keys if it exists in the cache, otherwise returns null
2817          * @param xpath
2818          * @param loc
2819          * @return the coverage level of the above two keys
2820          */
2821         public Level get(String xpath, String loc) {
2822             synchronized (LOCALE_LIST_ITER_SYNC) {
2823                 Iterator<Node> it = localeList.iterator();
2824                 Node reAddNode = null;
2825                 while (it.hasNext()) {
2826                     //            for (Iterator<Node> it = localeList.iterator(); it.hasNext();) {
2827                     Node node = it.next();
2828                     if (node.loc.equals(loc)) {
2829                         reAddNode = node;
2830                         it.remove();
2831                         break;
2832                     }
2833                 }
2834                 if (reAddNode != null) {
2835                     localeList.addFirst(reAddNode);
2836                     return reAddNode.map.get(xpath);
2837                 }
2838                 return null;
2839             }
2840         }
2841 
2842         /*
2843          * places a coverage level into the cache, with two keys
2844          * @param xpath
2845          * @param loc
2846          * @param covLevel    the coverage level of the above two keys
2847          */
2848         public void put(String xpath, String loc, Level covLevel) {
2849             synchronized (LOCALE_LIST_ITER_SYNC) {
2850                 // if locale's map is already in the cache add to it
2851                 //            for (Iterator<Node> it = localeList.iterator(); it.hasNext();) {
2852                 for (Node node : localeList) {
2853                     //                Node node = it.next();
2854                     if (node.loc.equals(loc)) {
2855                         node.map.put(xpath, covLevel);
2856                         return;
2857                     }
2858                 }
2859 
2860                 // if it is not, add a new map with the coverage level, and remove the last map in
2861                 // the list (used most seldom) if the list is too large
2862                 Map<String, Level> newMap = new ConcurrentHashMap<>();
2863                 newMap.put(xpath, covLevel);
2864                 localeList.addFirst(new Node(loc, newMap));
2865 
2866                 if (localeList.size() > MAX_LOCALES) {
2867                     localeList.removeLast();
2868                 }
2869             }
2870         }
2871 
2872         /*
2873          * node to hold a location and a Map
2874          */
2875         private class Node {
2876             // public fields to emulate a C/C++ struct
2877             public String loc;
2878             public Map<String, Level> map;
2879 
2880             public Node(String _loc, Map<String, Level> _map) {
2881                 loc = _loc;
2882                 map = _map;
2883             }
2884         }
2885     }
2886 
2887     /**
2888      * Used to get the coverage value for a path. Note, it is more efficient to create a
2889      * CoverageLevel2 for a language, and keep it around.
2890      *
2891      * @param xpath
2892      * @param loc
2893      * @return
2894      */
2895     public int getCoverageValue(String xpath, String loc) {
2896         return getCoverageLevel(xpath, loc).getLevel();
2897     }
2898 
2899     private RegexLookup<Level> coverageLookup = null;
2900 
2901     public synchronized RegexLookup<Level> getCoverageLookup() {
2902         if (coverageLookup == null) {
2903             RegexLookup<Level> lookup =
2904                     new RegexLookup<>(RegexLookup.LookupType.STAR_PATTERN_LOOKUP);
2905 
2906             Matcher variable = PatternCache.get("\\$\\{[A-Za-z][\\-A-Za-z]*\\}").matcher("");
2907 
2908             for (CoverageLevelInfo ci : getCoverageLevelInfo()) {
2909                 String pattern =
2910                         ci.match
2911                                 .replace('\'', '"')
2912                                 .replace("[@", "\\[@") // make sure that attributes are quoted
2913                                 .replace("(", "(?:") // make sure that there are no capturing groups
2914                                 // (beyond what we generate
2915                                 .replace("(?:?!", "(?!"); // Allow negative lookahead
2916                 pattern = "^//ldml/" + pattern + "$"; // for now, force a complete match
2917                 String variableType = null;
2918                 variable.reset(pattern);
2919                 if (variable.find()) {
2920                     pattern =
2921                             pattern.substring(0, variable.start())
2922                                     + "([^\"]*)"
2923                                     + pattern.substring(variable.end());
2924                     variableType = variable.group();
2925                     if (variable.find()) {
2926                         throw new IllegalArgumentException(
2927                                 "We can only handle a single variable on a line");
2928                     }
2929                 }
2930 
2931                 // .replaceAll("\\]","\\\\]");
2932                 lookup.add(new CoverageLevel2.MyRegexFinder(pattern, variableType, ci), ci.value);
2933             }
2934             coverageLookup = lookup;
2935         }
2936         return coverageLookup;
2937     }
2938 
2939     /**
2940      * Older version of code.
2941      *
2942      * @param xpath
2943      * @param loc
2944      * @return
2945      */
2946     public int getCoverageValueOld(String xpath, ULocale loc) {
2947         String targetLanguage = loc.getLanguage();
2948 
2949         CoverageVariableInfo cvi = getCoverageVariableInfo(targetLanguage);
2950         String targetScriptString = toRegexString(cvi.targetScripts);
2951         String targetTerritoryString = toRegexString(cvi.targetTerritories);
2952         String calendarListString = toRegexString(cvi.calendars);
2953         String targetCurrencyString = toRegexString(cvi.targetCurrencies);
2954         String targetTimeZoneString = toRegexString(cvi.targetTimeZones);
2955         String targetPluralsString = toRegexString(cvi.targetPlurals);
2956         Iterator<CoverageLevelInfo> i = coverageLevels.iterator();
2957         while (i.hasNext()) {
2958             CoverageLevelInfo ci = i.next();
2959             String regex =
2960                     "//ldml/"
2961                             + ci.match
2962                                     .replace('\'', '"')
2963                                     .replaceAll("\\[", "\\\\[")
2964                                     .replaceAll("\\]", "\\\\]")
2965                                     .replace("${Target-Language}", targetLanguage)
2966                                     .replace("${Target-Scripts}", targetScriptString)
2967                                     .replace("${Target-Territories}", targetTerritoryString)
2968                                     .replace("${Target-TimeZones}", targetTimeZoneString)
2969                                     .replace("${Target-Currencies}", targetCurrencyString)
2970                                     .replace("${Target-Plurals}", targetPluralsString)
2971                                     .replace("${Calendar-List}", calendarListString);
2972 
2973             // Special logic added for coverage fields that are only to be applicable
2974             // to certain territories
2975             if (ci.inTerritory != null) {
2976                 if (ci.inTerritory.equals("EU")) {
2977                     Set<String> containedTerritories = new HashSet<>();
2978                     containedTerritories.addAll(getContained(ci.inTerritory));
2979                     containedTerritories.retainAll(cvi.targetTerritories);
2980                     if (containedTerritories.isEmpty()) {
2981                         continue;
2982                     }
2983                 } else {
2984                     if (!cvi.targetTerritories.contains(ci.inTerritory)) {
2985                         continue;
2986                     }
2987                 }
2988             }
2989             // Special logic added for coverage fields that are only to be applicable
2990             // to certain languages
2991             if (ci.inLanguage != null && !ci.inLanguage.matcher(targetLanguage).matches()) {
2992                 continue;
2993             }
2994 
2995             // Special logic added for coverage fields that are only to be applicable
2996             // to certain scripts
2997             if (ci.inScript != null && !cvi.targetScripts.contains(ci.inScript)) {
2998                 continue;
2999             }
3000 
3001             if (xpath.matches(regex)) {
3002                 return ci.value.getLevel();
3003             }
3004 
3005             if (xpath.matches(regex)) {
3006                 return ci.value.getLevel();
3007             }
3008         }
3009         return Level.OPTIONAL.getLevel(); // If no match then return highest possible value
3010     }
3011 
3012     // The following is for mapping language to the explicit script and region codes in
3013     // the locale IDs for CLDR locales. We don't need to worry about the unmarked default
3014     // script or region since they are already supplied by the <languageData>.
3015     private Map<String, BasicLanguageData> doMapLanguagesToScriptsRegion() {
3016         Map<String, BasicLanguageData> langToScriptsRegions = new TreeMap<>();
3017         org.unicode.cldr.util.Factory factory = CLDRConfig.getInstance().getCldrFactory();
3018         for (CLDRLocale locale : factory.getAvailableCLDRLocales()) {
3019             String language = locale.getLanguage();
3020             if (language.length() == 0 || language.equals(LocaleNames.ROOT)) {
3021                 continue;
3022             }
3023             BasicLanguageData scriptsAndRegions = langToScriptsRegions.get(language);
3024             if (scriptsAndRegions == null) {
3025                 scriptsAndRegions = new BasicLanguageData();
3026                 langToScriptsRegions.put(language, scriptsAndRegions);
3027             }
3028             String script = locale.getScript();
3029             if (script.length() > 0) {
3030                 scriptsAndRegions.addScript(script);
3031             }
3032             String region = locale.getCountry();
3033             if (region.length() > 0
3034                     && region.length() < 3) { // per CLDR TC, do not want 001, 419 etc.
3035                 scriptsAndRegions.addTerritory(region);
3036             }
3037         }
3038         for (String language : langToScriptsRegions.keySet()) {
3039             BasicLanguageData scriptsAndRegions = langToScriptsRegions.get(language);
3040             langToScriptsRegions.put(language, scriptsAndRegions.freeze());
3041         }
3042         return Collections.unmodifiableMap(langToScriptsRegions);
3043     }
3044 
3045     private static Map<String, BasicLanguageData> languageToScriptsAndRegions = null;
3046 
3047     private synchronized Map<String, BasicLanguageData> getLanguageToScriptsAndRegions() {
3048         if (languageToScriptsAndRegions == null) {
3049             languageToScriptsAndRegions = doMapLanguagesToScriptsRegion();
3050         }
3051         return languageToScriptsAndRegions;
3052     }
3053 
3054     public CoverageVariableInfo getCoverageVariableInfo(String targetLanguage) {
3055         CoverageVariableInfo cvi;
3056         if (localeSpecificVariables.containsKey(targetLanguage)) {
3057             cvi = localeSpecificVariables.get(targetLanguage);
3058         } else {
3059             cvi = new CoverageVariableInfo();
3060             cvi.targetScripts = getTargetScripts(targetLanguage);
3061             cvi.targetTerritories = getTargetTerritories(targetLanguage);
3062             cvi.calendars = getCalendars(cvi.targetTerritories);
3063             cvi.targetCurrencies = getCurrentCurrencies(cvi.targetTerritories);
3064             cvi.targetTimeZones = getCurrentTimeZones(cvi.targetTerritories);
3065             cvi.targetPlurals = getTargetPlurals(targetLanguage);
3066             localeSpecificVariables.put(targetLanguage, cvi);
3067         }
3068         return cvi;
3069     }
3070 
3071     private Set<String> getTargetScripts(String language) {
3072         Set<String> targetScripts = new HashSet<>();
3073         try {
3074             Set<BasicLanguageData> langData = getBasicLanguageData(language);
3075             Iterator<BasicLanguageData> ldi = langData.iterator();
3076             while (ldi.hasNext()) {
3077                 BasicLanguageData bl = ldi.next();
3078                 Set<String> addScripts = bl.scripts;
3079                 if (addScripts != null && bl.getType() != BasicLanguageData.Type.secondary) {
3080                     targetScripts.addAll(addScripts);
3081                 }
3082             }
3083             Map<String, BasicLanguageData> languageToScriptsAndRegions =
3084                     getLanguageToScriptsAndRegions();
3085             if (languageToScriptsAndRegions != null) {
3086                 BasicLanguageData scriptsAndRegions = languageToScriptsAndRegions.get(language);
3087                 if (scriptsAndRegions != null) {
3088                     targetScripts.addAll(scriptsAndRegions.getScripts());
3089                 }
3090             }
3091         } catch (Exception e) {
3092             // fall through
3093         }
3094 
3095         if (targetScripts.size() == 0) {
3096             targetScripts.add("Zzzz"); // Unknown Script
3097         }
3098         return targetScripts;
3099     }
3100 
3101     private Set<String> getTargetTerritories(String language) {
3102         Set<String> targetTerritories = new HashSet<>();
3103         Set<String> secondaryTerritories = new HashSet<>();
3104         try {
3105             Set<BasicLanguageData> langData = getBasicLanguageData(language);
3106             Iterator<BasicLanguageData> ldi = langData.iterator();
3107             while (ldi.hasNext()) {
3108                 BasicLanguageData bl = ldi.next();
3109                 Set<String> addTerritories = bl.territories;
3110                 if (addTerritories != null) {
3111                     if (bl.getType() == BasicLanguageData.Type.secondary) {
3112                         secondaryTerritories.addAll(addTerritories);
3113                     } else {
3114                         targetTerritories.addAll(addTerritories);
3115                     }
3116                 }
3117             }
3118             Map<String, BasicLanguageData> languageToScriptsAndRegions =
3119                     getLanguageToScriptsAndRegions();
3120             if (languageToScriptsAndRegions != null) {
3121                 BasicLanguageData scriptsAndRegions = languageToScriptsAndRegions.get(language);
3122                 if (scriptsAndRegions != null) {
3123                     targetTerritories.addAll(scriptsAndRegions.getTerritories());
3124                 }
3125             }
3126         } catch (Exception e) {
3127             // fall through
3128         }
3129         if (targetTerritories.size() == 0) {
3130             getFallbackTargetTerritories(language, targetTerritories, secondaryTerritories);
3131         }
3132         return targetTerritories;
3133     }
3134 
3135     private void getFallbackTargetTerritories(
3136             String language, Set<String> targetTerritories, Set<String> secondaryTerritories) {
3137         String region = null;
3138         String maximized = new LikelySubtags().maximize(language);
3139         if (maximized != null) {
3140             CLDRLocale cldrLocale = CLDRLocale.getInstance(maximized);
3141             region = cldrLocale.getCountry();
3142         }
3143         if (region != null) {
3144             targetTerritories.add(region);
3145         } else if (secondaryTerritories.size() > 0) {
3146             targetTerritories.addAll(secondaryTerritories);
3147         } else {
3148             targetTerritories.add("ZZ");
3149         }
3150     }
3151 
3152     private Set<String> getCalendars(Set<String> territories) {
3153         Set<String> targetCalendars = new HashSet<>();
3154         Iterator<String> it = territories.iterator();
3155         while (it.hasNext()) {
3156             List<String> addCalendars = getCalendars(it.next());
3157             if (addCalendars == null) {
3158                 continue;
3159             }
3160             targetCalendars.addAll(addCalendars);
3161         }
3162         return targetCalendars;
3163     }
3164 
3165     /**
3166      * @param territory
3167      * @return a list the calendars used in the specified territorys
3168      */
3169     public List<String> getCalendars(String territory) {
3170         return calendarPreferences.get(territory);
3171     }
3172 
3173     private Set<String> getCurrentCurrencies(Set<String> territories) {
3174         Date now = new Date();
3175         return getCurrentCurrencies(territories, now, now);
3176     }
3177 
3178     public Set<String> getCurrentCurrencies(
3179             Set<String> territories, Date startsBefore, Date endsAfter) {
3180         Set<String> targetCurrencies = new HashSet<>();
3181         Iterator<String> it = territories.iterator();
3182         while (it.hasNext()) {
3183             Set<CurrencyDateInfo> targetCurrencyInfo = getCurrencyDateInfo(it.next());
3184             if (targetCurrencyInfo == null) {
3185                 continue;
3186             }
3187             Iterator<CurrencyDateInfo> it2 = targetCurrencyInfo.iterator();
3188             while (it2.hasNext()) {
3189                 CurrencyDateInfo cdi = it2.next();
3190                 if (cdi.getStart().before(startsBefore)
3191                         && cdi.getEnd().after(endsAfter)
3192                         && cdi.isLegalTender()) {
3193                     targetCurrencies.add(cdi.getCurrency());
3194                 }
3195             }
3196         }
3197         return targetCurrencies;
3198     }
3199 
3200     private Set<String> getCurrentTimeZones(Set<String> territories) {
3201         Set<String> targetTimeZones = new HashSet<>();
3202         Iterator<String> it = territories.iterator();
3203         while (it.hasNext()) {
3204             String[] countryIDs = TimeZone.getAvailableIDs(it.next());
3205             for (int i = 0; i < countryIDs.length; i++) {
3206                 targetTimeZones.add(countryIDs[i]);
3207             }
3208         }
3209         return targetTimeZones;
3210     }
3211 
3212     private Set<String> getTargetPlurals(String language) {
3213         Set<String> targetPlurals = new HashSet<>();
3214         targetPlurals.addAll(getPlurals(PluralType.cardinal, language).getCanonicalKeywords());
3215         // TODO: Kept 0 and 1 specifically until Mark figures out what to do with them.
3216         // They should be removed once this is done.
3217         targetPlurals.add("0");
3218         targetPlurals.add("1");
3219         return targetPlurals;
3220     }
3221 
3222     public enum ParentLocaleComponent {
3223         main,
3224         collations,
3225         segmentations,
3226         grammaticalFeatures,
3227         plurals;
3228 
3229         public static ParentLocaleComponent fromString(String s) {
3230             return s == null
3231                     ? ParentLocaleComponent.main // handle empty
3232                     : ParentLocaleComponent.valueOf(s);
3233         }
3234     }
3235 
3236     public boolean parentLocalesSkipNonLikely(ParentLocaleComponent component) {
3237         return parentLocalesSkipNonLikely.contains(component);
3238     }
3239 
3240     public String getExplicitParentLocale(String loc, ParentLocaleComponent component) {
3241         return parentLocales.get(component).get(loc);
3242     }
3243 
3244     //  These are not (now) used by the current code.
3245     //  They should not be used, because the answer is incorrect for parentLocalesSkipNonLikely
3246     //
3247     //    public String getExplicitParentLocale(String loc) {
3248     //        return getExplicitParentLocale(loc, ParentLocaleComponent.main);
3249     //    }
3250     //
3251 
3252     //
3253     //    public Set<String> getExplicitChildren() {
3254     //        return getExplicitChildren(ParentLocaleComponent.main);
3255     //    }
3256     //
3257     //    public Set<String> getExplicitChildren(ParentLocaleComponent component) {
3258     //        return parentLocales.get(component).keySet();
3259     //    }
3260 
3261     public Collection<String> getExplicitParents() {
3262         return getExplicitParents(ParentLocaleComponent.main);
3263     }
3264 
3265     public Collection<String> getExplicitParents(ParentLocaleComponent component) {
3266         return parentLocales.get(component).values();
3267     }
3268 
3269     public static final class ApprovalRequirementMatcher {
3270         @Override
3271         public String toString() {
3272             return locales + " / " + xpathMatcher + " = " + requiredVotes;
3273         }
3274 
3275         ApprovalRequirementMatcher(String xpath) {
3276             XPathParts parts = XPathParts.getFrozenInstance(xpath);
3277             if (parts.containsElement("approvalRequirement")) {
3278                 requiredVotes = getRequiredVotes(parts);
3279                 String localeAttrib = parts.getAttributeValue(-1, "locales");
3280                 if (localeAttrib == null || localeAttrib.equals(STAR) || localeAttrib.isEmpty()) {
3281                     locales = null; // no locale listed == '*'
3282                 } else {
3283                     Set<CLDRLocale> localeList = new HashSet<>();
3284                     String[] el = localeAttrib.split(" ");
3285                     for (int i = 0; i < el.length; i++) {
3286                         if (el[i].indexOf(":") == -1) { // Just a simple locale designation
3287                             localeList.add(CLDRLocale.getInstance(el[i]));
3288                         } else { // Org:CoverageLevel
3289                             String[] coverageLocaleParts = el[i].split(":", 2);
3290                             String org = coverageLocaleParts[0];
3291                             String level = coverageLocaleParts[1].toUpperCase();
3292                             Set<String> coverageLocales =
3293                                     sc.getLocaleCoverageLocales(
3294                                             Organization.fromString(org),
3295                                             EnumSet.of(Level.fromString(level)));
3296                             for (String cl : coverageLocales) {
3297                                 localeList.add(CLDRLocale.getInstance(cl));
3298                             }
3299                         }
3300                     }
3301                     locales = Collections.unmodifiableSet(localeList);
3302                 }
3303                 String xpathMatch = parts.getAttributeValue(-1, "paths");
3304                 if (xpathMatch == null || xpathMatch.isEmpty() || xpathMatch.equals(STAR)) {
3305                     xpathMatcher = null;
3306                 } else {
3307                     xpathMatcher = PatternCache.get(xpathMatch);
3308                 }
3309             } else {
3310                 throw new RuntimeException("Unknown approval requirement: " + xpath);
3311             }
3312         }
3313 
3314         static int getRequiredVotes(XPathParts parts) {
3315             String votesStr = parts.getAttributeValue(-1, "votes");
3316             if (votesStr.charAt(0) == '=') {
3317                 votesStr = votesStr.substring(1);
3318                 if (votesStr.equals("HIGH_BAR")) {
3319                     return VoteResolver.HIGH_BAR;
3320                 } else if (votesStr.equals("LOWER_BAR")) {
3321                     return VoteResolver.LOWER_BAR;
3322                 }
3323                 final VoteResolver.Level l = VoteResolver.Level.valueOf(votesStr);
3324                 return l.getVotes(Organization.unaffiliated); // use non-TC vote count
3325             } else {
3326                 return Integer.parseInt(votesStr);
3327             }
3328         }
3329 
3330         private final Set<CLDRLocale> locales;
3331         private final Pattern xpathMatcher;
3332         final int requiredVotes;
3333 
3334         public static List<ApprovalRequirementMatcher> buildAll(List<String> approvalRequirements) {
3335             List<ApprovalRequirementMatcher> newList = new LinkedList<>();
3336 
3337             for (String xpath : approvalRequirements) {
3338                 newList.add(new ApprovalRequirementMatcher(xpath));
3339             }
3340 
3341             return Collections.unmodifiableList(newList);
3342         }
3343 
3344         public boolean matches(CLDRLocale loc, PathHeader ph) {
3345             if (DEBUG) System.err.println(">> testing " + loc + " / " + ph + " vs " + toString());
3346             if (locales != null) {
3347                 if (!locales.contains(loc)) {
3348                     return false;
3349                 }
3350             }
3351             if (xpathMatcher != null) {
3352                 if (ph != null) {
3353                     if (!xpathMatcher.matcher(ph.getOriginalPath()).matches()) {
3354                         return false;
3355                     } else {
3356                         return true;
3357                     }
3358                 } else {
3359                     return false;
3360                 }
3361             }
3362             return true;
3363         }
3364 
3365         public int getRequiredVotes() {
3366             return requiredVotes;
3367         }
3368     }
3369 
3370     // run these from first to last to get the approval info.
3371     volatile List<ApprovalRequirementMatcher> approvalMatchers = null;
3372 
3373     /**
3374      * Get the preliminary number of required votes based on the given locale and PathHeader
3375      *
3376      * <p>Important: this number may not agree with VoteResolver.getRequiredVotes since VoteResolver
3377      * also takes the baseline status into account.
3378      *
3379      * <p>Called by VoteResolver, ShowStarredCoverage, TestCoverage, and TestCoverageLevel.
3380      *
3381      * @param loc the CLDRLocale
3382      * @param ph the PathHeader - which path this is applied to, or null if unknown.
3383      * @return a number such as 4 or 8
3384      */
3385     public int getRequiredVotes(CLDRLocale loc, PathHeader ph) {
3386         if (approvalMatchers == null) {
3387             approvalMatchers = ApprovalRequirementMatcher.buildAll(approvalRequirements);
3388         }
3389 
3390         for (ApprovalRequirementMatcher m : approvalMatchers) {
3391             if (m.matches(loc, ph)) {
3392                 return m.getRequiredVotes();
3393             }
3394         }
3395         throw new RuntimeException(
3396                 "Error: " + loc + " " + ph + " ran off the end of the approvalMatchers.");
3397     }
3398 
3399     /**
3400      * Return the canonicalized zone, or null if there is none.
3401      *
3402      * @param alias
3403      * @return
3404      */
3405     public String getZoneFromAlias(String alias) {
3406         String zone = alias_zone.get(alias);
3407         if (zone != null) return zone;
3408         if (zone_territory.get(alias) != null) return alias;
3409         return null;
3410     }
3411 
3412     public boolean isCanonicalZone(String alias) {
3413         return zone_territory.get(alias) != null;
3414     }
3415 
3416     /**
3417      * Return the approximate economic weight of this language, computed by taking all of the
3418      * languages in each territory, looking at the literate population and dividing up the GDP of
3419      * the territory (in PPP) according to the proportion that language has of the total. This is
3420      * only an approximation, since the language information is not complete, languages may overlap
3421      * (bilingual speakers), the literacy figures may be estimated, and literacy is only a rough
3422      * proxy for weight of each language in the economy of the territory.
3423      *
3424      * @param targetLanguage
3425      * @return
3426      */
3427     public double getApproximateEconomicWeight(String targetLanguage) {
3428         double weight = 0;
3429         Set<String> territories = getTerritoriesForPopulationData(targetLanguage);
3430         if (territories == null) return weight;
3431         for (String territory : territories) {
3432             Set<String> languagesInTerritory = getTerritoryToLanguages(territory);
3433             double totalLiteratePopulation = 0;
3434             double targetLiteratePopulation = 0;
3435             for (String language : languagesInTerritory) {
3436                 PopulationData populationData =
3437                         getLanguageAndTerritoryPopulationData(language, territory);
3438                 totalLiteratePopulation += populationData.getLiteratePopulation();
3439                 if (language.equals(targetLanguage)) {
3440                     targetLiteratePopulation = populationData.getLiteratePopulation();
3441                 }
3442             }
3443             PopulationData territoryPopulationData = getPopulationDataForTerritory(territory);
3444             final double gdp = territoryPopulationData.getGdp();
3445             final double scaledGdp = gdp * targetLiteratePopulation / totalLiteratePopulation;
3446             if (scaledGdp > 0) {
3447                 weight += scaledGdp;
3448             } else {
3449                 // System.out.println("?\t" + territory + "\t" + targetLanguage);
3450             }
3451         }
3452         return weight;
3453     }
3454 
3455     public PopulationData getPopulationDataForTerritory(String territory) {
3456         return territoryToPopulationData.get(territory);
3457     }
3458 
3459     public Set<String> getScriptVariantsForPopulationData(String language) {
3460         return languageToScriptVariants.getAll(language);
3461     }
3462 
3463     public Map<String, Pair<String, String>> getReferences() {
3464         return references;
3465     }
3466 
3467     public Map<String, Map<String, String>> getMetazoneToRegionToZone() {
3468         return typeToZoneToRegionToZone.get("metazones");
3469     }
3470 
3471     public String getZoneForMetazoneByRegion(String metazone, String region) {
3472         String result = null;
3473         if (getMetazoneToRegionToZone().containsKey(metazone)) {
3474             Map<String, String> myMap = getMetazoneToRegionToZone().get(metazone);
3475             if (myMap.containsKey(region)) {
3476                 result = myMap.get(region);
3477             } else {
3478                 result = myMap.get("001");
3479             }
3480         }
3481 
3482         if (result == null) {
3483             result = "Etc/GMT";
3484         }
3485 
3486         return result;
3487     }
3488 
3489     public Map<String, Map<String, Map<String, String>>> getTypeToZoneToRegionToZone() {
3490         return typeToZoneToRegionToZone;
3491     }
3492 
3493     /**
3494      * @deprecated, use PathHeader.getMetazonePageTerritory
3495      */
3496     public Map<String, String> getMetazoneToContinentMap() {
3497         return metazoneContinentMap;
3498     }
3499 
3500     public Set<String> getAllMetazones() {
3501         return allMetazones;
3502     }
3503 
3504     /**
3505      * Is the given metazone outdated?
3506      *
3507      * @param metazone the metazone such as "Liberia"
3508      * @param tzid the timezone id such as "Africa/Monrovia"
3509      * @param timeInMillis the time in milliseconds since 1970
3510      * @return true if the metazone is outdated
3511      */
3512     public boolean metazoneIsOutdated(String metazone, String tzid, long timeInMillis) {
3513         final MetaZoneRange range = getMetaZoneRange(tzid, timeInMillis);
3514         // For example, for metazone = "Liberia", if range.metazone = "GMT",
3515         // that implies "GMT" is current and "Liberia" is outdated
3516         if (range == null) {
3517             if (DEBUG) {
3518                 System.out.println(
3519                         "metazoneIsOutdated: " + metazone + "; tzid = " + tzid + "; range is null");
3520             }
3521             return true;
3522         }
3523         if (!metazone.equals(range.metazone)) {
3524             if (DEBUG) {
3525                 System.out.println(
3526                         "metazoneIsOutdated: "
3527                                 + metazone
3528                                 + "; tzid = "
3529                                 + tzid
3530                                 + "; range.metazone = "
3531                                 + range.metazone);
3532             }
3533             return true;
3534         }
3535         return false;
3536     }
3537 
3538     public Map<String, String> getLikelySubtags() {
3539         return likelySubtags;
3540     }
3541 
3542     public Map<String, String> getLikelyOrigins() {
3543         return likelyOrigins;
3544     }
3545 
3546     public enum PluralType {
3547         cardinal(PluralRules.PluralType.CARDINAL),
3548         ordinal(PluralRules.PluralType.ORDINAL);
3549 
3550         // add some gorp to interwork until we clean things up
3551 
3552         public final PluralRules.PluralType standardType;
3553 
3554         PluralType(PluralRules.PluralType standardType) {
3555             this.standardType = standardType;
3556         }
3557 
3558         public static PluralType fromStandardType(PluralRules.PluralType standardType) {
3559             return standardType == null
3560                     ? null
3561                     : standardType == PluralRules.PluralType.CARDINAL ? cardinal : ordinal;
3562         }
3563     }
3564 
3565     private EnumMap<PluralType, Map<String, PluralInfo>> localeToPluralInfo2 =
3566             new EnumMap<>(PluralType.class);
3567 
3568     {
3569         localeToPluralInfo2.put(PluralType.cardinal, new LinkedHashMap<String, PluralInfo>());
3570         localeToPluralInfo2.put(PluralType.ordinal, new LinkedHashMap<String, PluralInfo>());
3571     }
3572 
3573     private Map<String, PluralRanges> localeToPluralRanges = new LinkedHashMap<>();
3574 
3575     private Map<DayPeriodInfo.Type, Map<String, DayPeriodInfo>> typeToLocaleToDayPeriodInfo =
3576             new EnumMap<>(DayPeriodInfo.Type.class);
3577     private Map<String, CoverageLevel2> localeToCoverageLevelInfo = new ConcurrentHashMap<>();
3578     private CoverageCache coverageCache = new CoverageCache();
3579     private transient String lastPluralLocales = "";
3580     private transient PluralType lastPluralWasOrdinal = null;
3581     private transient Map<Count, String> lastPluralMap = new EnumMap<>(Count.class);
3582     private transient String lastDayPeriodLocales = null;
3583     private transient DayPeriodInfo.Type lastDayPeriodType = null;
3584     private transient DayPeriodInfo.Builder dayPeriodBuilder = new DayPeriodInfo.Builder();
3585 
3586     private void addDayPeriodPath(XPathParts path) {
3587         // ldml/dates/calendars/calendar[@type="gregorian"]/dayPeriods/dayPeriodContext[@type="format"]/dayPeriodWidth[@type="wide"]/dayPeriod[@type="am"]
3588         /*
3589          * <supplementalData>
3590          * <version number="$Revision$"/>
3591          * <generation date="$D..e... $"/>
3592          * <dayPeriodRuleSet>
3593          * <dayPeriodRules locales = "en"> <!-- default for any locales not listed under other dayPeriods -->
3594          * <dayPeriodRule type = "am" from = "0:00" before="12:00"/>
3595          * <dayPeriodRule type = "pm" from = "12:00" to="24:00"/>
3596          */
3597         String typeString = path.getAttributeValue(1, "type");
3598         String locales = path.getAttributeValue(2, "locales").trim();
3599         DayPeriodInfo.Type type =
3600                 typeString == null
3601                         ? DayPeriodInfo.Type.format
3602                         : DayPeriodInfo.Type.valueOf(typeString.trim());
3603         if (!locales.equals(lastDayPeriodLocales) || type != lastDayPeriodType) {
3604             if (lastDayPeriodLocales != null) {
3605                 addDayPeriodInfo();
3606             }
3607             lastDayPeriodLocales = locales;
3608             lastDayPeriodType = type;
3609             // System.out.println(type + ", " + locales + ", " + path);
3610         }
3611         if (path.size() != 4) {
3612             if (locales.equals(LocaleNames.ROOT)) return; // we allow root to be empty
3613             throw new IllegalArgumentException(locales + " must have dayPeriodRule elements");
3614         }
3615         DayPeriod dayPeriod;
3616         try {
3617             dayPeriod = DayPeriod.fromString(path.getAttributeValue(-1, "type"));
3618         } catch (Exception e) {
3619             System.err.println(e.getMessage());
3620             return;
3621         }
3622         String at = path.getAttributeValue(-1, "at");
3623         String from = path.getAttributeValue(-1, "from");
3624         String after = path.getAttributeValue(-1, "after");
3625         String to = path.getAttributeValue(-1, "to");
3626         String before = path.getAttributeValue(-1, "before");
3627         if (at != null) {
3628             if (from != null || after != null || to != null || before != null) {
3629                 throw new IllegalArgumentException();
3630             }
3631             from = at;
3632             to = at;
3633         } else if ((from == null) == (after == null) || (to == null) == (before == null)) {
3634             throw new IllegalArgumentException();
3635         }
3636         //        if (dayPeriodBuilder.contains(dayPeriod)) { // disallow multiple rules with same
3637         // dayperiod
3638         //            throw new IllegalArgumentException("Multiple rules with same dayperiod are
3639         // disallowed: "
3640         //                + lastDayPeriodLocales + ", " + lastDayPeriodType + ", " + dayPeriod);
3641         //        }
3642         boolean includesStart = from != null;
3643         boolean includesEnd = to != null;
3644         int start = parseTime(includesStart ? from : after);
3645         int end = parseTime(includesEnd ? to : before);
3646         // Check if any periods contain 0, e.g. 1700 - 300
3647         if (start > end) {
3648             // System.out.println("start " + start + " end " + end);
3649             dayPeriodBuilder.add(dayPeriod, start, includesStart, parseTime("24:00"), includesEnd);
3650             dayPeriodBuilder.add(dayPeriod, parseTime("0:00"), includesStart, end, includesEnd);
3651         } else {
3652             dayPeriodBuilder.add(dayPeriod, start, includesStart, end, includesEnd);
3653         }
3654     }
3655 
3656     static Pattern PARSE_TIME = PatternCache.get("(\\d\\d?):(\\d\\d)");
3657 
3658     private int parseTime(String string) {
3659         Matcher matcher = PARSE_TIME.matcher(string);
3660         if (!matcher.matches()) {
3661             throw new IllegalArgumentException();
3662         }
3663         return (Integer.parseInt(matcher.group(1)) * 60 + Integer.parseInt(matcher.group(2)))
3664                 * 60
3665                 * 1000;
3666     }
3667 
3668     private void addDayPeriodInfo() {
3669         String[] locales = lastDayPeriodLocales.split("\\s+");
3670         DayPeriodInfo temp = dayPeriodBuilder.finish(locales);
3671         Map<String, DayPeriodInfo> locale2DPI = typeToLocaleToDayPeriodInfo.get(lastDayPeriodType);
3672         if (locale2DPI == null) {
3673             typeToLocaleToDayPeriodInfo.put(lastDayPeriodType, locale2DPI = new LinkedHashMap<>());
3674             // System.out.println(lastDayPeriodType + ", " + locale2DPI);
3675         }
3676         for (String locale : locales) {
3677             locale2DPI.put(locale, temp);
3678         }
3679     }
3680 
3681     static String lastPluralRangesLocales = null;
3682     static PluralRanges lastPluralRanges = null;
3683 
3684     private boolean addPluralPath(XPathParts path, String value) {
3685         /*
3686         * Adding
3687         <pluralRanges locales="am">
3688          <pluralRange start="one" end="one" result="one" />
3689         </pluralRanges>
3690         */
3691         String locales = path.getAttributeValue(2, "locales").trim();
3692         String element = path.getElement(2);
3693         if ("pluralRanges".equals(element)) {
3694             if (!locales.equals(lastPluralRangesLocales)) {
3695                 addPluralRanges(locales);
3696             }
3697             if (path.size() == 3) {
3698                 // ok for ranges to be empty
3699                 return true;
3700             }
3701             String rangeStart = path.getAttributeValue(-1, "start");
3702             String rangeEnd = path.getAttributeValue(-1, "end");
3703             String result = path.getAttributeValue(-1, "result");
3704             lastPluralRanges.add(
3705                     rangeStart == null ? null : Count.valueOf(rangeStart),
3706                     rangeEnd == null ? null : Count.valueOf(rangeEnd),
3707                     Count.valueOf(result));
3708             return true;
3709         } else if ("pluralRules".equals(element)) {
3710 
3711             String type = path.getAttributeValue(1, "type");
3712             PluralType pluralType = type == null ? PluralType.cardinal : PluralType.valueOf(type);
3713             if (!lastPluralLocales.equals(locales)) {
3714                 addPluralInfo(pluralType);
3715                 lastPluralLocales = locales;
3716             }
3717             final String countString = path.getAttributeValue(-1, "count");
3718             if (countString == null) {
3719                 return false;
3720             }
3721             Count count = Count.valueOf(countString);
3722             if (lastPluralMap.containsKey(count)) {
3723                 throw new IllegalArgumentException(
3724                         "Duplicate plural count: " + count + " in " + locales);
3725             }
3726             lastPluralMap.put(count, value);
3727             lastPluralWasOrdinal = pluralType;
3728             return true;
3729         } else {
3730             return false;
3731         }
3732     }
3733 
3734     private void addPluralRanges(String localesString) {
3735         final String[] locales = localesString.split("\\s+");
3736         lastPluralRanges = new PluralRanges();
3737         for (String locale : locales) {
3738             if (localeToPluralRanges.containsKey(locale)) {
3739                 throw new IllegalArgumentException("Duplicate plural locale: " + locale);
3740             }
3741             localeToPluralRanges.put(locale, lastPluralRanges);
3742         }
3743         lastPluralRangesLocales = localesString;
3744     }
3745 
3746     private void addPluralInfo(PluralType pluralType) {
3747         final String[] locales = lastPluralLocales.split("\\s+");
3748         PluralInfo info = new PluralInfo(lastPluralMap, pluralType);
3749         Map<String, PluralInfo> localeToInfo = localeToPluralInfo2.get(pluralType);
3750         for (String locale : locales) {
3751             if (localeToInfo.containsKey(locale)) {
3752                 throw new IllegalArgumentException("Duplicate plural locale: " + locale);
3753             } else if (!locale.isEmpty()) {
3754                 localeToInfo.put(locale, info);
3755             }
3756         }
3757         lastPluralMap.clear();
3758     }
3759 
3760     public static class SampleList {
3761         public static final SampleList EMPTY = new SampleList().freeze();
3762 
3763         private UnicodeSet uset = new UnicodeSet();
3764         private List<FixedDecimal> fractions = new ArrayList<>(0);
3765 
3766         @Override
3767         public String toString() {
3768             return toString(6, 3);
3769         }
3770 
3771         public String toString(int intLimit, int fractionLimit) {
3772             StringBuilder b = new StringBuilder();
3773             int intCount = 0;
3774             int fractionCount = 0;
3775             int limit = uset.getRangeCount();
3776             for (int i = 0; i < limit; ++i) {
3777                 if (intCount >= intLimit) {
3778                     b.append(", …");
3779                     break;
3780                 }
3781                 if (b.length() != 0) {
3782                     b.append(", ");
3783                 }
3784                 int start = uset.getRangeStart(i);
3785                 int end = uset.getRangeEnd(i);
3786                 if (start == end) {
3787                     b.append(start);
3788                     ++intCount;
3789                 } else if (start + 1 == end) {
3790                     b.append(start).append(", ").append(end);
3791                     intCount += 2;
3792                 } else {
3793                     b.append(start).append('-').append(end);
3794                     intCount += 2;
3795                 }
3796             }
3797             if (fractions.size() > 0) {
3798                 for (int i = 0; i < fractions.size(); ++i) {
3799                     if (fractionCount >= fractionLimit) {
3800                         break;
3801                     }
3802                     if (b.length() != 0) {
3803                         b.append(", ");
3804                     }
3805                     FixedDecimal fraction = fractions.get(i);
3806                     String formatted =
3807                             String.format(
3808                                     Locale.ROOT,
3809                                     "%." + fraction.getVisibleDecimalDigitCount() + "f",
3810                                     fraction.getSource());
3811                     b.append(formatted);
3812                     ++fractionCount;
3813                 }
3814                 b.append(", …");
3815             }
3816             return b.toString();
3817         }
3818 
getRangeCount()3819         public int getRangeCount() {
3820             return uset.getRangeCount();
3821         }
3822 
getRangeStart(int index)3823         public int getRangeStart(int index) {
3824             return uset.getRangeStart(index);
3825         }
3826 
getRangeEnd(int index)3827         public int getRangeEnd(int index) {
3828             return uset.getRangeEnd(index);
3829         }
3830 
getFractions()3831         public List<FixedDecimal> getFractions() {
3832             return fractions;
3833         }
3834 
intSize()3835         public int intSize() {
3836             return uset.size();
3837         }
3838 
remove(int i)3839         public SampleList remove(int i) {
3840             uset.remove(i);
3841             return this;
3842         }
3843 
add(int i)3844         public SampleList add(int i) {
3845             uset.add(i);
3846             return this;
3847         }
3848 
freeze()3849         public SampleList freeze() {
3850             uset.freeze();
3851             if (fractions instanceof ArrayList) {
3852                 fractions = Collections.unmodifiableList(fractions);
3853             }
3854             return this;
3855         }
3856 
add(FixedDecimal i)3857         public void add(FixedDecimal i) {
3858             fractions.add(i);
3859         }
3860 
fractionSize()3861         public int fractionSize() {
3862             return fractions.size();
3863         }
3864     }
3865 
3866     public static class CountSampleList {
3867         private final Map<Count, SampleList> countToIntegerSamples9999;
3868         private final Map<Count, SampleList[]> countToDigitToIntegerSamples9999;
3869 
CountSampleList(PluralRules pluralRules, Set<Count> keywords, PluralType pluralType)3870         CountSampleList(PluralRules pluralRules, Set<Count> keywords, PluralType pluralType) {
3871             // Create the integer counts
3872             countToIntegerSamples9999 = new EnumMap<>(Count.class);
3873             countToDigitToIntegerSamples9999 = new EnumMap<>(Count.class);
3874             for (Count c : keywords) {
3875                 countToIntegerSamples9999.put(c, new SampleList());
3876                 SampleList[] row = new SampleList[5];
3877                 countToDigitToIntegerSamples9999.put(c, row);
3878                 for (int i = 1; i < 5; ++i) {
3879                     row[i] = new SampleList();
3880                 }
3881             }
3882             for (int ii = 0; ii < 10000; ++ii) {
3883                 int i = ii;
3884                 int digit;
3885                 if (i > 999) {
3886                     digit = 4;
3887                 } else if (i > 99) {
3888                     digit = 3;
3889                 } else if (i > 9) {
3890                     digit = 2;
3891                 } else {
3892                     digit = 1;
3893                 }
3894                 Count count = Count.valueOf(pluralRules.select(i));
3895                 addSimple(countToIntegerSamples9999, i, count);
3896                 addDigit(countToDigitToIntegerSamples9999, i, count, digit);
3897                 if (haveFractions(keywords, digit)) {
3898                     continue;
3899                 }
3900                 if (pluralType == PluralType.cardinal) {
3901                     for (int f = 0; f < 30; ++f) {
3902                         FixedDecimal ni = new FixedDecimal(i + f / 10.0d, f < 10 ? 1 : 2, f);
3903                         count = Count.valueOf(pluralRules.select(ni));
3904                         addSimple(countToIntegerSamples9999, ni, count);
3905                         addDigit(countToDigitToIntegerSamples9999, ni, count, digit);
3906                     }
3907                 }
3908             }
3909             // HACK for Breton
3910             addSimple(
3911                     countToIntegerSamples9999, 1000000, Count.valueOf(pluralRules.select(1000000)));
3912 
3913             for (Count count : keywords) {
3914                 SampleList uset = countToIntegerSamples9999.get(count);
3915                 uset.freeze();
3916                 SampleList[] map = countToDigitToIntegerSamples9999.get(count);
3917                 for (int i = 1; i < map.length; ++i) {
3918                     map[i].freeze();
3919                 }
3920             }
3921         }
3922 
haveFractions(Set<Count> keywords, int digit)3923         private boolean haveFractions(Set<Count> keywords, int digit) {
3924             for (Count c : keywords) {
3925                 int size = countToDigitToIntegerSamples9999.get(c)[digit].fractionSize();
3926                 if (size < MAX_COLLECTED_FRACTION) {
3927                     return false;
3928                 }
3929             }
3930             return true;
3931         }
3932 
3933         static final int MAX_COLLECTED_FRACTION = 5;
3934 
addDigit( Map<Count, SampleList[]> countToDigitToIntegerSamples9999, FixedDecimal i, Count count, int digit)3935         private boolean addDigit(
3936                 Map<Count, SampleList[]> countToDigitToIntegerSamples9999,
3937                 FixedDecimal i,
3938                 Count count,
3939                 int digit) {
3940             return addFraction(i, countToDigitToIntegerSamples9999.get(count)[digit]);
3941         }
3942 
addFraction(FixedDecimal i, SampleList sampleList)3943         private boolean addFraction(FixedDecimal i, SampleList sampleList) {
3944             if (sampleList.fractionSize() < MAX_COLLECTED_FRACTION) {
3945                 sampleList.add(i);
3946                 return true;
3947             } else {
3948                 return false;
3949             }
3950         }
3951 
addSimple( Map<Count, SampleList> countToIntegerSamples9999, FixedDecimal i, Count count)3952         private boolean addSimple(
3953                 Map<Count, SampleList> countToIntegerSamples9999, FixedDecimal i, Count count) {
3954             return addFraction(i, countToIntegerSamples9999.get(count));
3955         }
3956 
addDigit( Map<Count, SampleList[]> countToDigitToIntegerSamples9999, int i, Count count, int digit)3957         private void addDigit(
3958                 Map<Count, SampleList[]> countToDigitToIntegerSamples9999,
3959                 int i,
3960                 Count count,
3961                 int digit) {
3962             countToDigitToIntegerSamples9999.get(count)[digit].add(i);
3963         }
3964 
addSimple( Map<Count, SampleList> countToIntegerSamples9999, int i, Count count)3965         private void addSimple(
3966                 Map<Count, SampleList> countToIntegerSamples9999, int i, Count count) {
3967             countToIntegerSamples9999.get(count).add(i);
3968         }
3969 
get(Count type)3970         public SampleList get(Count type) {
3971             return countToIntegerSamples9999.get(type);
3972         }
3973 
get(Count c, int digit)3974         public SampleList get(Count c, int digit) {
3975             SampleList[] sampleLists = countToDigitToIntegerSamples9999.get(c);
3976             return sampleLists == null ? null : sampleLists[digit];
3977         }
3978     }
3979 
3980     /**
3981      * Immutable class with plural info for different locales
3982      *
3983      * @author markdavis
3984      */
3985     public static class PluralInfo implements Comparable<PluralInfo> {
3986         static final Set<Double> explicits = new HashSet<>();
3987 
3988         static {
3989             explicits.add(0.0d);
3990             explicits.add(1.0d);
3991         }
3992 
3993         public enum Count {
3994             zero,
3995             one,
3996             two,
3997             few,
3998             many,
3999             other;
4000             public static final int LENGTH = Count.values().length;
4001             public static final List<Count> VALUES =
4002                     Collections.unmodifiableList(Arrays.asList(values()));
4003         }
4004 
4005         static final Pattern pluralPaths = PatternCache.get(".*pluralRule.*");
4006         static final int fractDecrement = 13;
4007         static final int fractStart = 20;
4008 
4009         private final Map<Count, Set<Double>> countToExampleSet;
4010         private final Map<Count, String> countToStringExample;
4011         private final Map<Integer, Count> exampleToCount;
4012         private final PluralRules pluralRules;
4013         private final String pluralRulesString;
4014         private final Set<String> canonicalKeywords;
4015         private final Set<Count> keywords;
4016         private final Set<Count> integerKeywords;
4017         private final Set<Count> decimalKeywords;
4018         private final CountSampleList countSampleList;
4019         private final Map<Count, String> countToRule;
4020         private final Set<Count> adjustedCounts;
4021         private final Set<String> adjustedCountStrings;
4022 
4023         // e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5
4024         static final Pattern hasE = Pattern.compile("e\\s*!?=");
4025 
PluralInfo(Map<Count, String> countToRule, PluralType pluralType)4026         private PluralInfo(Map<Count, String> countToRule, PluralType pluralType) {
4027             EnumMap<Count, String> tempCountToRule = new EnumMap<>(Count.class);
4028             tempCountToRule.putAll(countToRule);
4029             this.countToRule = Collections.unmodifiableMap(tempCountToRule);
4030 
4031             // now build rules
4032             NumberFormat nf = NumberFormat.getNumberInstance(ULocale.ENGLISH);
4033             nf.setMaximumFractionDigits(2);
4034             StringBuilder pluralRuleBuilder = new StringBuilder();
4035             for (Count count : countToRule.keySet()) {
4036                 if (pluralRuleBuilder.length() != 0) {
4037                     pluralRuleBuilder.append(';');
4038                 }
4039                 pluralRuleBuilder.append(count).append(':').append(countToRule.get(count));
4040             }
4041             pluralRulesString = pluralRuleBuilder.toString();
4042             try {
4043                 pluralRules = PluralRules.parseDescription(pluralRulesString);
4044             } catch (ParseException e) {
4045                 throw new IllegalArgumentException(
4046                         "Can't create plurals from <" + pluralRulesString + ">", e);
4047             }
4048             EnumSet<Count> _keywords = EnumSet.noneOf(Count.class);
4049             EnumSet<Count> _integerKeywords = EnumSet.noneOf(Count.class);
4050             EnumSet<Count> _decimalKeywords = EnumSet.noneOf(Count.class);
4051             Matcher hasEMatcher = hasE.matcher("");
4052             Set<Count> _adjustedCounts = null;
4053             Set<String> _adjustedCountStrings = null;
4054 
4055             for (String s : pluralRules.getKeywords()) {
4056                 Count c = Count.valueOf(s);
4057                 _keywords.add(c);
4058                 if (pluralRules.getDecimalSamples(s, SampleType.DECIMAL) != null) {
4059                     _decimalKeywords.add(c);
4060                 } else {
4061                     int debug = 1;
4062                 }
4063                 if (pluralRules.getDecimalSamples(s, SampleType.INTEGER) != null) {
4064                     _integerKeywords.add(c);
4065                 } else {
4066                     int debug = 1;
4067                 }
4068                 String parsedRules = pluralRules.getRules(s);
4069                 if (!hasEMatcher.reset(parsedRules).find()) {
4070                     if (_adjustedCounts == null) {
4071                         _adjustedCounts = new TreeSet<>();
4072                         _adjustedCountStrings = new TreeSet<>();
4073                     }
4074                     _adjustedCounts.add(c);
4075                     _adjustedCountStrings.add(s);
4076                 }
4077             }
4078             adjustedCounts =
4079                     _adjustedCounts == null
4080                             ? Collections.emptySet()
4081                             : ImmutableSet.copyOf(_adjustedCounts);
4082             adjustedCountStrings =
4083                     _adjustedCounts == null
4084                             ? Collections.emptySet()
4085                             : ImmutableSet.copyOf(_adjustedCountStrings);
4086 
4087             keywords = Collections.unmodifiableSet(_keywords);
4088             decimalKeywords = Collections.unmodifiableSet(_decimalKeywords);
4089             integerKeywords = Collections.unmodifiableSet(_integerKeywords);
4090 
4091             countSampleList = new CountSampleList(pluralRules, keywords, pluralType);
4092 
4093             Map<Count, Set<Double>> countToExampleSetRaw = new TreeMap<>();
4094             Map<Integer, Count> exampleToCountRaw = new TreeMap<>();
4095 
4096             Output<Map<Count, SampleList[]>> output = new Output();
4097 
4098             // double check
4099             // if (!targetKeywords.equals(typeToExamples2.keySet())) {
4100             // throw new IllegalArgumentException ("Problem in plurals " + targetKeywords + ", " +
4101             // this);
4102             // }
4103             // now fix the longer examples
4104             String otherFractionalExamples = "";
4105             List<Double> otherFractions = new ArrayList<>(0);
4106 
4107             // add fractional samples
4108             Map<Count, String> countToStringExampleRaw = new TreeMap<>();
4109             for (Count type : keywords) {
4110                 SampleList uset = countSampleList.get(type);
4111                 countToStringExampleRaw.put(type, uset.toString(5, 5));
4112             }
4113             final String baseOtherExamples = countToStringExampleRaw.get(Count.other);
4114             String otherExamples =
4115                     (baseOtherExamples == null ? "" : baseOtherExamples + "; ")
4116                             + otherFractionalExamples
4117                             + "...";
4118             countToStringExampleRaw.put(Count.other, otherExamples);
4119 
4120             // Now do double examples (previously unused & not working).
4121             // Currently a bit of a hack, we should enhance SampleList to make this easier
4122             // and then use SampleList directly, see http://unicode.org/cldr/trac/ticket/9813
4123             for (Count type : countToStringExampleRaw.keySet()) {
4124                 Set<Double> doublesSet = new LinkedHashSet<>(0);
4125                 String examples = countToStringExampleRaw.get(type);
4126                 if (examples == null) {
4127                     examples = "";
4128                 }
4129                 String strippedExamples = examples.replaceAll("(, …)|(; ...)", "");
4130                 String[] exampleArray = strippedExamples.split("(, )|(-)");
4131                 for (String example : exampleArray) {
4132                     if (example == null || example.length() == 0) {
4133                         continue;
4134                     }
4135                     Double doubleValue = Double.valueOf(example);
4136                     doublesSet.add(doubleValue);
4137                 }
4138                 doublesSet = Collections.unmodifiableSet(doublesSet);
4139                 countToExampleSetRaw.put(type, doublesSet);
4140             }
4141 
4142             countToExampleSet = Collections.unmodifiableMap(countToExampleSetRaw);
4143             countToStringExample = Collections.unmodifiableMap(countToStringExampleRaw);
4144             exampleToCount = Collections.unmodifiableMap(exampleToCountRaw);
4145             Set<String> temp = new LinkedHashSet<>();
4146             // String keyword = pluralRules.select(0.0d);
4147             // double value = pluralRules.getUniqueKeywordValue(keyword);
4148             // if (value == pluralRules.NO_UNIQUE_VALUE) {
4149             // temp.add("0");
4150             // }
4151             // keyword = pluralRules.select(1.0d);
4152             // value = pluralRules.getUniqueKeywordValue(keyword);
4153             // if (value == pluralRules.NO_UNIQUE_VALUE) {
4154             // temp.add("1");
4155             // }
4156             Set<String> keywords = pluralRules.getKeywords();
4157             for (Count count : Count.values()) {
4158                 String keyword = count.toString();
4159                 if (keywords.contains(keyword)) {
4160                     temp.add(keyword);
4161                 }
4162             }
4163             // if (false) {
4164             // change to this after rationalizing 0/1
4165             // temp.add("0");
4166             // temp.add("1");
4167             // for (Count count : Count.values()) {
4168             // temp.add(count.toString());
4169             // KeywordStatus status =
4170             // org.unicode.cldr.util.PluralRulesUtil.getKeywordStatus(pluralRules,
4171             // count.toString(), 0, explicits, true);
4172             // if (status != KeywordStatus.SUPPRESSED && status != KeywordStatus.INVALID) {
4173             // temp.add(count.toString());
4174             // }
4175             // }
4176             // }
4177             canonicalKeywords = Collections.unmodifiableSet(temp);
4178         }
4179 
4180         @Override
toString()4181         public String toString() {
4182             return countToExampleSet + "; " + exampleToCount + "; " + pluralRules;
4183         }
4184 
getCountToExamplesMap()4185         public Map<Count, Set<Double>> getCountToExamplesMap() {
4186             return countToExampleSet;
4187         }
4188 
getCountToStringExamplesMap()4189         public Map<Count, String> getCountToStringExamplesMap() {
4190             return countToStringExample;
4191         }
4192 
getCount(double exampleCount)4193         public Count getCount(double exampleCount) {
4194             return Count.valueOf(pluralRules.select(exampleCount));
4195         }
4196 
getCount(DecimalQuantity exampleCount)4197         public Count getCount(DecimalQuantity exampleCount) {
4198             return Count.valueOf(pluralRules.select(exampleCount));
4199         }
4200 
getPluralRules()4201         public PluralRules getPluralRules() {
4202             return pluralRules;
4203         }
4204 
getRules()4205         public String getRules() {
4206             return pluralRulesString;
4207         }
4208 
getDefault()4209         public Count getDefault() {
4210             return null;
4211         }
4212 
getCanonicalKeywords()4213         public Set<String> getCanonicalKeywords() {
4214             return canonicalKeywords;
4215         }
4216 
getCounts()4217         public Set<Count> getCounts() {
4218             return keywords;
4219         }
4220 
4221         /**
4222          * Return the counts returned by the plural rules, adjusted to remove values that are not
4223          * used in collecting data.
4224          */
getAdjustedCounts()4225         public Set<Count> getAdjustedCounts() {
4226             return adjustedCounts;
4227         }
4228 
4229         /**
4230          * Return the counts returned by the plural rules, adjusted to remove values that are not
4231          * used in collecting data.
4232          */
getAdjustedCountStrings()4233         public Set<String> getAdjustedCountStrings() {
4234             return adjustedCountStrings;
4235         }
4236 
getCounts(SampleType sampleType)4237         public Set<Count> getCounts(SampleType sampleType) {
4238             return sampleType == SampleType.DECIMAL ? decimalKeywords : integerKeywords;
4239         }
4240 
4241         /**
4242          * Return the integer samples from 0 to 9999. For simplicity and compactness, this is a
4243          * UnicodeSet, but the interpretation is simply as a list of integers. UnicodeSet.EMPTY is
4244          * returned if there are none.
4245          *
4246          * @param c
4247          * @return
4248          */
getSamples9999(Count c)4249         public SampleList getSamples9999(Count c) {
4250             return countSampleList.get(c);
4251         }
4252 
4253         /**
4254          * Return the integer samples for the specified digit, eg 1 => 0..9. For simplicity and
4255          * compactness, this is a UnicodeSet, but the interpretation is simply as a list of
4256          * integers.
4257          *
4258          * @param c
4259          * @return
4260          */
getSamples9999(Count c, int digit)4261         public SampleList getSamples9999(Count c, int digit) {
4262             return countSampleList.get(c, digit);
4263         }
4264 
hasSamples(Count c, int digits)4265         public boolean hasSamples(Count c, int digits) {
4266             SampleList samples = countSampleList.get(c, digits);
4267             return samples != null && (samples.fractionSize() > 0 || samples.intSize() > 0);
4268         }
4269 
getRule(Count keyword)4270         public String getRule(Count keyword) {
4271             return countToRule.get(keyword);
4272         }
4273 
4274         @Override
compareTo(PluralInfo other)4275         public int compareTo(PluralInfo other) {
4276             int size1 = this.countToRule.size();
4277             int size2 = other.countToRule.size();
4278             int diff = size1 - size2;
4279             if (diff != 0) {
4280                 return diff;
4281             }
4282             Iterator<Count> it1 = countToRule.keySet().iterator();
4283             Iterator<Count> it2 = other.countToRule.keySet().iterator();
4284             while (it1.hasNext()) {
4285                 Count a1 = it1.next();
4286                 Count a2 = it2.next();
4287                 diff = a1.ordinal() - a2.ordinal();
4288                 if (diff != 0) {
4289                     return diff;
4290                 }
4291             }
4292             return pluralRules.compareTo(other.pluralRules);
4293         }
4294 
4295         enum MinMax {
4296             MIN,
4297             MAX
4298         }
4299 
4300         public static final DecimalQuantity NEGATIVE_INFINITY =
4301                 new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY);
4302         public static final DecimalQuantity POSITIVE_INFINITY =
4303                 new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY);
4304 
doubleValue(DecimalQuantity a)4305         static double doubleValue(DecimalQuantity a) {
4306             return a.toDouble();
4307         }
4308 
rangeExists( Count s, Count e, Output<DecimalQuantity> minSample, Output<DecimalQuantity> maxSample)4309         public boolean rangeExists(
4310                 Count s,
4311                 Count e,
4312                 Output<DecimalQuantity> minSample,
4313                 Output<DecimalQuantity> maxSample) {
4314             if (!getCounts().contains(s) || !getCounts().contains(e)) {
4315                 return false;
4316             }
4317             DecimalQuantity temp;
4318             minSample.value =
4319                     getLeastIn(s, SampleType.INTEGER, NEGATIVE_INFINITY, POSITIVE_INFINITY);
4320             temp = getLeastIn(s, SampleType.DECIMAL, NEGATIVE_INFINITY, POSITIVE_INFINITY);
4321             if (lessOrFewerDecimals(temp, minSample.value)) {
4322                 minSample.value = temp;
4323             }
4324             maxSample.value =
4325                     getGreatestIn(e, SampleType.INTEGER, NEGATIVE_INFINITY, POSITIVE_INFINITY);
4326             temp = getGreatestIn(e, SampleType.DECIMAL, NEGATIVE_INFINITY, POSITIVE_INFINITY);
4327             if (greaterOrFewerDecimals(temp, maxSample.value)) {
4328                 maxSample.value = temp;
4329             }
4330             // if there is no range, just return
4331             if (doubleValue(minSample.value) >= doubleValue(maxSample.value)) {
4332                 return false;
4333             }
4334             // see if we can get a better range, with not such a large end range
4335 
4336             DecimalQuantity lowestMax =
4337                     new DecimalQuantity_DualStorageBCD(
4338                             minSample
4339                                     .value
4340                                     .toBigDecimal()
4341                                     .add(new java.math.BigDecimal("0.00001")));
4342             lowestMax.setMinFraction(5);
4343             SampleType bestType =
4344                     getCounts(SampleType.INTEGER).contains(e)
4345                             ? SampleType.INTEGER
4346                             : SampleType.DECIMAL;
4347             temp = getLeastIn(e, bestType, lowestMax, POSITIVE_INFINITY);
4348             if (lessOrFewerDecimals(temp, maxSample.value)) {
4349                 maxSample.value = temp;
4350             }
4351             if (maxSample.value.toDouble() > 100000) {
4352                 temp = getLeastIn(e, bestType, lowestMax, POSITIVE_INFINITY);
4353                 if (lessOrFewerDecimals(temp, maxSample.value)) {
4354                     maxSample.value = temp;
4355                 }
4356             }
4357 
4358             return true;
4359         }
4360 
greaterOrFewerDecimals(DecimalQuantity a, DecimalQuantity b)4361         public boolean greaterOrFewerDecimals(DecimalQuantity a, DecimalQuantity b) {
4362             return doubleValue(a) > doubleValue(b)
4363                     || doubleValue(b) == doubleValue(a)
4364                             && b.getPluralOperand(Operand.f) > a.getPluralOperand(Operand.f);
4365         }
4366 
lessOrFewerDecimals(DecimalQuantity a, DecimalQuantity b)4367         public boolean lessOrFewerDecimals(DecimalQuantity a, DecimalQuantity b) {
4368             return doubleValue(a) < doubleValue(b)
4369                     || doubleValue(b) == doubleValue(a)
4370                             && b.getPluralOperand(Operand.f) > a.getPluralOperand(Operand.f);
4371         }
4372 
getLeastIn( Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max)4373         private DecimalQuantity getLeastIn(
4374                 Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max) {
4375             DecimalQuantity result = POSITIVE_INFINITY;
4376             DecimalQuantitySamples sSamples1 =
4377                     pluralRules.getDecimalSamples(s.toString(), sampleType);
4378             if (sSamples1 != null) {
4379                 for (DecimalQuantitySamplesRange x : sSamples1.samples) {
4380                     // overlap in ranges??
4381                     if (doubleValue(x.start) > doubleValue(max)
4382                             || doubleValue(x.end) < doubleValue(min)) {
4383                         continue; // no, continue
4384                     }
4385                     // get restricted range
4386                     DecimalQuantity minOverlap =
4387                             greaterOrFewerDecimals(min, x.start) ? max : x.start;
4388                     // DecimalQuantity maxOverlap = lessOrFewerDecimals(max, x.end) ? max : x.end;
4389 
4390                     // replace if better
4391                     if (lessOrFewerDecimals(minOverlap, result)) {
4392                         result = minOverlap;
4393                     }
4394                 }
4395             }
4396             return result;
4397         }
4398 
getGreatestIn( Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max)4399         private DecimalQuantity getGreatestIn(
4400                 Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max) {
4401             DecimalQuantity result = NEGATIVE_INFINITY;
4402             DecimalQuantitySamples sSamples1 =
4403                     pluralRules.getDecimalSamples(s.toString(), sampleType);
4404             if (sSamples1 != null) {
4405                 for (DecimalQuantitySamplesRange x : sSamples1.getSamples()) {
4406                     // overlap in ranges??
4407                     if (doubleValue(x.start) > doubleValue(max)
4408                             || doubleValue(x.end) < doubleValue(min)) {
4409                         continue; // no, continue
4410                     }
4411                     // get restricted range
4412                     // DecimalQuantity minOverlap = greaterOrFewerDecimals(min, x.start) ? max :
4413                     // x.start;
4414                     DecimalQuantity maxOverlap = lessOrFewerDecimals(max, x.end) ? max : x.end;
4415 
4416                     // replace if better
4417                     if (greaterOrFewerDecimals(maxOverlap, result)) {
4418                         result = maxOverlap;
4419                     }
4420                 }
4421             }
4422             return result;
4423         }
4424 
getNonZeroSampleIfPossible( DecimalQuantitySamples exampleList)4425         public static DecimalQuantity getNonZeroSampleIfPossible(
4426                 DecimalQuantitySamples exampleList) {
4427             Set<DecimalQuantitySamplesRange> sampleSet = exampleList.getSamples();
4428             DecimalQuantity sampleDecimal = null;
4429             // skip 0 if possible
4430             for (DecimalQuantitySamplesRange range : sampleSet) {
4431                 sampleDecimal = range.start;
4432                 if (sampleDecimal.toDouble() != 0.0) {
4433                     break;
4434                 }
4435                 sampleDecimal = range.end;
4436                 if (sampleDecimal.toDouble() != 0.0) {
4437                     break;
4438                 }
4439             }
4440             return sampleDecimal;
4441         }
4442     }
4443 
4444     /**
4445      * @deprecated use {@link #getPlurals(PluralType)} instead
4446      */
4447     @Deprecated
getPluralLocales()4448     public Set<String> getPluralLocales() {
4449         return getPluralLocales(PluralType.cardinal);
4450     }
4451 
4452     /**
4453      * @param type
4454      * @return the set of locales that have rules for the specified plural type
4455      */
getPluralLocales(PluralType type)4456     public Set<String> getPluralLocales(PluralType type) {
4457         return localeToPluralInfo2.get(type).keySet();
4458     }
4459 
getPluralRangesLocales()4460     public Set<String> getPluralRangesLocales() {
4461         return localeToPluralRanges.keySet();
4462     }
4463 
getPluralRanges(String locale)4464     public PluralRanges getPluralRanges(String locale) {
4465         return localeToPluralRanges.get(locale);
4466     }
4467 
4468     /**
4469      * @deprecated use {@link #getPlurals(PluralType, String)} instead
4470      */
4471     @Deprecated
getPlurals(String locale)4472     public PluralInfo getPlurals(String locale) {
4473         return getPlurals(locale, true);
4474     }
4475 
4476     /**
4477      * Returns the plural info for a given locale.
4478      *
4479      * @param locale
4480      * @return
4481      */
getPlurals(PluralType type, String locale)4482     public PluralInfo getPlurals(PluralType type, String locale) {
4483         return getPlurals(type, locale, true);
4484     }
4485 
4486     /**
4487      * @deprecated use {@link #getPlurals(PluralType, String, boolean)} instead.
4488      */
4489     @Deprecated
getPlurals(String locale, boolean allowRoot)4490     public PluralInfo getPlurals(String locale, boolean allowRoot) {
4491         return getPlurals(PluralType.cardinal, locale, allowRoot);
4492     }
4493 
4494     /**
4495      * Returns the plural info for a given locale.
4496      *
4497      * @param locale
4498      * @param allowRoot
4499      * @param type
4500      * @return
4501      */
getPlurals(PluralType type, String locale, boolean allowRoot)4502     public PluralInfo getPlurals(PluralType type, String locale, boolean allowRoot) {
4503         Map<String, PluralInfo> infoMap = localeToPluralInfo2.get(type);
4504         while (locale != null) {
4505             if (!allowRoot && locale.equals(LocaleNames.ROOT)) {
4506                 break;
4507             }
4508             PluralInfo result = infoMap.get(locale);
4509             if (result != null) {
4510                 return result;
4511             }
4512             locale = LocaleIDParser.getSimpleParent(locale);
4513         }
4514         return null;
4515     }
4516 
4517     /**
4518      * CLDR equivalent of com.ibm.icu.text.PluralRules.forLocale()
4519      *
4520      * @param loc
4521      * @param type
4522      * @return an ICU PluralRules, from CLDR data
4523      */
getPluralRules(ULocale loc, PluralRules.PluralType type)4524     public PluralRules getPluralRules(ULocale loc, PluralRules.PluralType type) {
4525         return getPluralRules(loc.getBaseName(), type);
4526     }
4527 
4528     /**
4529      * CLDR equivalent of com.ibm.icu.text.PluralRules.forLocale()
4530      *
4531      * @param loc
4532      * @param type
4533      * @return an ICU PluralRules, from CLDR data
4534      */
getPluralRules(String loc, PluralRules.PluralType type)4535     public PluralRules getPluralRules(String loc, PluralRules.PluralType type) {
4536         return getPlurals(PluralType.fromStandardType(type), loc).getPluralRules();
4537     }
4538 
getDayPeriods(DayPeriodInfo.Type type, String locale)4539     public DayPeriodInfo getDayPeriods(DayPeriodInfo.Type type, String locale) {
4540         Map<String, DayPeriodInfo> map1 = typeToLocaleToDayPeriodInfo.get(type);
4541         while (locale != null) {
4542             DayPeriodInfo result = map1.get(locale);
4543             if (result != null) {
4544                 return result;
4545             }
4546             locale = LocaleIDParser.getSimpleParent(locale);
4547         }
4548         return null;
4549     }
4550 
getDayPeriodLocales(DayPeriodInfo.Type type)4551     public Set<String> getDayPeriodLocales(DayPeriodInfo.Type type) {
4552         return typeToLocaleToDayPeriodInfo.get(type).keySet();
4553     }
4554 
4555     private static CurrencyNumberInfo DEFAULT_NUMBER_INFO = new CurrencyNumberInfo(2, -1, -1, -1);
4556 
getCurrencyNumberInfo(String currency)4557     public CurrencyNumberInfo getCurrencyNumberInfo(String currency) {
4558         CurrencyNumberInfo result = currencyToCurrencyNumberInfo.get(currency);
4559         if (result == null) {
4560             result = DEFAULT_NUMBER_INFO;
4561         }
4562         return result;
4563     }
4564 
4565     /**
4566      * Returns ordered set of currency data information
4567      *
4568      * @param territory
4569      * @return
4570      */
getCurrencyDateInfo(String territory)4571     public Set<CurrencyDateInfo> getCurrencyDateInfo(String territory) {
4572         return territoryToCurrencyDateInfo.getAll(territory);
4573     }
4574 
4575     /**
4576      * Returns ordered set of currency data information
4577      *
4578      * @return
4579      */
getCurrencyTerritories()4580     public Set<String> getCurrencyTerritories() {
4581         return territoryToCurrencyDateInfo.keySet();
4582     }
4583 
4584     /**
4585      * Returns the ISO4217 currency code of the default currency for a given territory. The default
4586      * currency is the first one listed which is legal tender at the present moment.
4587      *
4588      * @param territory
4589      * @return
4590      */
getDefaultCurrency(String territory)4591     public String getDefaultCurrency(String territory) {
4592 
4593         String result = "XXX";
4594         Set<CurrencyDateInfo> targetCurrencyInfo = getCurrencyDateInfo(territory);
4595         if (targetCurrencyInfo == null) {
4596             /*
4597              * This happens during ConsoleCheckCLDR
4598              * territory = "419"
4599              * path = //ldml/numbers/currencyFormats[@numberSystem="latn"]/currencyFormatLength/currencyFormat[@type="accounting"]/pattern[@type="standard"]
4600              * value = ¤#,##0.00
4601              * Prevent NullPointerException
4602              */
4603             return result;
4604         }
4605         Date now = new Date();
4606         for (CurrencyDateInfo cdi : targetCurrencyInfo) {
4607             if (cdi.getStart().before(now) && cdi.getEnd().after(now) && cdi.isLegalTender()) {
4608                 result = cdi.getCurrency();
4609                 break;
4610             }
4611         }
4612         return result;
4613     }
4614 
4615     /**
4616      * Returns the ISO4217 currency code of the default currency for a given CLDRLocale. The default
4617      * currency is the first one listed which is legal tender at the present moment.
4618      *
4619      * @param loc
4620      * @return
4621      */
getDefaultCurrency(CLDRLocale loc)4622     public String getDefaultCurrency(CLDRLocale loc) {
4623         return getDefaultCurrency(loc.getCountry());
4624     }
4625 
getTerritoryToTelephoneCodeInfo()4626     public Map<String, Set<TelephoneCodeInfo>> getTerritoryToTelephoneCodeInfo() {
4627         return territoryToTelephoneCodeInfo;
4628     }
4629 
getTelephoneCodeInfoForTerritory(String territory)4630     public Set<TelephoneCodeInfo> getTelephoneCodeInfoForTerritory(String territory) {
4631         return territoryToTelephoneCodeInfo.get(territory);
4632     }
4633 
getTerritoriesForTelephoneCodeInfo()4634     public Set<String> getTerritoriesForTelephoneCodeInfo() {
4635         return territoryToTelephoneCodeInfo.keySet();
4636     }
4637 
4638     private List<String> serialElements;
4639     private Collection<String> distinguishingAttributes;
4640 
4641     //    @Deprecated
4642     //    public List<String> getSerialElements() {
4643     //        return serialElements;
4644     //    }
4645 
4646     //    @Deprecated
4647     //    public Collection<String> getDistinguishingAttributes() {
4648     //        return distinguishingAttributes;
4649     //    }
4650 
4651     /**
4652      * The Row is: desired, supported, percent, oneway
4653      *
4654      * @param string the type (written-new, for new format)
4655      * @return
4656      */
getLanguageMatcherData(String string)4657     public List<R4<String, String, Integer, Boolean>> getLanguageMatcherData(String string) {
4658         return languageMatch.get(string);
4659     }
4660 
getLanguageMatcherKeys()4661     public Set<String> getLanguageMatcherKeys() {
4662         return languageMatch.keySet();
4663     }
4664 
4665     /** Return mapping from type to territory to data. 001 is the default. */
getTerritoryMeasurementData()4666     public Map<MeasurementType, Map<String, String>> getTerritoryMeasurementData() {
4667         return measurementData;
4668     }
4669 
4670     /** Return mapping from keys to subtypes */
getBcp47Keys()4671     public Relation<String, String> getBcp47Keys() {
4672         return bcp47Key2Subtypes;
4673     }
4674 
4675     /** Return mapping from extensions to keys */
getBcp47Extension2Keys()4676     public Relation<String, String> getBcp47Extension2Keys() {
4677         return bcp47Extension2Keys;
4678     }
4679 
4680     /** Return mapping from &lt;key,subtype> to aliases */
getBcp47Aliases()4681     public Relation<R2<String, String>, String> getBcp47Aliases() {
4682         return bcp47Aliases;
4683     }
4684 
4685     /** Return mapping from &lt;key,subtype> to description */
getBcp47Descriptions()4686     public Map<R2<String, String>, String> getBcp47Descriptions() {
4687         return bcp47Descriptions;
4688     }
4689 
4690     /** Return mapping from &lt;key,subtype> to since */
getBcp47Since()4691     public Map<R2<String, String>, String> getBcp47Since() {
4692         return bcp47Since;
4693     }
4694 
4695     /** Return mapping from &lt;key,subtype> to preferred */
getBcp47Preferred()4696     public Map<R2<String, String>, String> getBcp47Preferred() {
4697         return bcp47Preferred;
4698     }
4699 
4700     /** Return mapping from &lt;key,subtype> to deprecated */
getBcp47Deprecated()4701     public Map<R2<String, String>, String> getBcp47Deprecated() {
4702         return bcp47Deprecated;
4703     }
4704 
4705     /** Return mapping from subtype to deprecated */
getBcp47ValueType()4706     public Map<String, String> getBcp47ValueType() {
4707         return bcp47ValueType;
4708     }
4709 
4710     static Set<String> MainTimeZones;
4711 
4712     /**
4713      * Return canonical timezones
4714      *
4715      * @return
4716      */
getCanonicalTimeZones()4717     public Set<String> getCanonicalTimeZones() {
4718         synchronized (SupplementalDataInfo.class) {
4719             if (MainTimeZones == null) {
4720                 MainTimeZones = new TreeSet<>();
4721                 SupplementalDataInfo info = SupplementalDataInfo.getInstance();
4722                 for (Entry<R2<String, String>, Set<String>> entry :
4723                         info.getBcp47Aliases().keyValuesSet()) {
4724                     R2<String, String> subtype_aliases = entry.getKey();
4725                     if (!subtype_aliases.get0().equals("timezone")) {
4726                         continue;
4727                     }
4728                     MainTimeZones.add(entry.getValue().iterator().next());
4729                 }
4730                 MainTimeZones = Collections.unmodifiableSet(MainTimeZones);
4731             }
4732             return MainTimeZones;
4733         }
4734     }
4735 
getMetaZoneRanges(String zone)4736     public Set<MetaZoneRange> getMetaZoneRanges(String zone) {
4737         return zoneToMetaZoneRanges.get(zone);
4738     }
4739 
4740     /**
4741      * Return the metazone containing this zone at this date
4742      *
4743      * @param zone
4744      * @param date
4745      * @return
4746      */
getMetaZoneRange(String zone, long date)4747     public MetaZoneRange getMetaZoneRange(String zone, long date) {
4748         Set<MetaZoneRange> metazoneRanges = zoneToMetaZoneRanges.get(zone);
4749         if (metazoneRanges != null) {
4750             for (MetaZoneRange metazoneRange : metazoneRanges) {
4751                 if (metazoneRange.dateRange.getFrom() <= date
4752                         && date < metazoneRange.dateRange.getTo()) {
4753                     return metazoneRange;
4754                 }
4755             }
4756         }
4757         return null;
4758     }
4759 
isDeprecated(DtdType type, String element, String attribute, String value)4760     public boolean isDeprecated(DtdType type, String element, String attribute, String value) {
4761         if (type.getStatus() == DtdStatus.removed) {
4762             // if the DTD was removed, skip
4763             return true;
4764         }
4765         return DtdData.getInstance(type).isDeprecated(element, attribute, value);
4766     }
4767 
isDeprecated(DtdType type, String path)4768     public boolean isDeprecated(DtdType type, String path) {
4769 
4770         XPathParts parts = XPathParts.getFrozenInstance(path);
4771         for (int i = 0; i < parts.size(); ++i) {
4772             String element = parts.getElement(i);
4773             if (isDeprecated(type, element, "*", "*")) {
4774                 return true;
4775             }
4776             for (Entry<String, String> entry : parts.getAttributes(i).entrySet()) {
4777                 String attribute = entry.getKey();
4778                 String value = entry.getValue();
4779                 if (isDeprecated(type, element, attribute, value)) {
4780                     return true;
4781                 }
4782             }
4783         }
4784         return false;
4785     }
4786 
4787     /**
4788      * Returns map of ID/Type/Value, such as id="$integer" type="regex" value=[0-9]+
4789      *
4790      * @return
4791      */
getValidityInfo()4792     public Map<String, R2<String, String>> getValidityInfo() {
4793         return validityInfo;
4794     }
4795 
getCLDRLanguageCodes()4796     public Set<String> getCLDRLanguageCodes() {
4797         return CLDRLanguageCodes;
4798     }
4799 
isCLDRLanguageCode(String code)4800     public boolean isCLDRLanguageCode(String code) {
4801         return CLDRLanguageCodes.contains(code);
4802     }
4803 
getCLDRScriptCodes()4804     public Set<String> getCLDRScriptCodes() {
4805         return CLDRScriptCodes;
4806     }
4807 
isCLDRScriptCode(String code)4808     public boolean isCLDRScriptCode(String code) {
4809         return CLDRScriptCodes.contains(code);
4810     }
4811 
initCLDRLocaleBasedData()4812     private synchronized void initCLDRLocaleBasedData() throws InternalError {
4813         // This initialization depends on SDI being initialized.
4814         if (defaultContentToBase == null) {
4815             Map<CLDRLocale, CLDRLocale> p2c = new TreeMap<>();
4816             Map<CLDRLocale, CLDRLocale> c2p = new TreeMap<>();
4817             TreeSet<CLDRLocale> tmpAllLocales = new TreeSet<>();
4818             // copied from SupplementalData.java - CLDRLocale based
4819             for (String l : defaultContentLocales) {
4820                 CLDRLocale child = CLDRLocale.getInstance(l);
4821                 tmpAllLocales.add(child);
4822             }
4823 
4824             for (CLDRLocale child : tmpAllLocales) {
4825                 // Find a parent of this locale which is NOT itself also a defaultContent
4826                 CLDRLocale nextParent = child.getParent();
4827                 // /System.err.println(">> considering " + child + " with parent " + nextParent);
4828                 while (nextParent != null) {
4829                     if (!tmpAllLocales.contains(
4830                             nextParent)) { // Did we find a parent that's also not itself a
4831                         // defaultContent?
4832                         // /System.err.println(">>>> Got 1? considering " + child + " with parent "
4833                         // + nextParent);
4834                         break;
4835                     }
4836                     // /System.err.println(">>>>> considering " + child + " with parent " +
4837                     // nextParent);
4838                     nextParent = nextParent.getParent();
4839                 }
4840                 // parent
4841                 if (nextParent == null) {
4842                     throw new InternalError(
4843                             "SupplementalDataInfo.defaultContentToChild(): No valid parent for "
4844                                     + child);
4845                 } else if (nextParent == CLDRLocale.ROOT
4846                         || nextParent == CLDRLocale.getInstance(LocaleNames.ROOT)) {
4847                     throw new InternalError(
4848                             "SupplementalDataInfo.defaultContentToChild(): Parent is root for default content locale "
4849                                     + child);
4850                 } else {
4851                     c2p.put(child, nextParent); // wo_Arab_SN -> wo
4852                     CLDRLocale oldChild = p2c.get(nextParent);
4853                     if (oldChild != null) {
4854                         CLDRLocale childParent = child.getParent();
4855                         if (!childParent.equals(oldChild)) {
4856                             throw new InternalError(
4857                                     "SupplementalData.defaultContentToChild(): defaultContent list in wrong order? Tried to map "
4858                                             + nextParent
4859                                             + " -> "
4860                                             + child
4861                                             + ", replacing "
4862                                             + oldChild
4863                                             + " (should have been "
4864                                             + childParent
4865                                             + ")");
4866                         }
4867                     }
4868                     p2c.put(nextParent, child); // wo -> wo_Arab_SN
4869                 }
4870             }
4871 
4872             // done, save the hashtables..
4873             baseToDefaultContent = Collections.unmodifiableMap(p2c); // wo -> wo_Arab_SN
4874             defaultContentToBase = Collections.unmodifiableMap(c2p); // wo_Arab_SN -> wo
4875         }
4876     }
4877 
getTimeData()4878     public Map<String, PreferredAndAllowedHour> getTimeData() {
4879         return timeData;
4880     }
4881 
getDefaultScript(String baseLanguage)4882     public String getDefaultScript(String baseLanguage) {
4883         String ls = likelySubtags.get(baseLanguage);
4884         if (ls == null) {
4885             return UNKNOWN_SCRIPT;
4886         }
4887         LocaleIDParser lp = new LocaleIDParser().set(ls);
4888         String defaultScript = lp.getScript();
4889         if (defaultScript.length() > 0) {
4890             return defaultScript;
4891         } else {
4892             return UNKNOWN_SCRIPT;
4893         }
4894     }
4895 
4896     private XEquivalenceClass<String, String> equivalentLocales = null;
4897 
getEquivalentsForLocale(String localeId)4898     public Set<String> getEquivalentsForLocale(String localeId) {
4899         if (equivalentLocales == null) {
4900             equivalentLocales = getEquivalentsForLocale();
4901         }
4902         Set<String> result = new TreeSet(LENGTH_FIRST);
4903         result.add(localeId);
4904         Set<String> equiv = equivalentLocales.getEquivalences(localeId);
4905         //        if (equiv == null) {
4906         //            result.add(localeId);
4907         //            return result;
4908         //        }
4909         if (equiv != null) {
4910             result.addAll(equivalentLocales.getEquivalences(localeId));
4911         }
4912         Map<String, String> likely = getLikelySubtags();
4913         String newMax = LikelySubtags.maximize(localeId, likely);
4914         if (newMax != null) {
4915             result.add(newMax);
4916             newMax = LikelySubtags.minimize(localeId, likely, true);
4917             if (newMax != null) {
4918                 result.add(newMax);
4919             }
4920             newMax = LikelySubtags.minimize(localeId, likely, false);
4921             if (newMax != null) {
4922                 result.add(newMax);
4923             }
4924         }
4925 
4926         //        if (result.size() == 1) {
4927         //            LanguageTagParser ltp = new LanguageTagParser().set(localeId);
4928         //            if (ltp.getScript().isEmpty()) {
4929         //                String ds = getDefaultScript(ltp.getLanguage());
4930         //                if (ds != null) {
4931         //                    ltp.setScript(ds);
4932         //                    result.add(ltp.toString());
4933         //                }
4934         //            }
4935         //        }
4936         return result;
4937     }
4938 
4939     public static final class LengthFirstComparator<T> implements Comparator<T> {
4940         @Override
compare(T a, T b)4941         public int compare(T a, T b) {
4942             String as = a.toString();
4943             String bs = b.toString();
4944             if (as.length() < bs.length()) return -1;
4945             if (as.length() > bs.length()) return 1;
4946             return as.compareTo(bs);
4947         }
4948     }
4949 
4950     public static final LengthFirstComparator LENGTH_FIRST = new LengthFirstComparator();
4951 
getEquivalentsForLocale()4952     private synchronized XEquivalenceClass<String, String> getEquivalentsForLocale() {
4953         SupplementalDataInfo sdi = this;
4954         Relation<String, String> localeToDefaultContents =
4955                 Relation.of(new HashMap<String, Set<String>>(), LinkedHashSet.class);
4956 
4957         Set<String> dcl = sdi.getDefaultContentLocales();
4958         Map<String, String> likely = sdi.getLikelySubtags();
4959         XEquivalenceClass<String, String> locales = new XEquivalenceClass<>();
4960         LanguageTagParser ltp = new LanguageTagParser();
4961         Set<String> temp = new HashSet<>();
4962         for (Entry<String, String> entry : likely.entrySet()) {
4963             String source = entry.getKey();
4964             if (source.startsWith(LocaleNames.UND)) {
4965                 continue;
4966             }
4967             for (String s : getCombinations(source, ltp, likely, temp)) {
4968                 locales.add(source, s);
4969             }
4970             for (String s : getCombinations(entry.getValue(), ltp, likely, temp)) {
4971                 locales.add(source, s);
4972             }
4973         }
4974         //        Set<String> sorted = new TreeSet(locales.getExplicitItems());
4975         //        for (String s : sorted) {
4976         //            System.out.println(locales.getEquivalences(s));
4977         //        }
4978         for (String defaultContentLocale : dcl) {
4979             if (defaultContentLocale.startsWith("zh")) {
4980                 int x = 0;
4981             }
4982             Set<String> set = locales.getEquivalences(defaultContentLocale);
4983 
4984             String parent = LocaleIDParser.getSimpleParent(defaultContentLocale);
4985             if (!set.contains(parent)) {
4986                 localeToDefaultContents.put(parent, defaultContentLocale);
4987                 // System.out.println("Mismatch " + parent + ", " + set);
4988             }
4989             if (parent.contains("_")) {
4990                 continue;
4991             }
4992             // only base locales after this point
4993             String ds = sdi.getDefaultScript(parent);
4994             if (ds != null) {
4995                 ltp.set(parent);
4996                 ltp.setScript(ds);
4997                 String trial = ltp.toString();
4998                 if (!set.contains(trial)) {
4999                     // System.out.println("Mismatch " + trial + ", " + set);
5000                     localeToDefaultContents.put(parent, trial);
5001                 }
5002             }
5003         }
5004         return locales;
5005     }
5006 
getCombinations( String source, LanguageTagParser ltp, Map<String, String> likely, Set<String> locales)5007     private Set<String> getCombinations(
5008             String source, LanguageTagParser ltp, Map<String, String> likely, Set<String> locales) {
5009         locales.clear();
5010 
5011         String max = LikelySubtags.maximize(source, likely);
5012         locales.add(max);
5013 
5014         ltp.set(source);
5015         ltp.setScript("");
5016         String trial = ltp.toString();
5017         String newMax = LikelySubtags.maximize(trial, likely);
5018         if (Objects.equals(newMax, max)) {
5019             locales.add(trial);
5020         }
5021 
5022         ltp.set(source);
5023         ltp.setRegion("");
5024         trial = ltp.toString();
5025         newMax = LikelySubtags.maximize(trial, likely);
5026         if (Objects.equals(newMax, max)) {
5027             locales.add(trial);
5028         }
5029 
5030         return locales;
5031     }
5032 
getCldrVersion()5033     public VersionInfo getCldrVersion() {
5034         return cldrVersion;
5035     }
5036 
getUnicodeVersionString()5037     public String getUnicodeVersionString() {
5038         return unicodeVersion;
5039     }
5040 
getUnicodeVersion()5041     public VersionInfo getUnicodeVersion() {
5042         return VersionInfo.getInstance(getUnicodeVersionString());
5043     }
5044 
getCldrVersionString()5045     public String getCldrVersionString() {
5046         return cldrVersionString;
5047     }
5048 
getDirectory()5049     public File getDirectory() {
5050         return directory;
5051     }
5052 
5053     public static final Splitter WHITESPACE_SPLTTER =
5054             Splitter.on(PatternCache.get("\\s+")).omitEmptyStrings();
5055 
5056     public static final class AttributeValidityInfo {
5057         // <attributeValues elements="alias" attributes="path"
5058         // type="path">notDoneYet</attributeValues>
5059 
5060         final String type;
5061         final Set<DtdType> dtds;
5062         final Set<String> elements;
5063         final Set<String> attributes;
5064         final String order;
5065 
5066         @Override
toString()5067         public String toString() {
5068             return "type:"
5069                     + type
5070                     + ", elements:"
5071                     + elements
5072                     + ", attributes:"
5073                     + attributes
5074                     + ", order:"
5075                     + order;
5076         }
5077 
add( Map<String, String> inputAttibutes, String inputValue, Map<AttributeValidityInfo, String> data)5078         static void add(
5079                 Map<String, String> inputAttibutes,
5080                 String inputValue,
5081                 Map<AttributeValidityInfo, String> data) {
5082             final AttributeValidityInfo key =
5083                     new AttributeValidityInfo(
5084                             inputAttibutes.get("dtds"),
5085                             inputAttibutes.get("type"),
5086                             inputAttibutes.get("attributes"),
5087                             inputAttibutes.get("elements"),
5088                             inputAttibutes.get("order"));
5089             if (data.containsKey(key)) {
5090                 throw new IllegalArgumentException(key + " declared twice");
5091             }
5092             data.put(key, inputValue);
5093         }
5094 
AttributeValidityInfo( String dtds, String type, String attributes, String elements, String order)5095         public AttributeValidityInfo(
5096                 String dtds, String type, String attributes, String elements, String order) {
5097             if (dtds == null) {
5098                 this.dtds = Collections.singleton(DtdType.ldml);
5099             } else {
5100                 Set<DtdType> temp = EnumSet.noneOf(DtdType.class);
5101                 for (String s : WHITESPACE_SPLTTER.split(dtds)) {
5102                     temp.add(DtdType.fromElement(s));
5103                 }
5104                 this.dtds = Collections.unmodifiableSet(temp);
5105             }
5106             this.type = type != null ? type : order != null ? "choice" : null;
5107             this.elements =
5108                     elements == null
5109                             ? Collections.EMPTY_SET
5110                             : With.in(WHITESPACE_SPLTTER.split(elements))
5111                                     .toUnmodifiableCollection(new HashSet<String>());
5112             this.attributes =
5113                     With.in(WHITESPACE_SPLTTER.split(attributes))
5114                             .toUnmodifiableCollection(new HashSet<String>());
5115             this.order = order;
5116         }
5117 
getType()5118         public String getType() {
5119             return type;
5120         }
5121 
getDtds()5122         public Set<DtdType> getDtds() {
5123             return dtds;
5124         }
5125 
getElements()5126         public Set<String> getElements() {
5127             return elements;
5128         }
5129 
getAttributes()5130         public Set<String> getAttributes() {
5131             return attributes;
5132         }
5133 
getOrder()5134         public String getOrder() {
5135             return order;
5136         }
5137 
5138         @Override
equals(Object obj)5139         public boolean equals(Object obj) {
5140             AttributeValidityInfo other = (AttributeValidityInfo) obj;
5141             return CldrUtility.deepEquals(
5142                     type, other.type,
5143                     dtds, other.dtds,
5144                     elements, other.elements,
5145                     attributes, other.attributes,
5146                     order, other.order);
5147         }
5148 
5149         @Override
hashCode()5150         public int hashCode() {
5151             return Objects.hash(type, dtds, elements, attributes, order);
5152         }
5153     }
5154 
getAttributeValidity()5155     public Map<AttributeValidityInfo, String> getAttributeValidity() {
5156         return attributeValidityInfo;
5157     }
5158 
getLanguageGroups()5159     public Multimap<String, String> getLanguageGroups() {
5160         return languageGroups;
5161     }
5162 
getUnitConverter()5163     public UnitConverter getUnitConverter() {
5164         return unitConverter;
5165     }
5166 
getRationalParser()5167     public RationalParser getRationalParser() {
5168         return rationalParser;
5169     }
5170 
getUnitPreferences()5171     public UnitPreferences getUnitPreferences() {
5172         return unitPreferences;
5173     }
5174 
getUnitIdComponentType(String component)5175     public UnitIdComponentType getUnitIdComponentType(String component) {
5176         UnitIdComponentType result = unitIdComponentType.get(component);
5177         return result == null ? UnitIdComponentType.base : result;
5178     }
5179 
5180     /** Locales that have grammar info */
hasGrammarInfo()5181     public Set<String> hasGrammarInfo() {
5182         return grammarLocaleToTargetToFeatureToValues.keySet();
5183     }
5184 
5185     /**
5186      * Locales that have grammar info for at least one of the features (with the given target and
5187      * scope).
5188      */
getLocalesWithFeatures( GrammaticalTarget target, GrammaticalScope scope, GrammaticalFeature... features)5189     public Set<String> getLocalesWithFeatures(
5190             GrammaticalTarget target, GrammaticalScope scope, GrammaticalFeature... features) {
5191         Set<String> locales = new TreeSet<>();
5192         for (Entry<String, GrammarInfo> localeAndGrammar :
5193                 grammarLocaleToTargetToFeatureToValues.entrySet()) {
5194             final GrammarInfo grammarInfo = localeAndGrammar.getValue();
5195             for (GrammaticalFeature feature : features) {
5196                 Collection<String> featureInfo = grammarInfo.get(target, feature, scope);
5197                 if (!featureInfo.isEmpty()) {
5198                     locales.add(localeAndGrammar.getKey());
5199                 }
5200             }
5201         }
5202         return ImmutableSet.copyOf(locales);
5203     }
5204 
5205     /**
5206      * Grammar info for locales, with inheritance
5207      *
5208      * @param locale
5209      * @return
5210      */
getGrammarInfo(String locale)5211     public GrammarInfo getGrammarInfo(String locale) {
5212         return getGrammarInfo(locale, false);
5213     }
5214 
5215     /**
5216      * Special hack for v38; should drop seedOnly later.
5217      *
5218      * @param locale
5219      * @param seedOnly
5220      * @return
5221      */
5222     @Deprecated
getGrammarInfo(String locale, boolean seedOnly)5223     public GrammarInfo getGrammarInfo(String locale, boolean seedOnly) {
5224         for (; locale != null; locale = LocaleIDParser.getParent(locale)) {
5225             if (seedOnly && !GrammarInfo.getGrammarLocales().contains(locale)) {
5226                 continue;
5227             }
5228             GrammarInfo result = grammarLocaleToTargetToFeatureToValues.get(locale);
5229             if (result != null) {
5230                 return result;
5231             }
5232         }
5233         return null;
5234     }
5235 
hasGrammarDerivation()5236     public Set<String> hasGrammarDerivation() {
5237         return localeToGrammarDerivation.keySet();
5238     }
5239 
getGrammarDerivation(String locale)5240     public GrammarDerivation getGrammarDerivation(String locale) {
5241         for (; locale != null; locale = LocaleIDParser.getParent(locale)) {
5242             GrammarDerivation result = localeToGrammarDerivation.get(locale);
5243             if (result != null) {
5244                 return result;
5245             }
5246         }
5247         return null;
5248     }
5249 
getPersonNameOrder()5250     public Multimap<Order, String> getPersonNameOrder() {
5251         return personNameOrder;
5252     }
5253 
getUnitPrefixInfo(String prefix)5254     public UnitPrefixInfo getUnitPrefixInfo(String prefix) {
5255         return unitPrefixInfo.get(prefix);
5256     }
5257 
getUnitPrefixes()5258     public Set<String> getUnitPrefixes() {
5259         return unitPrefixInfo.keySet();
5260     }
5261 
5262     /**
5263      * Filter out deprecated items. This is more complicated than it seems. The deprecation is in
5264      * timezones.xml, eg: <type name="cathu" description="Thunder Bay, Canada" deprecated="true"
5265      * preferred="cator"/> <type name="cator" description="Toronto, Canada" alias="America/Toronto
5266      * America/Montreal Canada/Eastern America/Nipigon America/Thunder_Bay"/> We need to find the
5267      * short id's that are deprecated, put there is a problem due to
5268      * https://unicode-org.atlassian.net/browse/CLDR-17412.
5269      *
5270      * <p>America/Nipigon, America/Thunder_Bay, America/Rainy_River
5271      */
5272     Supplier<Set<String>> goodTimezones =
5273             Suppliers.memoize(
5274                     new Supplier<Set<String>>() {
5275 
5276                         @Override
5277                         public Set<String> get() {
5278                             Set<String> availableLongTz = sc.getAvailableCodes(CodeType.tzid);
5279                             Set<String> result = null;
5280                             if (true) { // hack for now
5281                                 final Set<String> hack =
5282                                         Set.of(
5283                                                 "America/Santa_Isabel",
5284                                                 "Australia/Currie",
5285                                                 "America/Yellowknife",
5286                                                 "America/Rainy_River",
5287                                                 "America/Thunder_Bay",
5288                                                 "America/Nipigon",
5289                                                 "America/Pangnirtung",
5290                                                 "Europe/Uzhgorod",
5291                                                 "Europe/Zaporozhye",
5292                                                 "Pacific/Johnston");
5293                                 result = Set.copyOf(Sets.difference(availableLongTz, hack));
5294                             } else { // TODO restore when CLDR-17412 is fixed
5295                                 Map<String, String> aliasToRegular =
5296                                         bcp47KeyToAliasToSubtype.get("tz");
5297                                 Map<String, Bcp47KeyInfo> subtypeToInfo =
5298                                         bcp47KeyToSubtypeToInfo.get("tz");
5299                                 result =
5300                                         availableLongTz.stream()
5301                                                 .filter(
5302                                                         x -> {
5303                                                             String shortId = aliasToRegular.get(x);
5304                                                             Bcp47KeyInfo info =
5305                                                                     subtypeToInfo.get(shortId);
5306                                                             System.out.println(
5307                                                                     String.format(
5308                                                                             "%s %s %s",
5309                                                                             x, shortId, info));
5310                                                             return !info.deprecated;
5311                                                         })
5312                                                 .collect(Collectors.toUnmodifiableSet());
5313                             }
5314                             return result;
5315                         }
5316                     });
5317 
getCLDRTimezoneCodes()5318     public Set<String> getCLDRTimezoneCodes() {
5319         return goodTimezones.get();
5320     }
5321 }
5322