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