• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  ******************************************************************************
3  * Copyright (C) 2005-2014, International Business Machines Corporation and   *
4  * others. All Rights Reserved.                                               *
5  ******************************************************************************
6  */
7 
8 package org.unicode.cldr.test;
9 
10 import java.io.BufferedReader;
11 import java.io.IOException;
12 import java.text.ParsePosition;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.HashMap;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.TreeSet;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24 
25 import org.unicode.cldr.test.CheckCLDR.CheckStatus.Subtype;
26 import org.unicode.cldr.util.CLDRFile;
27 import org.unicode.cldr.util.CLDRInfo.CandidateInfo;
28 import org.unicode.cldr.util.CLDRInfo.PathValueInfo;
29 import org.unicode.cldr.util.CLDRInfo.UserInfo;
30 import org.unicode.cldr.util.CLDRLocale;
31 import org.unicode.cldr.util.CldrUtility;
32 import org.unicode.cldr.util.Factory;
33 import org.unicode.cldr.util.InternalCldrException;
34 import org.unicode.cldr.util.Level;
35 import org.unicode.cldr.util.PathHeader;
36 import org.unicode.cldr.util.PathHeader.SurveyToolStatus;
37 import org.unicode.cldr.util.PatternCache;
38 import org.unicode.cldr.util.RegexFileParser;
39 import org.unicode.cldr.util.RegexFileParser.RegexLineParser;
40 import org.unicode.cldr.util.StandardCodes;
41 import org.unicode.cldr.util.TransliteratorUtilities;
42 import org.unicode.cldr.util.VoteResolver;
43 import org.unicode.cldr.util.VoteResolver.Status;
44 
45 import com.google.common.collect.ImmutableSet;
46 import com.ibm.icu.dev.util.ElapsedTimer;
47 import com.ibm.icu.impl.Row.R3;
48 import com.ibm.icu.text.ListFormatter;
49 import com.ibm.icu.text.MessageFormat;
50 import com.ibm.icu.text.Transliterator;
51 import com.ibm.icu.util.ICUUncheckedIOException;
52 
53 /**
54  * This class provides a foundation for both console-driven CLDR tests, and
55  * Survey Tool Tests.
56  * <p>
57  * To add a test, subclass CLDRFile and override handleCheck and possibly setCldrFileToCheck. Then put the test into
58  * getCheckAll.
59  * <p>
60  * To use the test, take a look at the main in ConsoleCheckCLDR. Note that you need to call setDisplayInformation with
61  * the CLDRFile for the locale that you want the display information (eg names for codes) to be in.<br>
62  * Some options are passed in the Map options. Examples: boolean SHOW_TIMES = options.containsKey("SHOW_TIMES"); // for
63  * printing times for doing setCldrFileToCheck.
64  * <p>
65  * Some errors/warnings will be explicitly filtered out when calling CheckCLDR's check() method.
66  * The full list of filters can be found in org/unicode/cldr/util/data/CheckCLDR-exceptions.txt.
67  *
68  * @author davis
69  */
70 abstract public class CheckCLDR {
71 
72     public static final boolean LIMITED_SUBMISSION = false; // TODO represent differently
73 
74     private static CLDRFile displayInformation;
75 
76     private CLDRFile cldrFileToCheck;
77     private CLDRFile englishFile = null;
78 
79     private boolean skipTest = false;
80     private Phase phase;
81     private Map<Subtype, List<Pattern>> filtersForLocale = new HashMap<>();
82 
83     public enum InputMethod {
84         DIRECT, BULK
85     }
86 
87     public enum StatusAction {
88         /**
89          * Allow voting and add new values (in Change column).
90          */
91         ALLOW,
92         /**
93          * Allow voting and ticket (in Change column).
94          */
95         ALLOW_VOTING_AND_TICKET,
96         /**
97          * Allow voting but no add new values (in Change column).
98          */
99         ALLOW_VOTING_BUT_NO_ADD,
100         /**
101          * Only allow filing a ticket.
102          */
103         ALLOW_TICKET_ONLY,
104         /**
105          * Disallow (for various reasons)
106          */
107         FORBID_ERRORS(true),
108         FORBID_READONLY(true),
109         FORBID_UNLESS_DATA_SUBMISSION(true),
110         FORBID_NULL(true),
111         FORBID_ROOT(true),
112         FORBID_CODE(true),
113         FORBID_PERMANENT_WITHOUT_FORUM(true);
114 
115         private final boolean isForbidden;
116 
StatusAction()117         private StatusAction() {
118             isForbidden = false;
119         }
120 
StatusAction(boolean isForbidden)121         private StatusAction(boolean isForbidden) {
122             this.isForbidden = isForbidden;
123         }
124 
isForbidden()125         public boolean isForbidden() {
126             return isForbidden;
127         }
128 
canShow()129         public boolean canShow() {
130             return !isForbidden;
131         }
132     }
133 
134     private static final HashMap<String, Phase> PHASE_NAMES = new HashMap<>();
135 
136     public enum Phase {
137         BUILD, SUBMISSION, VETTING, FINAL_TESTING("RESOLUTION");
Phase(String... alternateName)138         Phase(String... alternateName) {
139             for (String name : alternateName) {
140                 PHASE_NAMES.put(name.toUpperCase(Locale.ENGLISH), this);
141             }
142         }
143 
forString(String value)144         public static Phase forString(String value) {
145             if (value == null) {
146                 return org.unicode.cldr.util.CLDRConfig.getInstance().getPhase();
147             }
148             value = value.toUpperCase(Locale.ENGLISH);
149             Phase result = PHASE_NAMES.get(value);
150             return result != null ? result
151                 : Phase.valueOf(value);
152         }
153 
154         /**
155          * Return whether or not to show a row, and if so, how.
156          *
157          * @param pathValueInfo
158          * @param inputMethod
159          * @param ph the path header
160          * @param userInfo
161          *            null if there is no userInfo (nobody logged in).
162          * @return
163          */
getShowRowAction( PathValueInfo pathValueInfo, InputMethod inputMethod, PathHeader ph, UserInfo userInfo )164         public StatusAction getShowRowAction(
165             PathValueInfo pathValueInfo,
166             InputMethod inputMethod,
167             PathHeader ph,
168             UserInfo userInfo // can get voterInfo from this.
169             ) {
170 
171             PathHeader.SurveyToolStatus status = ph.getSurveyToolStatus();
172             /*
173              * Always forbid DEPRECATED items - don't show.
174              *
175              * Currently, bulk submission and TC voting are allowed even for SurveyToolStatus.HIDE,
176              * but not for SurveyToolStatus.DEPRECATED. If we ever want to treat HIDE and DEPRECATED
177              * the same here, then it would be simpler to call ph.shouldHide which is true for both.
178              */
179             if (status == SurveyToolStatus.DEPRECATED) {
180                 return StatusAction.FORBID_READONLY;
181             }
182 
183             if (status == SurveyToolStatus.READ_ONLY) {
184                 return StatusAction.ALLOW_TICKET_ONLY;
185             }
186 
187 
188             // always forbid bulk import except in data submission.
189             if (inputMethod == InputMethod.BULK && this != Phase.SUBMISSION) {
190                 return StatusAction.FORBID_UNLESS_DATA_SUBMISSION;
191             }
192 
193             // if TC+, allow anything else, even suppressed items and errors
194             if (userInfo != null && userInfo.getVoterInfo().getLevel().compareTo(VoteResolver.Level.tc) >= 0) {
195                 return StatusAction.ALLOW;
196             }
197 
198             if (status == SurveyToolStatus.HIDE) {
199                 return StatusAction.FORBID_READONLY;
200             }
201 
202             CandidateInfo winner = pathValueInfo.getCurrentItem();
203             ValueStatus valueStatus = getValueStatus(winner, ValueStatus.NONE, null);
204 
205             // if limited submission, and winner doesn't have an error, limit the values
206 
207             if (LIMITED_SUBMISSION) {
208                 if (!SubmissionLocales.allowEvenIfLimited(pathValueInfo.getLocale().toString(), pathValueInfo.getXpath(), valueStatus == ValueStatus.ERROR, pathValueInfo.getBaselineStatus() == Status.missing)) {
209                     return StatusAction.FORBID_READONLY;
210                 }
211             }
212 
213             if (this == Phase.SUBMISSION) {
214                 return (ph.canReadAndWrite())
215                     ? StatusAction.ALLOW
216                         : StatusAction.ALLOW_VOTING_AND_TICKET;
217             }
218 
219             // We are in vetting, not in submission
220 
221             // Only allow ADD if we have an error or warning
222             // Only check winning value for errors/warnings per ticket #8677
223             if (valueStatus != ValueStatus.NONE) {
224                 return (ph.canReadAndWrite())
225                     ? StatusAction.ALLOW
226                         : StatusAction.ALLOW_VOTING_AND_TICKET;
227             }
228 
229             // No warnings, so allow just voting.
230             return StatusAction.ALLOW_VOTING_BUT_NO_ADD;
231         }
232 
233         /**
234          * getAcceptNewItemAction. MUST only be called if getShowRowAction(...).canShow()
235          * TODO Consider moving Phase, StatusAction, etc into CLDRInfo.
236          *
237          * @param enteredValue
238          *            If null, means an abstention.
239          *            If voting for an existing value, pathValueInfo.getValues().contains(enteredValue) MUST be true
240          * @param pathValueInfo
241          * @param inputMethod
242          * @param status
243          * @param userInfo
244          * @return
245          */
getAcceptNewItemAction( CandidateInfo enteredValue, PathValueInfo pathValueInfo, InputMethod inputMethod, PathHeader ph, UserInfo userInfo )246         public StatusAction getAcceptNewItemAction(
247             CandidateInfo enteredValue,
248             PathValueInfo pathValueInfo,
249             InputMethod inputMethod,
250             PathHeader ph,
251             UserInfo userInfo // can get voterInfo from this.
252             ) {
253             if (!ph.canReadAndWrite()) {
254                 return StatusAction.FORBID_READONLY;
255             }
256 
257             // only logged in users can add items.
258             if (userInfo == null) {
259                 return StatusAction.FORBID_ERRORS;
260             }
261 
262             // we can always abstain
263             if (enteredValue == null) {
264                 return StatusAction.ALLOW;
265             }
266 
267             // if TC+, allow anything else, even suppressed items and errors
268             if (userInfo.getVoterInfo().getLevel().compareTo(VoteResolver.Level.tc) >= 0) {
269                 return StatusAction.ALLOW;
270             }
271 
272             // Disallow errors.
273             ValueStatus valueStatus = getValueStatus(enteredValue, ValueStatus.NONE, CheckStatus.crossCheckSubtypes);
274             if (valueStatus == ValueStatus.ERROR) {
275                 return StatusAction.FORBID_ERRORS;
276             }
277 
278             // Allow items if submission
279             if (this == Phase.SUBMISSION) {
280                 return StatusAction.ALLOW;
281             }
282 
283             // Voting for an existing value is ok
284             valueStatus = ValueStatus.NONE;
285             for (CandidateInfo value : pathValueInfo.getValues()) {
286                 if (value == enteredValue) {
287                     return StatusAction.ALLOW;
288                 }
289                 valueStatus = getValueStatus(value, valueStatus, CheckStatus.crossCheckSubtypes);
290             }
291 
292             // If there were any errors/warnings on other values, allow
293             if (valueStatus != ValueStatus.NONE) {
294                 return StatusAction.ALLOW;
295             }
296 
297             // Otherwise (we are vetting, but with no errors or warnings)
298             // DISALLOW NEW STUFF
299 
300             return StatusAction.FORBID_UNLESS_DATA_SUBMISSION;
301         }
302 
303         public enum ValueStatus {
304             ERROR, WARNING, NONE
305         }
306 
getValueStatus(CandidateInfo value, ValueStatus previous, Set<Subtype> changeErrorToWarning)307         public ValueStatus getValueStatus(CandidateInfo value, ValueStatus previous, Set<Subtype> changeErrorToWarning) {
308             if (previous == ValueStatus.ERROR || value == null) {
309                 return previous;
310             }
311 
312             for (CheckStatus item : value.getCheckStatusList()) {
313                 CheckStatus.Type type = item.getType();
314                 if (type.equals(CheckStatus.Type.Error)) {
315                     if (changeErrorToWarning != null && changeErrorToWarning.contains(item.getSubtype())) {
316                         return ValueStatus.WARNING;
317                     } else {
318                         return ValueStatus.ERROR;
319                     }
320                 } else if (type.equals(CheckStatus.Type.Warning)) {
321                     previous = ValueStatus.WARNING;
322                 }
323             }
324             return previous;
325         }
326     }
327 
328     public static final class Options implements Comparable<Options> {
329 
330         public enum Option {
331             locale,
332             CoverageLevel_requiredLevel("CoverageLevel.requiredLevel"),
333             CoverageLevel_localeType("CoverageLevel.localeType"),
334             SHOW_TIMES,
335             phase,
336             lgWarningCheck,
337             CheckCoverage_skip("CheckCoverage.skip"),
338             exemplarErrors;
339 
340             private String key;
341 
getKey()342             public String getKey() {
343                 return key;
344             }
345 
Option(String key)346             Option(String key) {
347                 this.key = key;
348             }
349 
Option()350             Option() {
351                 this.key = name();
352             }
353         }
354 
355         private static StandardCodes sc = StandardCodes.make();
356 
357         private final boolean DEBUG_OPTS = false;
358 
359         String options[] = new String[Option.values().length];
360         CLDRLocale locale = null;
361 
362         private final String key; // for fast compare
363 
364         /**
365          * Adopt some other map
366          * @param fromOptions
367          */
Options(Map<String, String> fromOptions)368         public Options(Map<String, String> fromOptions) {
369             clear();
370             setAll(fromOptions);
371             key = null; // no key = slow compare
372         }
373 
setAll(Map<String, String> fromOptions)374         private void setAll(Map<String, String> fromOptions) {
375             for (Map.Entry<String, String> e : fromOptions.entrySet()) {
376                 set(e.getKey(), e.getValue());
377             }
378         }
379 
380         /**
381          * @param key
382          * @param value
383          */
set(String key, String value)384         public void set(String key, String value) {
385             // TODO- cache the map
386             for (Option o : Option.values()) {
387                 if (o.getKey().equals(key)) {
388                     set(o, value);
389                     return;
390                 }
391             }
392             throw new IllegalArgumentException("Unknown CLDR option: '" + key + "' - valid keys are: " + Options.getValidKeys());
393         }
394 
getValidKeys()395         private static String getValidKeys() {
396             Set<String> allkeys = new TreeSet<>();
397             for (Option o : Option.values()) {
398                 allkeys.add(o.getKey());
399             }
400             return ListFormatter.getInstance().format(allkeys);
401         }
402 
Options()403         public Options() {
404             clear();
405             key = "".intern(); // null Options.
406         }
407 
408         /**
409          * Deep clone
410          * @param options2
411          */
Options(Options options2)412         public Options(Options options2) {
413             this.options = Arrays.copyOf(options2.options, options2.options.length);
414             this.key = options2.key;
415         }
416 
Options(CLDRLocale locale, CheckCLDR.Phase testPhase, String requiredLevel, String localeType)417         public Options(CLDRLocale locale, CheckCLDR.Phase testPhase, String requiredLevel, String localeType) {
418             this.locale = locale;
419             options = new String[Option.values().length];
420             StringBuilder sb = new StringBuilder();
421             set(Option.locale, locale.getBaseName());
422             sb.append(locale.getBaseName()).append('/');
423             set(Option.CoverageLevel_requiredLevel, requiredLevel);
424             sb.append(requiredLevel).append('/');
425             set(Option.CoverageLevel_localeType, localeType);
426             sb.append(localeType).append('/');
427             set(Option.phase, testPhase.name().toLowerCase());
428             sb.append(localeType).append('/');
429             key = sb.toString().intern();
430         }
431 
432         @Override
clone()433         public Options clone() {
434             return new Options(this);
435         }
436 
437         @Override
equals(Object other)438         public boolean equals(Object other) {
439             if (this == other) return true;
440             if (!(other instanceof Options)) return false;
441             if (this.key != null && ((Options) other).key != null) {
442                 return (this.key == ((Options) other).key);
443             } else {
444                 return this.compareTo((Options) other) == 0;
445             }
446         }
447 
clear()448         private Options clear() {
449             for (int i = 0; i < options.length; i++) {
450                 options[i] = null;
451             }
452             return this;
453         }
454 
set(Option o, String v)455         private Options set(Option o, String v) {
456             options[o.ordinal()] = v;
457             if (DEBUG_OPTS) System.err.println("Setting " + o + " = " + v);
458             return this;
459         }
460 
get(Option o)461         public String get(Option o) {
462             final String v = options[o.ordinal()];
463             if (DEBUG_OPTS) System.err.println("Getting " + o + " = " + v);
464             return v;
465         }
466 
getLocale()467         public CLDRLocale getLocale() {
468             if (locale != null) return locale;
469             return CLDRLocale.getInstance(get(Option.locale));
470         }
471 
472         /**
473          * Get the required coverage level for the specified locale, for this CheckCLDR object.
474          *
475          * @param localeID
476          * @return the Level
477          *
478          * Called by CheckCoverage.setCldrFileToCheck and CheckDates.setCldrFileToCheck
479          */
getRequiredLevel(String localeID)480         public Level getRequiredLevel(String localeID) {
481             Level result;
482             // see if there is an explicit level
483             String localeType = get(Option.CoverageLevel_requiredLevel);
484             if (localeType != null) {
485                 result = Level.get(localeType);
486                 if (result != Level.UNDETERMINED) {
487                     return result;
488                 }
489             }
490             // otherwise, see if there is an organization level for the "Cldr" organization.
491             // This is not user-specific.
492             return sc.getLocaleCoverageLevel("Cldr", localeID);
493         }
494 
contains(Option o)495         public boolean contains(Option o) {
496             String s = get(o);
497             return (s != null && !s.isEmpty());
498         }
499 
500         @Override
compareTo(Options other)501         public int compareTo(Options other) {
502             if (other == this) return 0;
503             if (key != null && other.key != null) {
504                 if (key == other.key) return 0;
505                 return key.compareTo(other.key);
506             }
507             for (int i = 0; i < options.length; i++) {
508                 final String s1 = options[i];
509                 final String s2 = other.options[i];
510                 if (s1 == null && s2 == null) {
511                     // no difference
512                 } else if (s1 == null) {
513                     return -1;
514                 } else if (s2 == null) {
515                     return 1;
516                 } else {
517                     int rv = s1.compareTo(s2);
518                     if (rv != 0) {
519                         return rv;
520                     }
521                 }
522             }
523             return 0;
524         }
525 
526         @Override
hashCode()527         public int hashCode() {
528             if (key != null) return key.hashCode();
529 
530             int h = 1;
531             for (int i = 0; i < options.length; i++) {
532                 if (options[i] == null) {
533                     h *= 11;
534                 } else {
535                     h = (h * 11) + options[i].hashCode();
536                 }
537             }
538             return h;
539         }
540 
541         @Override
toString()542         public String toString() {
543             if (key != null) return "Options:" + key;
544             StringBuilder sb = new StringBuilder();
545             for (Option o : Option.values()) {
546                 if (options[o.ordinal()] != null) {
547                     sb.append(o)
548                     .append('=')
549                     .append(options[o.ordinal()])
550                     .append(' ');
551                 }
552             }
553             return sb.toString();
554         }
555     }
556 
isSkipTest()557     public boolean isSkipTest() {
558         return skipTest;
559     }
560 
561     // this should only be set for the test in setCldrFileToCheck
setSkipTest(boolean skipTest)562     public void setSkipTest(boolean skipTest) {
563         this.skipTest = skipTest;
564     }
565 
566     /**
567      * Here is where the list of all checks is found.
568      *
569      * @param nameMatcher
570      *            Regex pattern that determines which checks are run,
571      *            based on their class name (such as .* for all checks, .*Collisions.* for CheckDisplayCollisions, etc.)
572      * @return
573      */
getCheckAll(Factory factory, String nameMatcher)574     public static CompoundCheckCLDR getCheckAll(Factory factory, String nameMatcher) {
575         return new CompoundCheckCLDR()
576             .setFilter(Pattern.compile(nameMatcher, Pattern.CASE_INSENSITIVE).matcher(""))
577             //.add(new CheckAttributeValues(factory))
578             .add(new CheckChildren(factory))
579             .add(new CheckCoverage(factory))
580             .add(new CheckDates(factory))
581             .add(new CheckForCopy(factory))
582             .add(new CheckDisplayCollisions(factory))
583             .add(new CheckExemplars(factory))
584             .add(new CheckForExemplars(factory))
585             .add(new CheckForInheritanceMarkers())
586             .add(new CheckNames())
587             .add(new CheckNumbers(factory))
588             // .add(new CheckZones()) // this doesn't work; many spurious errors that user can't correct
589             .add(new CheckMetazones())
590             .add(new CheckLogicalGroupings(factory))
591             .add(new CheckAlt())
592             .add(new CheckCurrencies())
593             .add(new CheckCasing())
594             .add(new CheckConsistentCasing(factory)) // this doesn't work; many spurious errors that user can't correct
595             .add(new CheckQuotes())
596             .add(new CheckUnits())
597             .add(new CheckWidths())
598             .add(new CheckPlaceHolders())
599             .add(new CheckNew(factory)) // this is at the end; it will check for other certain other errors and warnings and
600             // not add a message if there are any.
601             ;
602     }
603 
604     /**
605      * These determine what language is used to display information. Must be set before use.
606      *
607      * @param locale
608      * @return
609      */
getDisplayInformation()610     public static synchronized CLDRFile getDisplayInformation() {
611         return displayInformation;
612     }
613 
setDisplayInformation(CLDRFile inputDisplayInformation)614     public static synchronized void setDisplayInformation(CLDRFile inputDisplayInformation) {
615         displayInformation = inputDisplayInformation;
616     }
617 
618     /**
619      * [Warnings - please zoom in] dates/timeZoneNames/singleCountries
620      * (empty)
621      * [refs][hide] Ref: [Zoom...]
622      * [Warnings - please zoom in] dates/timeZoneNames/hours {0}/{1} {0}/{1}
623      * [refs][hide] Ref: [Zoom...]
624      * [Warnings - please zoom in] dates/timeZoneNames/hour +HH:mm;-HH:mm
625      * +HH:mm;-HH:mm
626      * [refs][hide] Ref: [Zoom...]
627      * [ok] layout/orientation (empty)
628      * [refs][hide] Ref: [Zoom...]
629      * [ok] dates/localizedPatternChars GyMdkHmsSEDFwWahKzYeugAZvcL
630      * GaMjkHmsSEDFwWxhKzAeugXZvcL
631      * [refs][hide] Ref: [Zoom...]
632      */
633 
634     /**
635      * Get the CLDRFile.
636      *
637      * @param cldrFileToCheck
638      */
getCldrFileToCheck()639     public final CLDRFile getCldrFileToCheck() {
640         return cldrFileToCheck;
641     }
642 
643     /**
644      * Don't override this, use the other setCldrFileToCheck which takes an Options instead of a Map<>
645      * @param cldrFileToCheck
646      * @param options
647      * @param possibleErrors
648      * @return
649      * @see #setCldrFileToCheck(CLDRFile, Options, List)
650      * @deprecated
651      */
652     @Deprecated
setCldrFileToCheck(CLDRFile cldrFileToCheck, Map<String, String> options, List<CheckStatus> possibleErrors)653     final public CheckCLDR setCldrFileToCheck(CLDRFile cldrFileToCheck, Map<String, String> options,
654         List<CheckStatus> possibleErrors) {
655         return setCldrFileToCheck(cldrFileToCheck, new Options(options), possibleErrors);
656     }
657 
658     /**
659      * Set the CLDRFile. Must be done before calling check. If null is called, just skip
660      * Often subclassed for initializing. If so, make the first 2 lines:
661      * if (cldrFileToCheck == null) return this;
662      * super.setCldrFileToCheck(cldrFileToCheck);
663      * do stuff
664      *
665      * @param cldrFileToCheck
666      * @param options (not currently used)
667      * @param possibleErrors
668      *            TODO
669      */
setCldrFileToCheck(CLDRFile cldrFileToCheck, Options options, List<CheckStatus> possibleErrors)670     public CheckCLDR setCldrFileToCheck(CLDRFile cldrFileToCheck, Options options,
671         List<CheckStatus> possibleErrors) {
672         this.cldrFileToCheck = cldrFileToCheck;
673 
674         // Shortlist error filters for this locale.
675         loadFilters();
676         String locale = cldrFileToCheck.getLocaleID();
677         filtersForLocale.clear();
678         for (R3<Pattern, Subtype, Pattern> filter : allFilters) {
679             if (filter.get0() == null || !filter.get0().matcher(locale).matches()) continue;
680             Subtype subtype = filter.get1();
681             List<Pattern> xpaths = filtersForLocale.get(subtype);
682             if (xpaths == null) {
683                 filtersForLocale.put(subtype, xpaths = new ArrayList<>());
684             }
685             xpaths.add(filter.get2());
686         }
687         return this;
688     }
689 
690     /**
691      * Status value returned from check
692      */
693     public static class CheckStatus {
694         public static final Type alertType = Type.Comment,
695             warningType = Type.Warning,
696             errorType = Type.Error,
697             exampleType = Type.Example,
698             demoType = Type.Demo;
699 
700         public enum Type {
701             Comment, Warning, Error, Example, Demo
702         }
703 
704         public enum Subtype {
705             none, noUnproposedVariant, deprecatedAttribute, illegalPlural, invalidLocale, incorrectCasing,
706             valueMustBeOverridden,
707             valueAlwaysOverridden, nullChildFile, internalError, coverageLevel, missingPluralInfo,
708             currencySymbolTooWide, incorrectDatePattern, abbreviatedDateFieldTooWide, displayCollision,
709             illegalExemplarSet, missingAuxiliaryExemplars, extraPlaceholders, missingPlaceholders,
710             shouldntHavePlaceholders, couldNotAccessExemplars, noExemplarCharacters, modifiedEnglishValue,
711             invalidCurrencyMatchSet, multipleMetazoneMappings, noMetazoneMapping, noMetazoneMappingAfter1970,
712             noMetazoneMappingBeforeNow, cannotCreateZoneFormatter, insufficientCoverage, missingLanguageTerritoryInfo,
713             missingEuroCountryInfo, deprecatedAttributeWithReplacement, missingOrExtraDateField, internalUnicodeSetFormattingError,
714             auxiliaryExemplarsOverlap, missingPunctuationCharacters,
715 
716             charactersNotInCurrencyExemplars, asciiCharactersNotInCurrencyExemplars,
717             charactersNotInMainOrAuxiliaryExemplars, asciiCharactersNotInMainOrAuxiliaryExemplars,
718 
719             narrowDateFieldTooWide, illegalCharactersInExemplars, orientationDisagreesWithExemplars,
720             inconsistentDatePattern, inconsistentTimePattern, missingDatePattern, illegalDatePattern,
721             missingMainExemplars, mustNotStartOrEndWithSpace, illegalCharactersInNumberPattern,
722             numberPatternNotCanonical, currencyPatternMissingCurrencySymbol, missingMinusSign,
723             badNumericType, percentPatternMissingPercentSymbol, illegalNumberFormat, unexpectedAttributeValue,
724             metazoneContainsDigit, tooManyGroupingSeparators, inconsistentPluralFormat, missingZeros, sameAsEnglish, sameAsCode,
725             dateSymbolCollision, incompleteLogicalGroup, extraMetazoneString, inconsistentDraftStatus,
726             errorOrWarningInLogicalGroup, valueTooWide, valueTooNarrow, nameContainsYear, patternCannotContainDigits,
727             patternContainsInvalidCharacters, parenthesesNotAllowed, illegalNumberingSystem, unexpectedOrderOfEraYear,
728             invalidPlaceHolder, asciiQuotesNotAllowed, badMinimumGroupingDigits, inconsistentPeriods,
729             inheritanceMarkerNotAllowed, invalidDurationUnitPattern, invalidDelimiter, illegalCharactersInPattern,
730             badParseLenient, tooManyValues, invalidSymbol, invalidGenderCode,
731             mismatchedUnitComponent, longPowerWithSubscripts, gapsInPlaceholderNumbers, duplicatePlaceholders, largerDifferences
732             ;
733 
734             @Override
toString()735             public String toString() {
736                 // converts "thisThisThis" to "this this this"
737                 return TO_STRING.matcher(name()).replaceAll(" $1").toLowerCase();
738             }
739 
740             static Pattern TO_STRING = PatternCache.get("([A-Z])");
741         }
742 
743         /**
744          * These error don't prevent entry during submission, since they become valid if a different row is changed.
745          */
746         public static Set<Subtype> crossCheckSubtypes = ImmutableSet.of(
747             Subtype.dateSymbolCollision,
748             Subtype.displayCollision,
749             Subtype.inconsistentDraftStatus,
750             Subtype.incompleteLogicalGroup,
751             Subtype.inconsistentPeriods,
752             Subtype.abbreviatedDateFieldTooWide,
753             Subtype.narrowDateFieldTooWide,
754             Subtype.coverageLevel);
755 
756         public static Set<Subtype> errorCodesPath = ImmutableSet.of(
757             Subtype.duplicatePlaceholders,
758             Subtype.extraPlaceholders,
759             Subtype.gapsInPlaceholderNumbers,
760             Subtype.invalidPlaceHolder,
761             Subtype.missingPlaceholders,
762             Subtype.shouldntHavePlaceholders);
763 
764 
765         private Type type;
766         private Subtype subtype = Subtype.none;
767         private String messageFormat;
768         private Object[] parameters;
769         private String htmlMessage;
770         private CheckCLDR cause;
771         private boolean checkOnSubmit = true;
772 
CheckStatus()773         public CheckStatus() {
774 
775         }
776 
isCheckOnSubmit()777         public boolean isCheckOnSubmit() {
778             return checkOnSubmit;
779         }
780 
setCheckOnSubmit(boolean dependent)781         public CheckStatus setCheckOnSubmit(boolean dependent) {
782             this.checkOnSubmit = dependent;
783             return this;
784         }
785 
getType()786         public Type getType() {
787             return type;
788         }
789 
setMainType(CheckStatus.Type type)790         public CheckStatus setMainType(CheckStatus.Type type) {
791             this.type = type;
792             return this;
793         }
794 
getMessage()795         public String getMessage() {
796             String message = messageFormat;
797             if (messageFormat != null && parameters != null) {
798                 try {
799                     String fixedApos = MessageFormat.autoQuoteApostrophe(messageFormat);
800                     MessageFormat format = new MessageFormat(fixedApos);
801                     message = format.format(parameters);
802                     if (errorCodesPath.contains(subtype)) {
803                         message += "; see <a href='http://cldr.unicode.org/translation/error-codes#" + subtype.name() + "'  target='cldr_error_codes'>" + subtype + "</a>.";
804                     }
805                 } catch (Exception e) {
806                     message = messageFormat;
807                     System.err.println("MessageFormat Failure: " + subtype + "; " + messageFormat + "; "
808                         + (parameters == null ? null : Arrays.asList(parameters)));
809                     // throw new IllegalArgumentException(subtype + "; " + messageFormat + "; "
810                     // + (parameters == null ? null : Arrays.asList(parameters)), e);
811                 }
812             }
813             Exception[] exceptionParameters = getExceptionParameters();
814             if (exceptionParameters != null) {
815                 for (Exception exception : exceptionParameters) {
816                     message += "; " + exception.getMessage(); // + " \t(" + exception.getClass().getName() + ")";
817                     // for (StackTraceElement item : exception.getStackTrace()) {
818                     // message += "\n\t" + item;
819                     // }
820                 }
821             }
822             return message.replace('\t', ' ');
823         }
824 
825         /**
826          * @deprecated
827          */
828         @Deprecated
getHTMLMessage()829         public String getHTMLMessage() {
830             return htmlMessage;
831         }
832 
833         /**
834          * @deprecated
835          */
836         @Deprecated
setHTMLMessage(String message)837         public CheckStatus setHTMLMessage(String message) {
838             htmlMessage = message;
839             return this;
840         }
841 
setMessage(String message)842         public CheckStatus setMessage(String message) {
843             if (cause == null) {
844                 throw new IllegalArgumentException("Must have cause set.");
845             }
846             if (message == null) {
847                 throw new IllegalArgumentException("Message cannot be null.");
848             }
849             this.messageFormat = message;
850             this.parameters = null;
851             return this;
852         }
853 
setMessage(String message, Object... messageArguments)854         public CheckStatus setMessage(String message, Object... messageArguments) {
855             if (cause == null) {
856                 throw new IllegalArgumentException("Must have cause set.");
857             }
858             this.messageFormat = message;
859             this.parameters = messageArguments;
860             return this;
861         }
862 
863         @Override
toString()864         public String toString() {
865             return getType() + ": " + getMessage();
866         }
867 
868         /**
869          * Warning: don't change the contents of the parameters after retrieving.
870          */
getParameters()871         public Object[] getParameters() {
872             return parameters;
873         }
874 
875         /**
876          * Returns any Exception parameters in the status, or null if there are none.
877          *
878          * @return
879          */
getExceptionParameters()880         public Exception[] getExceptionParameters() {
881             if (parameters == null) {
882                 return null;
883             }
884 
885             List<Exception> errors = new ArrayList<>();
886             for (Object o : parameters) {
887                 if (o instanceof Exception) {
888                     errors.add((Exception) o);
889                 }
890             }
891             if (errors.size() == 0) {
892                 return null;
893             }
894             return errors.toArray(new Exception[errors.size()]);
895         }
896 
897         /**
898          * Warning: don't change the contents of the parameters after passing in.
899          */
setParameters(Object[] parameters)900         public CheckStatus setParameters(Object[] parameters) {
901             if (cause == null) {
902                 throw new IllegalArgumentException("Must have cause set.");
903             }
904             this.parameters = parameters;
905             return this;
906         }
907 
getDemo()908         public SimpleDemo getDemo() {
909             return null;
910         }
911 
getCause()912         public CheckCLDR getCause() {
913             return cause;
914         }
915 
setCause(CheckCLDR cause)916         public CheckStatus setCause(CheckCLDR cause) {
917             this.cause = cause;
918             return this;
919         }
920 
getSubtype()921         public Subtype getSubtype() {
922             return subtype;
923         }
924 
setSubtype(Subtype subtype)925         public CheckStatus setSubtype(Subtype subtype) {
926             this.subtype = subtype;
927             return this;
928         }
929 
930         /**
931          * Convenience function: return true if any items in this list are of errorType
932          *
933          * @param result
934          *            the list to check (could be null for empty)
935          * @return true if any items in result are of errorType
936          */
hasError(List<CheckStatus> result)937         public static final boolean hasError(List<CheckStatus> result) {
938             return hasType(result, errorType);
939         }
940 
941         /**
942          * Convenience function: return true if any items in this list are of errorType
943          *
944          * @param result
945          *            the list to check (could be null for empty)
946          * @return true if any items in result are of errorType
947          */
hasType(List<CheckStatus> result, Type type)948         public static boolean hasType(List<CheckStatus> result, Type type) {
949             if (result == null) return false;
950             for (CheckStatus s : result) {
951                 if (s.getType().equals(type)) {
952                     return true;
953                 }
954             }
955             return false;
956         }
957     }
958 
959     public static abstract class SimpleDemo {
960         Map<String, String> internalPostArguments = new HashMap<>();
961 
962         /**
963          * @param postArguments
964          *            A read-write map containing post-style arguments. eg TEXTBOX=abcd, etc. <br>
965          *            The first time this is called, the Map should be empty.
966          * @return true if the map has been changed
967          */
getHTML(Map<String, String> postArguments)968         public abstract String getHTML(Map<String, String> postArguments) throws Exception;
969 
970         /**
971          * Only here for compatibility. Use the other getHTML instead
972          */
getHTML(String path, String fullPath, String value)973         public final String getHTML(String path, String fullPath, String value) throws Exception {
974             return getHTML(internalPostArguments);
975         }
976 
977         /**
978          * THIS IS ONLY FOR COMPATIBILITY: you can call this, then the non-postArguments form of getHTML; or better,
979          * call
980          * getHTML with the postArguments.
981          *
982          * @param postArguments
983          *            A read-write map containing post-style arguments. eg TEXTBOX=abcd, etc.
984          * @return true if the map has been changed
985          */
processPost(Map<String, String> postArguments)986         public final boolean processPost(Map<String, String> postArguments) {
987             internalPostArguments.clear();
988             internalPostArguments.putAll(postArguments);
989             return true;
990         }
991     }
992 
993     public static abstract class FormatDemo extends SimpleDemo {
994         protected String currentPattern, currentInput, currentFormatted, currentReparsed;
995         protected ParsePosition parsePosition = new ParsePosition(0);
996 
getPattern()997         protected abstract String getPattern();
998 
getSampleInput()999         protected abstract String getSampleInput();
1000 
getArguments(Map<String, String> postArguments)1001         protected abstract void getArguments(Map<String, String> postArguments);
1002 
1003         @Override
getHTML(Map<String, String> postArguments)1004         public String getHTML(Map<String, String> postArguments) throws Exception {
1005             getArguments(postArguments);
1006             StringBuffer htmlMessage = new StringBuffer();
1007             FormatDemo.appendTitle(htmlMessage);
1008             FormatDemo.appendLine(htmlMessage, currentPattern, currentInput, currentFormatted, currentReparsed);
1009             htmlMessage.append("</table>");
1010             return htmlMessage.toString();
1011         }
1012 
getPlainText(Map<String, String> postArguments)1013         public String getPlainText(Map<String, String> postArguments) {
1014             getArguments(postArguments);
1015             return MessageFormat.format("<\"\u200E{0}\u200E\", \"{1}\"> \u2192 \"\u200E{2}\u200E\" \u2192 \"{3}\"",
1016                 (Object[]) new String[] { currentPattern, currentInput, currentFormatted, currentReparsed });
1017         }
1018 
1019         /**
1020          * @param htmlMessage
1021          * @param pattern
1022          * @param input
1023          * @param formatted
1024          * @param reparsed
1025          */
appendLine(StringBuffer htmlMessage, String pattern, String input, String formatted, String reparsed)1026         public static void appendLine(StringBuffer htmlMessage, String pattern, String input, String formatted,
1027             String reparsed) {
1028             htmlMessage.append("<tr><td><input type='text' name='pattern' value='")
1029             .append(TransliteratorUtilities.toXML.transliterate(pattern))
1030             .append("'></td><td><input type='text' name='input' value='")
1031             .append(TransliteratorUtilities.toXML.transliterate(input))
1032             .append("'></td><td>")
1033             .append("<input type='submit' value='Test' name='Test'>")
1034             .append("</td><td>" + "<input type='text' name='formatted' value='")
1035             .append(TransliteratorUtilities.toXML.transliterate(formatted))
1036             .append("'></td><td>" + "<input type='text' name='reparsed' value='")
1037             .append(TransliteratorUtilities.toXML.transliterate(reparsed))
1038             .append("'></td></tr>");
1039         }
1040 
1041         /**
1042          * @param htmlMessage
1043          */
appendTitle(StringBuffer htmlMessage)1044         public static void appendTitle(StringBuffer htmlMessage) {
1045             htmlMessage.append("<table border='1' cellspacing='0' cellpadding='2'" +
1046                 // " style='border-collapse: collapse' style='width: 100%'" +
1047                 "><tr>" +
1048                 "<th>Pattern</th>" +
1049                 "<th>Unlocalized Input</th>" +
1050                 "<th></th>" +
1051                 "<th>Localized Format</th>" +
1052                 "<th>Re-Parsed</th>" +
1053                 "</tr>");
1054         }
1055     }
1056 
1057     /**
1058      * Wraps the options in an Options and delegates.
1059      * @param path
1060      *            Must be a distinguished path, such as what comes out of CLDRFile.iterator()
1061      * @param fullPath
1062      *            Must be the full path
1063      * @param value
1064      *            the value associated with the path
1065      * @param options
1066      *            A set of test-specific options. Set these with code of the form:<br>
1067      *            options.put("CoverageLevel.localeType", "G0")<br>
1068      *            That is, the key is of the form <testname>.<optiontype>, and the value is of the form <optionvalue>.<br>
1069      *            There is one general option; the following will select only the tests that should be run during this
1070      *            phase.<br>
1071      *            options.put("phase", Phase.<something>);
1072      *            It can be used for new data entry.
1073      * @param result
1074      * @return
1075      * @deprecated use CheckCLDR#check(String, String, String, Options, List)
1076      */
1077     @Deprecated
check(String path, String fullPath, String value, Map<String, String> options, List<CheckStatus> result)1078     public final CheckCLDR check(String path, String fullPath, String value, Map<String, String> options,
1079         List<CheckStatus> result) {
1080         return check(path, fullPath, value, new Options(options), result);
1081     }
1082 
1083     /**
1084      * Checks the path/value in the cldrFileToCheck for correctness, according to some criterion.
1085      * If the path is relevant to the check, there is an alert or warning, then a CheckStatus is added to List.
1086      *
1087      * @param path
1088      *            Must be a distinguished path, such as what comes out of CLDRFile.iterator()
1089      * @param fullPath
1090      *            Must be the full path
1091      * @param value
1092      *            the value associated with the path
1093      * @param result
1094      */
check(String path, String fullPath, String value, Options options, List<CheckStatus> result)1095     public final CheckCLDR check(String path, String fullPath, String value, Options options,
1096         List<CheckStatus> result) {
1097         if (cldrFileToCheck == null) {
1098             throw new InternalCldrException("CheckCLDR problem: cldrFileToCheck must not be null");
1099         }
1100         if (path == null) {
1101             throw new InternalCldrException("CheckCLDR problem: path must not be null");
1102         }
1103         // if (fullPath == null) {
1104         // throw new InternalError("CheckCLDR problem: fullPath must not be null");
1105         // }
1106         // if (value == null) {
1107         // throw new InternalError("CheckCLDR problem: value must not be null");
1108         // }
1109         result.clear();
1110 
1111         /*
1112          * If the item is non-winning, and either inherited or it is code-fallback, then don't run
1113          * any tests on this item.  See http://unicode.org/cldr/trac/ticket/7574
1114          *
1115          * The following conditional formerly used "value == ..." and "value != ...", which in Java doesn't
1116          * mean what it does in some other languages. The condition has been changed to use the equals() method.
1117          * Since value can be null, check for that first.
1118          */
1119         // if (value == cldrFileToCheck.getBaileyValue(path, null, null) && value != cldrFileToCheck.getWinningValue(path)) {
1120         if (value != null
1121             && !value.equals(cldrFileToCheck.getWinningValue(path))
1122             && cldrFileToCheck.getUnresolved().getStringValue(path) == null) {
1123                 return this;
1124         }
1125 
1126         // If we're being asked to run tests for an inheritance marker, then we need to change it
1127         // to the "real" value first before running tests. Testing the value CldrUtility.INHERITANCE_MARKER ("↑↑↑") doesn't make sense.
1128         if (CldrUtility.INHERITANCE_MARKER.equals(value)) {
1129             value = cldrFileToCheck.getConstructedBaileyValue(path, null, null);
1130             // If it hasn't changed, then don't run any tests.
1131             if (CldrUtility.INHERITANCE_MARKER.equals(value)) {
1132                 return this;
1133             }
1134         }
1135         CheckCLDR instance = handleCheck(path, fullPath, value, options, result);
1136         Iterator<CheckStatus> iterator = result.iterator();
1137         // Filter out any errors/warnings that match the filter list in CheckCLDR-exceptions.txt.
1138         while (iterator.hasNext()) {
1139             CheckStatus status = iterator.next();
1140             if (shouldExcludeStatus(fullPath, status)) {
1141                 iterator.remove();
1142             }
1143         }
1144         return instance;
1145     }
1146 
1147     /**
1148      * Returns any examples in the result parameter. Both examples and demos can
1149      * be returned. A demo will have getType() == CheckStatus.demoType. In that
1150      * case, there will be no getMessage or getHTMLMessage available; instead,
1151      * call getDemo() to get the demo, then call getHTML() to get the initial
1152      * HTML.
1153      */
getExamples(String path, String fullPath, String value, Options options, List<CheckStatus> result)1154     public final CheckCLDR getExamples(String path, String fullPath, String value, Options options,
1155         List<CheckStatus> result) {
1156         result.clear();
1157         return handleGetExamples(path, fullPath, value, options, result);
1158     }
1159 
1160     @SuppressWarnings("unused")
handleGetExamples(String path, String fullPath, String value, Options options2, List<CheckStatus> result)1161     protected CheckCLDR handleGetExamples(String path, String fullPath, String value, Options options2,
1162         List<CheckStatus> result) {
1163         return this; // NOOP unless overridden
1164     }
1165 
1166     /**
1167      * This is what the subclasses override.
1168      * If they ever use pathParts or fullPathParts, they need to call initialize() with the respective
1169      * path. Otherwise they must NOT change pathParts or fullPathParts.
1170      * <p>
1171      * If something is found, a CheckStatus is added to result. This can be done multiple times in one call, if multiple
1172      * errors or warnings are found. The CheckStatus may return warnings, errors, examples, or demos. We may expand that
1173      * in the future.
1174      * <p>
1175      * The code to add the CheckStatus will look something like::
1176      *
1177      * <pre>
1178      * result.add(new CheckStatus()
1179      *     .setType(CheckStatus.errorType)
1180      *     .setMessage(&quot;Value should be {0}&quot;, new Object[] { pattern }));
1181      * </pre>
1182      *
1183      * @param options
1184      *            TODO
1185      */
handleCheck(String path, String fullPath, String value, Options options, List<CheckStatus> result)1186     abstract public CheckCLDR handleCheck(String path, String fullPath, String value,
1187         Options options, List<CheckStatus> result);
1188 
1189     /**
1190      * Only for use in ConsoleCheck, for debugging
1191      */
handleFinish()1192     public void handleFinish() {
1193     }
1194 
1195     /**
1196      * Internal class used to bundle up a number of Checks.
1197      *
1198      * @author davis
1199      *
1200      */
1201     static class CompoundCheckCLDR extends CheckCLDR {
1202         private Matcher filter;
1203         private List<CheckCLDR> checkList = new ArrayList<>();
1204         private List<CheckCLDR> filteredCheckList = new ArrayList<>();
1205 
add(CheckCLDR item)1206         public CompoundCheckCLDR add(CheckCLDR item) {
1207             checkList.add(item);
1208             if (filter == null) {
1209                 filteredCheckList.add(item);
1210             } else {
1211                 final String className = item.getClass().getName();
1212                 if (filter.reset(className).find()) {
1213                     filteredCheckList.add(item);
1214                 }
1215             }
1216             return this;
1217         }
1218 
1219         @Override
handleCheck(String path, String fullPath, String value, Options options, List<CheckStatus> result)1220         public CheckCLDR handleCheck(String path, String fullPath, String value,
1221             Options options, List<CheckStatus> result) {
1222             result.clear();
1223             // If we're being asked to run tests for an inheritance marker, then we need to change it
1224             // to the "real" value first before running tests. Testing the value CldrUtility.INHERITANCE_MARKER ("↑↑↑") doesn't make sense.
1225             if (CldrUtility.INHERITANCE_MARKER.equals(value)) {
1226                 value = getCldrFileToCheck().getConstructedBaileyValue(path, null, null);
1227             }
1228             for (Iterator<CheckCLDR> it = filteredCheckList.iterator(); it.hasNext();) {
1229                 CheckCLDR item = it.next();
1230                 // skip proposed items in final testing.
1231                 if (Phase.FINAL_TESTING == item.getPhase()) {
1232                     if (path.contains("proposed") && path.contains("[@alt=")) {
1233                         continue;
1234                     }
1235                 }
1236                 try {
1237                     if (!item.isSkipTest()) {
1238                         item.handleCheck(path, fullPath, value, options, result);
1239                     }
1240                 } catch (Exception e) {
1241                     addError(result, item, e);
1242                     return this;
1243                 }
1244             }
1245             return this;
1246         }
1247 
1248         @Override
handleFinish()1249         public void handleFinish() {
1250             for (Iterator<CheckCLDR> it = filteredCheckList.iterator(); it.hasNext();) {
1251                 CheckCLDR item = it.next();
1252                 item.handleFinish();
1253             }
1254         }
1255 
1256         @Override
handleGetExamples(String path, String fullPath, String value, Options options, List<CheckStatus> result)1257         protected CheckCLDR handleGetExamples(String path, String fullPath, String value, Options options,
1258             List<CheckStatus> result) {
1259             result.clear();
1260             for (Iterator<CheckCLDR> it = filteredCheckList.iterator(); it.hasNext();) {
1261                 CheckCLDR item = it.next();
1262                 try {
1263                     item.handleGetExamples(path, fullPath, value, options, result);
1264                 } catch (Exception e) {
1265                     addError(result, item, e);
1266                     return this;
1267                 }
1268             }
1269             return this;
1270         }
1271 
addError(List<CheckStatus> result, CheckCLDR item, Exception e)1272         private void addError(List<CheckStatus> result, CheckCLDR item, Exception e) {
1273             result.add(new CheckStatus()
1274                 .setCause(this)
1275                 .setMainType(CheckStatus.errorType)
1276                 .setSubtype(Subtype.internalError)
1277                 .setMessage("Internal error in {0}. Exception: {1}, Message: {2}, Trace: {3}",
1278                     new Object[] { item.getClass().getName(), e.getClass().getName(), e,
1279                         Arrays.asList(e.getStackTrace())
1280                 }));
1281         }
1282 
1283         @Override
setCldrFileToCheck(CLDRFile cldrFileToCheck, Options options, List<CheckStatus> possibleErrors)1284         public CheckCLDR setCldrFileToCheck(CLDRFile cldrFileToCheck, Options options,
1285             List<CheckStatus> possibleErrors) {
1286             ElapsedTimer testTime = null, testOverallTime = null;
1287             if (cldrFileToCheck == null) return this;
1288             boolean SHOW_TIMES = options.contains(Options.Option.SHOW_TIMES);
1289             setPhase(Phase.forString(options.get(Options.Option.phase)));
1290             if (SHOW_TIMES) testOverallTime = new ElapsedTimer("Test setup time for setCldrFileToCheck: {0}");
1291             super.setCldrFileToCheck(cldrFileToCheck, options, possibleErrors);
1292             possibleErrors.clear();
1293 
1294             for (Iterator<CheckCLDR> it = filteredCheckList.iterator(); it.hasNext();) {
1295                 CheckCLDR item = it.next();
1296                 if (SHOW_TIMES)
1297                     testTime = new ElapsedTimer("Test setup time for " + item.getClass().toString() + ": {0}");
1298                 try {
1299                     item.setPhase(getPhase());
1300                     item.setCldrFileToCheck(cldrFileToCheck, options, possibleErrors);
1301                     if (SHOW_TIMES) {
1302                         if (item.isSkipTest()) {
1303                             System.out.println("Disabled : " + testTime);
1304                         } else {
1305                             System.out.println("OK : " + testTime);
1306                         }
1307                     }
1308                 } catch (RuntimeException e) {
1309                     addError(possibleErrors, item, e);
1310                     if (SHOW_TIMES) System.out.println("ERR: " + testTime + " - " + e.toString());
1311                 }
1312             }
1313             if (SHOW_TIMES) System.out.println("Overall: " + testOverallTime + ": {0}");
1314             return this;
1315         }
1316 
getFilter()1317         public Matcher getFilter() {
1318             return filter;
1319         }
1320 
setFilter(Matcher filter)1321         public CompoundCheckCLDR setFilter(Matcher filter) {
1322             this.filter = filter;
1323             filteredCheckList.clear();
1324             for (Iterator<CheckCLDR> it = checkList.iterator(); it.hasNext();) {
1325                 CheckCLDR item = it.next();
1326                 if (filter == null || filter.reset(item.getClass().getName()).matches()) {
1327                     filteredCheckList.add(item);
1328                     item.setCldrFileToCheck(getCldrFileToCheck(), (Options) null, null);
1329                 }
1330             }
1331             return this;
1332         }
1333 
getFilteredTests()1334         public String getFilteredTests() {
1335             return filteredCheckList.toString();
1336         }
1337 
getFilteredTestList()1338         public List<CheckCLDR> getFilteredTestList() {
1339             return filteredCheckList;
1340         }
1341     }
1342 
getTransliteratorFromFile(String ID, String file)1343     public static Transliterator getTransliteratorFromFile(String ID, String file) {
1344         try {
1345             BufferedReader br = CldrUtility.getUTF8Data(file);
1346             StringBuffer input = new StringBuffer();
1347             while (true) {
1348                 String line = br.readLine();
1349                 if (line == null) break;
1350                 if (line.startsWith("\uFEFF")) line = line.substring(1); // remove BOM
1351                 input.append(line);
1352                 input.append('\n');
1353             }
1354             return Transliterator.createFromRules(ID, input.toString(), Transliterator.FORWARD);
1355         } catch (IOException e) {
1356             throw new ICUUncheckedIOException("Can't open transliterator file " + file, e);
1357         }
1358     }
1359 
getPhase()1360     public Phase getPhase() {
1361         return phase;
1362     }
1363 
setPhase(Phase phase)1364     public void setPhase(Phase phase) {
1365         this.phase = phase;
1366     }
1367 
1368     /**
1369      * A map of error/warning types to their filters.
1370      */
1371     private static List<R3<Pattern, Subtype, Pattern>> allFilters;
1372 
1373     /**
1374      * Loads the set of filters used for CheckCLDR results.
1375      */
loadFilters()1376     private void loadFilters() {
1377         if (allFilters != null) return;
1378         allFilters = new ArrayList<>();
1379         RegexFileParser fileParser = new RegexFileParser();
1380         fileParser.setLineParser(new RegexLineParser() {
1381             @Override
1382             public void parse(String line) {
1383                 String[] fields = line.split("\\s*;\\s*");
1384                 Subtype subtype = Subtype.valueOf(fields[0]);
1385                 Pattern locale = PatternCache.get(fields[1]);
1386                 Pattern xpathRegex = PatternCache.get(fields[2].replaceAll("\\[@", "\\\\[@"));
1387                 allFilters.add(new R3<>(locale, subtype, xpathRegex));
1388             }
1389         });
1390         fileParser.parse(CheckCLDR.class, "/org/unicode/cldr/util/data/CheckCLDR-exceptions.txt");
1391     }
1392 
1393     /**
1394      * Checks if a status should be excluded from the list of results returned
1395      * from CheckCLDR.
1396      * @param xpath the xpath that the status belongs to
1397      * @param status the status
1398      * @return true if the status should be included
1399      */
shouldExcludeStatus(String xpath, CheckStatus status)1400     private boolean shouldExcludeStatus(String xpath, CheckStatus status) {
1401         List<Pattern> xpathPatterns = filtersForLocale.get(status.getSubtype());
1402         if (xpathPatterns == null) {
1403             return false;
1404         }
1405         for (Pattern xpathPattern : xpathPatterns) {
1406             if (xpathPattern.matcher(xpath).matches()) {
1407                 return true;
1408             }
1409         }
1410         return false;
1411     }
1412 
getEnglishFile()1413     public CLDRFile getEnglishFile() {
1414         return englishFile;
1415     }
1416 
setEnglishFile(CLDRFile englishFile)1417     public void setEnglishFile(CLDRFile englishFile) {
1418         this.englishFile = englishFile;
1419     }
1420 
fixedValueIfInherited(String value, String path)1421     public CharSequence fixedValueIfInherited(String value, String path) {
1422         return !CldrUtility.INHERITANCE_MARKER.equals(value) ? value: getCldrFileToCheck().getStringValueWithBailey(path);
1423     }
1424 }
1425