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