1 package org.unicode.cldr.tool;
2 
3 import java.io.IOException;
4 import java.io.PrintWriter;
5 import java.util.Comparator;
6 import java.util.EnumMap;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.LinkedHashSet;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Objects;
13 import java.util.Set;
14 import java.util.TreeMap;
15 import java.util.TreeSet;
16 
17 import org.unicode.cldr.draft.FileUtilities;
18 import org.unicode.cldr.util.Builder;
19 import org.unicode.cldr.util.Builder.CBuilder;
20 import org.unicode.cldr.util.CLDRConfig;
21 import org.unicode.cldr.util.CLDRFile;
22 import org.unicode.cldr.util.CLDRFile.Status;
23 import org.unicode.cldr.util.CLDRPaths;
24 import org.unicode.cldr.util.Counter;
25 import org.unicode.cldr.util.CoverageInfo;
26 import org.unicode.cldr.util.DtdType;
27 import org.unicode.cldr.util.Factory;
28 import org.unicode.cldr.util.LanguageTagParser;
29 import org.unicode.cldr.util.Level;
30 import org.unicode.cldr.util.SupplementalDataInfo;
31 import org.unicode.cldr.util.SupplementalDataInfo.PluralType;
32 import org.unicode.cldr.util.XPathParts;
33 
34 import com.ibm.icu.impl.Relation;
35 import com.ibm.icu.impl.Row;
36 import com.ibm.icu.impl.Row.R2;
37 import com.ibm.icu.impl.Row.R3;
38 import com.ibm.icu.impl.Row.R5;
39 import com.ibm.icu.text.NumberFormat;
40 import com.ibm.icu.text.Transform;
41 
Pseudolocalizer()42 public class GenerateCoverageLevels {
43     // see ShowLocaleCoverage.java
44     private static boolean SKIP_UNCONFIRMED = true;
45     private static int SHOW_EXAMPLES = 5;
46     private static final String FILES = ".*";
47     private static final String MAIN_DIRECTORY = CLDRPaths.MAIN_DIRECTORY;// CldrUtility.SUPPLEMENTAL_DIRECTORY;
48     // //CldrUtility.MAIN_DIRECTORY;
49     private static final String COLLATION_DIRECTORY = CLDRPaths.COMMON_DIRECTORY + "/collation/";// CldrUtility.SUPPLEMENTAL_DIRECTORY;
50     // //CldrUtility.MAIN_DIRECTORY;
51     private static final String RBNF_DIRECTORY = CLDRPaths.COMMON_DIRECTORY + "/rbnf/";// CldrUtility.SUPPLEMENTAL_DIRECTORY;
52     // //CldrUtility.MAIN_DIRECTORY;
53     private static final String OUT_DIRECTORY = CLDRPaths.GEN_DIRECTORY + "/coverage/"; // CldrUtility.MAIN_DIRECTORY;
54     private static final Factory cldrFactory = Factory.make(MAIN_DIRECTORY, FILES);
55     private static final Comparator<String> attributeComparator = CLDRFile.getAttributeOrdering();
56     private static final CLDRFile english = cldrFactory.make("en", true);
57     private static SupplementalDataInfo supplementalData = CLDRConfig.getInstance().getSupplementalDataInfo();
58     // SupplementalDataInfo.getInstance(english.getSupplementalDirectory());
59     private static Set<String> defaultContents = supplementalData.getDefaultContentLocales();
60     private static Map<String, R2<List<String>, String>> languageAliasInfo = supplementalData.getLocaleAliasInfo().get(
61         "language");
62     private static LocaleFilter localeFilter = new LocaleFilter(true);
63     private static BooleanLocaleFilter nonAliasLocaleFilter = new BooleanLocaleFilter();
64 
65     private static final long COLLATION_WEIGHT = 50;
66     private static final Level COLLATION_LEVEL = Level.POSIX;
67     private static final long PLURALS_WEIGHT = 20;
68     private static final Level PLURALS_LEVEL = Level.MINIMAL;
69     private static final long RBNF_WEIGHT = 20;
70     private static final Level RBNF_LEVEL = Level.MODERATE;
71 
72     static int totalCount = 0;
73 
74     enum Inheritance {
75         actual, inherited
76     }
77 
78     public static void main(String[] args) throws IOException {
79         if (true) {
80             throw new IllegalArgumentException("See ShowLocaleCoverage (TODO: merge these).");
81         }
82         PrintWriter out = FileUtilities.openUTF8Writer(OUT_DIRECTORY, "fullpaths.txt");
83         showEnglish(out);
84         out.close();
85 
86         System.out.println("*** TODO check collations, RBNF, Transforms (if non-Latin)");
87         PrintWriter summary = FileUtilities.openUTF8Writer(OUT_DIRECTORY, "summary.txt");
88         PrintWriter samples = FileUtilities.openUTF8Writer(OUT_DIRECTORY, "samples.txt");
89         PrintWriter counts = FileUtilities.openUTF8Writer(OUT_DIRECTORY, "counts.txt");
90         summarizeCoverage(summary, samples, counts);
91         summary.close();
92         samples.close();
93         counts.close();
94     }
95 
96     private static void showEnglish(PrintWriter out) throws IOException {
97         CLDRFile cldrFile = english;
98         String locale = "en";
99         Set<String> sorted = Builder.with(new TreeSet<String>()).addAll(cldrFile.iterator())
100             .addAll(cldrFile.getExtraPaths()).get();
101         Set<R3<Level, String, Inheritance>> items = new TreeSet<>(new RowComparator());
102         for (String path : sorted) {
103             if (path.endsWith("/alias")) {
104                 continue;
105             }
106             String source = cldrFile.getSourceLocaleID(path, null);
107             Inheritance inherited = !source.equals(locale) ? Inheritance.inherited : Inheritance.actual;
108 
109 //            Level level = supplementalData.getCoverageLevel(path, locale);
110             Level level = CLDRConfig.getInstance().getCoverageInfo().getCoverageLevel(path, locale);
111 
112             items.add(Row.of(level, path, inherited));
113         }
114 
115         PathStore store = new PathStore();
116         for (R3<Level, String, Inheritance> item : items) {
117             show(out, item, store);
118         }
119         show(out, null, store);
120     }
121 
122     private static class RowComparator implements Comparator<R3<Level, String, Inheritance>> {
123 
124         @Override
125         public int compare(R3<Level, String, Inheritance> o1, R3<Level, String, Inheritance> o2) {
126             int result = o1.get0().compareTo(o2.get0());
127             if (result != 0) return result;
128             result = CLDRFile.getComparator(DtdType.ldml).compare(o1.get1(), o2.get1());
129             if (result != 0) return result;
130             result = o1.get2().compareTo(o2.get2());
131             return result;
132         }
133     }
134 
135     private static void show(PrintWriter out, R3<Level, String, Inheritance> next, PathStore store) {
136         R5<Level, Inheritance, Integer, String, TreeMap<String, Relation<String, String>>> results = store.add(next);
137         if (results != null) {
138             Level lastLevel = results.get0();
139             int count = results.get2();
140             String path = results.get3();
141             totalCount += count;
142             try {
143                 StringBuilder resultString = new StringBuilder();
144                 TreeMap<String, Relation<String, String>> types = results.get4();
145                 for (String key : types.keySet()) {
146                     Relation<String, String> attr_values = types.get(key);
147                     for (String attr : attr_values.keySet()) {
148                         resultString.append("\t").append(key + ":\u200b" + attr).append("=\u200b")
149                             .append(attr_values.getAll(attr));
150                     }
151                 }
152                 out.println(lastLevel.ordinal()
153                     + "\t" + lastLevel
154                     + "\t" + count
155                     + "\t" + totalCount
156                     + "\t" + path + resultString);
157             } catch (RuntimeException e) {
158                 throw e;
159             }
160         }
161     }
162 
163     static class PathStore {
164         XPathParts lastParts = new XPathParts();
165         XPathParts nextParts = new XPathParts();
166         Level lastLevel;
167         Inheritance lastInheritance;
168         int count = 0;
169 
170         TreeMap<String, Relation<String, String>> differences = new TreeMap<>();
171 
172         R5<Level, Inheritance, Integer, String, TreeMap<String, Relation<String, String>>> add(
173             R3<Level, String, Inheritance> next) {
174             count++;
175             boolean wasNull = lastLevel == null;
176             Level level = null;
177             String path = null;
178             Inheritance inherited = null;
179 
180             if (next != null) {
181                 level = next.get0();
182                 path = next.get1();
183                 inherited = next.get2();
184 
185                 nextParts = setNewParts(path);
186                 if (sameElements()) {
187                     addDifferences();
188                     return null;
189                 }
190             }
191             // clear the values
192             clean(differences);
193             R5<Level, Inheritance, Integer, String, TreeMap<String, Relation<String, String>>> results = Row.of(
194                 lastLevel, lastInheritance, count - 1, lastParts.toString().replace("/", "\u200B/"), differences);
195             lastParts = nextParts;
196             differences = new TreeMap<>();
197             nextParts = new XPathParts();
198             lastLevel = level;
199             lastInheritance = inherited;
200             count = 1;
201             if (wasNull) return null;
202             return results;
203         }
204 
205         private void clean(TreeMap<String, Relation<String, String>> differences2) {
206             for (int i = 0; i < lastParts.size(); ++i) {
207                 String element = lastParts.getElement(i);
208                 Relation<String, String> attr_values = differences2.get(element);
209                 if (attr_values == null) continue;
210                 for (String attr : attr_values.keySet()) {
211                     lastParts.putAttributeValue(i, attr, "*");
212                 }
213             }
214         }
215 
216         private XPathParts setNewParts(String path) {
217             XPathParts parts = XPathParts.getFrozenInstance(path).cloneAsThawed(); // not frozen, for removeElement
218             if (path.startsWith("//ldml/dates/timeZoneNames/metazone")
219                 || path.startsWith("//ldml/dates/timeZoneNames/zone")) {
220                 String element = nextParts.getElement(-1);
221                 nextParts.setElement(-1, "zoneChoice");
222                 nextParts.putAttributeValue(-1, "type", element);
223                 element = nextParts.getElement(-2);
224                 nextParts.setElement(-2, "zoneLength");
225                 nextParts.putAttributeValue(-2, "type", element);
226             } else if (path.startsWith("//ldml/dates/calendars/calendar")) {
227                 if (!"gregorian".equals(parts.getAttributeValue(3, "type"))) {
228                     for (int i = parts.size() - 1; i > 3; --i) {
229                         parts.removeElement(i);
230                     }
231                     parts.addElement("*");
232                 }
233             }
234             return parts;
235         }
236 
237         private void addDifferences() {
238             for (int i = 0; i < lastParts.size(); ++i) {
239                 Map<String, String> lastAttrs = lastParts.getAttributes(i);
240                 Map<String, String> nextAttrs = nextParts.getAttributes(i);
241                 if (!lastAttrs.equals(nextAttrs)) {
242                     String element = lastParts.getElement(i);
243                     Relation<String, String> old = differences.get(element);
244                     if (old == null) {
245                         old = Relation.of(new TreeMap<String, Set<String>>(attributeComparator), TreeSet.class);
246                         differences.put(element, old);
247                     }
248                     Set<String> union = Builder.with(new TreeSet<String>()).addAll(lastAttrs.keySet())
249                         .addAll(nextAttrs.keySet()).get();
250                     for (String key : union) {
251                         String lastValue = lastAttrs.get(key);
252                         String nextValue = nextAttrs.get(key);
253                         if (!Objects.equals(lastValue, nextValue)) {
254                             if (lastValue != null) old.put(key, lastValue);
255                             if (nextValue != null) old.put(key, nextValue);
256                         }
257                     }
258                 }
259             }
260         }
261 
262         private boolean sameElements() {
263             if (lastParts.size() != nextParts.size()) return false;
264             for (int i = 0; i < lastParts.size(); ++i) {
265                 if (!lastParts.getElement(i).equals(nextParts.getElement(i))) return false;
266             }
267             return true;
268         }
269 
270     }
271 
272     private static void summarizeCoverage(PrintWriter summary, PrintWriter samples2, PrintWriter counts) {
273         final Factory cldrFactory = Factory.make(MAIN_DIRECTORY, FILES);
274         final Factory collationFactory = Factory.make(COLLATION_DIRECTORY, FILES);
275         final Factory rbnfFactory = Factory.make(RBNF_DIRECTORY, FILES);
276 
277         // CLDRFile sd = CLDRFile.make(CLDRFile.SUPPLEMENTAL_NAME, CldrUtility.SUPPLEMENTAL_DIRECTORY, true);
278         // CLDRFile smd = CLDRFile.make(CLDRFile.SUPPLEMENTAL_METADATA, CldrUtility.SUPPLEMENTAL_DIRECTORY, true);
279         //
280         // CoverageLevel.init(sd, smd);
281 
282         NumberFormat percent = NumberFormat.getPercentInstance();
283         NumberFormat decimal = NumberFormat.getInstance();
284         decimal.setGroupingUsed(true);
285         decimal.setMaximumFractionDigits(2);
286         percent.setMaximumFractionDigits(2);
287         NumberFormat integer = NumberFormat.getIntegerInstance();
288         Set<String> localesFound = new TreeSet<>();
289 
290         // get list of locales
291         LocaleLevelData mapLevelData = new LocaleLevelData();
292         TreeSet<String> mainAvailableSource = new TreeSet<>(cldrFactory.getAvailable());
293         TreeSet<String> mainAvailable = new TreeSet<>();
294         Relation<String, String> localeToVariants = Relation.of(new HashMap(), HashSet.class);
295         for (String locale : mainAvailableSource) {
296             if (localeFilter.skipLocale(locale, localeToVariants)) {
297                 continue;
298             }
299             mainAvailable.add(locale);
300         }
301 
302         System.out.println("gathering rbnf data");
303         Set<String> ordinals = new TreeSet<>();
304         Set<String> spellout = new TreeSet<>();
305         localesFound.clear();
306         for (String locale : rbnfFactory.getAvailable()) {
307             if (localeFilter.skipLocale(locale, null)) continue;
308             System.out.println(locale + "\t" + english.getName(locale));
309             getRBNFData(locale, rbnfFactory.make(locale, true), ordinals, spellout, localesFound);
310         }
311         markData("RBNF-Ordinals", ordinals, mapLevelData, mainAvailable, RBNF_LEVEL, RBNF_WEIGHT,
312             Row.of("//ldml/rbnf/ordinals", "?"));
313         markData("RBNF-Spellout", spellout, mapLevelData, mainAvailable, RBNF_LEVEL, RBNF_WEIGHT,
314             Row.of("//ldml/rbnf/spellout", "?"));
315         if (localesFound.size() != 0) {
316             System.out.println("Other rbnf found:\t" + localesFound);
317         }
318 
319         System.out.println("gathering plural data");
320         localesFound = new TreeSet<>(supplementalData.getPluralLocales(PluralType.cardinal));
321         markData("Plurals", localesFound, mapLevelData, mainAvailable, PLURALS_LEVEL, PLURALS_WEIGHT,
322             Row.of("//supplementalData/plurals", "UCA"));
323 
324         System.out.println("gathering collation data");
325         localesFound.clear();
326         for (String locale : collationFactory.getAvailable()) {
327             if (localeFilter.skipLocale(locale, null)) continue;
328             System.out.println(locale + "\t" + english.getName(locale));
329             getCollationData(locale, collationFactory.make(locale, true), localesFound);
330         }
331         markData("Collation", localesFound, mapLevelData, mainAvailable, COLLATION_LEVEL, COLLATION_WEIGHT,
332             Row.of("//ldml/collations", "UCA"));
333 
334         System.out.println("gathering main data");
335         for (String locale : mainAvailable) {
336             System.out.println(locale + "\t" + english.getName(locale));
337             LevelData levelData = mapLevelData.get(locale);
338             getMainData(locale, levelData, cldrFactory.make(locale, true));
339         }
340 
341         System.out.println("printing data");
342         String summaryLineHeader = "Code\tName\tWeighted Missing:\tFound:\tScore:";
343         summary.println(summaryLineHeader);
344         LanguageTagParser languageTagParser = new LanguageTagParser();
345 
346         StringBuilder header = new StringBuilder();
347         //EnumSet<Level> skipLevels = EnumSet.of(Level.CORE, Level.POSIX, Level.COMPREHENSIVE, Level.OPTIONAL);
348         for (String locale : mapLevelData.keySet()) {
349             LevelData levelData = mapLevelData.get(locale);
350             String max = LikelySubtags.maximize(locale, supplementalData.getLikelySubtags());
351             String lang = languageTagParser.set(max).getLanguage();
352             String script = languageTagParser.set(max).getScript();
353 
354             Counter<Level> missing = levelData.missing;
355             Counter<Level> found = levelData.found;
356             Relation<Level, R2<String, String>> samples = levelData.samples;
357             StringBuilder countLine = new StringBuilder(
358                 script
359                     + "\t" + english.getName(CLDRFile.SCRIPT_NAME, script)
360                     + "\t" + lang
361                     + "\t" + english.getName(CLDRFile.LANGUAGE_NAME, lang));
362             if (header != null) {
363                 header.append("Code\tScript\tCode\tLocale");
364             }
365             // Now print the information
366             samples2.println();
367             samples2.println(locale + "\t" + english.getName(locale));
368             double weightedFound = 0;
369             double weightedMissing = 0;
370             long missingCountTotal = 0;
371             long foundCountTotal = 0;
372 
373             for (Level level : Level.values()) {
374                 if (level == Level.UNDETERMINED) {
375                     continue;
376                 }
377                 long missingCount = missing.get(level);
378                 missingCountTotal += missingCount;
379                 long foundCount = found.get(level);
380                 foundCountTotal += foundCount;
381                 weightedFound += foundCount * level.getValue();
382                 weightedMissing += missingCount * level.getValue();
383 
384                 countLine.append('\t').append(missingCountTotal).append('\t').append(foundCountTotal);
385                 if (header != null) {
386                     header.append("\t" + level + "-Missing\tFound");
387                 }
388 
389                 samples2.println(level + "\tMissing:\t" + integer.format(missingCount) + "\tFound:\t"
390                     + integer.format(foundCount)
391                     + "\tScore:\t" + percent.format(foundCount / (double) (foundCount + missingCount))
392                     + "\tLevel-Value:\t" + level.getValue());
393                 Set<R2<String, String>> samplesAlready = samples.getAll(level);
394                 if (samplesAlready != null) {
395                     for (R2<String, String> row : samplesAlready) {
396                         samples2.println("\t" + row);
397                     }
398                     if (samplesAlready.size() >= SHOW_EXAMPLES) {
399                         samples2.println("\t...");
400                     }
401                 }
402             }
403             int base = Level.POSIX.getValue();
404             double foundCount = weightedFound / base;
405             double missingCount = weightedMissing / base;
406             String summaryLine = "Weighted Missing:\t" + decimal.format(missingCount) + "\tFound:\t"
407                 + decimal.format(foundCount) + "\tScore:\t"
408                 + percent.format(foundCount / (foundCount + missingCount));
409             String summaryLine2 = "\t" + decimal.format(missingCount) + "\t" + decimal.format(foundCount) + "\t"
410                 + percent.format(foundCount / (foundCount + missingCount));
411             samples2.println(summaryLine);
412             summary.println(locale + "\t" + english.getName(locale) + "\t" + summaryLine2);
413             if (header != null) {
414                 counts.println(header);
415                 header = null;
416             }
417             counts.println(countLine);
418         }
419     }
420 
421     private static void getRBNFData(String locale, CLDRFile cldrFile, Set<String> ordinals, Set<String> spellout,
422         Set<String> others) {
423         for (String path : cldrFile) {
424             if (path.endsWith("/alias")) {
425                 continue;
426             }
427             if (!path.contains("rulesetGrouping")) {
428                 continue;
429             }
430             if (skipUnconfirmed(path)) {
431                 continue;
432             }
433             XPathParts parts = XPathParts.getFrozenInstance(path);
434             String ruleSetGrouping = parts.getAttributeValue(2, "type");
435             if (ruleSetGrouping.equals("SpelloutRules")) {
436                 spellout.add(locale);
437             } else if (ruleSetGrouping.equals("OrdinalRules")) {
438                 ordinals.add(locale);
439             } else {
440                 others.add(ruleSetGrouping);
441             }
442         }
443     }
444 
445     private static void markData(String title, Set<String> localesFound, LocaleLevelData mapLevelData,
446         TreeSet<String> mainAvailable, Level level, long weight, R2<String, String> samples) {
447         if (!mainAvailable.containsAll(localesFound)) {
448             final CBuilder<String, TreeSet<String>> cb = Builder.with(new TreeSet<String>());
449             System.out.println(title + " Locales that are not in main: " + cb
450                 .addAll(localesFound)
451                 .removeAll(mainAvailable)
452                 .filter(nonAliasLocaleFilter).get());
453         }
454         for (String locale : mainAvailable) {
455             if (localesFound.contains(locale)) {
456                 mapLevelData.get(locale).found.add(level, weight);
457             } else {
458                 System.out.println(locale + "\t" + english.getName(locale) + "\t" + "missing " + title);
459                 mapLevelData.get(locale).missing.add(level, weight);
460                 mapLevelData.get(locale).samples.put(level, samples);
461             }
462         }
463     }
464 
465     enum LocaleStatus {
466         BASE, ALIAS, VARIANT, DEFAULT_CONTENTS
467     }
468 
469     private static class LocaleFilter implements Transform<String, LocaleStatus> {
470         private final LanguageTagParser ltp = new LanguageTagParser();
471         private final boolean checkAliases;
472 
473         public LocaleFilter(boolean checkAliases) {
474             this.checkAliases = checkAliases;
475         }
476 
477         private boolean skipLocale(String locale, Relation<String, String> localeToVariants) {
478             LocaleStatus result = transform(locale);
479             if (localeToVariants != null) {
480                 localeToVariants.put(ltp.getLanguageScript(), ltp.getRegion());
481             }
482             return result != LocaleStatus.BASE;
483         }
484 
485         @Override
486         public LocaleStatus transform(String locale) {
487             ltp.set(locale);
488             if (checkAliases) {
489                 String language = ltp.getLanguage();
490                 if (languageAliasInfo.get(language) != null) {
491                     return LocaleStatus.ALIAS;
492                 }
493             }
494             if (ltp.getRegion().length() != 0 || !ltp.getVariants().isEmpty()) {
495                 // skip country locales, variants
496                 return LocaleStatus.VARIANT;
497             }
498             if (defaultContents.contains(locale)) {
499                 return LocaleStatus.DEFAULT_CONTENTS;
500             }
501             return LocaleStatus.BASE;
502         }
503     }
504 
505     private static class BooleanLocaleFilter implements Transform<String, Boolean> {
506         private final LocaleFilter filter = new LocaleFilter(false);
507 
508         @Override
509         public Boolean transform(String locale) {
510             return filter.transform(locale) == LocaleStatus.BASE ? Boolean.TRUE : Boolean.FALSE;
511         }
512     }
513 
514     private static void getCollationData(String locale, CLDRFile cldrFile, Set<String> localesFound) {
515         for (String path : cldrFile) {
516             if (path.endsWith("/alias")) {
517                 continue;
518             }
519             if (!path.contains("collations")) {
520                 continue;
521             }
522             if (skipUnconfirmed(path)) {
523                 continue;
524             }
525             localesFound.add(locale);
526 
527             String fullPath = cldrFile.getFullXPath(path);
528             if (fullPath == null) {
529                 fullPath = path;
530             }
531             XPathParts parts = XPathParts.getFrozenInstance(fullPath);
532             String validSubLocales = parts.getAttributeValue(1, "validSubLocales");
533             if (validSubLocales != null) {
534                 String[] sublocales = validSubLocales.split("\\s+");
535                 for (String sublocale : sublocales) {
536                     if (localeFilter.skipLocale(locale, null)) continue;
537                     localesFound.add(sublocale);
538                 }
539             }
540             break;
541         }
542     }
543 
544     public static boolean skipUnconfirmed(String path) {
545         return SKIP_UNCONFIRMED && (path.contains("unconfirmed") || path.contains("provisional"));
546     }
547 
548     private static void getMainData(String locale, LevelData levelData, CLDRFile cldrFile) {
549         Status status = new Status();
550         Set<String> sorted = Builder.with(new TreeSet<String>()).addAll(cldrFile.iterator())
551             .addAll(cldrFile.getExtraPaths()).get();
552         CoverageInfo coverageInfo = CLDRConfig.getInstance().getCoverageInfo();
553         for (String path : sorted) {
554             if (path.endsWith("/alias")) {
555                 continue;
556             }
557 
558             String fullPath = cldrFile.getFullXPath(path);
559             String source = cldrFile.getSourceLocaleID(path, status);
560             Inheritance inherited = !source.equals(locale) || skipUnconfirmed(path)
561                 ? Inheritance.inherited
562                 : Inheritance.actual;
563 
564 //            Level level = sdi.getCoverageLevel(fullPath, locale);
565             Level level = coverageInfo.getCoverageLevel(fullPath, locale);
566             if (inherited == Inheritance.actual) {
567                 levelData.found.add(level, 1);
568             } else {
569                 levelData.missing.add(level, 1);
570                 if (SHOW_EXAMPLES > 0) {
571                     Set<R2<String, String>> samplesAlready = levelData.samples.getAll(level);
572                     if (samplesAlready == null || samplesAlready.size() < SHOW_EXAMPLES) {
573                         levelData.samples.put(level, Row.of(path, cldrFile.getStringValue(path)));
574                     }
575                 }
576             }
577         }
578     }
579 
580     static class LevelData {
581         Counter<Level> missing = new Counter<>();
582         Relation<Level, R2<String, String>> samples = Relation.of(new EnumMap<Level, Set<R2<String, String>>>(
583             Level.class), LinkedHashSet.class);
584         Counter<Level> found = new Counter<>();
585     }
586 
587     static class LocaleLevelData {
588         Map<String, LevelData> locale_levelData = new TreeMap<>();
589 
590         public LevelData get(String locale) {
591             if (locale.equals("zh_Hans") || locale.equals("iw")) {
592                 throw new IllegalArgumentException();
593             }
594             LevelData result = locale_levelData.get(locale);
595             if (result == null) {
596                 locale_levelData.put(locale, result = new LevelData());
597             }
598             return result;
599         }
600 
601         public Set<String> keySet() {
602             return locale_levelData.keySet();
603         }
604     }
605 
606 }
607