1 package org.unicode.cldr.unittest; 2 3 import com.google.common.base.Objects; 4 import com.google.common.base.Splitter; 5 import com.google.common.collect.ImmutableSet; 6 import com.google.common.collect.Multimap; 7 import com.google.common.collect.TreeMultimap; 8 import com.ibm.icu.impl.Row.R2; 9 import com.ibm.icu.text.UnicodeSet; 10 import com.ibm.icu.util.ULocale; 11 import java.io.File; 12 import java.util.ArrayList; 13 import java.util.Arrays; 14 import java.util.Collection; 15 import java.util.Collections; 16 import java.util.EnumSet; 17 import java.util.HashSet; 18 import java.util.LinkedHashMap; 19 import java.util.LinkedHashSet; 20 import java.util.List; 21 import java.util.Map; 22 import java.util.Map.Entry; 23 import java.util.Set; 24 import java.util.TreeMap; 25 import java.util.TreeSet; 26 import java.util.stream.Collectors; 27 import org.unicode.cldr.util.CLDRConfig; 28 import org.unicode.cldr.util.CLDRPaths; 29 import org.unicode.cldr.util.CldrUtility; 30 import org.unicode.cldr.util.LanguageTagCanonicalizer; 31 import org.unicode.cldr.util.LanguageTagParser; 32 import org.unicode.cldr.util.LocaleNames; 33 import org.unicode.cldr.util.StandardCodes; 34 import org.unicode.cldr.util.StandardCodes.LstrField; 35 import org.unicode.cldr.util.StandardCodes.LstrType; 36 import org.unicode.cldr.util.TestCLDRPaths; 37 import org.unicode.cldr.util.TransliteratorUtilities; 38 import org.unicode.cldr.util.Units; 39 import org.unicode.cldr.util.Validity; 40 import org.unicode.cldr.util.Validity.Status; 41 42 public class TestValidity extends TestFmwkPlus { 43 44 private boolean DEBUG = false; 45 main(String[] args)46 public static void main(String[] args) { 47 new TestValidity().run(args); 48 } 49 50 Validity validity = Validity.getInstance(); 51 TestBasicValidity()52 public void TestBasicValidity() { 53 Object[][] tests = { 54 {LstrType.language, Validity.Status.regular, true, "aa", "en"}, 55 {LstrType.language, null, false, "eng"}, // null means never found under any status 56 {LstrType.language, null, false, LocaleNames.ROOT}, 57 {LstrType.language, Validity.Status.special, true, LocaleNames.MUL}, 58 {LstrType.language, Validity.Status.deprecated, true, "aju"}, 59 {LstrType.language, Validity.Status.reserved, true, "qaa", "qfy"}, 60 {LstrType.language, Validity.Status.private_use, true, "qfz"}, 61 {LstrType.language, Validity.Status.unknown, true, LocaleNames.UND}, 62 {LstrType.script, Validity.Status.reserved, true, "Qaaa", "Qaap"}, 63 {LstrType.script, Validity.Status.private_use, true, "Qaaq", "Qabx"}, 64 {LstrType.script, Validity.Status.special, true, "Zinh"}, 65 {LstrType.script, Validity.Status.special, true, "Zmth"}, 66 {LstrType.script, Validity.Status.special, true, "Zsye"}, 67 {LstrType.script, Validity.Status.special, true, "Zsym"}, 68 {LstrType.script, Validity.Status.special, true, "Zxxx"}, 69 {LstrType.script, Validity.Status.special, true, "Zyyy"}, 70 {LstrType.script, Validity.Status.unknown, true, "Zzzz"}, 71 {LstrType.region, Validity.Status.deprecated, true, "QU"}, 72 {LstrType.region, Validity.Status.macroregion, true, "EU"}, 73 {LstrType.region, Validity.Status.regular, true, "XK"}, 74 {LstrType.region, Validity.Status.macroregion, true, "001"}, 75 {LstrType.region, Validity.Status.reserved, true, "AA", "QM", "QZ"}, 76 {LstrType.region, Validity.Status.private_use, true, "XC", "XZ"}, 77 {LstrType.region, Validity.Status.unknown, true, "ZZ"}, 78 {LstrType.subdivision, Validity.Status.unknown, true, "kzzzzz"}, 79 {LstrType.subdivision, Validity.Status.regular, true, "usca"}, 80 {LstrType.subdivision, Validity.Status.deprecated, true, "albr"}, 81 {LstrType.currency, Validity.Status.regular, true, "USD"}, 82 {LstrType.currency, Validity.Status.unknown, true, "XXX"}, 83 {LstrType.currency, Validity.Status.deprecated, true, "ADP"}, 84 {LstrType.unit, Validity.Status.regular, true, "area-acre"}, 85 }; 86 for (Object[] test : tests) { 87 LstrType lstr = (LstrType) test[0]; 88 Validity.Status subtypeRaw = (Validity.Status) test[1]; 89 Boolean desired = (Boolean) test[2]; 90 for (int i = 3; i < test.length; ++i) { 91 String code = (String) test[i]; 92 List<Status> subtypes = 93 subtypeRaw == null 94 ? Arrays.asList(Status.values()) 95 : Collections.singletonList(subtypeRaw); 96 for (Status subtype : subtypes) { 97 Set<String> actual = validity.getStatusToCodes(lstr).get(subtype); 98 if (!assertRelation( 99 "Validity", 100 desired, 101 CldrUtility.ifNull(actual, Collections.EMPTY_SET), 102 TestFmwkPlus.CONTAINS, 103 code)) { 104 int debug = 0; 105 } 106 } 107 } 108 } 109 if (isVerbose()) { 110 111 for (LstrType lstrType : LstrType.values()) { 112 logln(lstrType.toString()); 113 final Map<Status, Set<String>> statusToCodes = validity.getStatusToCodes(lstrType); 114 for (Entry<Validity.Status, Set<String>> entry2 : statusToCodes.entrySet()) { 115 logln("\t" + entry2.getKey()); 116 logln("\t\t" + entry2.getValue()); 117 } 118 } 119 } 120 } 121 122 static final Set<String> ALLOWED_UNDELETIONS = 123 ImmutableSet.of( 124 "ug331", 125 "nlbq1", 126 "nlbq2", 127 "nlbq3", 128 "no21", 129 "no22", 130 // 2022 131 "no", 132 "escn", 133 "gbeng", 134 "gbnir", 135 "gbsct", 136 "gbwls", 137 "itgo", 138 "itpn", 139 "itts", 140 "itud", 141 "SLE", 142 // 2024 143 "dzd", 144 "knn"); 145 static final Set<String> ALLOWED_MISSING = 146 ImmutableSet.of(LocaleNames.ROOT, "POSIX", "REVISED", "SAAHO"); 147 static final Set<String> ALLOWED_REGULAR_TO_SPECIAL = ImmutableSet.of("Zanb", "Zinh", "Zyyy"); 148 TestCompatibility()149 public void TestCompatibility() { 150 if (!TestCLDRPaths.canUseArchiveDirectory()) { 151 return; // skipped 152 } 153 154 Set<String> messages = new HashSet<>(); 155 File archive = new File(CLDRPaths.ARCHIVE_DIRECTORY); 156 for (File cldrArchive : archive.listFiles()) { 157 if (!cldrArchive.getName().startsWith("cldr-")) { 158 continue; 159 } 160 File oldValidityLocation = 161 new File( 162 cldrArchive, 163 File.separator 164 + "common" 165 + File.separator 166 + "validity" 167 + File.separator); 168 if (!oldValidityLocation.exists()) { 169 logln("Skipping " + oldValidityLocation); 170 continue; 171 } 172 logln("Checking " + oldValidityLocation.toString()); 173 // final String oldValidityLocation = CLDRPaths.ARCHIVE_DIRECTORY + "cldr-" + 174 // ToolConstants.PREVIOUS_CHART_VERSION + 175 // File.separator + "common" + File.separator + "validity" + 176 // File.separator; 177 Validity oldValidity = 178 Validity.getInstance(oldValidityLocation.toString() + File.separator); 179 180 for (LstrType type : LstrType.values()) { 181 final Map<Status, Set<String>> statusToCodes = oldValidity.getStatusToCodes(type); 182 if (statusToCodes == null) { 183 logln("validity data unavailable: " + type); 184 continue; 185 } 186 for (Entry<Status, Set<String>> e2 : statusToCodes.entrySet()) { 187 Status oldStatus = e2.getKey(); 188 for (String code : e2.getValue()) { 189 Status newStatus = getNewStatus(type, code); 190 if (oldStatus == newStatus) { 191 continue; 192 } 193 194 if (newStatus == null) { 195 if (ALLOWED_MISSING.contains(code)) { 196 continue; 197 } 198 errln( 199 messages, 200 type 201 + ":" 202 + code 203 + ":" 204 + oldStatus 205 + " => " 206 + newStatus 207 + " — missing in new data vs. " 208 + cldrArchive.getName()); 209 } 210 211 if (oldStatus == Status.private_use && newStatus == Status.special) { 212 logln( 213 messages, 214 "OK: " + type + ":" + code + " was " + oldStatus + " => " 215 + newStatus); 216 continue; 217 } 218 if (oldStatus == Status.special && newStatus == Status.unknown) { 219 if (type == LstrType.subdivision && code.endsWith("zzzz")) { 220 continue; 221 } 222 logln( 223 messages, 224 "OK: " + type + ":" + code + " was " + oldStatus + " => " 225 + newStatus); 226 continue; 227 } 228 if (oldStatus == Status.regular) { 229 if (newStatus == Status.deprecated) { 230 // logln(messages, "OK: " + type + 231 // ":" + code + " was " + oldStatus + " => " + newStatus); 232 continue; 233 } else if (newStatus == Status.special 234 && ALLOWED_REGULAR_TO_SPECIAL.contains(code)) { 235 // logln(messages, "OK: " + type + ":" 236 // + code + " was " + oldStatus + " => " + newStatus); 237 continue; 238 } 239 errln( 240 messages, 241 type 242 + ":" 243 + code 244 + ":" 245 + oldStatus 246 + " => " 247 + newStatus 248 + " — regular item changed, and didn't become deprecated"); 249 } 250 if (oldStatus == Status.deprecated) { 251 if (ALLOWED_UNDELETIONS.contains(code)) { 252 continue; 253 } 254 errln( 255 messages, 256 type 257 + ":" 258 + code 259 + ":" 260 + oldStatus 261 + " => " 262 + newStatus 263 + " // add to exception list (ALLOWED_UNDELETIONS) if really un-deprecated"); 264 } else if (oldStatus == Status.private_use && newStatus == Status.regular) { 265 // logln(messages, "OK: " + type + ":" + code + 266 // " was " + oldStatus + " => " + newStatus); 267 } else if (oldStatus == Status.deprecated) { 268 errln( 269 messages, 270 type + ":" + code + " was " + oldStatus + " => " + newStatus); 271 } 272 } 273 } 274 } 275 } 276 } 277 logln(Set<String> messages, String string)278 private void logln(Set<String> messages, String string) { 279 if (!messages.contains(string)) { 280 logln(string); 281 messages.add(string); 282 } 283 } 284 errln(Set<String> messages, String string)285 private void errln(Set<String> messages, String string) { 286 if (!messages.contains(string)) { 287 errln(string); 288 messages.add(string); 289 } 290 } 291 getNewStatus(LstrType type, String code)292 private Status getNewStatus(LstrType type, String code) { 293 Map<Status, Set<String>> info = validity.getStatusToCodes(type); 294 for (Entry<Status, Set<String>> e : info.entrySet()) { 295 if (e.getValue().contains(code)) { 296 return e.getKey(); 297 } 298 } 299 return null; 300 } 301 TestBothDirections()302 public void TestBothDirections() { 303 for (LstrType type : LstrType.values()) { 304 Map<Status, Set<String>> statusToCodes = validity.getStatusToCodes(type); 305 Map<String, Status> codeToStatus = validity.getCodeToStatus(type); 306 assertEquals("null at same time", statusToCodes == null, codeToStatus == null); 307 if (statusToCodes == null) { 308 logln("validity data unavailable: " + type); 309 continue; 310 } 311 for (Entry<Status, Set<String>> entry : statusToCodes.entrySet()) { 312 Status status = entry.getKey(); 313 for (String code : entry.getValue()) { 314 assertEquals("Forward works", status, codeToStatus.get(code)); 315 } 316 } 317 for (Entry<String, Status> entry : codeToStatus.entrySet()) { 318 final String code = entry.getKey(); 319 final Status status = entry.getValue(); 320 assertTrue("Reverse works: " + status, statusToCodes.get(status).contains(code)); 321 } 322 } 323 } 324 TestValidityUniqueness()325 public void TestValidityUniqueness() { 326 Splitter HYPHEN_SPLITTER = Splitter.on('-'); 327 UnicodeSet allowed = new UnicodeSet("[a-z0-9A-Z]").freeze(); 328 Validity validity = Validity.getInstance(); 329 for (Entry<LstrType, Map<Status, Set<String>>> e1 : validity.getData().entrySet()) { 330 final LstrType lstrType = e1.getKey(); 331 final boolean lstrTypeUnit = lstrType == LstrType.unit; 332 333 // Try truncating every key to 8 letters, to ensure that it is unique 334 335 Multimap<String, String> truncatedToFull = TreeMultimap.create(); 336 for (Entry<Status, Set<String>> e2 : e1.getValue().entrySet()) { 337 final Status status = e2.getKey(); 338 final boolean statusDeprecated = status != Status.deprecated; 339 for (String codeLong : e2.getValue()) { 340 String code = codeLong; 341 String shortCode = code; 342 if (lstrTypeUnit) { 343 shortCode = Units.getShort(codeLong); 344 if (shortCode == null) { 345 if (statusDeprecated) { 346 errln("No short form of " + codeLong); 347 } 348 continue; 349 } 350 } 351 for (String subcode : HYPHEN_SPLITTER.split(code)) { 352 truncatedToFull.put( 353 subcode.length() <= 8 ? subcode : subcode.substring(0, 8), subcode); 354 if (!allowed.containsAll(subcode)) { 355 errln("subcode has illegal character: " + subcode + ", in " + code); 356 } 357 } 358 } 359 } 360 361 checkTruncationStatus(truncatedToFull); 362 } 363 } 364 checkTruncationStatus(Multimap<String, String> truncatedToFull)365 public void checkTruncationStatus(Multimap<String, String> truncatedToFull) { 366 for (Entry<String, Collection<String>> entry : truncatedToFull.asMap().entrySet()) { 367 final String truncated = entry.getKey(); 368 final Collection<String> longForms = entry.getValue(); 369 if (longForms.size() > 1) { 370 errln("Ambiguous subkey: " + entry); 371 } else if (isVerbose()) { 372 if (!longForms.contains(truncated)) { 373 logln(entry.toString()); 374 } 375 } 376 } 377 } 378 TestLanguageTagParser()379 public void TestLanguageTagParser() { 380 String[][] tests = { 381 { 382 "en-cyrl_ru_variant2_variant1", 383 "en_Cyrl_RU_VARIANT1_VARIANT2", 384 "en-Cyrl-RU-variant1-variant2" 385 }, 386 // Hold off, since ICU doesn't canonicalize: doesn't correctly interpret 387 // en@co=PHONEBK;em=EMOJI;t=RU 388 // { "EN-U-CO-PHONEBK-EM-EMOJI-T_RU", "en@T=RU;CO=PHONEBK;EM=EMOJI", 389 // "en-t-ru-u-co-phonebk-em-emoji" }, 390 }; 391 LanguageTagParser ltp = new LanguageTagParser(); 392 for (String[] test : tests) { 393 String source = test[0]; 394 String expectedLanguageSubtagParserIcu = test[1]; 395 String expectedLanguageSubtagParserBCP = test[2]; 396 397 // check that field 2 is the same as ICU 398 ULocale icuFromICU = new ULocale(expectedLanguageSubtagParserIcu); 399 ULocale icuFromBCP = ULocale.forLanguageTag(expectedLanguageSubtagParserBCP); 400 assertEquals("ICU from BCP47 => ICU from legacy:\t" + source, icuFromBCP, icuFromICU); 401 402 ltp.set(source); 403 String actualLanguageSubtagParserIcu = ltp.toString(); 404 assertEquals( 405 "Language subtag (ICU) for " + source, 406 expectedLanguageSubtagParserIcu, 407 actualLanguageSubtagParserIcu); 408 String actualLanguageSubtagParserBCP = 409 ltp.toString(LanguageTagParser.OutputOption.BCP47); 410 assertEquals( 411 "Language subtag (BCP47) for " + source, 412 expectedLanguageSubtagParserBCP, 413 actualLanguageSubtagParserBCP); 414 } 415 } 416 TestLanguageTagCanonicalizer()417 public void TestLanguageTagCanonicalizer() { 418 String[][] tests = { 419 {"dE-foniPa", "de_fonipa"}, 420 // { "el-1901-polytoni-aaland", "el_AX_1901_polyton" }, // doesn't yet handle 421 // polyton 422 // { "en-POLYTONI-WHATEVER-ANYTHING-AALAND", 423 // "en_AX_anything_polyton_whatever" }, // doesn't yet handle polyton 424 {"eng-840", "en"}, 425 {"sh_ba", "sr_Latn_BA"}, 426 {"iw-arab-010", "he_Arab_AQ"}, 427 {LocaleNames.UND, LocaleNames.UND}, 428 {"und_us", "und_US"}, 429 {"und_su", "und_RU"}, 430 }; 431 LanguageTagCanonicalizer canon = new LanguageTagCanonicalizer(); 432 for (String[] inputExpected : tests) { 433 assertEquals("Canonicalize", inputExpected[1], canon.transform(inputExpected[0])); 434 } 435 } 436 437 final Map<LstrType, Map<String, Map<LstrField, String>>> lstr = StandardCodes.getEnumLstreg(); 438 final Map<String, Map<String, R2<List<String>, String>>> typeToCodeToReplacement = 439 CLDRConfig.getInstance().getSupplementalDataInfo().getLocaleAliasInfo(); 440 TestLstrConsistency()441 public void TestLstrConsistency() { 442 // get the alias info, and process 443 // eg "language" -> "sh" -> <{"sr_Latn"}, reason> 444 445 // quick consistency check of lstr 446 // hack for extlang. 447 Map<String, Map<LstrField, String>> extlangItems = lstr.get(LstrType.extlang); 448 Map<String, Map<LstrField, String>> languageItems = lstr.get(LstrType.language); 449 if (!languageItems.keySet().containsAll(extlangItems.keySet())) { 450 errln( 451 "extlang not subset of language: " 452 + setDifference(extlangItems.keySet(), languageItems.keySet())); 453 } 454 455 ImmutableSet<LstrType> LstrTypesToSkip = 456 ImmutableSet.of(LstrType.extlang, LstrType.legacy, LstrType.redundant); 457 Set<LstrType> lstrTypesToTest = EnumSet.allOf(LstrType.class); 458 lstrTypesToTest.removeAll(LstrTypesToSkip); 459 Set<String> missingAliases = new LinkedHashSet<>(); 460 Map<String, String> changedAliases = new LinkedHashMap<>(); 461 462 for (LstrType lstrType : lstrTypesToTest) { 463 Map<String, Map<LstrField, String>> lstrValue = lstr.get(lstrType); 464 if (lstrValue == null) { 465 continue; 466 } 467 Map<String, R2<List<String>, String>> codeToReplacement = 468 typeToCodeToReplacement.get(lstrType.toCompatString()); 469 470 Set<String> lstrDeprecated = new TreeSet<>(); 471 Set<String> aliased = new TreeSet<>(); 472 Map<String, String> lstrPreferred = new TreeMap<>(); 473 Map<String, String> aliasPreferred = new TreeMap<>(); 474 475 for (Entry<String, Map<LstrField, String>> codeToData : lstrValue.entrySet()) { 476 String code = codeToData.getKey(); 477 Map<LstrField, String> data = codeToData.getValue(); 478 boolean deprecated = data.get(LstrField.Deprecated) != null; 479 if (deprecated) { 480 lstrDeprecated.add(code); 481 } 482 String preferred = data.get(LstrField.Preferred_Value); 483 if (preferred != null) { 484 lstrPreferred.put(code, preferred); 485 } 486 if (codeToReplacement != null) { 487 R2<List<String>, String> replacement = codeToReplacement.get(code); 488 if (replacement != null) { 489 aliased.add(code); 490 List<String> replacementList = replacement.get0(); 491 aliasPreferred.put(code, CldrUtility.join(replacementList, " ")); 492 } 493 } 494 } 495 Set<String> diff = setDifference(aliased, lstrDeprecated); 496 logln(lstrType + ": aliased and not deprecated in lstr: " + diff); 497 lstrDeprecated.addAll(diff); 498 499 // // special exceptions 500 // switch(lstrType) { 501 // case script: lstrDeprecated.add("Qaai"); break; 502 // case region: lstrDeprecated.add("QU"); break; 503 // default: break; 504 // } 505 506 Map<Status, Set<String>> statusToCodes = validity.getStatusToCodes(lstrType); 507 Set<String> validityDeprecated = 508 statusToCodes == null ? null : statusToCodes.get(Status.deprecated); 509 510 if (!Objects.equal(lstrDeprecated, validityDeprecated)) { 511 showMinus( 512 "Deprecated lstr - validity", lstrType, lstrDeprecated, validityDeprecated); 513 showMinus( 514 "Deprecated validity - lstr", lstrType, validityDeprecated, lstrDeprecated); 515 } 516 517 if (!Objects.equal(lstrPreferred, aliasPreferred)) { 518 // showMinus("Preferred lstr - alias", lstrType, lstrPreferred.entrySet(), 519 // aliasPreferred.entrySet()); 520 for (Entry<String, String> entry : lstrPreferred.entrySet()) { 521 String code = entry.getKey(); 522 String lstrReplacement = entry.getValue(); 523 String aliasValue = aliasPreferred.get(code); 524 if (lstrReplacement.equals(aliasValue)) { 525 continue; 526 } 527 String newAlias = makeAliasXml(lstrType, code, lstrReplacement, "deprecated"); 528 if (aliasValue == null) { 529 missingAliases.add(newAlias); 530 } else { 531 changedAliases.put( 532 newAlias, makeAliasXml(lstrType, code, aliasValue, "deprecated")); 533 } 534 } 535 } 536 } 537 if (!missingAliases.isEmpty()) { 538 errln("Missing aliases for supplementalMetadata: " + missingAliases.size()); 539 for (String s : missingAliases) { 540 System.out.println(s); 541 } 542 } 543 if (!changedAliases.isEmpty()) { 544 warnln("Changed aliases from LSTR, just double-check: " + changedAliases.size()); 545 for (Entry<String, String> entry : changedAliases.entrySet()) { 546 getLogger().fine("\tcurrent=" + entry.getValue() + "\n\tlstr=" + entry.getKey()); 547 } 548 } 549 } 550 551 // <languageAlias type="art_lojban" replacement="jbo" reason="deprecated"/> <!-- Lojban --> 552 // <scriptAlias type="Qaai" replacement="Zinh" reason="deprecated"/> 553 // <territoryAlias type="AAA" replacement="AA" reason="overlong"/> <!-- null --> 554 // <variantAlias type="AALAND" replacement="AX" reason="deprecated"/> makeAliasXml( LstrType lstrType, String code, String lstrReplacement, String reason)555 private String makeAliasXml( 556 LstrType lstrType, String code, String lstrReplacement, String reason) { 557 return "<" 558 + lstrType.toCompatString() 559 + "Alias" 560 + " type=\"" 561 + code 562 + "\"" 563 + " replacement=\"" 564 + lstrReplacement 565 + "\"" 566 + " reason=\"" 567 + reason 568 + "\"/>" 569 + " <!-- " 570 + TransliteratorUtilities.toXML.transform( 571 CLDRConfig.getInstance().getEnglish().getName(code) 572 + " ⇒ " 573 + CLDRConfig.getInstance().getEnglish().getName(lstrReplacement)) 574 + " -->"; 575 } 576 setDifference(U a, U b)577 private <T, U extends Collection<T>> Set<T> setDifference(U a, U b) { 578 Set<T> diff = new LinkedHashSet<>(a); 579 diff.removeAll(b); 580 return diff; 581 } 582 showMinus(String title, LstrType lstrType, Set<T> a, Set<T> b)583 private <T> Set<T> showMinus(String title, LstrType lstrType, Set<T> a, Set<T> b) { 584 if (a == null) { 585 a = Collections.emptySet(); 586 } 587 if (b == null) { 588 b = Collections.emptySet(); 589 } 590 591 Set<T> diff = setDifference(a, b); 592 if (!diff.isEmpty()) { 593 T first = diff.iterator().next(); 594 if (first instanceof String) { 595 List<String> names = 596 diff.stream() 597 .map( 598 code -> 599 "\n\t\t" 600 + code 601 + " ⇒\t" 602 + lstr.get(lstrType).get(code) 603 + "\n\t\tvalid.⇒\t" 604 + CldrUtility.ifNull( 605 validity.getCodeToStatus( 606 lstrType), 607 Collections.emptyMap()) 608 .get(code) 609 + "\n\t\talias⇒\t" 610 + CldrUtility.ifNull( 611 typeToCodeToReplacement.get( 612 lstrType 613 .toCompatString()), 614 Collections.emptyMap()) 615 .get(code)) 616 .collect(Collectors.toList()); 617 errln( 618 title 619 + "\n\tLstrType=\t" 620 + lstrType 621 + "\n\tsize=\t" 622 + diff.size() 623 + "\n\tcodes=\t" 624 + diff 625 + "\n\tnames=\t" 626 + names); 627 } else { 628 errln( 629 title 630 + "\n\tLstrType=\t" 631 + lstrType 632 + "\n\tsize=\t" 633 + diff.size() 634 + "\n\tcodes=\t" 635 + diff); 636 } 637 } 638 return diff; 639 } 640 641 final Set<LstrType> EXPECTED_NULL = 642 ImmutableSet.of( 643 LstrType.extlang, 644 LstrType.legacy, 645 LstrType.redundant, 646 LstrType.usage, 647 LstrType.zone, 648 LstrType.extension); 649 final Set<LstrType> EXPECTED_UNSORTED = ImmutableSet.of(LstrType.unit); 650 testOrder()651 public void testOrder() { 652 for (LstrType type : LstrType.values()) { 653 Map<Status, Set<String>> statusToCodes = validity.getStatusToCodes(type); 654 assertEquals(type + " is null", EXPECTED_NULL.contains(type), statusToCodes == null); 655 if (statusToCodes == null) { 656 continue; 657 } 658 for (Entry<Status, Set<String>> entry : statusToCodes.entrySet()) { 659 Status status = entry.getKey(); 660 Set<String> codes = entry.getValue(); 661 List<String> list = new ArrayList<>(codes); 662 List<String> sorted = new ArrayList<>(new TreeSet<>(codes)); 663 boolean isSorted = list.equals(sorted); 664 assertEquals( 665 type + ":" + status + " is sorted", 666 EXPECTED_UNSORTED.contains(type), 667 !isSorted); 668 } 669 } 670 } 671 } 672