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