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("Value should be {0}", 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