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