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