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