1 package org.unicode.cldr.util; 2 3 import static org.unicode.cldr.util.PathUtilities.getNormalizedPathString; 4 5 import com.google.common.base.Joiner; 6 import com.google.common.base.Splitter; 7 import com.google.common.base.Supplier; 8 import com.google.common.base.Suppliers; 9 import com.google.common.collect.ImmutableList; 10 import com.google.common.collect.ImmutableSet; 11 import com.google.common.collect.ImmutableSetMultimap; 12 import com.google.common.collect.Multimap; 13 import com.google.common.collect.Sets; 14 import com.google.common.collect.TreeMultimap; 15 import com.ibm.icu.impl.IterableComparator; 16 import com.ibm.icu.impl.Relation; 17 import com.ibm.icu.impl.Row; 18 import com.ibm.icu.impl.Row.R2; 19 import com.ibm.icu.impl.Row.R4; 20 import com.ibm.icu.impl.number.DecimalQuantity; 21 import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; 22 import com.ibm.icu.text.DateFormat; 23 import com.ibm.icu.text.MessageFormat; 24 import com.ibm.icu.text.NumberFormat; 25 import com.ibm.icu.text.PluralRules; 26 import com.ibm.icu.text.PluralRules.DecimalQuantitySamples; 27 import com.ibm.icu.text.PluralRules.DecimalQuantitySamplesRange; 28 import com.ibm.icu.text.PluralRules.FixedDecimal; 29 import com.ibm.icu.text.PluralRules.Operand; 30 import com.ibm.icu.text.PluralRules.SampleType; 31 import com.ibm.icu.text.SimpleDateFormat; 32 import com.ibm.icu.text.UnicodeSet; 33 import com.ibm.icu.util.Freezable; 34 import com.ibm.icu.util.ICUUncheckedIOException; 35 import com.ibm.icu.util.Output; 36 import com.ibm.icu.util.TimeZone; 37 import com.ibm.icu.util.ULocale; 38 import com.ibm.icu.util.VersionInfo; 39 import java.io.File; 40 import java.text.ParseException; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Collection; 44 import java.util.Collections; 45 import java.util.Comparator; 46 import java.util.Date; 47 import java.util.Deque; 48 import java.util.EnumMap; 49 import java.util.EnumSet; 50 import java.util.HashMap; 51 import java.util.HashSet; 52 import java.util.Iterator; 53 import java.util.LinkedHashMap; 54 import java.util.LinkedHashSet; 55 import java.util.LinkedList; 56 import java.util.List; 57 import java.util.Locale; 58 import java.util.Map; 59 import java.util.Map.Entry; 60 import java.util.Objects; 61 import java.util.Set; 62 import java.util.TreeMap; 63 import java.util.TreeSet; 64 import java.util.concurrent.ConcurrentHashMap; 65 import java.util.regex.Matcher; 66 import java.util.regex.Pattern; 67 import java.util.stream.Collectors; 68 import org.unicode.cldr.test.CoverageLevel2; 69 import org.unicode.cldr.tool.LikelySubtags; 70 import org.unicode.cldr.tool.SubdivisionNames; 71 import org.unicode.cldr.util.Builder.CBuilder; 72 import org.unicode.cldr.util.CldrUtility.VariableReplacer; 73 import org.unicode.cldr.util.DayPeriodInfo.DayPeriod; 74 import org.unicode.cldr.util.DtdType.DtdStatus; 75 import org.unicode.cldr.util.GrammarInfo.GrammaticalFeature; 76 import org.unicode.cldr.util.GrammarInfo.GrammaticalScope; 77 import org.unicode.cldr.util.GrammarInfo.GrammaticalTarget; 78 import org.unicode.cldr.util.Rational.RationalParser; 79 import org.unicode.cldr.util.StandardCodes.CodeType; 80 import org.unicode.cldr.util.StandardCodes.LstrType; 81 import org.unicode.cldr.util.SupplementalDataInfo.BasicLanguageData.Type; 82 import org.unicode.cldr.util.SupplementalDataInfo.NumberingSystemInfo.NumberingSystemType; 83 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count; 84 import org.unicode.cldr.util.Validity.Status; 85 import org.unicode.cldr.util.personname.PersonNameFormatter; 86 import org.unicode.cldr.util.personname.PersonNameFormatter.Order; 87 88 /** 89 * Singleton class to provide API access to supplemental data -- in all the supplemental data files. 90 * 91 * <p>To create, use SupplementalDataInfo.getInstance 92 * 93 * <p>To add API for new structure, you will generally: 94 * 95 * <ul> 96 * <li>add a Map or Relation as a data member, 97 * <li>put a check and handler in MyHandler for the paths that you consume, 98 * <li>make the data member immutable in makeStuffSave, and 99 * <li>add a getter for the data member 100 * </ul> 101 * 102 * @author markdavis 103 */ 104 public class SupplementalDataInfo { 105 private static final boolean DEBUG = false; 106 private static final StandardCodes sc = StandardCodes.make(); 107 private static final String UNKNOWN_SCRIPT = "Zzzz"; 108 109 public static final Splitter split_space = Splitter.on(' ').omitEmptyStrings(); 110 111 // TODO add structure for items shown by TestSupplementalData to be missing 112 /* 113 * [calendarData/calendar, 114 * characters/character-fallback, 115 * measurementData/measurementSystem, measurementData/paperSize, 116 * metadata/attributeOrder, metadata/blocking, metadata/deprecated, 117 * metadata/distinguishing, metadata/elementOrder, metadata/serialElements, metadata/skipDefaultLocale, 118 * metadata/suppress, metadata/validity, metazoneInfo/timezone, 119 * timezoneData/mapTimezones, 120 * weekData/firstDay, weekData/minDays, weekData/weekendEnd, weekData/weekendStart] 121 */ 122 // TODO: verify that we get everything by writing the files solely from the API, and verifying 123 // identity. 124 125 public enum UnitIdComponentType { 126 prefix, 127 base, 128 suffix, 129 per, 130 and, 131 power; 132 toShortId()133 public String toShortId() { 134 return name().substring(0, 1).toUpperCase(); 135 } 136 } 137 138 public class UnitPrefixInfo { 139 final String abbreviation; 140 final int base; 141 final int power; 142 UnitPrefixInfo(String abbreviation, int base, int power)143 public UnitPrefixInfo(String abbreviation, int base, int power) { 144 this.abbreviation = abbreviation; 145 this.base = base; 146 this.power = power; 147 } 148 149 @Override toString()150 public String toString() { 151 return String.format( 152 "%s\t%s", abbreviation, String.valueOf(base) + "^" + String.valueOf(power)); 153 } 154 } 155 156 /** Official status of languages */ 157 public enum OfficialStatus { 158 unknown("U", 1), 159 recognized("R", 1), 160 official_minority("OM", 2), 161 official_regional("OR", 3), 162 de_facto_official("OD", 10), 163 official("O", 10); 164 165 private final String shortName; 166 private final int weight; 167 OfficialStatus(String shortName, int weight)168 private OfficialStatus(String shortName, int weight) { 169 this.shortName = shortName; 170 this.weight = weight; 171 } 172 toShortString()173 public String toShortString() { 174 return shortName; 175 } 176 getWeight()177 public int getWeight() { 178 return weight; 179 } 180 isMajor()181 public boolean isMajor() { 182 return compareTo(OfficialStatus.de_facto_official) >= 0; 183 } 184 isOfficial()185 public boolean isOfficial() { 186 return compareTo(OfficialStatus.official_regional) >= 0; 187 } 188 } 189 190 /** Population data for different languages. */ 191 public static final class PopulationData implements Freezable<PopulationData> { 192 private double population = Double.NaN; 193 194 private double literatePopulation = Double.NaN; 195 196 private double writingPopulation = Double.NaN; 197 198 private double gdp = Double.NaN; 199 200 private OfficialStatus officialStatus = OfficialStatus.unknown; 201 getGdp()202 public double getGdp() { 203 return gdp; 204 } 205 getLiteratePopulation()206 public double getLiteratePopulation() { 207 return literatePopulation; 208 } 209 getLiteratePopulationPercent()210 public double getLiteratePopulationPercent() { 211 return 100 * literatePopulation / population; 212 } 213 getWritingPopulation()214 public double getWritingPopulation() { 215 return writingPopulation; 216 } 217 getWritingPercent()218 public double getWritingPercent() { 219 return 100 * writingPopulation / population; 220 } 221 getPopulation()222 public double getPopulation() { 223 return population; 224 } 225 setGdp(double gdp)226 public PopulationData setGdp(double gdp) { 227 if (frozen) { 228 throw new UnsupportedOperationException("Attempt to modify frozen object"); 229 } 230 this.gdp = gdp; 231 return this; 232 } 233 setLiteratePopulation(double literatePopulation)234 public PopulationData setLiteratePopulation(double literatePopulation) { 235 if (frozen) { 236 throw new UnsupportedOperationException("Attempt to modify frozen object"); 237 } 238 this.literatePopulation = literatePopulation; 239 return this; 240 } 241 setPopulation(double population)242 public PopulationData setPopulation(double population) { 243 if (frozen) { 244 throw new UnsupportedOperationException("Attempt to modify frozen object"); 245 } 246 this.population = population; 247 return this; 248 } 249 set(PopulationData other)250 public PopulationData set(PopulationData other) { 251 if (frozen) { 252 throw new UnsupportedOperationException("Attempt to modify frozen object"); 253 } 254 if (other == null) { 255 population = literatePopulation = gdp = Double.NaN; 256 } else { 257 population = other.population; 258 literatePopulation = other.literatePopulation; 259 writingPopulation = other.writingPopulation; 260 gdp = other.gdp; 261 } 262 return this; 263 } 264 add(PopulationData other)265 public void add(PopulationData other) { 266 if (frozen) { 267 throw new UnsupportedOperationException("Attempt to modify frozen object"); 268 } 269 population += other.population; 270 literatePopulation += other.literatePopulation; 271 writingPopulation += other.writingPopulation; 272 gdp += other.gdp; 273 } 274 275 @Override toString()276 public String toString() { 277 return MessageFormat.format( 278 "[pop: {0,number,#,##0},\t lit: {1,number,#,##0.00},\t gdp: {2,number,#,##0},\t status: {3}]", 279 new Object[] {population, literatePopulation, gdp, officialStatus}); 280 } 281 282 private boolean frozen; 283 284 @Override isFrozen()285 public boolean isFrozen() { 286 return frozen; 287 } 288 289 @Override freeze()290 public PopulationData freeze() { 291 frozen = true; 292 return this; 293 } 294 295 @Override cloneAsThawed()296 public PopulationData cloneAsThawed() { 297 throw new UnsupportedOperationException("not yet implemented"); 298 } 299 getOfficialStatus()300 public OfficialStatus getOfficialStatus() { 301 return officialStatus; 302 } 303 setOfficialStatus(OfficialStatus officialStatus)304 public PopulationData setOfficialStatus(OfficialStatus officialStatus) { 305 if (frozen) { 306 throw new UnsupportedOperationException("Attempt to modify frozen object"); 307 } 308 this.officialStatus = officialStatus; 309 return this; 310 } 311 setWritingPopulation(double writingPopulation)312 public PopulationData setWritingPopulation(double writingPopulation) { 313 if (frozen) { 314 throw new UnsupportedOperationException("Attempt to modify frozen object"); 315 } 316 this.writingPopulation = writingPopulation; 317 return this; 318 } 319 } 320 321 static final Pattern WHITESPACE_PATTERN = PatternCache.get("\\s+"); 322 323 /** Simple language/script/region information */ 324 public static class BasicLanguageData 325 implements Comparable<BasicLanguageData>, 326 com.ibm.icu.util.Freezable<BasicLanguageData> { 327 public enum Type { 328 primary, 329 secondary 330 } 331 332 private Type type = Type.primary; 333 334 private Set<String> scripts = Collections.emptySet(); 335 336 private Set<String> territories = Collections.emptySet(); 337 getType()338 public Type getType() { 339 return type; 340 } 341 setType(Type type)342 public BasicLanguageData setType(Type type) { 343 this.type = type; 344 return this; 345 } 346 setScripts(String scriptTokens)347 public BasicLanguageData setScripts(String scriptTokens) { 348 return setScripts( 349 scriptTokens == null 350 ? null 351 : Arrays.asList(WHITESPACE_PATTERN.split(scriptTokens))); 352 } 353 setTerritories(String territoryTokens)354 public BasicLanguageData setTerritories(String territoryTokens) { 355 return setTerritories( 356 territoryTokens == null 357 ? null 358 : Arrays.asList(WHITESPACE_PATTERN.split(territoryTokens))); 359 } 360 setScripts(Collection<String> scriptTokens)361 public BasicLanguageData setScripts(Collection<String> scriptTokens) { 362 if (frozen) { 363 throw new UnsupportedOperationException(); 364 } 365 // TODO add error checking 366 scripts = Collections.emptySet(); 367 if (scriptTokens != null) { 368 for (String script : scriptTokens) { 369 addScript(script); 370 } 371 } 372 return this; 373 } 374 setTerritories(Collection<String> territoryTokens)375 public BasicLanguageData setTerritories(Collection<String> territoryTokens) { 376 if (frozen) { 377 throw new UnsupportedOperationException(); 378 } 379 territories = Collections.emptySet(); 380 if (territoryTokens != null) { 381 for (String territory : territoryTokens) { 382 addTerritory(territory); 383 } 384 } 385 return this; 386 } 387 set(BasicLanguageData other)388 public BasicLanguageData set(BasicLanguageData other) { 389 scripts = other.scripts; 390 territories = other.territories; 391 return this; 392 } 393 getScripts()394 public Set<String> getScripts() { 395 return scripts; 396 } 397 getTerritories()398 public Set<String> getTerritories() { 399 return territories; 400 } 401 toString(String languageSubtag)402 public String toString(String languageSubtag) { 403 if (scripts.size() == 0 && territories.size() == 0) return ""; 404 return "\t\t<language type=\"" 405 + languageSubtag 406 + "\"" 407 + (scripts.size() == 0 408 ? "" 409 : " scripts=\"" + CldrUtility.join(scripts, " ") + "\"") 410 + (territories.size() == 0 411 ? "" 412 : " territories=\"" + CldrUtility.join(territories, " ") + "\"") 413 + (type == Type.primary ? "" : " alt=\"" + type + "\"") 414 + "/>"; 415 } 416 417 @Override toString()418 public String toString() { 419 return "[" 420 + type 421 + (scripts.isEmpty() ? "" : "; scripts=" + Joiner.on(" ").join(scripts)) 422 + (scripts.isEmpty() ? "" : "; territories=" + Joiner.on(" ").join(territories)) 423 + "]"; 424 } 425 426 @Override compareTo(BasicLanguageData o)427 public int compareTo(BasicLanguageData o) { 428 int result; 429 if (0 != (result = type.compareTo(o.type))) return result; 430 if (0 != (result = IterableComparator.compareIterables(scripts, o.scripts))) 431 return result; 432 if (0 != (result = IterableComparator.compareIterables(territories, o.territories))) 433 return result; 434 return 0; 435 } 436 437 @Override equals(Object input)438 public boolean equals(Object input) { 439 return compareTo((BasicLanguageData) input) == 0; 440 } 441 442 @Override hashCode()443 public int hashCode() { 444 // TODO Auto-generated method stub 445 return ((type.ordinal() * 37 + scripts.hashCode()) * 37) + territories.hashCode(); 446 } 447 addScript(String script)448 public BasicLanguageData addScript(String script) { 449 // simple error checking 450 if (script.length() != 4) { 451 throw new IllegalArgumentException("Illegal Script: " + script); 452 } 453 if (scripts == Collections.EMPTY_SET) { 454 scripts = new TreeSet<>(); 455 } 456 scripts.add(script); 457 return this; 458 } 459 addTerritory(String territory)460 public BasicLanguageData addTerritory(String territory) { 461 // simple error checking 462 if (territory.length() != 2) { 463 throw new IllegalArgumentException("Illegal Territory: " + territory); 464 } 465 if (territories == Collections.EMPTY_SET) { 466 territories = new TreeSet<>(); 467 } 468 territories.add(territory); 469 return this; 470 } 471 472 boolean frozen = false; 473 474 @Override isFrozen()475 public boolean isFrozen() { 476 return frozen; 477 } 478 479 @Override freeze()480 public BasicLanguageData freeze() { 481 frozen = true; 482 if (scripts != Collections.EMPTY_SET) { 483 scripts = Collections.unmodifiableSet(scripts); 484 } 485 if (territories != Collections.EMPTY_SET) { 486 territories = Collections.unmodifiableSet(territories); 487 } 488 return this; 489 } 490 491 @Override cloneAsThawed()492 public BasicLanguageData cloneAsThawed() { 493 BasicLanguageData result = new BasicLanguageData(); 494 result.scripts = new TreeSet<>(scripts); 495 result.territories = new TreeSet<>(territories); 496 return this; 497 } 498 addScripts(Set<String> scripts2)499 public void addScripts(Set<String> scripts2) { 500 for (String script : scripts2) { 501 addScript(script); 502 } 503 } 504 } 505 506 /** Information about currency digits and rounding. */ 507 public static class CurrencyNumberInfo { 508 public final int digits; 509 public final int rounding; 510 public final double roundingIncrement; 511 public final int cashDigits; 512 public final int cashRounding; 513 public final double cashRoundingIncrement; 514 getDigits()515 public int getDigits() { 516 return digits; 517 } 518 getRounding()519 public int getRounding() { 520 return rounding; 521 } 522 getRoundingIncrement()523 public double getRoundingIncrement() { 524 return roundingIncrement; 525 } 526 CurrencyNumberInfo(int _digits, int _rounding, int _cashDigits, int _cashRounding)527 public CurrencyNumberInfo(int _digits, int _rounding, int _cashDigits, int _cashRounding) { 528 digits = _digits; 529 rounding = _rounding < 0 ? 0 : _rounding; 530 roundingIncrement = rounding * Math.pow(10.0, -digits); 531 // if the values are not set, use the above values 532 cashDigits = _cashDigits < 0 ? digits : _cashDigits; 533 cashRounding = _cashRounding < 0 ? rounding : _cashRounding; 534 cashRoundingIncrement = this.cashRounding * Math.pow(10.0, -digits); 535 } 536 } 537 538 public static class NumberingSystemInfo { 539 public enum NumberingSystemType { 540 algorithmic, 541 numeric, 542 unknown 543 } 544 545 public final String name; 546 public final NumberingSystemType type; 547 public final String digits; 548 public final String rules; 549 550 public NumberingSystemInfo(XPathParts parts) { 551 name = parts.getAttributeValue(-1, "id"); 552 digits = parts.getAttributeValue(-1, "digits"); 553 rules = parts.getAttributeValue(-1, "rules"); 554 type = NumberingSystemType.valueOf(parts.getAttributeValue(-1, "type")); 555 } 556 } 557 558 /** 559 * Class for a range of two dates, refactored to share code. 560 * 561 * @author markdavis 562 */ 563 public static final class DateRange implements Comparable<DateRange> { 564 public static final long START_OF_TIME = Long.MIN_VALUE; 565 public static final long END_OF_TIME = Long.MAX_VALUE; 566 public final long from; 567 public final long to; 568 569 public DateRange(String fromString, String toString) { 570 from = parseDate(fromString, START_OF_TIME); 571 to = parseDate(toString, END_OF_TIME); 572 } 573 574 public long getFrom() { 575 return from; 576 } 577 578 public long getTo() { 579 return to; 580 } 581 582 static final DateFormat[] simpleFormats = { 583 new SimpleDateFormat("yyyy-MM-dd HH:mm"), 584 new SimpleDateFormat("yyyy-MM-dd"), 585 new SimpleDateFormat("yyyy-MM"), 586 new SimpleDateFormat("yyyy"), 587 }; 588 589 static { 590 TimeZone gmt = TimeZone.getTimeZone("GMT"); 591 for (DateFormat format : simpleFormats) { 592 format.setTimeZone(gmt); 593 } 594 } 595 596 long parseDate(String dateString, long defaultDate) { 597 if (dateString == null) { 598 return defaultDate; 599 } 600 ParseException e2 = null; 601 for (int i = 0; i < simpleFormats.length; ++i) { 602 try { 603 synchronized (simpleFormats[i]) { 604 Date result = simpleFormats[i].parse(dateString); 605 return result.getTime(); 606 } 607 } catch (ParseException e) { 608 if (e2 == null) { 609 e2 = e; 610 } 611 } 612 } 613 throw new IllegalArgumentException(e2); 614 } 615 616 @Override 617 public String toString() { 618 return "{" + formatDate(from) + ", " + formatDate(to) + "}"; 619 } 620 621 public static String formatDate(long date) { 622 if (date == START_OF_TIME) { 623 return "-∞"; 624 } 625 if (date == END_OF_TIME) { 626 return "∞"; 627 } 628 synchronized (simpleFormats[0]) { 629 return simpleFormats[0].format(date); 630 } 631 } 632 633 @Override 634 public int compareTo(DateRange arg0) { 635 return to > arg0.to 636 ? 1 637 : to < arg0.to ? -1 : from > arg0.from ? 1 : from < arg0.from ? -1 : 0; 638 } 639 } 640 641 /** Information about when currencies are in use in territories */ 642 public static class CurrencyDateInfo implements Comparable<CurrencyDateInfo> { 643 644 public static final Date END_OF_TIME = new Date(DateRange.END_OF_TIME); 645 public static final Date START_OF_TIME = new Date(DateRange.START_OF_TIME); 646 647 private String currency; 648 private DateRange dateRange; 649 private boolean isLegalTender; 650 private String errors = ""; 651 652 public CurrencyDateInfo(String currency, String startDate, String endDate, String tender) { 653 this.currency = currency; 654 this.dateRange = new DateRange(startDate, endDate); 655 this.isLegalTender = (tender == null || !tender.equals("false")); 656 } 657 658 public String getCurrency() { 659 return currency; 660 } 661 662 public Date getStart() { 663 return new Date(dateRange.getFrom()); 664 } 665 666 public Date getEnd() { 667 return new Date(dateRange.getTo()); 668 } 669 670 public String getErrors() { 671 return errors; 672 } 673 674 public boolean isLegalTender() { 675 return isLegalTender; 676 } 677 678 @Override 679 public int compareTo(CurrencyDateInfo o) { 680 int result = dateRange.compareTo(o.dateRange); 681 if (result != 0) return result; 682 return currency.compareTo(o.currency); 683 } 684 685 @Override 686 public String toString() { 687 return "{" + dateRange + ", " + currency + "}"; 688 } 689 690 public static String formatDate(Date date) { 691 return DateRange.formatDate(date.getTime()); 692 } 693 } 694 695 public static final class MetaZoneRange implements Comparable<MetaZoneRange> { 696 public final DateRange dateRange; 697 public final String metazone; 698 699 /** 700 * @param metazone 701 * @param fromString 702 * @param toString 703 */ 704 public MetaZoneRange(String metazone, String fromString, String toString) { 705 super(); 706 this.metazone = metazone; 707 dateRange = new DateRange(fromString, toString); 708 } 709 710 @Override 711 public int compareTo(MetaZoneRange arg0) { 712 int result; 713 if (0 != (result = dateRange.compareTo(arg0.dateRange))) { 714 return result; 715 } 716 return metazone.compareTo(arg0.metazone); 717 } 718 719 @Override 720 public String toString() { 721 return "{" + dateRange + ", " + metazone + "}"; 722 } 723 } 724 725 /** Information about telephone code(s) for a given territory */ 726 public static class TelephoneCodeInfo implements Comparable<TelephoneCodeInfo> { 727 public static final Date END_OF_TIME = new Date(Long.MAX_VALUE); 728 public static final Date START_OF_TIME = new Date(Long.MIN_VALUE); 729 private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 730 731 private String code; 732 private Date start; 733 private Date end; 734 private String alt; 735 private String errors = ""; 736 737 // code must not be null, the others can be 738 public TelephoneCodeInfo(String code, String startDate, String endDate, String alt) { 739 if (code == null) throw new NullPointerException(); 740 this.code = code; // code will not be null 741 this.start = parseDate(startDate, START_OF_TIME); // start will not be null 742 this.end = parseDate(endDate, END_OF_TIME); // end willl not be null 743 this.alt = (alt == null) ? "" : alt; // alt will not be null 744 } 745 746 static DateFormat[] simpleFormats = { 747 new SimpleDateFormat("yyyy-MM-dd"), 748 new SimpleDateFormat("yyyy-MM"), 749 new SimpleDateFormat("yyyy"), 750 }; 751 752 Date parseDate(String dateString, Date defaultDate) { 753 if (dateString == null) { 754 return defaultDate; 755 } 756 ParseException e2 = null; 757 for (int i = 0; i < simpleFormats.length; ++i) { 758 try { 759 Date result = simpleFormats[i].parse(dateString); 760 return result; 761 } catch (ParseException e) { 762 if (i == 0) { 763 errors += dateString + " "; 764 } 765 if (e2 == null) { 766 e2 = e; 767 } 768 } 769 } 770 throw (IllegalArgumentException) new IllegalArgumentException().initCause(e2); 771 } 772 773 public String getCode() { 774 return code; 775 } 776 777 public Date getStart() { 778 return start; 779 } 780 781 public Date getEnd() { 782 return end; 783 } 784 785 public String getAlt() { 786 return alt; // may return null 787 } 788 789 public String getErrors() { 790 return errors; 791 } 792 793 @Override 794 public boolean equals(Object o) { 795 if (!(o instanceof TelephoneCodeInfo)) return false; 796 TelephoneCodeInfo tc = (TelephoneCodeInfo) o; 797 return tc.code.equals(code) 798 && tc.start.equals(start) 799 && tc.end.equals(end) 800 && tc.alt.equals(alt); 801 } 802 803 @Override 804 public int hashCode() { 805 return 31 * code.hashCode() + start.hashCode() + end.hashCode() + alt.hashCode(); 806 } 807 808 @Override 809 public int compareTo(TelephoneCodeInfo o) { 810 int result = code.compareTo(o.code); 811 if (result != 0) return result; 812 result = start.compareTo(o.start); 813 if (result != 0) return result; 814 result = end.compareTo(o.end); 815 if (result != 0) return result; 816 return alt.compareTo(o.alt); 817 } 818 819 @Override 820 public String toString() { 821 return "{" 822 + code 823 + ", " 824 + formatDate(start) 825 + ", " 826 + formatDate(end) 827 + ", " 828 + alt 829 + "}"; 830 } 831 832 public static String formatDate(Date date) { 833 if (date.equals(START_OF_TIME)) return "-∞"; 834 if (date.equals(END_OF_TIME)) return "∞"; 835 return dateFormat.format(date); 836 } 837 } 838 839 public static class CoverageLevelInfo { 840 public final String match; 841 public final Level value; 842 public final Pattern inLanguage; 843 public final String inScript; 844 public final Set<String> inScriptSet; 845 public final String inTerritory; 846 public final Set<String> inTerritorySet; 847 private Set<String> inTerritorySetInternal; 848 849 public CoverageLevelInfo( 850 String match, int value, String language, String script, String territory) { 851 this.inLanguage = language != null ? PatternCache.get(language) : null; 852 this.inScript = script; 853 this.inTerritory = territory; 854 this.inScriptSet = toSet(script); 855 this.inTerritorySet = toSet(territory); // MUST BE LAST, sets inTerritorySetInternal 856 this.match = match; 857 this.value = Level.fromLevel(value); 858 } 859 860 public static final Pattern NON_ASCII_LETTER = PatternCache.get("[^A-Za-z]+"); 861 862 private Set<String> toSet(String source) { 863 if (source == null) { 864 return null; 865 } 866 Set<String> result = new HashSet<>(Arrays.asList(NON_ASCII_LETTER.split(source))); 867 result.remove(""); 868 inTerritorySetInternal = result; 869 return Collections.unmodifiableSet(result); 870 } 871 872 public static void fixEU(Collection<CoverageLevelInfo> targets, SupplementalDataInfo info) { 873 Set<String> euCountries = info.getContained("EU"); 874 for (CoverageLevelInfo item : targets) { 875 if (item.inTerritorySet != null && item.inTerritorySet.contains("EU")) { 876 item.inTerritorySetInternal.addAll(euCountries); 877 } 878 } 879 } 880 } 881 882 public enum RBNFGroup { 883 SpelloutRules, 884 OrdinalRules, 885 NumberingSystemRules 886 } 887 888 public static final String STAR = "*"; 889 public static final Set<String> STAR_SET = 890 Builder.with(new HashSet<String>()).add("*").freeze(); 891 892 private VersionInfo cldrVersion; 893 894 private String cldrVersionString = null; 895 private String unicodeVersion = null; 896 897 private Map<String, PopulationData> territoryToPopulationData = new TreeMap<>(); 898 899 private Map<String, Map<String, PopulationData>> territoryToLanguageToPopulationData = 900 new TreeMap<>(); 901 902 private Map<String, PopulationData> languageToPopulation = new TreeMap<>(); 903 904 private Map<String, PopulationData> baseLanguageToPopulation = new TreeMap<>(); 905 906 private Relation<String, String> languageToScriptVariants = 907 Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class); 908 909 private Relation<String, String> languageToTerritories = 910 Relation.of(new TreeMap<String, Set<String>>(), LinkedHashSet.class); 911 912 private transient Relation<String, Pair<Boolean, Pair<Double, String>>> languageToTerritories2 = 913 Relation.of( 914 new TreeMap<String, Set<Pair<Boolean, Pair<Double, String>>>>(), TreeSet.class); 915 916 private Map<String, Map<BasicLanguageData.Type, BasicLanguageData>> 917 languageToBasicLanguageData = new TreeMap<>(); 918 919 private Set<String> allLanguages = new TreeSet<>(); 920 private final List<String> approvalRequirements = new LinkedList<>(); // xpath array 921 922 private Relation<String, String> containment = 923 Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class); 924 private Relation<String, String> containmentCore = 925 Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class); 926 private Relation<String, String> containmentGrouping = 927 Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class); 928 private Relation<String, String> containmentDeprecated = 929 Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class); 930 private Relation<String, String> containerToSubdivision = 931 Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class); 932 933 private Map<String, CurrencyNumberInfo> currencyToCurrencyNumberInfo = new TreeMap<>(); 934 935 private Relation<String, CurrencyDateInfo> territoryToCurrencyDateInfo = 936 Relation.of(new TreeMap<String, Set<CurrencyDateInfo>>(), LinkedHashSet.class); 937 938 private Map<String, Set<TelephoneCodeInfo>> territoryToTelephoneCodeInfo = new TreeMap<>(); 939 940 private Map<String, String> zone_territory = new TreeMap<>(); 941 942 private Relation<String, String> zone_aliases = 943 Relation.of(new TreeMap<String, Set<String>>(), LinkedHashSet.class); 944 945 private Map<String, Map<String, Map<String, String>>> typeToZoneToRegionToZone = 946 new TreeMap<>(); 947 private Relation<String, MetaZoneRange> zoneToMetaZoneRanges = 948 Relation.of(new TreeMap<String, Set<MetaZoneRange>>(), TreeSet.class); 949 950 private Map<String, String> metazoneContinentMap = new HashMap<>(); 951 private Set<String> allMetazones = new TreeSet<>(); 952 953 private Map<String, String> alias_zone = new TreeMap<>(); 954 955 public Relation<String, Integer> numericTerritoryMapping = 956 Relation.of(new HashMap<String, Set<Integer>>(), HashSet.class); 957 958 public Relation<String, String> alpha3TerritoryMapping = 959 Relation.of(new HashMap<String, Set<String>>(), HashSet.class); 960 961 public Relation<String, Integer> numericCurrencyCodeMapping = 962 Relation.of(new HashMap<String, Set<Integer>>(), HashSet.class); 963 964 static Map<String, SupplementalDataInfo> directory_instance = new HashMap<>(); 965 966 public Map<String, Map<String, Row.R2<List<String>, String>>> typeToTagToReplacement = 967 new TreeMap<>(); 968 969 Map<String, List<Row.R4<String, String, Integer, Boolean>>> languageMatch = new HashMap<>(); 970 971 public Relation<String, String> bcp47Key2Subtypes = 972 Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class); 973 public Relation<String, String> bcp47Extension2Keys = 974 Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class); 975 public Relation<Row.R2<String, String>, String> bcp47Aliases = 976 Relation.of(new TreeMap<Row.R2<String, String>, Set<String>>(), LinkedHashSet.class); 977 public Map<Row.R2<String, String>, String> bcp47Descriptions = new TreeMap<>(); 978 public Map<Row.R2<String, String>, String> bcp47Since = new TreeMap<>(); 979 public Map<Row.R2<String, String>, String> bcp47Preferred = new TreeMap<>(); 980 public Map<Row.R2<String, String>, String> bcp47Deprecated = new TreeMap<>(); 981 982 Map<String, Map<String, Bcp47KeyInfo>> bcp47KeyToSubtypeToInfo = new TreeMap<>(); 983 Map<String, Map<String, String>> bcp47KeyToAliasToSubtype = new TreeMap<>(); 984 985 public Map<String, String> bcp47ValueType = new TreeMap<>(); 986 987 public Map<String, Row.R2<String, String>> validityInfo = new LinkedHashMap<>(); 988 public Map<AttributeValidityInfo, String> attributeValidityInfo = new LinkedHashMap<>(); 989 990 public Multimap<String, String> languageGroups = TreeMultimap.create(); 991 992 public RationalParser rationalParser = new RationalParser(); 993 994 private UnitConverter unitConverter = null; 995 996 private final UnitPreferences unitPreferences = new UnitPreferences(); 997 998 private Map<String, UnitIdComponentType> unitIdComponentType = new TreeMap<>(); 999 1000 private Map<String, UnitPrefixInfo> unitPrefixInfo = new TreeMap<>(); 1001 1002 public Map<String, GrammarInfo> grammarLocaleToTargetToFeatureToValues = new TreeMap<>(); 1003 public Map<String, GrammarDerivation> localeToGrammarDerivation = new TreeMap<>(); 1004 1005 public Multimap<PersonNameFormatter.Order, String> personNameOrder = TreeMultimap.create(); 1006 1007 public enum MeasurementType { 1008 measurementSystem, 1009 paperSize 1010 } 1011 1012 Map<MeasurementType, Map<String, String>> measurementData = new HashMap<>(); 1013 Map<String, PreferredAndAllowedHour> timeData = new HashMap<>(); 1014 1015 public Relation<String, String> getAlpha3TerritoryMapping() { 1016 return alpha3TerritoryMapping; 1017 } 1018 1019 public Relation<String, Integer> getNumericTerritoryMapping() { 1020 return numericTerritoryMapping; 1021 } 1022 1023 public Relation<String, Integer> getNumericCurrencyCodeMapping() { 1024 return numericCurrencyCodeMapping; 1025 } 1026 1027 /** 1028 * Returns type -> tag -> <replacementList, reason>, like "language" -> "sh" -> <{"sr_Latn"}, 1029 * reason> 1030 * 1031 * @return 1032 */ 1033 public Map<String, Map<String, R2<List<String>, String>>> getLocaleAliasInfo() { 1034 return typeToTagToReplacement; 1035 } 1036 1037 public R2<List<String>, String> getDeprecatedInfo(String type, String code) { 1038 return typeToTagToReplacement.get(type).get(code); 1039 } 1040 1041 public static SupplementalDataInfo getInstance(File supplementalDirectory) { 1042 return getInstance(getNormalizedPathString(supplementalDirectory)); 1043 } 1044 1045 /** Which directory did we come from? */ 1046 private final File directory; 1047 1048 private Validity validity; 1049 1050 /** 1051 * Get an instance chosen using setAsDefaultInstance(), otherwise return an instance using the 1052 * default directory CldrUtility.SUPPLEMENTAL_DIRECTORY 1053 * 1054 * @return 1055 */ 1056 public static SupplementalDataInfo getInstance() { 1057 return SupplementalDataInfoHelper.SINGLETON; 1058 } 1059 1060 /** Mark this as the default instance to be returned by getInstance() */ 1061 public void setAsDefaultInstance() { 1062 SupplementalDataInfoHelper.SINGLETON = this; 1063 } 1064 1065 public static final class SupplementalDataInfoHelper { 1066 // Note: not final, because setAsDefaultInstance can modify it. 1067 static SupplementalDataInfo SINGLETON = CLDRConfig.getInstance().getSupplementalDataInfo(); 1068 } 1069 1070 public static SupplementalDataInfo getInstance(String supplementalDirectory) { 1071 synchronized (SupplementalDataInfo.class) { 1072 // Sanity checks - not null, not empty 1073 if (supplementalDirectory == null) { 1074 throw new IllegalArgumentException("Error: null supplemental directory."); 1075 } 1076 if (supplementalDirectory.isEmpty()) { 1077 throw new IllegalArgumentException( 1078 "Error: The string passed as a parameter resolves to the empty string."); 1079 } 1080 // canonicalize path 1081 String normalizedPath = getNormalizedPathString(supplementalDirectory); 1082 SupplementalDataInfo instance = directory_instance.get(normalizedPath); 1083 if (instance != null) { 1084 return instance; 1085 } 1086 // reaching here means we have not cached the entry 1087 File directory = new File(normalizedPath); 1088 instance = new SupplementalDataInfo(directory); 1089 MyHandler myHandler = instance.new MyHandler(); 1090 XMLFileReader xfr = new XMLFileReader().setHandler(myHandler); 1091 File files1[] = directory.listFiles(); 1092 if (files1 == null || files1.length == 0) { 1093 throw new ICUUncheckedIOException( 1094 "Error: Supplemental files missing from " + directory.getAbsolutePath()); 1095 } 1096 // get bcp47 files also 1097 File bcp47dir = instance.getBcp47Directory(); 1098 if (!bcp47dir.isDirectory()) { 1099 throw new ICUUncheckedIOException( 1100 "Error: BCP47 dir is not a directory: " + bcp47dir.getAbsolutePath()); 1101 } 1102 File files2[] = bcp47dir.listFiles(); 1103 if (files2 == null || files2.length == 0) { 1104 throw new ICUUncheckedIOException( 1105 "Error: BCP47 files missing from " + bcp47dir.getAbsolutePath()); 1106 } 1107 1108 CBuilder<File, ArrayList<File>> builder = Builder.with(new ArrayList<File>()); 1109 builder.addAll(files1); 1110 builder.addAll(files2); 1111 for (File file : builder.get()) { 1112 if (DEBUG) { 1113 System.out.println(getNormalizedPathString(file)); 1114 } 1115 String name = file.toString(); 1116 String shortName = file.getName(); 1117 if (!shortName.endsWith(".xml") 1118 || // skip non-XML 1119 shortName.startsWith("#") 1120 || // skip other junk files 1121 shortName.startsWith(".")) continue; // skip dot files (backups, etc) 1122 xfr.read(name, -1, true); 1123 myHandler.cleanup(); 1124 } 1125 1126 // xfr = new XMLFileReader().setHandler(instance.new MyHandler()); 1127 // .xfr.read(normalizedPath + "/supplementalMetadata.xml", -1, true); 1128 1129 instance.makeStuffSafe(); 1130 // cache 1131 // directory_instance.put(supplementalDirectory, instance); 1132 directory_instance.put(normalizedPath, instance); 1133 // if (!normalizedPath.equals(supplementalDirectory)) { 1134 // directory_instance.put(normalizedPath, instance); 1135 // } 1136 return instance; 1137 } 1138 } 1139 1140 private File getBcp47Directory() { 1141 return new File(getDirectory().getParent(), "bcp47"); 1142 } 1143 1144 private SupplementalDataInfo(File directory) { 1145 this.directory = directory; 1146 this.validity = Validity.getInstance(directory.toString() + "/../validity/"); 1147 } // hide 1148 1149 public static class Bcp47KeyInfo { 1150 public Bcp47KeyInfo( 1151 Set<String> aliases, 1152 String description, 1153 String since, 1154 String preferred, 1155 String deprecated) { 1156 this.description = description; 1157 this.deprecated = !(deprecated == null || deprecated.equals("false")); 1158 this.preferred = preferred; 1159 this.since = since == null ? null : VersionInfo.getInstance(since); 1160 this.aliases = aliases; 1161 } 1162 1163 final String description; 1164 final VersionInfo since; 1165 final String preferred; 1166 final boolean deprecated; 1167 final Set<String> aliases; 1168 1169 @Override 1170 public String toString() { 1171 return String.format( 1172 "{description=«%s» since=%s preferred=%s deprecated=%s aliases=%s}", 1173 description, since, preferred, deprecated, aliases); 1174 } 1175 } 1176 1177 private void makeStuffSafe() { 1178 // now make stuff safe 1179 allLanguages.addAll(languageToPopulation.keySet()); 1180 allLanguages.addAll(baseLanguageToPopulation.keySet()); 1181 allLanguages = Collections.unmodifiableSet(allLanguages); 1182 skippedElements = Collections.unmodifiableSet(skippedElements); 1183 zone_territory = Collections.unmodifiableMap(zone_territory); 1184 alias_zone = Collections.unmodifiableMap(alias_zone); 1185 references = Collections.unmodifiableMap(references); 1186 likelySubtags = Collections.unmodifiableMap(likelySubtags); 1187 likelyOrigins = Collections.unmodifiableMap(likelyOrigins); 1188 currencyToCurrencyNumberInfo = Collections.unmodifiableMap(currencyToCurrencyNumberInfo); 1189 territoryToCurrencyDateInfo.freeze(); 1190 // territoryToTelephoneCodeInfo.freeze(); 1191 territoryToTelephoneCodeInfo = Collections.unmodifiableMap(territoryToTelephoneCodeInfo); 1192 1193 typeToZoneToRegionToZone = CldrUtility.protectCollection(typeToZoneToRegionToZone); 1194 typeToTagToReplacement = CldrUtility.protectCollection(typeToTagToReplacement); 1195 1196 zoneToMetaZoneRanges.freeze(); 1197 1198 containment.freeze(); 1199 containmentCore.freeze(); 1200 // containmentNonDeprecated.freeze(); 1201 containmentGrouping.freeze(); 1202 containmentDeprecated.freeze(); 1203 1204 containerToSubdivision.freeze(); 1205 1206 CldrUtility.protectCollection(languageToBasicLanguageData); 1207 for (String language : languageToTerritories2.keySet()) { 1208 for (Pair<Boolean, Pair<Double, String>> pair : 1209 languageToTerritories2.getAll(language)) { 1210 languageToTerritories.put(language, pair.getSecond().getSecond()); 1211 } 1212 } 1213 languageToTerritories2 = null; // free up the memory. 1214 languageToTerritories.freeze(); 1215 zone_aliases.freeze(); 1216 languageToScriptVariants.freeze(); 1217 1218 numericTerritoryMapping.freeze(); 1219 alpha3TerritoryMapping.freeze(); 1220 numericCurrencyCodeMapping.freeze(); 1221 1222 // freeze contents 1223 for (String language : languageToPopulation.keySet()) { 1224 languageToPopulation.get(language).freeze(); 1225 } 1226 for (String language : baseLanguageToPopulation.keySet()) { 1227 baseLanguageToPopulation.get(language).freeze(); 1228 } 1229 for (String territory : territoryToPopulationData.keySet()) { 1230 territoryToPopulationData.get(territory).freeze(); 1231 } 1232 for (String territory : territoryToLanguageToPopulationData.keySet()) { 1233 Map<String, PopulationData> languageToPopulationDataTemp = 1234 territoryToLanguageToPopulationData.get(territory); 1235 for (String language : languageToPopulationDataTemp.keySet()) { 1236 languageToPopulationDataTemp.get(language).freeze(); 1237 } 1238 } 1239 localeToPluralInfo2.put( 1240 PluralType.cardinal, 1241 Collections.unmodifiableMap(localeToPluralInfo2.get(PluralType.cardinal))); 1242 localeToPluralInfo2.put( 1243 PluralType.ordinal, 1244 Collections.unmodifiableMap(localeToPluralInfo2.get(PluralType.ordinal))); 1245 1246 localeToPluralRanges = Collections.unmodifiableMap(localeToPluralRanges); 1247 for (PluralRanges pluralRanges : localeToPluralRanges.values()) { 1248 pluralRanges.freeze(); 1249 } 1250 1251 if (lastDayPeriodLocales != null) { 1252 addDayPeriodInfo(); 1253 } 1254 typeToLocaleToDayPeriodInfo = CldrUtility.protectCollection(typeToLocaleToDayPeriodInfo); 1255 languageMatch = CldrUtility.protectCollection(languageMatch); 1256 1257 bcp47Extension2Keys.freeze(); 1258 bcp47Key2Subtypes.freeze(); 1259 CldrUtility.protectCollection(bcp47ValueType); 1260 if (bcp47Key2Subtypes.isEmpty()) { 1261 throw new InternalError( 1262 "No BCP47 key 2 subtype data was loaded from bcp47 dir " 1263 + getBcp47Directory().getAbsolutePath()); 1264 } 1265 1266 bcp47Aliases.freeze(); 1267 CldrUtility.protectCollection(bcp47Descriptions); 1268 CldrUtility.protectCollection(bcp47Since); 1269 CldrUtility.protectCollection(bcp47Preferred); 1270 CldrUtility.protectCollection(bcp47Deprecated); 1271 1272 // create clean structure 1273 1274 for (Entry<String, Set<String>> entry : bcp47Extension2Keys.keyValuesSet()) { 1275 for (String key : entry.getValue()) { 1276 Map<String, Bcp47KeyInfo> subtypeToInfo = bcp47KeyToSubtypeToInfo.get(key); 1277 if (subtypeToInfo == null) { 1278 bcp47KeyToSubtypeToInfo.put(key, subtypeToInfo = new TreeMap<>()); 1279 } 1280 Map<String, String> aliasToRegular = bcp47KeyToAliasToSubtype.get(key); 1281 if (aliasToRegular == null) { 1282 bcp47KeyToAliasToSubtype.put(key, aliasToRegular = new TreeMap<>()); 1283 } 1284 for (String subtype : bcp47Key2Subtypes.get(key)) { 1285 final R2<String, String> pair = R2.of(key, subtype); 1286 final Set<String> aliases = bcp47Aliases.get(pair); 1287 final Bcp47KeyInfo info = 1288 new Bcp47KeyInfo( 1289 aliases, 1290 bcp47Descriptions.get(pair), 1291 bcp47Since.get(pair), 1292 bcp47Preferred.get(pair), 1293 bcp47Deprecated.get(pair)); 1294 subtypeToInfo.put(subtype, info); 1295 final Map<String, String> aliasToRegularFinal = aliasToRegular; 1296 if (aliases != null) { 1297 aliases.forEach(x -> aliasToRegularFinal.put(x, subtype)); 1298 } 1299 } 1300 } 1301 } 1302 bcp47KeyToSubtypeToInfo = CldrUtility.protectCollection(bcp47KeyToSubtypeToInfo); 1303 bcp47KeyToAliasToSubtype = CldrUtility.protectCollection(bcp47KeyToAliasToSubtype); 1304 1305 CoverageLevelInfo.fixEU(coverageLevels, this); 1306 coverageLevels = CldrUtility.protectCollection(coverageLevels); 1307 1308 measurementData = CldrUtility.protectCollection(measurementData); 1309 1310 final Map<String, R2<List<String>, String>> unitAliases = 1311 typeToTagToReplacement.get("unit"); 1312 if (unitAliases != null) { // don't load unless the information is there (for old releases); 1313 unitConverter.addAliases(unitAliases); 1314 } 1315 unitConverter.freeze(); 1316 rationalParser.freeze(); 1317 unitPreferences.freeze(); 1318 1319 unitIdComponentType = CldrUtility.protectCollection(unitIdComponentType); 1320 1321 unitPrefixInfo = CldrUtility.protectCollection(unitPrefixInfo); 1322 1323 timeData = CldrUtility.protectCollection(timeData); 1324 1325 validityInfo = CldrUtility.protectCollection(validityInfo); 1326 attributeValidityInfo = CldrUtility.protectCollection(attributeValidityInfo); 1327 parentLocales = CldrUtility.protectCollection(parentLocales); 1328 parentLocalesSkipNonLikely = ImmutableSet.copyOf(parentLocalesSkipNonLikely); 1329 languageGroups = ImmutableSetMultimap.copyOf(languageGroups); 1330 1331 grammarLocaleToTargetToFeatureToValues = 1332 CldrUtility.protectCollection(grammarLocaleToTargetToFeatureToValues); 1333 localeToGrammarDerivation = CldrUtility.protectCollection(localeToGrammarDerivation); 1334 personNameOrder = CldrUtility.protectCollection(personNameOrder); 1335 1336 ImmutableSet.Builder<String> newScripts = ImmutableSet.<String>builder(); 1337 Map<Validity.Status, Set<String>> scripts = 1338 Validity.getInstance().getStatusToCodes(LstrType.script); 1339 for (Entry<Status, Set<String>> e : scripts.entrySet()) { 1340 switch (e.getKey()) { 1341 case regular: 1342 case special: 1343 case unknown: 1344 newScripts.addAll(e.getValue()); 1345 break; 1346 default: 1347 break; // do nothing 1348 } 1349 } 1350 CLDRScriptCodes = newScripts.build(); 1351 } 1352 1353 /** 1354 * Core function used to process each of the paths, and add the data to the appropriate data 1355 * member. 1356 */ 1357 class MyHandler extends XMLFileReader.SimpleHandler { 1358 private static final double MAX_POPULATION = 3000000000.0; 1359 1360 LanguageTagParser languageTagParser = 1361 null; // postpone assignment until needed, to avoid re-entrance of 1362 // SupplementalDataInfo.getInstance 1363 1364 /** Finish processing anything left hanging in the file. */ 1365 public void cleanup() { 1366 if (lastPluralMap.size() > 0) { 1367 addPluralInfo(lastPluralWasOrdinal); 1368 } 1369 lastPluralLocales = ""; 1370 } 1371 1372 @Override 1373 public void handlePathValue(String path, String value) { 1374 try { 1375 XPathParts parts = XPathParts.getFrozenInstance(path); 1376 String level0 = parts.getElement(0); 1377 String level1 = parts.size() < 2 ? null : parts.getElement(1); 1378 String level2 = parts.size() < 3 ? null : parts.getElement(2); 1379 String level3 = parts.size() < 4 ? null : parts.getElement(3); 1380 // String level4 = parts.size() < 5 ? null : parts.getElement(4); 1381 if (level1.equals("generation")) { 1382 // skip 1383 return; 1384 } 1385 if (level1.equals("version")) { 1386 if (cldrVersion == null) { 1387 String version = parts.getAttributeValue(1, "cldrVersion"); 1388 if (version == null) { 1389 // old format 1390 version = parts.getAttributeValue(0, "version"); 1391 } 1392 cldrVersionString = version; 1393 cldrVersion = VersionInfo.getInstance(version); 1394 unicodeVersion = parts.getAttributeValue(1, "unicodeVersion"); 1395 } 1396 return; 1397 } 1398 1399 // copy the rest from ShowLanguages later 1400 if (level0.equals("ldmlBCP47")) { 1401 if (handleBcp47(level1, parts)) { 1402 return; 1403 } 1404 } else if (level1.equals("territoryInfo")) { 1405 if (handleTerritoryInfo(parts)) { 1406 return; 1407 } 1408 } else if (level1.equals("calendarPreferenceData")) { 1409 handleCalendarPreferenceData(parts); 1410 return; 1411 } else if (level1.equals("languageData")) { 1412 handleLanguageData(parts); 1413 return; 1414 } else if (level1.equals("territoryContainment")) { 1415 handleTerritoryContainment(parts); 1416 return; 1417 } else if (level1.equals("subdivisionContainment")) { 1418 handleSubdivisionContainment(parts); 1419 return; 1420 } else if (level1.equals("currencyData")) { 1421 if (handleCurrencyData(level2, parts)) { 1422 return; 1423 } 1424 } else if ("metazoneInfo".equals(level2)) { 1425 if (handleMetazoneInfo(level3, parts)) { 1426 return; 1427 } 1428 } else if ("mapTimezones".equals(level2)) { 1429 if (handleMetazoneData(level3, parts)) { 1430 return; 1431 } 1432 } else if (level1.equals("plurals")) { 1433 if (addPluralPath(parts, value)) { 1434 return; 1435 } 1436 } else if (level1.equals("dayPeriodRuleSet")) { 1437 addDayPeriodPath(parts); 1438 return; 1439 } else if (level1.equals("telephoneCodeData")) { 1440 handleTelephoneCodeData(parts); 1441 return; 1442 } else if (level1.equals("references")) { 1443 String type = parts.getAttributeValue(-1, "type"); 1444 String uri = parts.getAttributeValue(-1, "uri"); 1445 references.put(type, new Pair<>(uri, value).freeze()); 1446 return; 1447 } else if (level1.equals("likelySubtags")) { 1448 handleLikelySubtags(parts); 1449 return; 1450 } else if (level1.equals("numberingSystems")) { 1451 handleNumberingSystems(parts); 1452 return; 1453 } else if (level1.equals("coverageLevels")) { 1454 handleCoverageLevels(parts); 1455 return; 1456 } else if (level1.equals("parentLocales")) { 1457 handleParentLocales(parts); 1458 return; 1459 } else if (level1.equals("metadata")) { 1460 if (handleMetadata(level2, value, parts)) { 1461 return; 1462 } 1463 } else if (level1.equals("codeMappings")) { 1464 if (handleCodeMappings(level2, parts)) { 1465 return; 1466 } 1467 } else if (level1.equals("languageMatching")) { 1468 if (handleLanguageMatcher(parts)) { 1469 return; 1470 } 1471 } else if (level1.equals("measurementData")) { 1472 if (handleMeasurementData(level2, parts)) { 1473 return; 1474 } 1475 } else if (level1.equals("unitIdComponents")) { 1476 if (handleUnitUnitIdComponents(parts)) { 1477 return; 1478 } 1479 } else if (level1.equals("unitPrefixes")) { 1480 if (handleUnitPrefix(parts)) { 1481 return; 1482 } 1483 } else if (level1.equals("unitConstants")) { 1484 if (handleUnitConstants(parts)) { 1485 return; 1486 } 1487 } else if (level1.equals("unitQuantities")) { 1488 if (handleUnitQuantities(parts)) { 1489 return; 1490 } 1491 } else if (level1.equals("convertUnits")) { 1492 if (handleUnitConversion(parts)) { 1493 return; 1494 } 1495 } else if (level1.equals("unitPreferenceData")) { 1496 if (handleUnitPreferences(parts, value)) { 1497 return; 1498 } 1499 } else if (level1.equals("timeData")) { 1500 if (handleTimeData(parts)) { 1501 return; 1502 } 1503 } else if (level1.equals("languageGroups")) { 1504 if (handleLanguageGroups(value, parts)) { 1505 return; 1506 } 1507 } else if (level1.contentEquals("grammaticalData")) { 1508 if (handleGrammaticalData(value, parts)) { 1509 return; 1510 } 1511 } else if (level1.contentEquals("personNamesDefaults")) { 1512 if (handlePersonNamesDefaults(value, parts)) { 1513 return; 1514 } 1515 } 1516 1517 // capture elements we didn't look at, since we should cover everything. 1518 // this helps for updates 1519 1520 final String skipKey = level1 + (level2 == null ? "" : "/" + level2); 1521 if (!skippedElements.contains(skipKey)) { 1522 skippedElements.add(skipKey); 1523 } 1524 // System.out.println("Skipped Element: " + path); 1525 } catch (Exception e) { 1526 throw (IllegalArgumentException) 1527 new IllegalArgumentException( 1528 "Exception while processing path: " 1529 + path 1530 + ",\tvalue: " 1531 + value) 1532 .initCause(e); 1533 } 1534 } 1535 1536 private boolean handleUnitPrefix(XPathParts parts) { 1537 // <unitPrefix type='quecto' symbol='q' power10='-30'/> 1538 String power10 = parts.getAttributeValue(-1, "power10"); 1539 String power2 = parts.getAttributeValue(-1, "power2"); 1540 if ((power10 != null) == (power2 != null)) { 1541 throw new IllegalArgumentException("Must have exactly one @power2 or @power10"); 1542 } 1543 unitPrefixInfo.put( 1544 parts.getAttributeValue(-1, "type"), 1545 new UnitPrefixInfo( 1546 parts.getAttributeValue(-1, "symbol"), 1547 power10 != null ? 10 : 2, 1548 Integer.parseInt(power10 != null ? power10 : power2))); 1549 return true; 1550 } 1551 1552 private boolean handlePersonNamesDefaults(String value, XPathParts parts) { 1553 personNameOrder.putAll( 1554 Order.from(parts.getAttributeValue(-1, "order")), split_space.split(value)); 1555 return true; 1556 } 1557 1558 private boolean handleUnitUnitIdComponents(XPathParts parts) { 1559 // <unitIdComponent type="prefix" values="arc british dessert fluid light 1560 // nautical"/> 1561 UnitIdComponentType type = 1562 UnitIdComponentType.valueOf(parts.getAttributeValue(-1, "type")); 1563 for (String value : split_space.split(parts.getAttributeValue(-1, "values"))) { 1564 UnitIdComponentType old = unitIdComponentType.put(value, type); 1565 if (old != null) { 1566 throw new IllegalArgumentException("Duplicate component: " + value); 1567 } 1568 } 1569 return true; 1570 } 1571 1572 private boolean handleGrammaticalData(String value, XPathParts parts) { 1573 /* 1574 <!ATTLIST grammaticalFeatures targets NMTOKENS #REQUIRED > 1575 <!ATTLIST grammaticalFeatures locales NMTOKENS #REQUIRED > 1576 OR 1577 <!ATTLIST grammaticalDerivations locales NMTOKENS #REQUIRED > 1578 */ 1579 1580 for (String locale : split_space.split(parts.getAttributeValue(2, "locales"))) { 1581 switch (parts.getElement(2)) { 1582 case "grammaticalFeatures": 1583 GrammarInfo targetToFeatureToValues = 1584 grammarLocaleToTargetToFeatureToValues.get(locale); 1585 if (targetToFeatureToValues == null) { 1586 grammarLocaleToTargetToFeatureToValues.put( 1587 locale, targetToFeatureToValues = new GrammarInfo()); 1588 } 1589 final String targets = parts.getAttributeValue(2, "targets"); 1590 if (parts.size() < 4) { 1591 targetToFeatureToValues.add( 1592 targets, null, null, null); // special case "known no features" 1593 } else { 1594 targetToFeatureToValues.add( 1595 targets, 1596 parts.getElement(3), 1597 parts.getAttributeValue(3, "scope"), 1598 parts.getAttributeValue(3, "values")); 1599 } 1600 break; 1601 case "grammaticalDerivations": 1602 String feature = parts.getAttributeValue(3, "feature"); 1603 String structure = parts.getAttributeValue(3, "structure"); 1604 GrammarDerivation grammarCompoundDerivation = 1605 localeToGrammarDerivation.get(locale); 1606 if (grammarCompoundDerivation == null) { 1607 localeToGrammarDerivation.put( 1608 locale, grammarCompoundDerivation = new GrammarDerivation()); 1609 } 1610 1611 switch (parts.getElement(3)) { 1612 case "deriveCompound": 1613 grammarCompoundDerivation.add( 1614 feature, structure, parts.getAttributeValue(3, "value")); 1615 break; 1616 case "deriveComponent": 1617 grammarCompoundDerivation.add( 1618 feature, 1619 structure, 1620 parts.getAttributeValue(3, "value0"), 1621 parts.getAttributeValue(3, "value1")); 1622 break; 1623 default: 1624 throw new IllegalArgumentException( 1625 "Structure not handled: " + parts); 1626 } 1627 break; 1628 default: 1629 throw new IllegalArgumentException("Structure not handled: " + parts); 1630 } 1631 } 1632 return true; 1633 } 1634 1635 /* 1636 * Handles 1637 * <unitPreferences category="area" usage="_default"> 1638 *<unitPreference regions="001" draft="unconfirmed">square-centimeter</unitPreference> 1639 */ 1640 1641 private boolean handleUnitPreferences(XPathParts parts, String value) { 1642 String geq = parts.getAttributeValue(-1, "geq"); 1643 String small = parts.getAttributeValue(-2, "scope"); 1644 if (small != null) { 1645 geq = "0.1234"; 1646 } 1647 unitPreferences.add( 1648 parts.getAttributeValue(-2, "category"), 1649 parts.getAttributeValue(-2, "usage"), 1650 parts.getAttributeValue(-1, "regions"), 1651 geq, 1652 parts.getAttributeValue(-1, "skeleton"), 1653 value); 1654 return true; 1655 } 1656 1657 private boolean handleLanguageGroups(String value, XPathParts parts) { 1658 String parent = parts.getAttributeValue(-1, "parent"); 1659 List<String> children = WHITESPACE_SPLTTER.splitToList(value); 1660 languageGroups.putAll(parent, children); 1661 return true; 1662 } 1663 1664 private boolean handleMeasurementData(String level2, XPathParts parts) { 1665 /** 1666 * <measurementSystem type="US" territories="LR MM US"/> <paperSize type="A4" 1667 * territories="001"/> 1668 */ 1669 MeasurementType measurementType = MeasurementType.valueOf(level2); 1670 String type = parts.getAttributeValue(-1, "type"); 1671 String territories = parts.getAttributeValue(-1, "territories"); 1672 Map<String, String> data = measurementData.get(measurementType); 1673 if (data == null) { 1674 measurementData.put(measurementType, data = new HashMap<>()); 1675 } 1676 for (String territory : territories.trim().split("\\s+")) { 1677 data.put(territory, type); 1678 } 1679 return true; 1680 } 1681 1682 private boolean handleUnitConstants(XPathParts parts) { 1683 // <unitConstant constant="ft2m" value="0.3048"/> 1684 1685 final String constant = parts.getAttributeValue(-1, "constant"); 1686 final String value = parts.getAttributeValue(-1, "value"); 1687 final String status = parts.getAttributeValue(-1, "status"); 1688 rationalParser.addConstant(constant, value, status); 1689 return true; 1690 } 1691 1692 private boolean handleUnitQuantities(XPathParts parts) { 1693 // <unitQuantity quantity='wave-number' baseUnit='reciprocal-meter'/> 1694 1695 final String baseUnit = parts.getAttributeValue(-1, "baseUnit"); 1696 final String quantity = parts.getAttributeValue(-1, "quantity"); 1697 final String status = parts.getAttributeValue(-1, "status"); 1698 if (unitConverter == null) { 1699 unitConverter = new UnitConverter(rationalParser, validity); 1700 } 1701 unitConverter.addQuantityInfo(baseUnit, quantity, status); 1702 return true; 1703 } 1704 1705 private boolean handleUnitConversion(XPathParts parts) { 1706 // <convertUnit source='acre' target='square-meter' factor='ft2m^2 * 43560'/> 1707 1708 final String source = parts.getAttributeValue(-1, "source"); 1709 final String target = parts.getAttributeValue(-1, "baseUnit"); 1710 // if (source.contentEquals(target)) { 1711 // throw new IllegalArgumentException("Cannot convert from something to 1712 // itself " + parts); 1713 // } 1714 String factor = parts.getAttributeValue(-1, "factor"); 1715 String offset = parts.getAttributeValue(-1, "offset"); 1716 String special = parts.getAttributeValue(-1, "special"); 1717 String systems = parts.getAttributeValue(-1, "systems"); 1718 unitConverter.addRaw(source, target, factor, offset, special, systems); 1719 return true; 1720 } 1721 1722 private boolean handleTimeData(XPathParts parts) { 1723 /** <hours preferred="H" allowed="H" regions="IL RU"/> */ 1724 String preferred = parts.getAttributeValue(-1, "preferred"); 1725 PreferredAndAllowedHour preferredAndAllowedHour = 1726 new PreferredAndAllowedHour(preferred, parts.getAttributeValue(-1, "allowed")); 1727 for (String region : parts.getAttributeValue(-1, "regions").trim().split("\\s+")) { 1728 PreferredAndAllowedHour oldValue = timeData.put(region, preferredAndAllowedHour); 1729 if (oldValue != null) { 1730 throw new IllegalArgumentException( 1731 "timeData/hours must not have duplicate regions: " + region); 1732 } 1733 } 1734 return true; 1735 } 1736 1737 private boolean handleBcp47(String level1, XPathParts parts) { 1738 if (level1.equals("version") 1739 || level1.equals("generation") 1740 || level1.equals("cldrVersion")) { 1741 return true; // skip 1742 } 1743 if (!level1.equals("keyword")) { 1744 throw new IllegalArgumentException("Unexpected level1 element: " + level1); 1745 } 1746 1747 String finalElement = parts.getElement(-1); 1748 String key = parts.getAttributeValue(2, "name"); 1749 String extension = parts.getAttributeValue(2, "extension"); 1750 if (extension == null) { 1751 extension = "u"; 1752 } 1753 bcp47Extension2Keys.put(extension, key); 1754 1755 String keyAlias = parts.getAttributeValue(2, "alias"); 1756 String keyDescription = parts.getAttributeValue(2, "description"); 1757 String deprecated = parts.getAttributeValue(2, "deprecated"); 1758 // TODO add preferred, valueType, since 1759 1760 final R2<String, String> key_empty = (R2<String, String>) Row.of(key, "").freeze(); 1761 1762 if (keyAlias != null) { 1763 bcp47Aliases.putAll(key_empty, Arrays.asList(keyAlias.trim().split("\\s+"))); 1764 } 1765 1766 if (keyDescription != null) { 1767 bcp47Descriptions.put(key_empty, keyDescription); 1768 } 1769 if (deprecated != null && deprecated.equals("true")) { 1770 bcp47Deprecated.put(key_empty, deprecated); 1771 } 1772 1773 switch (finalElement) { 1774 case "key": 1775 break; // all actions taken above 1776 1777 case "type": 1778 String subtype = parts.getAttributeValue(3, "name"); 1779 String subtypeAlias = parts.getAttributeValue(3, "alias"); 1780 String desc = parts.getAttributeValue(3, "description"); 1781 String subtypeDescription = desc == null ? null : desc.replaceAll("\\s+", " "); 1782 String subtypeSince = parts.getAttributeValue(3, "since"); 1783 String subtypePreferred = parts.getAttributeValue(3, "preferred"); 1784 String subtypeDeprecated = parts.getAttributeValue(3, "deprecated"); 1785 String valueType = parts.getAttributeValue(3, "deprecated"); 1786 1787 Set<String> set = bcp47Key2Subtypes.get(key); 1788 if (set != null && set.contains(key)) { 1789 throw new IllegalArgumentException( 1790 "Collision with bcp47 key-value: " + key + "," + subtype); 1791 } 1792 bcp47Key2Subtypes.put(key, subtype); 1793 1794 final R2<String, String> key_subtype = 1795 (R2<String, String>) Row.of(key, subtype).freeze(); 1796 1797 if (subtypeAlias != null) { 1798 bcp47Aliases.putAll( 1799 key_subtype, Arrays.asList(subtypeAlias.trim().split("\\s+"))); 1800 } 1801 if (subtypeDescription != null) { 1802 bcp47Descriptions.put( 1803 key_subtype, subtypeDescription.replaceAll("\\s+", " ")); 1804 } 1805 if (subtypeSince != null) { 1806 bcp47Since.put(key_subtype, subtypeSince); 1807 } 1808 if (subtypePreferred != null) { 1809 bcp47Preferred.put(key_subtype, subtypePreferred); 1810 } 1811 if (subtypeDeprecated != null) { 1812 bcp47Deprecated.put(key_subtype, subtypeDeprecated); 1813 } 1814 if (valueType != null) { 1815 bcp47ValueType.put(subtype, valueType); 1816 } 1817 break; 1818 default: 1819 throw new IllegalArgumentException("Unexpected element: " + finalElement); 1820 } 1821 1822 return true; 1823 } 1824 1825 private boolean handleLanguageMatcher(XPathParts parts) { 1826 String type = parts.getAttributeValue(2, "type"); 1827 String alt = parts.getAttributeValue(2, "alt"); 1828 if (alt != null) { 1829 type += "_" + alt; 1830 } 1831 switch (parts.getElement(3)) { 1832 case "paradigmLocales": 1833 List<String> locales = 1834 WHITESPACE_SPLTTER.splitToList(parts.getAttributeValue(3, "locales")); 1835 // TODO 1836 // LanguageMatchData languageMatchData = 1837 // languageMatchData.get(type); 1838 // if (languageMatchData == null) { 1839 // languageMatch.put(type, languageMatchData = new 1840 // LanguageMatchData()); 1841 // } 1842 break; 1843 case "matchVariable": 1844 // String id = parts.getAttributeValue(3, "id"); 1845 // String value = parts.getAttributeValue(3, "value"); 1846 // TODO 1847 break; 1848 case "languageMatch": 1849 List<R4<String, String, Integer, Boolean>> matches = languageMatch.get(type); 1850 if (matches == null) { 1851 languageMatch.put(type, matches = new ArrayList<>()); 1852 } 1853 String percent = parts.getAttributeValue(3, "percent"); 1854 String distance = parts.getAttributeValue(3, "distance"); 1855 matches.add( 1856 Row.of( 1857 parts.getAttributeValue(3, "desired"), 1858 parts.getAttributeValue(3, "supported"), 1859 percent != null 1860 ? Integer.parseInt(percent) 1861 : 100 - Integer.parseInt(distance), 1862 "true".equals(parts.getAttributeValue(3, "oneway")))); 1863 break; 1864 default: 1865 throw new IllegalArgumentException("Unknown element"); 1866 } 1867 return true; 1868 } 1869 1870 private boolean handleCodeMappings(String level2, XPathParts parts) { 1871 if (level2.equals("territoryCodes")) { 1872 // <territoryCodes type="VU" numeric="548" alpha3="VUT"/> 1873 String type = parts.getAttributeValue(-1, "type"); 1874 final String numeric = parts.getAttributeValue(-1, "numeric"); 1875 if (numeric != null) { 1876 numericTerritoryMapping.put(type, Integer.parseInt(numeric)); 1877 } 1878 final String alpha3 = parts.getAttributeValue(-1, "alpha3"); 1879 if (alpha3 != null) { 1880 alpha3TerritoryMapping.put(type, alpha3); 1881 } 1882 return true; 1883 } else if (level2.equals("currencyCodes")) { 1884 // <currencyCodes type="BBD" numeric="52"/> 1885 String type = parts.getAttributeValue(-1, "type"); 1886 final String numeric = parts.getAttributeValue(-1, "numeric"); 1887 if (numeric != null) { 1888 numericCurrencyCodeMapping.put(type, Integer.parseInt(numeric)); 1889 } 1890 return true; 1891 } 1892 return false; 1893 } 1894 1895 private void handleNumberingSystems(XPathParts parts) { 1896 NumberingSystemInfo ns = new NumberingSystemInfo(parts); 1897 numberingSystems.put(ns.name, ns); 1898 if (ns.type == NumberingSystemType.numeric) { 1899 numericSystems.add(ns.name); 1900 } 1901 } 1902 1903 private void handleCoverageLevels(XPathParts parts) { 1904 if (parts.containsElement("approvalRequirement")) { 1905 approvalRequirements.add(parts.toString()); 1906 } else if (parts.containsElement("coverageLevel")) { 1907 String match = 1908 parts.containsAttribute("match") 1909 ? coverageVariables.replace(parts.getAttributeValue(-1, "match")) 1910 : null; 1911 String valueStr = parts.getAttributeValue(-1, "value"); 1912 // Ticket 7125: map the number to English. So switch from English to number for 1913 // construction 1914 valueStr = Integer.toString(Level.get(valueStr).getLevel()); 1915 1916 String inLanguage = 1917 parts.containsAttribute("inLanguage") 1918 ? coverageVariables.replace( 1919 parts.getAttributeValue(-1, "inLanguage")) 1920 : null; 1921 String inScript = 1922 parts.containsAttribute("inScript") 1923 ? coverageVariables.replace(parts.getAttributeValue(-1, "inScript")) 1924 : null; 1925 String inTerritory = 1926 parts.containsAttribute("inTerritory") 1927 ? coverageVariables.replace( 1928 parts.getAttributeValue(-1, "inTerritory")) 1929 : null; 1930 Integer value = 1931 (valueStr != null) ? Integer.valueOf(valueStr) : Integer.valueOf("101"); 1932 if (cldrVersion.getMajor() < 2) { 1933 value = 40; 1934 } 1935 CoverageLevelInfo ci = 1936 new CoverageLevelInfo(match, value, inLanguage, inScript, inTerritory); 1937 coverageLevels.add(ci); 1938 } else if (parts.containsElement("coverageVariable")) { 1939 String key = parts.getAttributeValue(-1, "key"); 1940 String value = parts.getAttributeValue(-1, "value"); 1941 coverageVariables.add(key, value); 1942 } 1943 } 1944 1945 public static final String NONLIKELYSCRIPT = "nonlikelyScript"; 1946 1947 private void handleParentLocales(XPathParts parts) { 1948 // CLDR-16253 added component-specific parents, which we ignore for now. 1949 // TODO(CLDR-16361): Figure out how to handle these in CLDR itself. 1950 String componentsString = parts.getAttributeValue(1, "component"); 1951 Set<ParentLocaleComponent> components; 1952 if (componentsString == null) { 1953 components = ImmutableSet.of(ParentLocaleComponent.main); 1954 } else { 1955 components = 1956 split_space 1957 .splitToStream(componentsString) 1958 .map(x -> ParentLocaleComponent.fromString(x)) 1959 .collect(Collectors.toSet()); 1960 } 1961 if (!parts.getElement(-1).equals("parentLocale")) { 1962 // If there is no parentLocale element , that means we have nothing to add 1963 // Since we have pre-populated the parentLocales with component -> empty map, 1964 // there is nothing more to do, and we can exit. 1965 // We have parsed the components, however, so they are valid 1966 return; 1967 } 1968 String parent = parts.getAttributeValue(-1, "parent"); 1969 String locales = parts.getAttributeValue(-1, "locales"); 1970 String localeRules = parts.getAttributeValue(-1, "localeRules"); 1971 Set<String> localeRuleSet = 1972 localeRules == null 1973 ? Set.of() 1974 : Set.copyOf(split_space.splitToList(localeRules)); 1975 1976 for (ParentLocaleComponent component : components) { 1977 Map<String, String> componentParentLocales = parentLocales.get(component); 1978 if (localeRuleSet.contains(NONLIKELYSCRIPT)) { 1979 // This will need to be modified if we add any other rules, 1980 // particularly if any rules are based on the particular parent 1981 parentLocalesSkipNonLikely.add(component); 1982 continue; 1983 } 1984 for (String childLocale : split_space.split(locales)) { 1985 String old = componentParentLocales.put(childLocale, parent); 1986 if (old != null) { 1987 throw new IllegalArgumentException( 1988 "Locale " 1989 + childLocale 1990 + " cannot have two parents: " 1991 + old 1992 + " and " 1993 + parent); 1994 } 1995 } 1996 } 1997 } 1998 1999 private void handleCalendarPreferenceData(XPathParts parts) { 2000 String territoryString = parts.getAttributeValue(-1, "territories"); 2001 String orderingString = parts.getAttributeValue(-1, "ordering"); 2002 String[] calendars = orderingString.split(" "); 2003 String[] territories = territoryString.split(" "); 2004 List<String> calendarList = Arrays.asList(calendars); 2005 for (int i = 0; i < territories.length; i++) { 2006 calendarPreferences.put(territories[i], calendarList); 2007 } 2008 } 2009 2010 private void handleLikelySubtags(XPathParts parts) { 2011 String from = parts.getAttributeValue(-1, "from"); 2012 String to = parts.getAttributeValue(-1, "to"); 2013 String origin = parts.getAttributeValue(-1, "origin"); 2014 String toOld = likelySubtags.get(from); 2015 if (toOld != null) { 2016 if (to.equals(toOld)) { 2017 System.err.println("Likely subtags repeats from=" + from + " to= " + to); 2018 } else { 2019 throw new IllegalArgumentException( 2020 "Likely subtags duplicate from=" 2021 + from 2022 + ", overrides values: " 2023 + toOld 2024 + " with " 2025 + to); 2026 } 2027 } else { 2028 likelySubtags.put(from, to); 2029 if (origin != null) { 2030 likelyOrigins.put(from, origin); 2031 } 2032 } 2033 } 2034 2035 /** 2036 * Only called if level2 = mapTimezones. Level 1 might be metaZones or might be windowsZones 2037 */ 2038 private boolean handleMetazoneData(String level3, XPathParts parts) { 2039 if (level3.equals("mapZone")) { 2040 String maintype = parts.getAttributeValue(2, "type"); 2041 if (maintype == null) { 2042 maintype = "windows"; 2043 } 2044 String mzone = parts.getAttributeValue(3, "other"); 2045 String region = parts.getAttributeValue(3, "territory"); 2046 String zone = parts.getAttributeValue(3, "type"); 2047 2048 Map<String, Map<String, String>> zoneToRegionToZone = 2049 typeToZoneToRegionToZone.get(maintype); 2050 if (zoneToRegionToZone == null) { 2051 typeToZoneToRegionToZone.put(maintype, zoneToRegionToZone = new TreeMap<>()); 2052 } 2053 Map<String, String> regionToZone = zoneToRegionToZone.get(mzone); 2054 if (regionToZone == null) { 2055 zoneToRegionToZone.put(mzone, regionToZone = new TreeMap<>()); 2056 } 2057 if (region != null) { 2058 regionToZone.put(region, zone); 2059 } 2060 if (maintype.equals("metazones")) { 2061 if (mzone != null && region.equals("001")) { 2062 metazoneContinentMap.put(mzone, zone.substring(0, zone.indexOf("/"))); 2063 } 2064 allMetazones.add(mzone); 2065 } 2066 return true; 2067 } 2068 return false; 2069 } 2070 2071 private Collection<String> getSpaceDelimited( 2072 int index, String attribute, Collection<String> defaultValue, XPathParts parts) { 2073 String temp = parts.getAttributeValue(index, attribute); 2074 Collection<String> elements = 2075 temp == null ? defaultValue : Arrays.asList(temp.split("\\s+")); 2076 return elements; 2077 } 2078 2079 /* 2080 * 2081 * <supplementalData> 2082 * <metaZones> 2083 * <metazoneInfo> 2084 * <timezone type="Asia/Yerevan"> 2085 * <usesMetazone to="1991-09-22 20:00" mzone="Yerevan"/> 2086 * <usesMetazone from="1991-09-22 20:00" mzone="Armenia"/> 2087 */ 2088 2089 private boolean handleMetazoneInfo(String level3, XPathParts parts) { 2090 if (level3.equals("timezone")) { 2091 String zone = parts.getAttributeValue(3, "type"); 2092 String mzone = parts.getAttributeValue(4, "mzone"); 2093 String from = parts.getAttributeValue(4, "from"); 2094 String to = parts.getAttributeValue(4, "to"); 2095 MetaZoneRange mzoneRange = new MetaZoneRange(mzone, from, to); 2096 zoneToMetaZoneRanges.put(zone, mzoneRange); 2097 return true; 2098 } 2099 return false; 2100 } 2101 2102 private boolean handleMetadata(String level2, String value, XPathParts parts) { 2103 if (parts.contains("defaultContent")) { 2104 String defContent = parts.getAttributeValue(-1, "locales").trim(); 2105 String[] defLocales = defContent.split("\\s+"); 2106 defaultContentLocales = 2107 Collections.unmodifiableSet(new TreeSet<>(Arrays.asList(defLocales))); 2108 return true; 2109 } 2110 if (level2.equals("alias")) { 2111 // <alias> 2112 // <languageAlias type="art-lojban" replacement="jbo"/> <!-- Lojban --> 2113 String level3 = parts.getElement(3); 2114 if (!level3.endsWith("Alias")) { 2115 throw new IllegalArgumentException(); 2116 } 2117 level3 = level3.substring(0, level3.length() - "Alias".length()); 2118 Map<String, R2<List<String>, String>> tagToReplacement = 2119 typeToTagToReplacement.get(level3); 2120 if (tagToReplacement == null) { 2121 typeToTagToReplacement.put(level3, tagToReplacement = new TreeMap<>()); 2122 } 2123 final String replacement = parts.getAttributeValue(3, "replacement"); 2124 List<String> replacementList = null; 2125 if (replacement != null) { 2126 Set<String> builder = new LinkedHashSet<>(); 2127 for (String item : replacement.split("\\s+")) { 2128 String cleaned = 2129 SubdivisionNames.isOldSubdivisionCode(item) 2130 ? replacement.replace("-", "").toLowerCase(Locale.ROOT) 2131 : item; 2132 builder.add(cleaned); 2133 } 2134 replacementList = ImmutableList.copyOf(builder); 2135 } 2136 final String reason = parts.getAttributeValue(3, "reason"); 2137 String cleanTag = parts.getAttributeValue(3, "type"); 2138 tagToReplacement.put( 2139 cleanTag, 2140 (R2<List<String>, String>) Row.of(replacementList, reason).freeze()); 2141 return true; 2142 } else if (level2.equals("validity")) { 2143 // <variable id="$grandfathered" type="choice"> 2144 String level3 = parts.getElement(3); 2145 if (level3.equals("variable")) { 2146 Map<String, String> attributes = parts.getAttributes(-1); 2147 validityInfo.put(attributes.get("id"), Row.of(attributes.get("type"), value)); 2148 String idString = attributes.get("id"); 2149 if (("$language".equals(idString) 2150 || "$languageExceptions".equals(attributes.get("id"))) 2151 && "choice".equals(attributes.get("type"))) { 2152 String[] validCodeArray = value.trim().split("\\s+"); 2153 CLDRLanguageCodes.addAll(Arrays.asList(validCodeArray)); 2154 } 2155 return true; 2156 } else if (level3.equals("attributeValues")) { 2157 AttributeValidityInfo.add( 2158 parts.getAttributes(-1), value, attributeValidityInfo); 2159 return true; 2160 } 2161 } else if (level2.equals("serialElements")) { 2162 serialElements = Arrays.asList(value.trim().split("\\s+")); 2163 return true; 2164 } else if (level2.equals("distinguishing")) { 2165 String level3 = parts.getElement(3); 2166 if (level3.equals("distinguishingItems")) { 2167 Map<String, String> attributes = parts.getAttributes(-1); 2168 // <distinguishingItems 2169 // attributes="key request id _q registry alt iso4217 iso3166 mzone from to type 2170 // numberSystem"/> 2171 // <distinguishingItems exclude="true" 2172 // elements="default measurementSystem mapping abbreviationFallback 2173 // preferenceOrdering" 2174 // attributes="type"/> 2175 2176 if (attributes.containsKey("exclude") 2177 && "true".equals(attributes.get("exclude"))) { 2178 return false; // don't handle the excludes -yet. 2179 } else { 2180 distinguishingAttributes = 2181 Collections.unmodifiableCollection( 2182 getSpaceDelimited(-1, "attributes", STAR_SET, parts)); 2183 return true; 2184 } 2185 } 2186 } 2187 return false; 2188 } 2189 2190 private boolean handleTerritoryInfo(XPathParts parts) { 2191 2192 // <territoryInfo> 2193 // <territory type="AD" gdp="1840000000" literacyPercent="100" 2194 // population="66000"> <!--Andorra--> 2195 // <languagePopulation type="ca" populationPercent="50"/> 2196 // <!--Catalan--> 2197 2198 Map<String, String> territoryAttributes = parts.getAttributes(2); 2199 String territory = territoryAttributes.get("type"); 2200 double territoryPopulation = parseDouble(territoryAttributes.get("population")); 2201 if (failsRangeCheck("population", territoryPopulation, 0, MAX_POPULATION)) { 2202 return true; 2203 } 2204 2205 double territoryLiteracyPercent = 2206 parseDouble(territoryAttributes.get("literacyPercent")); 2207 double territoryGdp = parseDouble(territoryAttributes.get("gdp")); 2208 if (territoryToPopulationData.get(territory) == null) { 2209 territoryToPopulationData.put( 2210 territory, 2211 new PopulationData() 2212 .setPopulation(territoryPopulation) 2213 .setLiteratePopulation( 2214 territoryLiteracyPercent * territoryPopulation / 100) 2215 .setGdp(territoryGdp)); 2216 } 2217 if (parts.size() > 3) { 2218 2219 Map<String, String> languageInTerritoryAttributes = parts.getAttributes(3); 2220 String language = languageInTerritoryAttributes.get("type"); 2221 double languageLiteracyPercent = 2222 parseDouble(languageInTerritoryAttributes.get("literacyPercent")); 2223 if (Double.isNaN(languageLiteracyPercent)) { 2224 languageLiteracyPercent = territoryLiteracyPercent; 2225 } 2226 double writingPercent = 2227 parseDouble(languageInTerritoryAttributes.get("writingPercent")); 2228 if (Double.isNaN(writingPercent)) { 2229 writingPercent = languageLiteracyPercent; 2230 } 2231 // else { 2232 // System.out.println("writingPercent\t" + languageLiteracyPercent 2233 // + "\tterritory\t" + territory 2234 // + "\tlanguage\t" + language); 2235 // } 2236 double languagePopulationPercent = 2237 parseDouble(languageInTerritoryAttributes.get("populationPercent")); 2238 double languagePopulation = languagePopulationPercent * territoryPopulation / 100; 2239 // double languageGdp = languagePopulationPercent * territoryGdp; 2240 2241 // store 2242 Map<String, PopulationData> territoryLanguageToPopulation = 2243 territoryToLanguageToPopulationData.get(territory); 2244 if (territoryLanguageToPopulation == null) { 2245 territoryToLanguageToPopulationData.put( 2246 territory, territoryLanguageToPopulation = new TreeMap<>()); 2247 } 2248 OfficialStatus officialStatus = OfficialStatus.unknown; 2249 String officialStatusString = languageInTerritoryAttributes.get("officialStatus"); 2250 if (officialStatusString != null) 2251 officialStatus = OfficialStatus.valueOf(officialStatusString); 2252 2253 PopulationData newData = 2254 new PopulationData() 2255 .setPopulation(languagePopulation) 2256 .setLiteratePopulation( 2257 languageLiteracyPercent * languagePopulation / 100) 2258 .setWritingPopulation(writingPercent * languagePopulation / 100) 2259 .setOfficialStatus(officialStatus) 2260 // .setGdp(languageGdp) 2261 ; 2262 newData.freeze(); 2263 if (territoryLanguageToPopulation.get(language) != null) { 2264 System.out.println( 2265 "Internal Problem in supplementalData: multiple data items for " 2266 + language 2267 + ", " 2268 + territory 2269 + "\tSkipping " 2270 + newData); 2271 return true; 2272 } 2273 2274 territoryLanguageToPopulation.put(language, newData); 2275 // add the language, using the Pair fields to get the ordering right 2276 languageToTerritories2.put( 2277 language, 2278 Pair.of( 2279 newData.getOfficialStatus().isMajor() ? false : true, 2280 Pair.of(-newData.getLiteratePopulation(), territory))); 2281 2282 // now collect data for languages globally 2283 PopulationData data = languageToPopulation.get(language); 2284 if (data == null) { 2285 languageToPopulation.put(language, data = new PopulationData().set(newData)); 2286 } else { 2287 data.add(newData); 2288 } 2289 // if (language.equals("en")) { 2290 // System.out.println(territory + "\tnewData:\t" + newData + "\tdata:\t" + data); 2291 // } 2292 2293 if (languageTagParser == null) { 2294 languageTagParser = new LanguageTagParser(); 2295 } 2296 String baseLanguage = languageTagParser.set(language).getLanguage(); 2297 data = baseLanguageToPopulation.get(baseLanguage); 2298 if (data == null) { 2299 baseLanguageToPopulation.put( 2300 baseLanguage, data = new PopulationData().set(newData)); 2301 } else { 2302 data.add(newData); 2303 } 2304 if (!baseLanguage.equals(language)) { 2305 languageToScriptVariants.put(baseLanguage, language); 2306 } 2307 } 2308 return true; 2309 } 2310 2311 private boolean handleCurrencyData(String level2, XPathParts parts) { 2312 if (level2.equals("fractions")) { 2313 // <info iso4217="ADP" digits="0" rounding="0" cashRounding="5"/> 2314 currencyToCurrencyNumberInfo.put( 2315 parts.getAttributeValue(3, "iso4217"), 2316 new CurrencyNumberInfo( 2317 parseIntegerOrNull(parts.getAttributeValue(3, "digits")), 2318 parseIntegerOrNull(parts.getAttributeValue(3, "rounding")), 2319 parseIntegerOrNull(parts.getAttributeValue(3, "cashDigits")), 2320 parseIntegerOrNull(parts.getAttributeValue(3, "cashRounding")))); 2321 return true; 2322 } 2323 /* 2324 * <region iso3166="AD"> 2325 * <currency iso4217="EUR" from="1999-01-01"/> 2326 * <currency iso4217="ESP" from="1873" to="2002-02-28"/> 2327 */ 2328 if (level2.equals("region")) { 2329 territoryToCurrencyDateInfo.put( 2330 parts.getAttributeValue(2, "iso3166"), 2331 new CurrencyDateInfo( 2332 parts.getAttributeValue(3, "iso4217"), 2333 parts.getAttributeValue(3, "from"), 2334 parts.getAttributeValue(3, "to"), 2335 parts.getAttributeValue(3, "tender"))); 2336 return true; 2337 } 2338 2339 return false; 2340 } 2341 2342 private void handleTelephoneCodeData(XPathParts parts) { 2343 // element 2: codesByTerritory territory [draft] [references] 2344 String terr = parts.getAttributeValue(2, "territory"); 2345 // element 3: telephoneCountryCode code [from] [to] [draft] [references] [alt] 2346 TelephoneCodeInfo tcInfo = 2347 new TelephoneCodeInfo( 2348 parts.getAttributeValue(3, "code"), 2349 parts.getAttributeValue(3, "from"), 2350 parts.getAttributeValue(3, "to"), 2351 parts.getAttributeValue(3, "alt")); 2352 2353 Set<TelephoneCodeInfo> tcSet = territoryToTelephoneCodeInfo.get(terr); 2354 if (tcSet == null) { 2355 tcSet = new LinkedHashSet<>(); 2356 territoryToTelephoneCodeInfo.put(terr, tcSet); 2357 } 2358 tcSet.add(tcInfo); 2359 } 2360 2361 private void handleTerritoryContainment(XPathParts parts) { 2362 // <group type="001" contains="002 009 019 142 150"/> 2363 final String container = parts.getAttributeValue(-1, "type"); 2364 final List<String> contained = 2365 Arrays.asList(parts.getAttributeValue(-1, "contains").split("\\s+")); 2366 // everything! 2367 containment.putAll(container, contained); 2368 2369 String status = parts.getAttributeValue(-1, "status"); 2370 String grouping = parts.getAttributeValue(-1, "grouping"); 2371 if (status == null && grouping == null) { 2372 containmentCore.putAll(container, contained); 2373 } 2374 if (status != null && status.equals("deprecated")) { 2375 containmentDeprecated.putAll(container, contained); 2376 } 2377 if (grouping != null) { 2378 containmentGrouping.putAll(container, contained); 2379 } 2380 } 2381 2382 private void handleSubdivisionContainment(XPathParts parts) { 2383 // <subgroup type="AL" subtype="04" contains="FR MK LU"/> 2384 final String country = parts.getAttributeValue(-1, "type"); 2385 final String subtype = parts.getAttributeValue(-1, "subtype"); 2386 final String container = 2387 subtype == null ? country : (country + subtype).toLowerCase(Locale.ROOT); 2388 for (String contained : parts.getAttributeValue(-1, "contains").split("\\s+")) { 2389 String newContained = 2390 contained.charAt(0) >= 'a' 2391 ? contained 2392 : (country + contained).toLowerCase(Locale.ROOT); 2393 containerToSubdivision.put(container, newContained); 2394 } 2395 } 2396 2397 private void handleLanguageData(XPathParts parts) { 2398 // <languageData> 2399 // <language type="aa" scripts="Latn" territories="DJ ER ET"/> <!-- 2400 // Reflecting submitted data, cldrbug #1013 --> 2401 // <language type="ab" scripts="Cyrl" territories="GE" 2402 // alt="secondary"/> 2403 String language = parts.getAttributeValue(2, "type"); 2404 BasicLanguageData languageData = new BasicLanguageData(); 2405 languageData.setType( 2406 parts.getAttributeValue(2, "alt") == null 2407 ? BasicLanguageData.Type.primary 2408 : BasicLanguageData.Type.secondary); 2409 languageData 2410 .setScripts(parts.getAttributeValue(2, "scripts")) 2411 .setTerritories(parts.getAttributeValue(2, "territories")); 2412 Map<Type, BasicLanguageData> map = languageToBasicLanguageData.get(language); 2413 if (map == null) { 2414 languageToBasicLanguageData.put( 2415 language, map = new EnumMap<>(BasicLanguageData.Type.class)); 2416 } 2417 if (map.containsKey(languageData.type)) { 2418 throw new IllegalArgumentException("Duplicate value:\t" + parts); 2419 } 2420 map.put(languageData.type, languageData); 2421 } 2422 2423 private boolean failsRangeCheck(String path, double input, double min, double max) { 2424 if (input >= min && input <= max) { 2425 return false; 2426 } 2427 System.out.println( 2428 "Internal Problem in supplementalData: range check fails for " 2429 + input 2430 + ", min: " 2431 + min 2432 + ", max:" 2433 + max 2434 + "\t" 2435 + path); 2436 2437 return false; 2438 } 2439 2440 private double parseDouble(String literacyString) { 2441 return literacyString == null ? Double.NaN : Double.parseDouble(literacyString); 2442 } 2443 } 2444 2445 public class CoverageVariableInfo { 2446 public Set<String> targetScripts; 2447 public Set<String> targetTerritories; 2448 public Set<String> calendars; 2449 public Set<String> targetCurrencies; 2450 public Set<String> targetTimeZones; 2451 public Set<String> targetPlurals; 2452 } 2453 2454 public static String toRegexString(Set<String> s) { 2455 Iterator<String> it = s.iterator(); 2456 StringBuilder sb = new StringBuilder("("); 2457 int count = 0; 2458 while (it.hasNext()) { 2459 if (count > 0) { 2460 sb.append("|"); 2461 } 2462 sb.append(it.next()); 2463 count++; 2464 } 2465 sb.append(")"); 2466 return sb.toString(); 2467 } 2468 2469 public int parseIntegerOrNull(String attributeValue) { 2470 return attributeValue == null ? -1 : Integer.parseInt(attributeValue); 2471 } 2472 2473 Set<String> skippedElements = new TreeSet<>(); 2474 2475 private Map<String, Pair<String, String>> references = new TreeMap<>(); 2476 private Map<String, String> likelySubtags = new TreeMap<>(); 2477 private Map<String, String> likelyOrigins = new TreeMap<>(); 2478 // make public temporarily until we resolve. 2479 private Set<CoverageLevelInfo> coverageLevels = new LinkedHashSet<>(); 2480 private Map<ParentLocaleComponent, Map<String, String>> parentLocales = new HashMap<>(); 2481 2482 { // Prefill, since we know we will need these 2483 Arrays.stream(ParentLocaleComponent.values()) 2484 .forEach(x -> parentLocales.put(x, new HashMap<>())); 2485 } 2486 2487 private Set<ParentLocaleComponent> parentLocalesSkipNonLikely = 2488 EnumSet.noneOf(ParentLocaleComponent.class); 2489 private Map<String, List<String>> calendarPreferences = new HashMap<>(); 2490 private Map<String, CoverageVariableInfo> localeSpecificVariables = new TreeMap<>(); 2491 private VariableReplacer coverageVariables = new VariableReplacer(); 2492 private Map<String, NumberingSystemInfo> numberingSystems = new HashMap<>(); 2493 private Set<String> numericSystems = new TreeSet<>(); 2494 private Set<String> defaultContentLocales; 2495 public Map<CLDRLocale, CLDRLocale> baseToDefaultContent; // wo -> wo_Arab_SN 2496 public Map<CLDRLocale, CLDRLocale> defaultContentToBase; // wo_Arab_SN -> wo 2497 private Set<String> CLDRLanguageCodes = new TreeSet<>(); 2498 private Set<String> CLDRScriptCodes; 2499 2500 /** 2501 * Get the population data for a language. Warning: if the language has script variants, cycle 2502 * on those variants. 2503 * 2504 * @param language 2505 * @return 2506 */ 2507 public PopulationData getLanguagePopulationData(String language) { 2508 return languageToPopulation.get(language); 2509 } 2510 2511 public PopulationData getBaseLanguagePopulationData(String language) { 2512 return baseLanguageToPopulation.get(language); 2513 } 2514 2515 public Set<String> getLanguages() { 2516 return allLanguages; 2517 } 2518 2519 public Set<String> getTerritoryToLanguages(String territory) { 2520 Map<String, PopulationData> result = territoryToLanguageToPopulationData.get(territory); 2521 if (result == null) { 2522 return Collections.emptySet(); 2523 } 2524 return result.keySet(); 2525 } 2526 2527 public PopulationData getLanguageAndTerritoryPopulationData(String language, String territory) { 2528 Map<String, PopulationData> result = territoryToLanguageToPopulationData.get(territory); 2529 if (result == null) { 2530 return null; 2531 } 2532 return result.get(language); 2533 } 2534 2535 public Set<String> getTerritoriesWithPopulationData() { 2536 return territoryToLanguageToPopulationData.keySet(); 2537 } 2538 2539 public Set<String> getLanguagesForTerritoryWithPopulationData(String territory) { 2540 Map<String, PopulationData> languageToPopulationMap = 2541 territoryToLanguageToPopulationData.get(territory); 2542 return languageToPopulationMap == null 2543 ? Collections.emptySet() 2544 : languageToPopulationMap.keySet(); 2545 } 2546 2547 public Set<BasicLanguageData> getBasicLanguageData(String language) { 2548 Map<Type, BasicLanguageData> map = languageToBasicLanguageData.get(language); 2549 if (map == null) { 2550 throw new IllegalArgumentException("Bad language code: " + language); 2551 } 2552 return new LinkedHashSet<>(map.values()); 2553 } 2554 2555 public Map<Type, BasicLanguageData> getBasicLanguageDataMap(String language) { 2556 return languageToBasicLanguageData.get(language); 2557 } 2558 2559 public Set<String> getBasicLanguageDataLanguages() { 2560 return languageToBasicLanguageData.keySet(); 2561 } 2562 2563 public Relation<String, String> getContainmentCore() { 2564 return containmentCore; 2565 } 2566 2567 public Set<String> getContained(String territoryCode) { 2568 return containment.getAll(territoryCode); 2569 } 2570 2571 public Set<String> getContainers() { 2572 return containment.keySet(); 2573 } 2574 2575 public Set<String> getContainedSubdivisions(String territoryOrSubdivisionCode) { 2576 return containerToSubdivision.getAll(territoryOrSubdivisionCode); 2577 } 2578 2579 public Set<String> getContainersForSubdivisions() { 2580 return containerToSubdivision.keySet(); 2581 } 2582 2583 public Relation<String, String> getTerritoryToContained() { 2584 return getTerritoryToContained(ContainmentStyle.all); // true 2585 } 2586 2587 // public Relation<String, String> getTerritoryToContained(boolean allowDeprecated) { 2588 // return allowDeprecated ? containment : containmentNonDeprecated; 2589 // } 2590 // 2591 public enum ContainmentStyle { 2592 all, 2593 core, 2594 grouping, 2595 deprecated 2596 } 2597 2598 public Relation<String, String> getTerritoryToContained(ContainmentStyle containmentStyle) { 2599 switch (containmentStyle) { 2600 case all: 2601 return containment; 2602 case core: 2603 return containmentCore; 2604 case grouping: 2605 return containmentGrouping; 2606 case deprecated: 2607 return containmentDeprecated; 2608 } 2609 throw new IllegalArgumentException("internal error"); 2610 } 2611 2612 public Set<String> getSkippedElements() { 2613 return skippedElements; 2614 } 2615 2616 public Set<String> getZone_aliases(String zone) { 2617 Set<String> result = zone_aliases.getAll(zone); 2618 if (result == null) { 2619 return Collections.emptySet(); 2620 } 2621 return result; 2622 } 2623 2624 public String getZone_territory(String zone) { 2625 return zone_territory.get(zone); 2626 } 2627 2628 public Set<String> getCanonicalZones() { 2629 return zone_territory.keySet(); 2630 } 2631 2632 public Set<String> getTerritoriesForPopulationData(String language) { 2633 return languageToTerritories.getAll(language); 2634 } 2635 2636 public Set<String> getLanguagesForTerritoriesPopulationData() { 2637 return languageToTerritories.keySet(); 2638 } 2639 2640 /** 2641 * Return the list of default content locales. 2642 * 2643 * @return 2644 */ 2645 public Set<String> getDefaultContentLocales() { 2646 return defaultContentLocales; 2647 } 2648 2649 public static Map<String, String> makeLocaleToDefaultContents( 2650 Set<String> defaultContents, Map<String, String> result, Set<String> errors) { 2651 for (String s : defaultContents) { 2652 String simpleParent = LanguageTagParser.getSimpleParent(s); 2653 String oldValue = result.get(simpleParent); 2654 if (oldValue != null) { 2655 errors.add( 2656 "*** Error: Default contents cannot contain two children for the same parent:\t" 2657 + oldValue 2658 + ", " 2659 + s 2660 + "; keeping " 2661 + oldValue); 2662 continue; 2663 } 2664 result.put(simpleParent, s); 2665 } 2666 return result; 2667 } 2668 2669 /** 2670 * Return the list of default content locales. 2671 * 2672 * @return 2673 */ 2674 public Set<CLDRLocale> getDefaultContentCLDRLocales() { 2675 initCLDRLocaleBasedData(); 2676 return defaultContentToBase.keySet(); 2677 } 2678 2679 /** 2680 * Get the default content locale for a specified language 2681 * 2682 * @param language language to search 2683 * @return default content, or null if none 2684 */ 2685 public String getDefaultContentLocale(String locale) { 2686 CLDRLocale cLocale = CLDRLocale.getInstance(locale); 2687 for (String dc : defaultContentLocales) { 2688 if (CLDRLocale.getInstance(dc).getParent() == cLocale) { 2689 return dc; 2690 } 2691 } 2692 return null; 2693 } 2694 2695 /** 2696 * Get the default content locale for a specified language and script. If script is null, 2697 * delegates to {@link #getDefaultContentLocale(String)} 2698 * 2699 * @param language 2700 * @param script if null, delegates to {@link #getDefaultContentLocale(String)} 2701 * @return default content, or null if none 2702 */ 2703 public String getDefaultContentLocale(String language, String script) { 2704 if (script == null) return getDefaultContentLocale(language); 2705 for (String dc : defaultContentLocales) { 2706 if (dc.startsWith(language + "_" + script + "_")) { 2707 return dc; 2708 } 2709 } 2710 return null; 2711 } 2712 2713 /** 2714 * Given a default locale (such as 'wo_Arab_SN') return the base locale (such as 'wo'), or null 2715 * if the input wasn't a default conetnt locale. 2716 * 2717 * @param dcLocale 2718 * @return 2719 */ 2720 public CLDRLocale getBaseFromDefaultContent(CLDRLocale dcLocale) { 2721 initCLDRLocaleBasedData(); 2722 return defaultContentToBase.get(dcLocale); 2723 } 2724 2725 /** 2726 * Given a base locale (such as 'wo') return the default content locale (such as 'wo_Arab_SN'), 2727 * or null. 2728 * 2729 * @param baseLocale 2730 * @return 2731 */ 2732 public CLDRLocale getDefaultContentFromBase(CLDRLocale baseLocale) { 2733 initCLDRLocaleBasedData(); 2734 return baseToDefaultContent.get(baseLocale); 2735 } 2736 2737 /** 2738 * Is this a default content locale? 2739 * 2740 * @param dcLocale 2741 * @return 2742 */ 2743 public boolean isDefaultContent(CLDRLocale dcLocale) { 2744 initCLDRLocaleBasedData(); 2745 if (dcLocale == null) throw new NullPointerException("null locale"); 2746 return (defaultContentToBase.get(dcLocale) != null); 2747 } 2748 2749 public Set<String> getNumberingSystems() { 2750 return numberingSystems.keySet(); 2751 } 2752 2753 public Set<String> getNumericNumberingSystems() { 2754 return Collections.unmodifiableSet(numericSystems); 2755 } 2756 2757 public String getDigits(String numberingSystem) { 2758 try { 2759 return numberingSystems.get(numberingSystem).digits; 2760 } catch (Exception e) { 2761 throw new IllegalArgumentException("Can't get digits for:" + numberingSystem); 2762 } 2763 } 2764 2765 public NumberingSystemType getNumberingSystemType(String numberingSystem) { 2766 return numberingSystems.get(numberingSystem).type; 2767 } 2768 2769 public Set<CoverageLevelInfo> getCoverageLevelInfo() { 2770 return coverageLevels; 2771 } 2772 2773 /** 2774 * Used to get the coverage value for a path. This is generally the most efficient way for tools 2775 * to get coverage. 2776 * 2777 * @param xpath 2778 * @param loc 2779 * @return 2780 */ 2781 public Level getCoverageLevel(String xpath, String loc) { 2782 Level result = null; 2783 result = coverageCache.get(xpath, loc); 2784 if (result == null) { 2785 CoverageLevel2 cov = localeToCoverageLevelInfo.get(loc); 2786 if (cov == null) { 2787 cov = CoverageLevel2.getInstance(this, loc); 2788 localeToCoverageLevelInfo.put(loc, cov); 2789 } 2790 2791 result = cov.getLevel(xpath); 2792 coverageCache.put(xpath, loc, result); 2793 } 2794 return result; 2795 } 2796 2797 /** 2798 * Cache Data structure with object expiry, List that can hold up to MAX_LOCALES caches of 2799 * locales, when one locale hasn't been used for a while it will removed and GC'd 2800 */ 2801 private class CoverageCache { 2802 private final Deque<Node> localeList = new LinkedList<>(); 2803 private final int MAX_LOCALES = 10; 2804 2805 /** Object to sync on for modifying the locale list */ 2806 private final Object LOCALE_LIST_ITER_SYNC = new Object(); 2807 2808 /* 2809 * constructor 2810 */ 2811 public CoverageCache() { 2812 // localeList = new LinkedList<Node>(); 2813 } 2814 2815 /* 2816 * retrieves coverage level associated with two keys if it exists in the cache, otherwise returns null 2817 * @param xpath 2818 * @param loc 2819 * @return the coverage level of the above two keys 2820 */ 2821 public Level get(String xpath, String loc) { 2822 synchronized (LOCALE_LIST_ITER_SYNC) { 2823 Iterator<Node> it = localeList.iterator(); 2824 Node reAddNode = null; 2825 while (it.hasNext()) { 2826 // for (Iterator<Node> it = localeList.iterator(); it.hasNext();) { 2827 Node node = it.next(); 2828 if (node.loc.equals(loc)) { 2829 reAddNode = node; 2830 it.remove(); 2831 break; 2832 } 2833 } 2834 if (reAddNode != null) { 2835 localeList.addFirst(reAddNode); 2836 return reAddNode.map.get(xpath); 2837 } 2838 return null; 2839 } 2840 } 2841 2842 /* 2843 * places a coverage level into the cache, with two keys 2844 * @param xpath 2845 * @param loc 2846 * @param covLevel the coverage level of the above two keys 2847 */ 2848 public void put(String xpath, String loc, Level covLevel) { 2849 synchronized (LOCALE_LIST_ITER_SYNC) { 2850 // if locale's map is already in the cache add to it 2851 // for (Iterator<Node> it = localeList.iterator(); it.hasNext();) { 2852 for (Node node : localeList) { 2853 // Node node = it.next(); 2854 if (node.loc.equals(loc)) { 2855 node.map.put(xpath, covLevel); 2856 return; 2857 } 2858 } 2859 2860 // if it is not, add a new map with the coverage level, and remove the last map in 2861 // the list (used most seldom) if the list is too large 2862 Map<String, Level> newMap = new ConcurrentHashMap<>(); 2863 newMap.put(xpath, covLevel); 2864 localeList.addFirst(new Node(loc, newMap)); 2865 2866 if (localeList.size() > MAX_LOCALES) { 2867 localeList.removeLast(); 2868 } 2869 } 2870 } 2871 2872 /* 2873 * node to hold a location and a Map 2874 */ 2875 private class Node { 2876 // public fields to emulate a C/C++ struct 2877 public String loc; 2878 public Map<String, Level> map; 2879 2880 public Node(String _loc, Map<String, Level> _map) { 2881 loc = _loc; 2882 map = _map; 2883 } 2884 } 2885 } 2886 2887 /** 2888 * Used to get the coverage value for a path. Note, it is more efficient to create a 2889 * CoverageLevel2 for a language, and keep it around. 2890 * 2891 * @param xpath 2892 * @param loc 2893 * @return 2894 */ 2895 public int getCoverageValue(String xpath, String loc) { 2896 return getCoverageLevel(xpath, loc).getLevel(); 2897 } 2898 2899 private RegexLookup<Level> coverageLookup = null; 2900 2901 public synchronized RegexLookup<Level> getCoverageLookup() { 2902 if (coverageLookup == null) { 2903 RegexLookup<Level> lookup = 2904 new RegexLookup<>(RegexLookup.LookupType.STAR_PATTERN_LOOKUP); 2905 2906 Matcher variable = PatternCache.get("\\$\\{[A-Za-z][\\-A-Za-z]*\\}").matcher(""); 2907 2908 for (CoverageLevelInfo ci : getCoverageLevelInfo()) { 2909 String pattern = 2910 ci.match 2911 .replace('\'', '"') 2912 .replace("[@", "\\[@") // make sure that attributes are quoted 2913 .replace("(", "(?:") // make sure that there are no capturing groups 2914 // (beyond what we generate 2915 .replace("(?:?!", "(?!"); // Allow negative lookahead 2916 pattern = "^//ldml/" + pattern + "$"; // for now, force a complete match 2917 String variableType = null; 2918 variable.reset(pattern); 2919 if (variable.find()) { 2920 pattern = 2921 pattern.substring(0, variable.start()) 2922 + "([^\"]*)" 2923 + pattern.substring(variable.end()); 2924 variableType = variable.group(); 2925 if (variable.find()) { 2926 throw new IllegalArgumentException( 2927 "We can only handle a single variable on a line"); 2928 } 2929 } 2930 2931 // .replaceAll("\\]","\\\\]"); 2932 lookup.add(new CoverageLevel2.MyRegexFinder(pattern, variableType, ci), ci.value); 2933 } 2934 coverageLookup = lookup; 2935 } 2936 return coverageLookup; 2937 } 2938 2939 /** 2940 * Older version of code. 2941 * 2942 * @param xpath 2943 * @param loc 2944 * @return 2945 */ 2946 public int getCoverageValueOld(String xpath, ULocale loc) { 2947 String targetLanguage = loc.getLanguage(); 2948 2949 CoverageVariableInfo cvi = getCoverageVariableInfo(targetLanguage); 2950 String targetScriptString = toRegexString(cvi.targetScripts); 2951 String targetTerritoryString = toRegexString(cvi.targetTerritories); 2952 String calendarListString = toRegexString(cvi.calendars); 2953 String targetCurrencyString = toRegexString(cvi.targetCurrencies); 2954 String targetTimeZoneString = toRegexString(cvi.targetTimeZones); 2955 String targetPluralsString = toRegexString(cvi.targetPlurals); 2956 Iterator<CoverageLevelInfo> i = coverageLevels.iterator(); 2957 while (i.hasNext()) { 2958 CoverageLevelInfo ci = i.next(); 2959 String regex = 2960 "//ldml/" 2961 + ci.match 2962 .replace('\'', '"') 2963 .replaceAll("\\[", "\\\\[") 2964 .replaceAll("\\]", "\\\\]") 2965 .replace("${Target-Language}", targetLanguage) 2966 .replace("${Target-Scripts}", targetScriptString) 2967 .replace("${Target-Territories}", targetTerritoryString) 2968 .replace("${Target-TimeZones}", targetTimeZoneString) 2969 .replace("${Target-Currencies}", targetCurrencyString) 2970 .replace("${Target-Plurals}", targetPluralsString) 2971 .replace("${Calendar-List}", calendarListString); 2972 2973 // Special logic added for coverage fields that are only to be applicable 2974 // to certain territories 2975 if (ci.inTerritory != null) { 2976 if (ci.inTerritory.equals("EU")) { 2977 Set<String> containedTerritories = new HashSet<>(); 2978 containedTerritories.addAll(getContained(ci.inTerritory)); 2979 containedTerritories.retainAll(cvi.targetTerritories); 2980 if (containedTerritories.isEmpty()) { 2981 continue; 2982 } 2983 } else { 2984 if (!cvi.targetTerritories.contains(ci.inTerritory)) { 2985 continue; 2986 } 2987 } 2988 } 2989 // Special logic added for coverage fields that are only to be applicable 2990 // to certain languages 2991 if (ci.inLanguage != null && !ci.inLanguage.matcher(targetLanguage).matches()) { 2992 continue; 2993 } 2994 2995 // Special logic added for coverage fields that are only to be applicable 2996 // to certain scripts 2997 if (ci.inScript != null && !cvi.targetScripts.contains(ci.inScript)) { 2998 continue; 2999 } 3000 3001 if (xpath.matches(regex)) { 3002 return ci.value.getLevel(); 3003 } 3004 3005 if (xpath.matches(regex)) { 3006 return ci.value.getLevel(); 3007 } 3008 } 3009 return Level.OPTIONAL.getLevel(); // If no match then return highest possible value 3010 } 3011 3012 // The following is for mapping language to the explicit script and region codes in 3013 // the locale IDs for CLDR locales. We don't need to worry about the unmarked default 3014 // script or region since they are already supplied by the <languageData>. 3015 private Map<String, BasicLanguageData> doMapLanguagesToScriptsRegion() { 3016 Map<String, BasicLanguageData> langToScriptsRegions = new TreeMap<>(); 3017 org.unicode.cldr.util.Factory factory = CLDRConfig.getInstance().getCldrFactory(); 3018 for (CLDRLocale locale : factory.getAvailableCLDRLocales()) { 3019 String language = locale.getLanguage(); 3020 if (language.length() == 0 || language.equals(LocaleNames.ROOT)) { 3021 continue; 3022 } 3023 BasicLanguageData scriptsAndRegions = langToScriptsRegions.get(language); 3024 if (scriptsAndRegions == null) { 3025 scriptsAndRegions = new BasicLanguageData(); 3026 langToScriptsRegions.put(language, scriptsAndRegions); 3027 } 3028 String script = locale.getScript(); 3029 if (script.length() > 0) { 3030 scriptsAndRegions.addScript(script); 3031 } 3032 String region = locale.getCountry(); 3033 if (region.length() > 0 3034 && region.length() < 3) { // per CLDR TC, do not want 001, 419 etc. 3035 scriptsAndRegions.addTerritory(region); 3036 } 3037 } 3038 for (String language : langToScriptsRegions.keySet()) { 3039 BasicLanguageData scriptsAndRegions = langToScriptsRegions.get(language); 3040 langToScriptsRegions.put(language, scriptsAndRegions.freeze()); 3041 } 3042 return Collections.unmodifiableMap(langToScriptsRegions); 3043 } 3044 3045 private static Map<String, BasicLanguageData> languageToScriptsAndRegions = null; 3046 3047 private synchronized Map<String, BasicLanguageData> getLanguageToScriptsAndRegions() { 3048 if (languageToScriptsAndRegions == null) { 3049 languageToScriptsAndRegions = doMapLanguagesToScriptsRegion(); 3050 } 3051 return languageToScriptsAndRegions; 3052 } 3053 3054 public CoverageVariableInfo getCoverageVariableInfo(String targetLanguage) { 3055 CoverageVariableInfo cvi; 3056 if (localeSpecificVariables.containsKey(targetLanguage)) { 3057 cvi = localeSpecificVariables.get(targetLanguage); 3058 } else { 3059 cvi = new CoverageVariableInfo(); 3060 cvi.targetScripts = getTargetScripts(targetLanguage); 3061 cvi.targetTerritories = getTargetTerritories(targetLanguage); 3062 cvi.calendars = getCalendars(cvi.targetTerritories); 3063 cvi.targetCurrencies = getCurrentCurrencies(cvi.targetTerritories); 3064 cvi.targetTimeZones = getCurrentTimeZones(cvi.targetTerritories); 3065 cvi.targetPlurals = getTargetPlurals(targetLanguage); 3066 localeSpecificVariables.put(targetLanguage, cvi); 3067 } 3068 return cvi; 3069 } 3070 3071 private Set<String> getTargetScripts(String language) { 3072 Set<String> targetScripts = new HashSet<>(); 3073 try { 3074 Set<BasicLanguageData> langData = getBasicLanguageData(language); 3075 Iterator<BasicLanguageData> ldi = langData.iterator(); 3076 while (ldi.hasNext()) { 3077 BasicLanguageData bl = ldi.next(); 3078 Set<String> addScripts = bl.scripts; 3079 if (addScripts != null && bl.getType() != BasicLanguageData.Type.secondary) { 3080 targetScripts.addAll(addScripts); 3081 } 3082 } 3083 Map<String, BasicLanguageData> languageToScriptsAndRegions = 3084 getLanguageToScriptsAndRegions(); 3085 if (languageToScriptsAndRegions != null) { 3086 BasicLanguageData scriptsAndRegions = languageToScriptsAndRegions.get(language); 3087 if (scriptsAndRegions != null) { 3088 targetScripts.addAll(scriptsAndRegions.getScripts()); 3089 } 3090 } 3091 } catch (Exception e) { 3092 // fall through 3093 } 3094 3095 if (targetScripts.size() == 0) { 3096 targetScripts.add("Zzzz"); // Unknown Script 3097 } 3098 return targetScripts; 3099 } 3100 3101 private Set<String> getTargetTerritories(String language) { 3102 Set<String> targetTerritories = new HashSet<>(); 3103 Set<String> secondaryTerritories = new HashSet<>(); 3104 try { 3105 Set<BasicLanguageData> langData = getBasicLanguageData(language); 3106 Iterator<BasicLanguageData> ldi = langData.iterator(); 3107 while (ldi.hasNext()) { 3108 BasicLanguageData bl = ldi.next(); 3109 Set<String> addTerritories = bl.territories; 3110 if (addTerritories != null) { 3111 if (bl.getType() == BasicLanguageData.Type.secondary) { 3112 secondaryTerritories.addAll(addTerritories); 3113 } else { 3114 targetTerritories.addAll(addTerritories); 3115 } 3116 } 3117 } 3118 Map<String, BasicLanguageData> languageToScriptsAndRegions = 3119 getLanguageToScriptsAndRegions(); 3120 if (languageToScriptsAndRegions != null) { 3121 BasicLanguageData scriptsAndRegions = languageToScriptsAndRegions.get(language); 3122 if (scriptsAndRegions != null) { 3123 targetTerritories.addAll(scriptsAndRegions.getTerritories()); 3124 } 3125 } 3126 } catch (Exception e) { 3127 // fall through 3128 } 3129 if (targetTerritories.size() == 0) { 3130 getFallbackTargetTerritories(language, targetTerritories, secondaryTerritories); 3131 } 3132 return targetTerritories; 3133 } 3134 3135 private void getFallbackTargetTerritories( 3136 String language, Set<String> targetTerritories, Set<String> secondaryTerritories) { 3137 String region = null; 3138 String maximized = new LikelySubtags().maximize(language); 3139 if (maximized != null) { 3140 CLDRLocale cldrLocale = CLDRLocale.getInstance(maximized); 3141 region = cldrLocale.getCountry(); 3142 } 3143 if (region != null) { 3144 targetTerritories.add(region); 3145 } else if (secondaryTerritories.size() > 0) { 3146 targetTerritories.addAll(secondaryTerritories); 3147 } else { 3148 targetTerritories.add("ZZ"); 3149 } 3150 } 3151 3152 private Set<String> getCalendars(Set<String> territories) { 3153 Set<String> targetCalendars = new HashSet<>(); 3154 Iterator<String> it = territories.iterator(); 3155 while (it.hasNext()) { 3156 List<String> addCalendars = getCalendars(it.next()); 3157 if (addCalendars == null) { 3158 continue; 3159 } 3160 targetCalendars.addAll(addCalendars); 3161 } 3162 return targetCalendars; 3163 } 3164 3165 /** 3166 * @param territory 3167 * @return a list the calendars used in the specified territorys 3168 */ 3169 public List<String> getCalendars(String territory) { 3170 return calendarPreferences.get(territory); 3171 } 3172 3173 private Set<String> getCurrentCurrencies(Set<String> territories) { 3174 Date now = new Date(); 3175 return getCurrentCurrencies(territories, now, now); 3176 } 3177 3178 public Set<String> getCurrentCurrencies( 3179 Set<String> territories, Date startsBefore, Date endsAfter) { 3180 Set<String> targetCurrencies = new HashSet<>(); 3181 Iterator<String> it = territories.iterator(); 3182 while (it.hasNext()) { 3183 Set<CurrencyDateInfo> targetCurrencyInfo = getCurrencyDateInfo(it.next()); 3184 if (targetCurrencyInfo == null) { 3185 continue; 3186 } 3187 Iterator<CurrencyDateInfo> it2 = targetCurrencyInfo.iterator(); 3188 while (it2.hasNext()) { 3189 CurrencyDateInfo cdi = it2.next(); 3190 if (cdi.getStart().before(startsBefore) 3191 && cdi.getEnd().after(endsAfter) 3192 && cdi.isLegalTender()) { 3193 targetCurrencies.add(cdi.getCurrency()); 3194 } 3195 } 3196 } 3197 return targetCurrencies; 3198 } 3199 3200 private Set<String> getCurrentTimeZones(Set<String> territories) { 3201 Set<String> targetTimeZones = new HashSet<>(); 3202 Iterator<String> it = territories.iterator(); 3203 while (it.hasNext()) { 3204 String[] countryIDs = TimeZone.getAvailableIDs(it.next()); 3205 for (int i = 0; i < countryIDs.length; i++) { 3206 targetTimeZones.add(countryIDs[i]); 3207 } 3208 } 3209 return targetTimeZones; 3210 } 3211 3212 private Set<String> getTargetPlurals(String language) { 3213 Set<String> targetPlurals = new HashSet<>(); 3214 targetPlurals.addAll(getPlurals(PluralType.cardinal, language).getCanonicalKeywords()); 3215 // TODO: Kept 0 and 1 specifically until Mark figures out what to do with them. 3216 // They should be removed once this is done. 3217 targetPlurals.add("0"); 3218 targetPlurals.add("1"); 3219 return targetPlurals; 3220 } 3221 3222 public enum ParentLocaleComponent { 3223 main, 3224 collations, 3225 segmentations, 3226 grammaticalFeatures, 3227 plurals; 3228 3229 public static ParentLocaleComponent fromString(String s) { 3230 return s == null 3231 ? ParentLocaleComponent.main // handle empty 3232 : ParentLocaleComponent.valueOf(s); 3233 } 3234 } 3235 3236 public boolean parentLocalesSkipNonLikely(ParentLocaleComponent component) { 3237 return parentLocalesSkipNonLikely.contains(component); 3238 } 3239 3240 public String getExplicitParentLocale(String loc, ParentLocaleComponent component) { 3241 return parentLocales.get(component).get(loc); 3242 } 3243 3244 // These are not (now) used by the current code. 3245 // They should not be used, because the answer is incorrect for parentLocalesSkipNonLikely 3246 // 3247 // public String getExplicitParentLocale(String loc) { 3248 // return getExplicitParentLocale(loc, ParentLocaleComponent.main); 3249 // } 3250 // 3251 3252 // 3253 // public Set<String> getExplicitChildren() { 3254 // return getExplicitChildren(ParentLocaleComponent.main); 3255 // } 3256 // 3257 // public Set<String> getExplicitChildren(ParentLocaleComponent component) { 3258 // return parentLocales.get(component).keySet(); 3259 // } 3260 3261 public Collection<String> getExplicitParents() { 3262 return getExplicitParents(ParentLocaleComponent.main); 3263 } 3264 3265 public Collection<String> getExplicitParents(ParentLocaleComponent component) { 3266 return parentLocales.get(component).values(); 3267 } 3268 3269 public static final class ApprovalRequirementMatcher { 3270 @Override 3271 public String toString() { 3272 return locales + " / " + xpathMatcher + " = " + requiredVotes; 3273 } 3274 3275 ApprovalRequirementMatcher(String xpath) { 3276 XPathParts parts = XPathParts.getFrozenInstance(xpath); 3277 if (parts.containsElement("approvalRequirement")) { 3278 requiredVotes = getRequiredVotes(parts); 3279 String localeAttrib = parts.getAttributeValue(-1, "locales"); 3280 if (localeAttrib == null || localeAttrib.equals(STAR) || localeAttrib.isEmpty()) { 3281 locales = null; // no locale listed == '*' 3282 } else { 3283 Set<CLDRLocale> localeList = new HashSet<>(); 3284 String[] el = localeAttrib.split(" "); 3285 for (int i = 0; i < el.length; i++) { 3286 if (el[i].indexOf(":") == -1) { // Just a simple locale designation 3287 localeList.add(CLDRLocale.getInstance(el[i])); 3288 } else { // Org:CoverageLevel 3289 String[] coverageLocaleParts = el[i].split(":", 2); 3290 String org = coverageLocaleParts[0]; 3291 String level = coverageLocaleParts[1].toUpperCase(); 3292 Set<String> coverageLocales = 3293 sc.getLocaleCoverageLocales( 3294 Organization.fromString(org), 3295 EnumSet.of(Level.fromString(level))); 3296 for (String cl : coverageLocales) { 3297 localeList.add(CLDRLocale.getInstance(cl)); 3298 } 3299 } 3300 } 3301 locales = Collections.unmodifiableSet(localeList); 3302 } 3303 String xpathMatch = parts.getAttributeValue(-1, "paths"); 3304 if (xpathMatch == null || xpathMatch.isEmpty() || xpathMatch.equals(STAR)) { 3305 xpathMatcher = null; 3306 } else { 3307 xpathMatcher = PatternCache.get(xpathMatch); 3308 } 3309 } else { 3310 throw new RuntimeException("Unknown approval requirement: " + xpath); 3311 } 3312 } 3313 3314 static int getRequiredVotes(XPathParts parts) { 3315 String votesStr = parts.getAttributeValue(-1, "votes"); 3316 if (votesStr.charAt(0) == '=') { 3317 votesStr = votesStr.substring(1); 3318 if (votesStr.equals("HIGH_BAR")) { 3319 return VoteResolver.HIGH_BAR; 3320 } else if (votesStr.equals("LOWER_BAR")) { 3321 return VoteResolver.LOWER_BAR; 3322 } 3323 final VoteResolver.Level l = VoteResolver.Level.valueOf(votesStr); 3324 return l.getVotes(Organization.unaffiliated); // use non-TC vote count 3325 } else { 3326 return Integer.parseInt(votesStr); 3327 } 3328 } 3329 3330 private final Set<CLDRLocale> locales; 3331 private final Pattern xpathMatcher; 3332 final int requiredVotes; 3333 3334 public static List<ApprovalRequirementMatcher> buildAll(List<String> approvalRequirements) { 3335 List<ApprovalRequirementMatcher> newList = new LinkedList<>(); 3336 3337 for (String xpath : approvalRequirements) { 3338 newList.add(new ApprovalRequirementMatcher(xpath)); 3339 } 3340 3341 return Collections.unmodifiableList(newList); 3342 } 3343 3344 public boolean matches(CLDRLocale loc, PathHeader ph) { 3345 if (DEBUG) System.err.println(">> testing " + loc + " / " + ph + " vs " + toString()); 3346 if (locales != null) { 3347 if (!locales.contains(loc)) { 3348 return false; 3349 } 3350 } 3351 if (xpathMatcher != null) { 3352 if (ph != null) { 3353 if (!xpathMatcher.matcher(ph.getOriginalPath()).matches()) { 3354 return false; 3355 } else { 3356 return true; 3357 } 3358 } else { 3359 return false; 3360 } 3361 } 3362 return true; 3363 } 3364 3365 public int getRequiredVotes() { 3366 return requiredVotes; 3367 } 3368 } 3369 3370 // run these from first to last to get the approval info. 3371 volatile List<ApprovalRequirementMatcher> approvalMatchers = null; 3372 3373 /** 3374 * Get the preliminary number of required votes based on the given locale and PathHeader 3375 * 3376 * <p>Important: this number may not agree with VoteResolver.getRequiredVotes since VoteResolver 3377 * also takes the baseline status into account. 3378 * 3379 * <p>Called by VoteResolver, ShowStarredCoverage, TestCoverage, and TestCoverageLevel. 3380 * 3381 * @param loc the CLDRLocale 3382 * @param ph the PathHeader - which path this is applied to, or null if unknown. 3383 * @return a number such as 4 or 8 3384 */ 3385 public int getRequiredVotes(CLDRLocale loc, PathHeader ph) { 3386 if (approvalMatchers == null) { 3387 approvalMatchers = ApprovalRequirementMatcher.buildAll(approvalRequirements); 3388 } 3389 3390 for (ApprovalRequirementMatcher m : approvalMatchers) { 3391 if (m.matches(loc, ph)) { 3392 return m.getRequiredVotes(); 3393 } 3394 } 3395 throw new RuntimeException( 3396 "Error: " + loc + " " + ph + " ran off the end of the approvalMatchers."); 3397 } 3398 3399 /** 3400 * Return the canonicalized zone, or null if there is none. 3401 * 3402 * @param alias 3403 * @return 3404 */ 3405 public String getZoneFromAlias(String alias) { 3406 String zone = alias_zone.get(alias); 3407 if (zone != null) return zone; 3408 if (zone_territory.get(alias) != null) return alias; 3409 return null; 3410 } 3411 3412 public boolean isCanonicalZone(String alias) { 3413 return zone_territory.get(alias) != null; 3414 } 3415 3416 /** 3417 * Return the approximate economic weight of this language, computed by taking all of the 3418 * languages in each territory, looking at the literate population and dividing up the GDP of 3419 * the territory (in PPP) according to the proportion that language has of the total. This is 3420 * only an approximation, since the language information is not complete, languages may overlap 3421 * (bilingual speakers), the literacy figures may be estimated, and literacy is only a rough 3422 * proxy for weight of each language in the economy of the territory. 3423 * 3424 * @param targetLanguage 3425 * @return 3426 */ 3427 public double getApproximateEconomicWeight(String targetLanguage) { 3428 double weight = 0; 3429 Set<String> territories = getTerritoriesForPopulationData(targetLanguage); 3430 if (territories == null) return weight; 3431 for (String territory : territories) { 3432 Set<String> languagesInTerritory = getTerritoryToLanguages(territory); 3433 double totalLiteratePopulation = 0; 3434 double targetLiteratePopulation = 0; 3435 for (String language : languagesInTerritory) { 3436 PopulationData populationData = 3437 getLanguageAndTerritoryPopulationData(language, territory); 3438 totalLiteratePopulation += populationData.getLiteratePopulation(); 3439 if (language.equals(targetLanguage)) { 3440 targetLiteratePopulation = populationData.getLiteratePopulation(); 3441 } 3442 } 3443 PopulationData territoryPopulationData = getPopulationDataForTerritory(territory); 3444 final double gdp = territoryPopulationData.getGdp(); 3445 final double scaledGdp = gdp * targetLiteratePopulation / totalLiteratePopulation; 3446 if (scaledGdp > 0) { 3447 weight += scaledGdp; 3448 } else { 3449 // System.out.println("?\t" + territory + "\t" + targetLanguage); 3450 } 3451 } 3452 return weight; 3453 } 3454 3455 public PopulationData getPopulationDataForTerritory(String territory) { 3456 return territoryToPopulationData.get(territory); 3457 } 3458 3459 public Set<String> getScriptVariantsForPopulationData(String language) { 3460 return languageToScriptVariants.getAll(language); 3461 } 3462 3463 public Map<String, Pair<String, String>> getReferences() { 3464 return references; 3465 } 3466 3467 public Map<String, Map<String, String>> getMetazoneToRegionToZone() { 3468 return typeToZoneToRegionToZone.get("metazones"); 3469 } 3470 3471 public String getZoneForMetazoneByRegion(String metazone, String region) { 3472 String result = null; 3473 if (getMetazoneToRegionToZone().containsKey(metazone)) { 3474 Map<String, String> myMap = getMetazoneToRegionToZone().get(metazone); 3475 if (myMap.containsKey(region)) { 3476 result = myMap.get(region); 3477 } else { 3478 result = myMap.get("001"); 3479 } 3480 } 3481 3482 if (result == null) { 3483 result = "Etc/GMT"; 3484 } 3485 3486 return result; 3487 } 3488 3489 public Map<String, Map<String, Map<String, String>>> getTypeToZoneToRegionToZone() { 3490 return typeToZoneToRegionToZone; 3491 } 3492 3493 /** 3494 * @deprecated, use PathHeader.getMetazonePageTerritory 3495 */ 3496 public Map<String, String> getMetazoneToContinentMap() { 3497 return metazoneContinentMap; 3498 } 3499 3500 public Set<String> getAllMetazones() { 3501 return allMetazones; 3502 } 3503 3504 /** 3505 * Is the given metazone outdated? 3506 * 3507 * @param metazone the metazone such as "Liberia" 3508 * @param tzid the timezone id such as "Africa/Monrovia" 3509 * @param timeInMillis the time in milliseconds since 1970 3510 * @return true if the metazone is outdated 3511 */ 3512 public boolean metazoneIsOutdated(String metazone, String tzid, long timeInMillis) { 3513 final MetaZoneRange range = getMetaZoneRange(tzid, timeInMillis); 3514 // For example, for metazone = "Liberia", if range.metazone = "GMT", 3515 // that implies "GMT" is current and "Liberia" is outdated 3516 if (range == null) { 3517 if (DEBUG) { 3518 System.out.println( 3519 "metazoneIsOutdated: " + metazone + "; tzid = " + tzid + "; range is null"); 3520 } 3521 return true; 3522 } 3523 if (!metazone.equals(range.metazone)) { 3524 if (DEBUG) { 3525 System.out.println( 3526 "metazoneIsOutdated: " 3527 + metazone 3528 + "; tzid = " 3529 + tzid 3530 + "; range.metazone = " 3531 + range.metazone); 3532 } 3533 return true; 3534 } 3535 return false; 3536 } 3537 3538 public Map<String, String> getLikelySubtags() { 3539 return likelySubtags; 3540 } 3541 3542 public Map<String, String> getLikelyOrigins() { 3543 return likelyOrigins; 3544 } 3545 3546 public enum PluralType { 3547 cardinal(PluralRules.PluralType.CARDINAL), 3548 ordinal(PluralRules.PluralType.ORDINAL); 3549 3550 // add some gorp to interwork until we clean things up 3551 3552 public final PluralRules.PluralType standardType; 3553 3554 PluralType(PluralRules.PluralType standardType) { 3555 this.standardType = standardType; 3556 } 3557 3558 public static PluralType fromStandardType(PluralRules.PluralType standardType) { 3559 return standardType == null 3560 ? null 3561 : standardType == PluralRules.PluralType.CARDINAL ? cardinal : ordinal; 3562 } 3563 } 3564 3565 private EnumMap<PluralType, Map<String, PluralInfo>> localeToPluralInfo2 = 3566 new EnumMap<>(PluralType.class); 3567 3568 { 3569 localeToPluralInfo2.put(PluralType.cardinal, new LinkedHashMap<String, PluralInfo>()); 3570 localeToPluralInfo2.put(PluralType.ordinal, new LinkedHashMap<String, PluralInfo>()); 3571 } 3572 3573 private Map<String, PluralRanges> localeToPluralRanges = new LinkedHashMap<>(); 3574 3575 private Map<DayPeriodInfo.Type, Map<String, DayPeriodInfo>> typeToLocaleToDayPeriodInfo = 3576 new EnumMap<>(DayPeriodInfo.Type.class); 3577 private Map<String, CoverageLevel2> localeToCoverageLevelInfo = new ConcurrentHashMap<>(); 3578 private CoverageCache coverageCache = new CoverageCache(); 3579 private transient String lastPluralLocales = ""; 3580 private transient PluralType lastPluralWasOrdinal = null; 3581 private transient Map<Count, String> lastPluralMap = new EnumMap<>(Count.class); 3582 private transient String lastDayPeriodLocales = null; 3583 private transient DayPeriodInfo.Type lastDayPeriodType = null; 3584 private transient DayPeriodInfo.Builder dayPeriodBuilder = new DayPeriodInfo.Builder(); 3585 3586 private void addDayPeriodPath(XPathParts path) { 3587 // ldml/dates/calendars/calendar[@type="gregorian"]/dayPeriods/dayPeriodContext[@type="format"]/dayPeriodWidth[@type="wide"]/dayPeriod[@type="am"] 3588 /* 3589 * <supplementalData> 3590 * <version number="$Revision$"/> 3591 * <generation date="$D..e... $"/> 3592 * <dayPeriodRuleSet> 3593 * <dayPeriodRules locales = "en"> <!-- default for any locales not listed under other dayPeriods --> 3594 * <dayPeriodRule type = "am" from = "0:00" before="12:00"/> 3595 * <dayPeriodRule type = "pm" from = "12:00" to="24:00"/> 3596 */ 3597 String typeString = path.getAttributeValue(1, "type"); 3598 String locales = path.getAttributeValue(2, "locales").trim(); 3599 DayPeriodInfo.Type type = 3600 typeString == null 3601 ? DayPeriodInfo.Type.format 3602 : DayPeriodInfo.Type.valueOf(typeString.trim()); 3603 if (!locales.equals(lastDayPeriodLocales) || type != lastDayPeriodType) { 3604 if (lastDayPeriodLocales != null) { 3605 addDayPeriodInfo(); 3606 } 3607 lastDayPeriodLocales = locales; 3608 lastDayPeriodType = type; 3609 // System.out.println(type + ", " + locales + ", " + path); 3610 } 3611 if (path.size() != 4) { 3612 if (locales.equals(LocaleNames.ROOT)) return; // we allow root to be empty 3613 throw new IllegalArgumentException(locales + " must have dayPeriodRule elements"); 3614 } 3615 DayPeriod dayPeriod; 3616 try { 3617 dayPeriod = DayPeriod.fromString(path.getAttributeValue(-1, "type")); 3618 } catch (Exception e) { 3619 System.err.println(e.getMessage()); 3620 return; 3621 } 3622 String at = path.getAttributeValue(-1, "at"); 3623 String from = path.getAttributeValue(-1, "from"); 3624 String after = path.getAttributeValue(-1, "after"); 3625 String to = path.getAttributeValue(-1, "to"); 3626 String before = path.getAttributeValue(-1, "before"); 3627 if (at != null) { 3628 if (from != null || after != null || to != null || before != null) { 3629 throw new IllegalArgumentException(); 3630 } 3631 from = at; 3632 to = at; 3633 } else if ((from == null) == (after == null) || (to == null) == (before == null)) { 3634 throw new IllegalArgumentException(); 3635 } 3636 // if (dayPeriodBuilder.contains(dayPeriod)) { // disallow multiple rules with same 3637 // dayperiod 3638 // throw new IllegalArgumentException("Multiple rules with same dayperiod are 3639 // disallowed: " 3640 // + lastDayPeriodLocales + ", " + lastDayPeriodType + ", " + dayPeriod); 3641 // } 3642 boolean includesStart = from != null; 3643 boolean includesEnd = to != null; 3644 int start = parseTime(includesStart ? from : after); 3645 int end = parseTime(includesEnd ? to : before); 3646 // Check if any periods contain 0, e.g. 1700 - 300 3647 if (start > end) { 3648 // System.out.println("start " + start + " end " + end); 3649 dayPeriodBuilder.add(dayPeriod, start, includesStart, parseTime("24:00"), includesEnd); 3650 dayPeriodBuilder.add(dayPeriod, parseTime("0:00"), includesStart, end, includesEnd); 3651 } else { 3652 dayPeriodBuilder.add(dayPeriod, start, includesStart, end, includesEnd); 3653 } 3654 } 3655 3656 static Pattern PARSE_TIME = PatternCache.get("(\\d\\d?):(\\d\\d)"); 3657 3658 private int parseTime(String string) { 3659 Matcher matcher = PARSE_TIME.matcher(string); 3660 if (!matcher.matches()) { 3661 throw new IllegalArgumentException(); 3662 } 3663 return (Integer.parseInt(matcher.group(1)) * 60 + Integer.parseInt(matcher.group(2))) 3664 * 60 3665 * 1000; 3666 } 3667 3668 private void addDayPeriodInfo() { 3669 String[] locales = lastDayPeriodLocales.split("\\s+"); 3670 DayPeriodInfo temp = dayPeriodBuilder.finish(locales); 3671 Map<String, DayPeriodInfo> locale2DPI = typeToLocaleToDayPeriodInfo.get(lastDayPeriodType); 3672 if (locale2DPI == null) { 3673 typeToLocaleToDayPeriodInfo.put(lastDayPeriodType, locale2DPI = new LinkedHashMap<>()); 3674 // System.out.println(lastDayPeriodType + ", " + locale2DPI); 3675 } 3676 for (String locale : locales) { 3677 locale2DPI.put(locale, temp); 3678 } 3679 } 3680 3681 static String lastPluralRangesLocales = null; 3682 static PluralRanges lastPluralRanges = null; 3683 3684 private boolean addPluralPath(XPathParts path, String value) { 3685 /* 3686 * Adding 3687 <pluralRanges locales="am"> 3688 <pluralRange start="one" end="one" result="one" /> 3689 </pluralRanges> 3690 */ 3691 String locales = path.getAttributeValue(2, "locales").trim(); 3692 String element = path.getElement(2); 3693 if ("pluralRanges".equals(element)) { 3694 if (!locales.equals(lastPluralRangesLocales)) { 3695 addPluralRanges(locales); 3696 } 3697 if (path.size() == 3) { 3698 // ok for ranges to be empty 3699 return true; 3700 } 3701 String rangeStart = path.getAttributeValue(-1, "start"); 3702 String rangeEnd = path.getAttributeValue(-1, "end"); 3703 String result = path.getAttributeValue(-1, "result"); 3704 lastPluralRanges.add( 3705 rangeStart == null ? null : Count.valueOf(rangeStart), 3706 rangeEnd == null ? null : Count.valueOf(rangeEnd), 3707 Count.valueOf(result)); 3708 return true; 3709 } else if ("pluralRules".equals(element)) { 3710 3711 String type = path.getAttributeValue(1, "type"); 3712 PluralType pluralType = type == null ? PluralType.cardinal : PluralType.valueOf(type); 3713 if (!lastPluralLocales.equals(locales)) { 3714 addPluralInfo(pluralType); 3715 lastPluralLocales = locales; 3716 } 3717 final String countString = path.getAttributeValue(-1, "count"); 3718 if (countString == null) { 3719 return false; 3720 } 3721 Count count = Count.valueOf(countString); 3722 if (lastPluralMap.containsKey(count)) { 3723 throw new IllegalArgumentException( 3724 "Duplicate plural count: " + count + " in " + locales); 3725 } 3726 lastPluralMap.put(count, value); 3727 lastPluralWasOrdinal = pluralType; 3728 return true; 3729 } else { 3730 return false; 3731 } 3732 } 3733 3734 private void addPluralRanges(String localesString) { 3735 final String[] locales = localesString.split("\\s+"); 3736 lastPluralRanges = new PluralRanges(); 3737 for (String locale : locales) { 3738 if (localeToPluralRanges.containsKey(locale)) { 3739 throw new IllegalArgumentException("Duplicate plural locale: " + locale); 3740 } 3741 localeToPluralRanges.put(locale, lastPluralRanges); 3742 } 3743 lastPluralRangesLocales = localesString; 3744 } 3745 3746 private void addPluralInfo(PluralType pluralType) { 3747 final String[] locales = lastPluralLocales.split("\\s+"); 3748 PluralInfo info = new PluralInfo(lastPluralMap, pluralType); 3749 Map<String, PluralInfo> localeToInfo = localeToPluralInfo2.get(pluralType); 3750 for (String locale : locales) { 3751 if (localeToInfo.containsKey(locale)) { 3752 throw new IllegalArgumentException("Duplicate plural locale: " + locale); 3753 } else if (!locale.isEmpty()) { 3754 localeToInfo.put(locale, info); 3755 } 3756 } 3757 lastPluralMap.clear(); 3758 } 3759 3760 public static class SampleList { 3761 public static final SampleList EMPTY = new SampleList().freeze(); 3762 3763 private UnicodeSet uset = new UnicodeSet(); 3764 private List<FixedDecimal> fractions = new ArrayList<>(0); 3765 3766 @Override 3767 public String toString() { 3768 return toString(6, 3); 3769 } 3770 3771 public String toString(int intLimit, int fractionLimit) { 3772 StringBuilder b = new StringBuilder(); 3773 int intCount = 0; 3774 int fractionCount = 0; 3775 int limit = uset.getRangeCount(); 3776 for (int i = 0; i < limit; ++i) { 3777 if (intCount >= intLimit) { 3778 b.append(", …"); 3779 break; 3780 } 3781 if (b.length() != 0) { 3782 b.append(", "); 3783 } 3784 int start = uset.getRangeStart(i); 3785 int end = uset.getRangeEnd(i); 3786 if (start == end) { 3787 b.append(start); 3788 ++intCount; 3789 } else if (start + 1 == end) { 3790 b.append(start).append(", ").append(end); 3791 intCount += 2; 3792 } else { 3793 b.append(start).append('-').append(end); 3794 intCount += 2; 3795 } 3796 } 3797 if (fractions.size() > 0) { 3798 for (int i = 0; i < fractions.size(); ++i) { 3799 if (fractionCount >= fractionLimit) { 3800 break; 3801 } 3802 if (b.length() != 0) { 3803 b.append(", "); 3804 } 3805 FixedDecimal fraction = fractions.get(i); 3806 String formatted = 3807 String.format( 3808 Locale.ROOT, 3809 "%." + fraction.getVisibleDecimalDigitCount() + "f", 3810 fraction.getSource()); 3811 b.append(formatted); 3812 ++fractionCount; 3813 } 3814 b.append(", …"); 3815 } 3816 return b.toString(); 3817 } 3818 getRangeCount()3819 public int getRangeCount() { 3820 return uset.getRangeCount(); 3821 } 3822 getRangeStart(int index)3823 public int getRangeStart(int index) { 3824 return uset.getRangeStart(index); 3825 } 3826 getRangeEnd(int index)3827 public int getRangeEnd(int index) { 3828 return uset.getRangeEnd(index); 3829 } 3830 getFractions()3831 public List<FixedDecimal> getFractions() { 3832 return fractions; 3833 } 3834 intSize()3835 public int intSize() { 3836 return uset.size(); 3837 } 3838 remove(int i)3839 public SampleList remove(int i) { 3840 uset.remove(i); 3841 return this; 3842 } 3843 add(int i)3844 public SampleList add(int i) { 3845 uset.add(i); 3846 return this; 3847 } 3848 freeze()3849 public SampleList freeze() { 3850 uset.freeze(); 3851 if (fractions instanceof ArrayList) { 3852 fractions = Collections.unmodifiableList(fractions); 3853 } 3854 return this; 3855 } 3856 add(FixedDecimal i)3857 public void add(FixedDecimal i) { 3858 fractions.add(i); 3859 } 3860 fractionSize()3861 public int fractionSize() { 3862 return fractions.size(); 3863 } 3864 } 3865 3866 public static class CountSampleList { 3867 private final Map<Count, SampleList> countToIntegerSamples9999; 3868 private final Map<Count, SampleList[]> countToDigitToIntegerSamples9999; 3869 CountSampleList(PluralRules pluralRules, Set<Count> keywords, PluralType pluralType)3870 CountSampleList(PluralRules pluralRules, Set<Count> keywords, PluralType pluralType) { 3871 // Create the integer counts 3872 countToIntegerSamples9999 = new EnumMap<>(Count.class); 3873 countToDigitToIntegerSamples9999 = new EnumMap<>(Count.class); 3874 for (Count c : keywords) { 3875 countToIntegerSamples9999.put(c, new SampleList()); 3876 SampleList[] row = new SampleList[5]; 3877 countToDigitToIntegerSamples9999.put(c, row); 3878 for (int i = 1; i < 5; ++i) { 3879 row[i] = new SampleList(); 3880 } 3881 } 3882 for (int ii = 0; ii < 10000; ++ii) { 3883 int i = ii; 3884 int digit; 3885 if (i > 999) { 3886 digit = 4; 3887 } else if (i > 99) { 3888 digit = 3; 3889 } else if (i > 9) { 3890 digit = 2; 3891 } else { 3892 digit = 1; 3893 } 3894 Count count = Count.valueOf(pluralRules.select(i)); 3895 addSimple(countToIntegerSamples9999, i, count); 3896 addDigit(countToDigitToIntegerSamples9999, i, count, digit); 3897 if (haveFractions(keywords, digit)) { 3898 continue; 3899 } 3900 if (pluralType == PluralType.cardinal) { 3901 for (int f = 0; f < 30; ++f) { 3902 FixedDecimal ni = new FixedDecimal(i + f / 10.0d, f < 10 ? 1 : 2, f); 3903 count = Count.valueOf(pluralRules.select(ni)); 3904 addSimple(countToIntegerSamples9999, ni, count); 3905 addDigit(countToDigitToIntegerSamples9999, ni, count, digit); 3906 } 3907 } 3908 } 3909 // HACK for Breton 3910 addSimple( 3911 countToIntegerSamples9999, 1000000, Count.valueOf(pluralRules.select(1000000))); 3912 3913 for (Count count : keywords) { 3914 SampleList uset = countToIntegerSamples9999.get(count); 3915 uset.freeze(); 3916 SampleList[] map = countToDigitToIntegerSamples9999.get(count); 3917 for (int i = 1; i < map.length; ++i) { 3918 map[i].freeze(); 3919 } 3920 } 3921 } 3922 haveFractions(Set<Count> keywords, int digit)3923 private boolean haveFractions(Set<Count> keywords, int digit) { 3924 for (Count c : keywords) { 3925 int size = countToDigitToIntegerSamples9999.get(c)[digit].fractionSize(); 3926 if (size < MAX_COLLECTED_FRACTION) { 3927 return false; 3928 } 3929 } 3930 return true; 3931 } 3932 3933 static final int MAX_COLLECTED_FRACTION = 5; 3934 addDigit( Map<Count, SampleList[]> countToDigitToIntegerSamples9999, FixedDecimal i, Count count, int digit)3935 private boolean addDigit( 3936 Map<Count, SampleList[]> countToDigitToIntegerSamples9999, 3937 FixedDecimal i, 3938 Count count, 3939 int digit) { 3940 return addFraction(i, countToDigitToIntegerSamples9999.get(count)[digit]); 3941 } 3942 addFraction(FixedDecimal i, SampleList sampleList)3943 private boolean addFraction(FixedDecimal i, SampleList sampleList) { 3944 if (sampleList.fractionSize() < MAX_COLLECTED_FRACTION) { 3945 sampleList.add(i); 3946 return true; 3947 } else { 3948 return false; 3949 } 3950 } 3951 addSimple( Map<Count, SampleList> countToIntegerSamples9999, FixedDecimal i, Count count)3952 private boolean addSimple( 3953 Map<Count, SampleList> countToIntegerSamples9999, FixedDecimal i, Count count) { 3954 return addFraction(i, countToIntegerSamples9999.get(count)); 3955 } 3956 addDigit( Map<Count, SampleList[]> countToDigitToIntegerSamples9999, int i, Count count, int digit)3957 private void addDigit( 3958 Map<Count, SampleList[]> countToDigitToIntegerSamples9999, 3959 int i, 3960 Count count, 3961 int digit) { 3962 countToDigitToIntegerSamples9999.get(count)[digit].add(i); 3963 } 3964 addSimple( Map<Count, SampleList> countToIntegerSamples9999, int i, Count count)3965 private void addSimple( 3966 Map<Count, SampleList> countToIntegerSamples9999, int i, Count count) { 3967 countToIntegerSamples9999.get(count).add(i); 3968 } 3969 get(Count type)3970 public SampleList get(Count type) { 3971 return countToIntegerSamples9999.get(type); 3972 } 3973 get(Count c, int digit)3974 public SampleList get(Count c, int digit) { 3975 SampleList[] sampleLists = countToDigitToIntegerSamples9999.get(c); 3976 return sampleLists == null ? null : sampleLists[digit]; 3977 } 3978 } 3979 3980 /** 3981 * Immutable class with plural info for different locales 3982 * 3983 * @author markdavis 3984 */ 3985 public static class PluralInfo implements Comparable<PluralInfo> { 3986 static final Set<Double> explicits = new HashSet<>(); 3987 3988 static { 3989 explicits.add(0.0d); 3990 explicits.add(1.0d); 3991 } 3992 3993 public enum Count { 3994 zero, 3995 one, 3996 two, 3997 few, 3998 many, 3999 other; 4000 public static final int LENGTH = Count.values().length; 4001 public static final List<Count> VALUES = 4002 Collections.unmodifiableList(Arrays.asList(values())); 4003 } 4004 4005 static final Pattern pluralPaths = PatternCache.get(".*pluralRule.*"); 4006 static final int fractDecrement = 13; 4007 static final int fractStart = 20; 4008 4009 private final Map<Count, Set<Double>> countToExampleSet; 4010 private final Map<Count, String> countToStringExample; 4011 private final Map<Integer, Count> exampleToCount; 4012 private final PluralRules pluralRules; 4013 private final String pluralRulesString; 4014 private final Set<String> canonicalKeywords; 4015 private final Set<Count> keywords; 4016 private final Set<Count> integerKeywords; 4017 private final Set<Count> decimalKeywords; 4018 private final CountSampleList countSampleList; 4019 private final Map<Count, String> countToRule; 4020 private final Set<Count> adjustedCounts; 4021 private final Set<String> adjustedCountStrings; 4022 4023 // e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5 4024 static final Pattern hasE = Pattern.compile("e\\s*!?="); 4025 PluralInfo(Map<Count, String> countToRule, PluralType pluralType)4026 private PluralInfo(Map<Count, String> countToRule, PluralType pluralType) { 4027 EnumMap<Count, String> tempCountToRule = new EnumMap<>(Count.class); 4028 tempCountToRule.putAll(countToRule); 4029 this.countToRule = Collections.unmodifiableMap(tempCountToRule); 4030 4031 // now build rules 4032 NumberFormat nf = NumberFormat.getNumberInstance(ULocale.ENGLISH); 4033 nf.setMaximumFractionDigits(2); 4034 StringBuilder pluralRuleBuilder = new StringBuilder(); 4035 for (Count count : countToRule.keySet()) { 4036 if (pluralRuleBuilder.length() != 0) { 4037 pluralRuleBuilder.append(';'); 4038 } 4039 pluralRuleBuilder.append(count).append(':').append(countToRule.get(count)); 4040 } 4041 pluralRulesString = pluralRuleBuilder.toString(); 4042 try { 4043 pluralRules = PluralRules.parseDescription(pluralRulesString); 4044 } catch (ParseException e) { 4045 throw new IllegalArgumentException( 4046 "Can't create plurals from <" + pluralRulesString + ">", e); 4047 } 4048 EnumSet<Count> _keywords = EnumSet.noneOf(Count.class); 4049 EnumSet<Count> _integerKeywords = EnumSet.noneOf(Count.class); 4050 EnumSet<Count> _decimalKeywords = EnumSet.noneOf(Count.class); 4051 Matcher hasEMatcher = hasE.matcher(""); 4052 Set<Count> _adjustedCounts = null; 4053 Set<String> _adjustedCountStrings = null; 4054 4055 for (String s : pluralRules.getKeywords()) { 4056 Count c = Count.valueOf(s); 4057 _keywords.add(c); 4058 if (pluralRules.getDecimalSamples(s, SampleType.DECIMAL) != null) { 4059 _decimalKeywords.add(c); 4060 } else { 4061 int debug = 1; 4062 } 4063 if (pluralRules.getDecimalSamples(s, SampleType.INTEGER) != null) { 4064 _integerKeywords.add(c); 4065 } else { 4066 int debug = 1; 4067 } 4068 String parsedRules = pluralRules.getRules(s); 4069 if (!hasEMatcher.reset(parsedRules).find()) { 4070 if (_adjustedCounts == null) { 4071 _adjustedCounts = new TreeSet<>(); 4072 _adjustedCountStrings = new TreeSet<>(); 4073 } 4074 _adjustedCounts.add(c); 4075 _adjustedCountStrings.add(s); 4076 } 4077 } 4078 adjustedCounts = 4079 _adjustedCounts == null 4080 ? Collections.emptySet() 4081 : ImmutableSet.copyOf(_adjustedCounts); 4082 adjustedCountStrings = 4083 _adjustedCounts == null 4084 ? Collections.emptySet() 4085 : ImmutableSet.copyOf(_adjustedCountStrings); 4086 4087 keywords = Collections.unmodifiableSet(_keywords); 4088 decimalKeywords = Collections.unmodifiableSet(_decimalKeywords); 4089 integerKeywords = Collections.unmodifiableSet(_integerKeywords); 4090 4091 countSampleList = new CountSampleList(pluralRules, keywords, pluralType); 4092 4093 Map<Count, Set<Double>> countToExampleSetRaw = new TreeMap<>(); 4094 Map<Integer, Count> exampleToCountRaw = new TreeMap<>(); 4095 4096 Output<Map<Count, SampleList[]>> output = new Output(); 4097 4098 // double check 4099 // if (!targetKeywords.equals(typeToExamples2.keySet())) { 4100 // throw new IllegalArgumentException ("Problem in plurals " + targetKeywords + ", " + 4101 // this); 4102 // } 4103 // now fix the longer examples 4104 String otherFractionalExamples = ""; 4105 List<Double> otherFractions = new ArrayList<>(0); 4106 4107 // add fractional samples 4108 Map<Count, String> countToStringExampleRaw = new TreeMap<>(); 4109 for (Count type : keywords) { 4110 SampleList uset = countSampleList.get(type); 4111 countToStringExampleRaw.put(type, uset.toString(5, 5)); 4112 } 4113 final String baseOtherExamples = countToStringExampleRaw.get(Count.other); 4114 String otherExamples = 4115 (baseOtherExamples == null ? "" : baseOtherExamples + "; ") 4116 + otherFractionalExamples 4117 + "..."; 4118 countToStringExampleRaw.put(Count.other, otherExamples); 4119 4120 // Now do double examples (previously unused & not working). 4121 // Currently a bit of a hack, we should enhance SampleList to make this easier 4122 // and then use SampleList directly, see http://unicode.org/cldr/trac/ticket/9813 4123 for (Count type : countToStringExampleRaw.keySet()) { 4124 Set<Double> doublesSet = new LinkedHashSet<>(0); 4125 String examples = countToStringExampleRaw.get(type); 4126 if (examples == null) { 4127 examples = ""; 4128 } 4129 String strippedExamples = examples.replaceAll("(, …)|(; ...)", ""); 4130 String[] exampleArray = strippedExamples.split("(, )|(-)"); 4131 for (String example : exampleArray) { 4132 if (example == null || example.length() == 0) { 4133 continue; 4134 } 4135 Double doubleValue = Double.valueOf(example); 4136 doublesSet.add(doubleValue); 4137 } 4138 doublesSet = Collections.unmodifiableSet(doublesSet); 4139 countToExampleSetRaw.put(type, doublesSet); 4140 } 4141 4142 countToExampleSet = Collections.unmodifiableMap(countToExampleSetRaw); 4143 countToStringExample = Collections.unmodifiableMap(countToStringExampleRaw); 4144 exampleToCount = Collections.unmodifiableMap(exampleToCountRaw); 4145 Set<String> temp = new LinkedHashSet<>(); 4146 // String keyword = pluralRules.select(0.0d); 4147 // double value = pluralRules.getUniqueKeywordValue(keyword); 4148 // if (value == pluralRules.NO_UNIQUE_VALUE) { 4149 // temp.add("0"); 4150 // } 4151 // keyword = pluralRules.select(1.0d); 4152 // value = pluralRules.getUniqueKeywordValue(keyword); 4153 // if (value == pluralRules.NO_UNIQUE_VALUE) { 4154 // temp.add("1"); 4155 // } 4156 Set<String> keywords = pluralRules.getKeywords(); 4157 for (Count count : Count.values()) { 4158 String keyword = count.toString(); 4159 if (keywords.contains(keyword)) { 4160 temp.add(keyword); 4161 } 4162 } 4163 // if (false) { 4164 // change to this after rationalizing 0/1 4165 // temp.add("0"); 4166 // temp.add("1"); 4167 // for (Count count : Count.values()) { 4168 // temp.add(count.toString()); 4169 // KeywordStatus status = 4170 // org.unicode.cldr.util.PluralRulesUtil.getKeywordStatus(pluralRules, 4171 // count.toString(), 0, explicits, true); 4172 // if (status != KeywordStatus.SUPPRESSED && status != KeywordStatus.INVALID) { 4173 // temp.add(count.toString()); 4174 // } 4175 // } 4176 // } 4177 canonicalKeywords = Collections.unmodifiableSet(temp); 4178 } 4179 4180 @Override toString()4181 public String toString() { 4182 return countToExampleSet + "; " + exampleToCount + "; " + pluralRules; 4183 } 4184 getCountToExamplesMap()4185 public Map<Count, Set<Double>> getCountToExamplesMap() { 4186 return countToExampleSet; 4187 } 4188 getCountToStringExamplesMap()4189 public Map<Count, String> getCountToStringExamplesMap() { 4190 return countToStringExample; 4191 } 4192 getCount(double exampleCount)4193 public Count getCount(double exampleCount) { 4194 return Count.valueOf(pluralRules.select(exampleCount)); 4195 } 4196 getCount(DecimalQuantity exampleCount)4197 public Count getCount(DecimalQuantity exampleCount) { 4198 return Count.valueOf(pluralRules.select(exampleCount)); 4199 } 4200 getPluralRules()4201 public PluralRules getPluralRules() { 4202 return pluralRules; 4203 } 4204 getRules()4205 public String getRules() { 4206 return pluralRulesString; 4207 } 4208 getDefault()4209 public Count getDefault() { 4210 return null; 4211 } 4212 getCanonicalKeywords()4213 public Set<String> getCanonicalKeywords() { 4214 return canonicalKeywords; 4215 } 4216 getCounts()4217 public Set<Count> getCounts() { 4218 return keywords; 4219 } 4220 4221 /** 4222 * Return the counts returned by the plural rules, adjusted to remove values that are not 4223 * used in collecting data. 4224 */ getAdjustedCounts()4225 public Set<Count> getAdjustedCounts() { 4226 return adjustedCounts; 4227 } 4228 4229 /** 4230 * Return the counts returned by the plural rules, adjusted to remove values that are not 4231 * used in collecting data. 4232 */ getAdjustedCountStrings()4233 public Set<String> getAdjustedCountStrings() { 4234 return adjustedCountStrings; 4235 } 4236 getCounts(SampleType sampleType)4237 public Set<Count> getCounts(SampleType sampleType) { 4238 return sampleType == SampleType.DECIMAL ? decimalKeywords : integerKeywords; 4239 } 4240 4241 /** 4242 * Return the integer samples from 0 to 9999. For simplicity and compactness, this is a 4243 * UnicodeSet, but the interpretation is simply as a list of integers. UnicodeSet.EMPTY is 4244 * returned if there are none. 4245 * 4246 * @param c 4247 * @return 4248 */ getSamples9999(Count c)4249 public SampleList getSamples9999(Count c) { 4250 return countSampleList.get(c); 4251 } 4252 4253 /** 4254 * Return the integer samples for the specified digit, eg 1 => 0..9. For simplicity and 4255 * compactness, this is a UnicodeSet, but the interpretation is simply as a list of 4256 * integers. 4257 * 4258 * @param c 4259 * @return 4260 */ getSamples9999(Count c, int digit)4261 public SampleList getSamples9999(Count c, int digit) { 4262 return countSampleList.get(c, digit); 4263 } 4264 hasSamples(Count c, int digits)4265 public boolean hasSamples(Count c, int digits) { 4266 SampleList samples = countSampleList.get(c, digits); 4267 return samples != null && (samples.fractionSize() > 0 || samples.intSize() > 0); 4268 } 4269 getRule(Count keyword)4270 public String getRule(Count keyword) { 4271 return countToRule.get(keyword); 4272 } 4273 4274 @Override compareTo(PluralInfo other)4275 public int compareTo(PluralInfo other) { 4276 int size1 = this.countToRule.size(); 4277 int size2 = other.countToRule.size(); 4278 int diff = size1 - size2; 4279 if (diff != 0) { 4280 return diff; 4281 } 4282 Iterator<Count> it1 = countToRule.keySet().iterator(); 4283 Iterator<Count> it2 = other.countToRule.keySet().iterator(); 4284 while (it1.hasNext()) { 4285 Count a1 = it1.next(); 4286 Count a2 = it2.next(); 4287 diff = a1.ordinal() - a2.ordinal(); 4288 if (diff != 0) { 4289 return diff; 4290 } 4291 } 4292 return pluralRules.compareTo(other.pluralRules); 4293 } 4294 4295 enum MinMax { 4296 MIN, 4297 MAX 4298 } 4299 4300 public static final DecimalQuantity NEGATIVE_INFINITY = 4301 new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY); 4302 public static final DecimalQuantity POSITIVE_INFINITY = 4303 new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY); 4304 doubleValue(DecimalQuantity a)4305 static double doubleValue(DecimalQuantity a) { 4306 return a.toDouble(); 4307 } 4308 rangeExists( Count s, Count e, Output<DecimalQuantity> minSample, Output<DecimalQuantity> maxSample)4309 public boolean rangeExists( 4310 Count s, 4311 Count e, 4312 Output<DecimalQuantity> minSample, 4313 Output<DecimalQuantity> maxSample) { 4314 if (!getCounts().contains(s) || !getCounts().contains(e)) { 4315 return false; 4316 } 4317 DecimalQuantity temp; 4318 minSample.value = 4319 getLeastIn(s, SampleType.INTEGER, NEGATIVE_INFINITY, POSITIVE_INFINITY); 4320 temp = getLeastIn(s, SampleType.DECIMAL, NEGATIVE_INFINITY, POSITIVE_INFINITY); 4321 if (lessOrFewerDecimals(temp, minSample.value)) { 4322 minSample.value = temp; 4323 } 4324 maxSample.value = 4325 getGreatestIn(e, SampleType.INTEGER, NEGATIVE_INFINITY, POSITIVE_INFINITY); 4326 temp = getGreatestIn(e, SampleType.DECIMAL, NEGATIVE_INFINITY, POSITIVE_INFINITY); 4327 if (greaterOrFewerDecimals(temp, maxSample.value)) { 4328 maxSample.value = temp; 4329 } 4330 // if there is no range, just return 4331 if (doubleValue(minSample.value) >= doubleValue(maxSample.value)) { 4332 return false; 4333 } 4334 // see if we can get a better range, with not such a large end range 4335 4336 DecimalQuantity lowestMax = 4337 new DecimalQuantity_DualStorageBCD( 4338 minSample 4339 .value 4340 .toBigDecimal() 4341 .add(new java.math.BigDecimal("0.00001"))); 4342 lowestMax.setMinFraction(5); 4343 SampleType bestType = 4344 getCounts(SampleType.INTEGER).contains(e) 4345 ? SampleType.INTEGER 4346 : SampleType.DECIMAL; 4347 temp = getLeastIn(e, bestType, lowestMax, POSITIVE_INFINITY); 4348 if (lessOrFewerDecimals(temp, maxSample.value)) { 4349 maxSample.value = temp; 4350 } 4351 if (maxSample.value.toDouble() > 100000) { 4352 temp = getLeastIn(e, bestType, lowestMax, POSITIVE_INFINITY); 4353 if (lessOrFewerDecimals(temp, maxSample.value)) { 4354 maxSample.value = temp; 4355 } 4356 } 4357 4358 return true; 4359 } 4360 greaterOrFewerDecimals(DecimalQuantity a, DecimalQuantity b)4361 public boolean greaterOrFewerDecimals(DecimalQuantity a, DecimalQuantity b) { 4362 return doubleValue(a) > doubleValue(b) 4363 || doubleValue(b) == doubleValue(a) 4364 && b.getPluralOperand(Operand.f) > a.getPluralOperand(Operand.f); 4365 } 4366 lessOrFewerDecimals(DecimalQuantity a, DecimalQuantity b)4367 public boolean lessOrFewerDecimals(DecimalQuantity a, DecimalQuantity b) { 4368 return doubleValue(a) < doubleValue(b) 4369 || doubleValue(b) == doubleValue(a) 4370 && b.getPluralOperand(Operand.f) > a.getPluralOperand(Operand.f); 4371 } 4372 getLeastIn( Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max)4373 private DecimalQuantity getLeastIn( 4374 Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max) { 4375 DecimalQuantity result = POSITIVE_INFINITY; 4376 DecimalQuantitySamples sSamples1 = 4377 pluralRules.getDecimalSamples(s.toString(), sampleType); 4378 if (sSamples1 != null) { 4379 for (DecimalQuantitySamplesRange x : sSamples1.samples) { 4380 // overlap in ranges?? 4381 if (doubleValue(x.start) > doubleValue(max) 4382 || doubleValue(x.end) < doubleValue(min)) { 4383 continue; // no, continue 4384 } 4385 // get restricted range 4386 DecimalQuantity minOverlap = 4387 greaterOrFewerDecimals(min, x.start) ? max : x.start; 4388 // DecimalQuantity maxOverlap = lessOrFewerDecimals(max, x.end) ? max : x.end; 4389 4390 // replace if better 4391 if (lessOrFewerDecimals(minOverlap, result)) { 4392 result = minOverlap; 4393 } 4394 } 4395 } 4396 return result; 4397 } 4398 getGreatestIn( Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max)4399 private DecimalQuantity getGreatestIn( 4400 Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max) { 4401 DecimalQuantity result = NEGATIVE_INFINITY; 4402 DecimalQuantitySamples sSamples1 = 4403 pluralRules.getDecimalSamples(s.toString(), sampleType); 4404 if (sSamples1 != null) { 4405 for (DecimalQuantitySamplesRange x : sSamples1.getSamples()) { 4406 // overlap in ranges?? 4407 if (doubleValue(x.start) > doubleValue(max) 4408 || doubleValue(x.end) < doubleValue(min)) { 4409 continue; // no, continue 4410 } 4411 // get restricted range 4412 // DecimalQuantity minOverlap = greaterOrFewerDecimals(min, x.start) ? max : 4413 // x.start; 4414 DecimalQuantity maxOverlap = lessOrFewerDecimals(max, x.end) ? max : x.end; 4415 4416 // replace if better 4417 if (greaterOrFewerDecimals(maxOverlap, result)) { 4418 result = maxOverlap; 4419 } 4420 } 4421 } 4422 return result; 4423 } 4424 getNonZeroSampleIfPossible( DecimalQuantitySamples exampleList)4425 public static DecimalQuantity getNonZeroSampleIfPossible( 4426 DecimalQuantitySamples exampleList) { 4427 Set<DecimalQuantitySamplesRange> sampleSet = exampleList.getSamples(); 4428 DecimalQuantity sampleDecimal = null; 4429 // skip 0 if possible 4430 for (DecimalQuantitySamplesRange range : sampleSet) { 4431 sampleDecimal = range.start; 4432 if (sampleDecimal.toDouble() != 0.0) { 4433 break; 4434 } 4435 sampleDecimal = range.end; 4436 if (sampleDecimal.toDouble() != 0.0) { 4437 break; 4438 } 4439 } 4440 return sampleDecimal; 4441 } 4442 } 4443 4444 /** 4445 * @deprecated use {@link #getPlurals(PluralType)} instead 4446 */ 4447 @Deprecated getPluralLocales()4448 public Set<String> getPluralLocales() { 4449 return getPluralLocales(PluralType.cardinal); 4450 } 4451 4452 /** 4453 * @param type 4454 * @return the set of locales that have rules for the specified plural type 4455 */ getPluralLocales(PluralType type)4456 public Set<String> getPluralLocales(PluralType type) { 4457 return localeToPluralInfo2.get(type).keySet(); 4458 } 4459 getPluralRangesLocales()4460 public Set<String> getPluralRangesLocales() { 4461 return localeToPluralRanges.keySet(); 4462 } 4463 getPluralRanges(String locale)4464 public PluralRanges getPluralRanges(String locale) { 4465 return localeToPluralRanges.get(locale); 4466 } 4467 4468 /** 4469 * @deprecated use {@link #getPlurals(PluralType, String)} instead 4470 */ 4471 @Deprecated getPlurals(String locale)4472 public PluralInfo getPlurals(String locale) { 4473 return getPlurals(locale, true); 4474 } 4475 4476 /** 4477 * Returns the plural info for a given locale. 4478 * 4479 * @param locale 4480 * @return 4481 */ getPlurals(PluralType type, String locale)4482 public PluralInfo getPlurals(PluralType type, String locale) { 4483 return getPlurals(type, locale, true); 4484 } 4485 4486 /** 4487 * @deprecated use {@link #getPlurals(PluralType, String, boolean)} instead. 4488 */ 4489 @Deprecated getPlurals(String locale, boolean allowRoot)4490 public PluralInfo getPlurals(String locale, boolean allowRoot) { 4491 return getPlurals(PluralType.cardinal, locale, allowRoot); 4492 } 4493 4494 /** 4495 * Returns the plural info for a given locale. 4496 * 4497 * @param locale 4498 * @param allowRoot 4499 * @param type 4500 * @return 4501 */ getPlurals(PluralType type, String locale, boolean allowRoot)4502 public PluralInfo getPlurals(PluralType type, String locale, boolean allowRoot) { 4503 Map<String, PluralInfo> infoMap = localeToPluralInfo2.get(type); 4504 while (locale != null) { 4505 if (!allowRoot && locale.equals(LocaleNames.ROOT)) { 4506 break; 4507 } 4508 PluralInfo result = infoMap.get(locale); 4509 if (result != null) { 4510 return result; 4511 } 4512 locale = LocaleIDParser.getSimpleParent(locale); 4513 } 4514 return null; 4515 } 4516 4517 /** 4518 * CLDR equivalent of com.ibm.icu.text.PluralRules.forLocale() 4519 * 4520 * @param loc 4521 * @param type 4522 * @return an ICU PluralRules, from CLDR data 4523 */ getPluralRules(ULocale loc, PluralRules.PluralType type)4524 public PluralRules getPluralRules(ULocale loc, PluralRules.PluralType type) { 4525 return getPluralRules(loc.getBaseName(), type); 4526 } 4527 4528 /** 4529 * CLDR equivalent of com.ibm.icu.text.PluralRules.forLocale() 4530 * 4531 * @param loc 4532 * @param type 4533 * @return an ICU PluralRules, from CLDR data 4534 */ getPluralRules(String loc, PluralRules.PluralType type)4535 public PluralRules getPluralRules(String loc, PluralRules.PluralType type) { 4536 return getPlurals(PluralType.fromStandardType(type), loc).getPluralRules(); 4537 } 4538 getDayPeriods(DayPeriodInfo.Type type, String locale)4539 public DayPeriodInfo getDayPeriods(DayPeriodInfo.Type type, String locale) { 4540 Map<String, DayPeriodInfo> map1 = typeToLocaleToDayPeriodInfo.get(type); 4541 while (locale != null) { 4542 DayPeriodInfo result = map1.get(locale); 4543 if (result != null) { 4544 return result; 4545 } 4546 locale = LocaleIDParser.getSimpleParent(locale); 4547 } 4548 return null; 4549 } 4550 getDayPeriodLocales(DayPeriodInfo.Type type)4551 public Set<String> getDayPeriodLocales(DayPeriodInfo.Type type) { 4552 return typeToLocaleToDayPeriodInfo.get(type).keySet(); 4553 } 4554 4555 private static CurrencyNumberInfo DEFAULT_NUMBER_INFO = new CurrencyNumberInfo(2, -1, -1, -1); 4556 getCurrencyNumberInfo(String currency)4557 public CurrencyNumberInfo getCurrencyNumberInfo(String currency) { 4558 CurrencyNumberInfo result = currencyToCurrencyNumberInfo.get(currency); 4559 if (result == null) { 4560 result = DEFAULT_NUMBER_INFO; 4561 } 4562 return result; 4563 } 4564 4565 /** 4566 * Returns ordered set of currency data information 4567 * 4568 * @param territory 4569 * @return 4570 */ getCurrencyDateInfo(String territory)4571 public Set<CurrencyDateInfo> getCurrencyDateInfo(String territory) { 4572 return territoryToCurrencyDateInfo.getAll(territory); 4573 } 4574 4575 /** 4576 * Returns ordered set of currency data information 4577 * 4578 * @return 4579 */ getCurrencyTerritories()4580 public Set<String> getCurrencyTerritories() { 4581 return territoryToCurrencyDateInfo.keySet(); 4582 } 4583 4584 /** 4585 * Returns the ISO4217 currency code of the default currency for a given territory. The default 4586 * currency is the first one listed which is legal tender at the present moment. 4587 * 4588 * @param territory 4589 * @return 4590 */ getDefaultCurrency(String territory)4591 public String getDefaultCurrency(String territory) { 4592 4593 String result = "XXX"; 4594 Set<CurrencyDateInfo> targetCurrencyInfo = getCurrencyDateInfo(territory); 4595 if (targetCurrencyInfo == null) { 4596 /* 4597 * This happens during ConsoleCheckCLDR 4598 * territory = "419" 4599 * path = //ldml/numbers/currencyFormats[@numberSystem="latn"]/currencyFormatLength/currencyFormat[@type="accounting"]/pattern[@type="standard"] 4600 * value = ¤#,##0.00 4601 * Prevent NullPointerException 4602 */ 4603 return result; 4604 } 4605 Date now = new Date(); 4606 for (CurrencyDateInfo cdi : targetCurrencyInfo) { 4607 if (cdi.getStart().before(now) && cdi.getEnd().after(now) && cdi.isLegalTender()) { 4608 result = cdi.getCurrency(); 4609 break; 4610 } 4611 } 4612 return result; 4613 } 4614 4615 /** 4616 * Returns the ISO4217 currency code of the default currency for a given CLDRLocale. The default 4617 * currency is the first one listed which is legal tender at the present moment. 4618 * 4619 * @param loc 4620 * @return 4621 */ getDefaultCurrency(CLDRLocale loc)4622 public String getDefaultCurrency(CLDRLocale loc) { 4623 return getDefaultCurrency(loc.getCountry()); 4624 } 4625 getTerritoryToTelephoneCodeInfo()4626 public Map<String, Set<TelephoneCodeInfo>> getTerritoryToTelephoneCodeInfo() { 4627 return territoryToTelephoneCodeInfo; 4628 } 4629 getTelephoneCodeInfoForTerritory(String territory)4630 public Set<TelephoneCodeInfo> getTelephoneCodeInfoForTerritory(String territory) { 4631 return territoryToTelephoneCodeInfo.get(territory); 4632 } 4633 getTerritoriesForTelephoneCodeInfo()4634 public Set<String> getTerritoriesForTelephoneCodeInfo() { 4635 return territoryToTelephoneCodeInfo.keySet(); 4636 } 4637 4638 private List<String> serialElements; 4639 private Collection<String> distinguishingAttributes; 4640 4641 // @Deprecated 4642 // public List<String> getSerialElements() { 4643 // return serialElements; 4644 // } 4645 4646 // @Deprecated 4647 // public Collection<String> getDistinguishingAttributes() { 4648 // return distinguishingAttributes; 4649 // } 4650 4651 /** 4652 * The Row is: desired, supported, percent, oneway 4653 * 4654 * @param string the type (written-new, for new format) 4655 * @return 4656 */ getLanguageMatcherData(String string)4657 public List<R4<String, String, Integer, Boolean>> getLanguageMatcherData(String string) { 4658 return languageMatch.get(string); 4659 } 4660 getLanguageMatcherKeys()4661 public Set<String> getLanguageMatcherKeys() { 4662 return languageMatch.keySet(); 4663 } 4664 4665 /** Return mapping from type to territory to data. 001 is the default. */ getTerritoryMeasurementData()4666 public Map<MeasurementType, Map<String, String>> getTerritoryMeasurementData() { 4667 return measurementData; 4668 } 4669 4670 /** Return mapping from keys to subtypes */ getBcp47Keys()4671 public Relation<String, String> getBcp47Keys() { 4672 return bcp47Key2Subtypes; 4673 } 4674 4675 /** Return mapping from extensions to keys */ getBcp47Extension2Keys()4676 public Relation<String, String> getBcp47Extension2Keys() { 4677 return bcp47Extension2Keys; 4678 } 4679 4680 /** Return mapping from <key,subtype> to aliases */ getBcp47Aliases()4681 public Relation<R2<String, String>, String> getBcp47Aliases() { 4682 return bcp47Aliases; 4683 } 4684 4685 /** Return mapping from <key,subtype> to description */ getBcp47Descriptions()4686 public Map<R2<String, String>, String> getBcp47Descriptions() { 4687 return bcp47Descriptions; 4688 } 4689 4690 /** Return mapping from <key,subtype> to since */ getBcp47Since()4691 public Map<R2<String, String>, String> getBcp47Since() { 4692 return bcp47Since; 4693 } 4694 4695 /** Return mapping from <key,subtype> to preferred */ getBcp47Preferred()4696 public Map<R2<String, String>, String> getBcp47Preferred() { 4697 return bcp47Preferred; 4698 } 4699 4700 /** Return mapping from <key,subtype> to deprecated */ getBcp47Deprecated()4701 public Map<R2<String, String>, String> getBcp47Deprecated() { 4702 return bcp47Deprecated; 4703 } 4704 4705 /** Return mapping from subtype to deprecated */ getBcp47ValueType()4706 public Map<String, String> getBcp47ValueType() { 4707 return bcp47ValueType; 4708 } 4709 4710 static Set<String> MainTimeZones; 4711 4712 /** 4713 * Return canonical timezones 4714 * 4715 * @return 4716 */ getCanonicalTimeZones()4717 public Set<String> getCanonicalTimeZones() { 4718 synchronized (SupplementalDataInfo.class) { 4719 if (MainTimeZones == null) { 4720 MainTimeZones = new TreeSet<>(); 4721 SupplementalDataInfo info = SupplementalDataInfo.getInstance(); 4722 for (Entry<R2<String, String>, Set<String>> entry : 4723 info.getBcp47Aliases().keyValuesSet()) { 4724 R2<String, String> subtype_aliases = entry.getKey(); 4725 if (!subtype_aliases.get0().equals("timezone")) { 4726 continue; 4727 } 4728 MainTimeZones.add(entry.getValue().iterator().next()); 4729 } 4730 MainTimeZones = Collections.unmodifiableSet(MainTimeZones); 4731 } 4732 return MainTimeZones; 4733 } 4734 } 4735 getMetaZoneRanges(String zone)4736 public Set<MetaZoneRange> getMetaZoneRanges(String zone) { 4737 return zoneToMetaZoneRanges.get(zone); 4738 } 4739 4740 /** 4741 * Return the metazone containing this zone at this date 4742 * 4743 * @param zone 4744 * @param date 4745 * @return 4746 */ getMetaZoneRange(String zone, long date)4747 public MetaZoneRange getMetaZoneRange(String zone, long date) { 4748 Set<MetaZoneRange> metazoneRanges = zoneToMetaZoneRanges.get(zone); 4749 if (metazoneRanges != null) { 4750 for (MetaZoneRange metazoneRange : metazoneRanges) { 4751 if (metazoneRange.dateRange.getFrom() <= date 4752 && date < metazoneRange.dateRange.getTo()) { 4753 return metazoneRange; 4754 } 4755 } 4756 } 4757 return null; 4758 } 4759 isDeprecated(DtdType type, String element, String attribute, String value)4760 public boolean isDeprecated(DtdType type, String element, String attribute, String value) { 4761 if (type.getStatus() == DtdStatus.removed) { 4762 // if the DTD was removed, skip 4763 return true; 4764 } 4765 return DtdData.getInstance(type).isDeprecated(element, attribute, value); 4766 } 4767 isDeprecated(DtdType type, String path)4768 public boolean isDeprecated(DtdType type, String path) { 4769 4770 XPathParts parts = XPathParts.getFrozenInstance(path); 4771 for (int i = 0; i < parts.size(); ++i) { 4772 String element = parts.getElement(i); 4773 if (isDeprecated(type, element, "*", "*")) { 4774 return true; 4775 } 4776 for (Entry<String, String> entry : parts.getAttributes(i).entrySet()) { 4777 String attribute = entry.getKey(); 4778 String value = entry.getValue(); 4779 if (isDeprecated(type, element, attribute, value)) { 4780 return true; 4781 } 4782 } 4783 } 4784 return false; 4785 } 4786 4787 /** 4788 * Returns map of ID/Type/Value, such as id="$integer" type="regex" value=[0-9]+ 4789 * 4790 * @return 4791 */ getValidityInfo()4792 public Map<String, R2<String, String>> getValidityInfo() { 4793 return validityInfo; 4794 } 4795 getCLDRLanguageCodes()4796 public Set<String> getCLDRLanguageCodes() { 4797 return CLDRLanguageCodes; 4798 } 4799 isCLDRLanguageCode(String code)4800 public boolean isCLDRLanguageCode(String code) { 4801 return CLDRLanguageCodes.contains(code); 4802 } 4803 getCLDRScriptCodes()4804 public Set<String> getCLDRScriptCodes() { 4805 return CLDRScriptCodes; 4806 } 4807 isCLDRScriptCode(String code)4808 public boolean isCLDRScriptCode(String code) { 4809 return CLDRScriptCodes.contains(code); 4810 } 4811 initCLDRLocaleBasedData()4812 private synchronized void initCLDRLocaleBasedData() throws InternalError { 4813 // This initialization depends on SDI being initialized. 4814 if (defaultContentToBase == null) { 4815 Map<CLDRLocale, CLDRLocale> p2c = new TreeMap<>(); 4816 Map<CLDRLocale, CLDRLocale> c2p = new TreeMap<>(); 4817 TreeSet<CLDRLocale> tmpAllLocales = new TreeSet<>(); 4818 // copied from SupplementalData.java - CLDRLocale based 4819 for (String l : defaultContentLocales) { 4820 CLDRLocale child = CLDRLocale.getInstance(l); 4821 tmpAllLocales.add(child); 4822 } 4823 4824 for (CLDRLocale child : tmpAllLocales) { 4825 // Find a parent of this locale which is NOT itself also a defaultContent 4826 CLDRLocale nextParent = child.getParent(); 4827 // /System.err.println(">> considering " + child + " with parent " + nextParent); 4828 while (nextParent != null) { 4829 if (!tmpAllLocales.contains( 4830 nextParent)) { // Did we find a parent that's also not itself a 4831 // defaultContent? 4832 // /System.err.println(">>>> Got 1? considering " + child + " with parent " 4833 // + nextParent); 4834 break; 4835 } 4836 // /System.err.println(">>>>> considering " + child + " with parent " + 4837 // nextParent); 4838 nextParent = nextParent.getParent(); 4839 } 4840 // parent 4841 if (nextParent == null) { 4842 throw new InternalError( 4843 "SupplementalDataInfo.defaultContentToChild(): No valid parent for " 4844 + child); 4845 } else if (nextParent == CLDRLocale.ROOT 4846 || nextParent == CLDRLocale.getInstance(LocaleNames.ROOT)) { 4847 throw new InternalError( 4848 "SupplementalDataInfo.defaultContentToChild(): Parent is root for default content locale " 4849 + child); 4850 } else { 4851 c2p.put(child, nextParent); // wo_Arab_SN -> wo 4852 CLDRLocale oldChild = p2c.get(nextParent); 4853 if (oldChild != null) { 4854 CLDRLocale childParent = child.getParent(); 4855 if (!childParent.equals(oldChild)) { 4856 throw new InternalError( 4857 "SupplementalData.defaultContentToChild(): defaultContent list in wrong order? Tried to map " 4858 + nextParent 4859 + " -> " 4860 + child 4861 + ", replacing " 4862 + oldChild 4863 + " (should have been " 4864 + childParent 4865 + ")"); 4866 } 4867 } 4868 p2c.put(nextParent, child); // wo -> wo_Arab_SN 4869 } 4870 } 4871 4872 // done, save the hashtables.. 4873 baseToDefaultContent = Collections.unmodifiableMap(p2c); // wo -> wo_Arab_SN 4874 defaultContentToBase = Collections.unmodifiableMap(c2p); // wo_Arab_SN -> wo 4875 } 4876 } 4877 getTimeData()4878 public Map<String, PreferredAndAllowedHour> getTimeData() { 4879 return timeData; 4880 } 4881 getDefaultScript(String baseLanguage)4882 public String getDefaultScript(String baseLanguage) { 4883 String ls = likelySubtags.get(baseLanguage); 4884 if (ls == null) { 4885 return UNKNOWN_SCRIPT; 4886 } 4887 LocaleIDParser lp = new LocaleIDParser().set(ls); 4888 String defaultScript = lp.getScript(); 4889 if (defaultScript.length() > 0) { 4890 return defaultScript; 4891 } else { 4892 return UNKNOWN_SCRIPT; 4893 } 4894 } 4895 4896 private XEquivalenceClass<String, String> equivalentLocales = null; 4897 getEquivalentsForLocale(String localeId)4898 public Set<String> getEquivalentsForLocale(String localeId) { 4899 if (equivalentLocales == null) { 4900 equivalentLocales = getEquivalentsForLocale(); 4901 } 4902 Set<String> result = new TreeSet(LENGTH_FIRST); 4903 result.add(localeId); 4904 Set<String> equiv = equivalentLocales.getEquivalences(localeId); 4905 // if (equiv == null) { 4906 // result.add(localeId); 4907 // return result; 4908 // } 4909 if (equiv != null) { 4910 result.addAll(equivalentLocales.getEquivalences(localeId)); 4911 } 4912 Map<String, String> likely = getLikelySubtags(); 4913 String newMax = LikelySubtags.maximize(localeId, likely); 4914 if (newMax != null) { 4915 result.add(newMax); 4916 newMax = LikelySubtags.minimize(localeId, likely, true); 4917 if (newMax != null) { 4918 result.add(newMax); 4919 } 4920 newMax = LikelySubtags.minimize(localeId, likely, false); 4921 if (newMax != null) { 4922 result.add(newMax); 4923 } 4924 } 4925 4926 // if (result.size() == 1) { 4927 // LanguageTagParser ltp = new LanguageTagParser().set(localeId); 4928 // if (ltp.getScript().isEmpty()) { 4929 // String ds = getDefaultScript(ltp.getLanguage()); 4930 // if (ds != null) { 4931 // ltp.setScript(ds); 4932 // result.add(ltp.toString()); 4933 // } 4934 // } 4935 // } 4936 return result; 4937 } 4938 4939 public static final class LengthFirstComparator<T> implements Comparator<T> { 4940 @Override compare(T a, T b)4941 public int compare(T a, T b) { 4942 String as = a.toString(); 4943 String bs = b.toString(); 4944 if (as.length() < bs.length()) return -1; 4945 if (as.length() > bs.length()) return 1; 4946 return as.compareTo(bs); 4947 } 4948 } 4949 4950 public static final LengthFirstComparator LENGTH_FIRST = new LengthFirstComparator(); 4951 getEquivalentsForLocale()4952 private synchronized XEquivalenceClass<String, String> getEquivalentsForLocale() { 4953 SupplementalDataInfo sdi = this; 4954 Relation<String, String> localeToDefaultContents = 4955 Relation.of(new HashMap<String, Set<String>>(), LinkedHashSet.class); 4956 4957 Set<String> dcl = sdi.getDefaultContentLocales(); 4958 Map<String, String> likely = sdi.getLikelySubtags(); 4959 XEquivalenceClass<String, String> locales = new XEquivalenceClass<>(); 4960 LanguageTagParser ltp = new LanguageTagParser(); 4961 Set<String> temp = new HashSet<>(); 4962 for (Entry<String, String> entry : likely.entrySet()) { 4963 String source = entry.getKey(); 4964 if (source.startsWith(LocaleNames.UND)) { 4965 continue; 4966 } 4967 for (String s : getCombinations(source, ltp, likely, temp)) { 4968 locales.add(source, s); 4969 } 4970 for (String s : getCombinations(entry.getValue(), ltp, likely, temp)) { 4971 locales.add(source, s); 4972 } 4973 } 4974 // Set<String> sorted = new TreeSet(locales.getExplicitItems()); 4975 // for (String s : sorted) { 4976 // System.out.println(locales.getEquivalences(s)); 4977 // } 4978 for (String defaultContentLocale : dcl) { 4979 if (defaultContentLocale.startsWith("zh")) { 4980 int x = 0; 4981 } 4982 Set<String> set = locales.getEquivalences(defaultContentLocale); 4983 4984 String parent = LocaleIDParser.getSimpleParent(defaultContentLocale); 4985 if (!set.contains(parent)) { 4986 localeToDefaultContents.put(parent, defaultContentLocale); 4987 // System.out.println("Mismatch " + parent + ", " + set); 4988 } 4989 if (parent.contains("_")) { 4990 continue; 4991 } 4992 // only base locales after this point 4993 String ds = sdi.getDefaultScript(parent); 4994 if (ds != null) { 4995 ltp.set(parent); 4996 ltp.setScript(ds); 4997 String trial = ltp.toString(); 4998 if (!set.contains(trial)) { 4999 // System.out.println("Mismatch " + trial + ", " + set); 5000 localeToDefaultContents.put(parent, trial); 5001 } 5002 } 5003 } 5004 return locales; 5005 } 5006 getCombinations( String source, LanguageTagParser ltp, Map<String, String> likely, Set<String> locales)5007 private Set<String> getCombinations( 5008 String source, LanguageTagParser ltp, Map<String, String> likely, Set<String> locales) { 5009 locales.clear(); 5010 5011 String max = LikelySubtags.maximize(source, likely); 5012 locales.add(max); 5013 5014 ltp.set(source); 5015 ltp.setScript(""); 5016 String trial = ltp.toString(); 5017 String newMax = LikelySubtags.maximize(trial, likely); 5018 if (Objects.equals(newMax, max)) { 5019 locales.add(trial); 5020 } 5021 5022 ltp.set(source); 5023 ltp.setRegion(""); 5024 trial = ltp.toString(); 5025 newMax = LikelySubtags.maximize(trial, likely); 5026 if (Objects.equals(newMax, max)) { 5027 locales.add(trial); 5028 } 5029 5030 return locales; 5031 } 5032 getCldrVersion()5033 public VersionInfo getCldrVersion() { 5034 return cldrVersion; 5035 } 5036 getUnicodeVersionString()5037 public String getUnicodeVersionString() { 5038 return unicodeVersion; 5039 } 5040 getUnicodeVersion()5041 public VersionInfo getUnicodeVersion() { 5042 return VersionInfo.getInstance(getUnicodeVersionString()); 5043 } 5044 getCldrVersionString()5045 public String getCldrVersionString() { 5046 return cldrVersionString; 5047 } 5048 getDirectory()5049 public File getDirectory() { 5050 return directory; 5051 } 5052 5053 public static final Splitter WHITESPACE_SPLTTER = 5054 Splitter.on(PatternCache.get("\\s+")).omitEmptyStrings(); 5055 5056 public static final class AttributeValidityInfo { 5057 // <attributeValues elements="alias" attributes="path" 5058 // type="path">notDoneYet</attributeValues> 5059 5060 final String type; 5061 final Set<DtdType> dtds; 5062 final Set<String> elements; 5063 final Set<String> attributes; 5064 final String order; 5065 5066 @Override toString()5067 public String toString() { 5068 return "type:" 5069 + type 5070 + ", elements:" 5071 + elements 5072 + ", attributes:" 5073 + attributes 5074 + ", order:" 5075 + order; 5076 } 5077 add( Map<String, String> inputAttibutes, String inputValue, Map<AttributeValidityInfo, String> data)5078 static void add( 5079 Map<String, String> inputAttibutes, 5080 String inputValue, 5081 Map<AttributeValidityInfo, String> data) { 5082 final AttributeValidityInfo key = 5083 new AttributeValidityInfo( 5084 inputAttibutes.get("dtds"), 5085 inputAttibutes.get("type"), 5086 inputAttibutes.get("attributes"), 5087 inputAttibutes.get("elements"), 5088 inputAttibutes.get("order")); 5089 if (data.containsKey(key)) { 5090 throw new IllegalArgumentException(key + " declared twice"); 5091 } 5092 data.put(key, inputValue); 5093 } 5094 AttributeValidityInfo( String dtds, String type, String attributes, String elements, String order)5095 public AttributeValidityInfo( 5096 String dtds, String type, String attributes, String elements, String order) { 5097 if (dtds == null) { 5098 this.dtds = Collections.singleton(DtdType.ldml); 5099 } else { 5100 Set<DtdType> temp = EnumSet.noneOf(DtdType.class); 5101 for (String s : WHITESPACE_SPLTTER.split(dtds)) { 5102 temp.add(DtdType.fromElement(s)); 5103 } 5104 this.dtds = Collections.unmodifiableSet(temp); 5105 } 5106 this.type = type != null ? type : order != null ? "choice" : null; 5107 this.elements = 5108 elements == null 5109 ? Collections.EMPTY_SET 5110 : With.in(WHITESPACE_SPLTTER.split(elements)) 5111 .toUnmodifiableCollection(new HashSet<String>()); 5112 this.attributes = 5113 With.in(WHITESPACE_SPLTTER.split(attributes)) 5114 .toUnmodifiableCollection(new HashSet<String>()); 5115 this.order = order; 5116 } 5117 getType()5118 public String getType() { 5119 return type; 5120 } 5121 getDtds()5122 public Set<DtdType> getDtds() { 5123 return dtds; 5124 } 5125 getElements()5126 public Set<String> getElements() { 5127 return elements; 5128 } 5129 getAttributes()5130 public Set<String> getAttributes() { 5131 return attributes; 5132 } 5133 getOrder()5134 public String getOrder() { 5135 return order; 5136 } 5137 5138 @Override equals(Object obj)5139 public boolean equals(Object obj) { 5140 AttributeValidityInfo other = (AttributeValidityInfo) obj; 5141 return CldrUtility.deepEquals( 5142 type, other.type, 5143 dtds, other.dtds, 5144 elements, other.elements, 5145 attributes, other.attributes, 5146 order, other.order); 5147 } 5148 5149 @Override hashCode()5150 public int hashCode() { 5151 return Objects.hash(type, dtds, elements, attributes, order); 5152 } 5153 } 5154 getAttributeValidity()5155 public Map<AttributeValidityInfo, String> getAttributeValidity() { 5156 return attributeValidityInfo; 5157 } 5158 getLanguageGroups()5159 public Multimap<String, String> getLanguageGroups() { 5160 return languageGroups; 5161 } 5162 getUnitConverter()5163 public UnitConverter getUnitConverter() { 5164 return unitConverter; 5165 } 5166 getRationalParser()5167 public RationalParser getRationalParser() { 5168 return rationalParser; 5169 } 5170 getUnitPreferences()5171 public UnitPreferences getUnitPreferences() { 5172 return unitPreferences; 5173 } 5174 getUnitIdComponentType(String component)5175 public UnitIdComponentType getUnitIdComponentType(String component) { 5176 UnitIdComponentType result = unitIdComponentType.get(component); 5177 return result == null ? UnitIdComponentType.base : result; 5178 } 5179 5180 /** Locales that have grammar info */ hasGrammarInfo()5181 public Set<String> hasGrammarInfo() { 5182 return grammarLocaleToTargetToFeatureToValues.keySet(); 5183 } 5184 5185 /** 5186 * Locales that have grammar info for at least one of the features (with the given target and 5187 * scope). 5188 */ getLocalesWithFeatures( GrammaticalTarget target, GrammaticalScope scope, GrammaticalFeature... features)5189 public Set<String> getLocalesWithFeatures( 5190 GrammaticalTarget target, GrammaticalScope scope, GrammaticalFeature... features) { 5191 Set<String> locales = new TreeSet<>(); 5192 for (Entry<String, GrammarInfo> localeAndGrammar : 5193 grammarLocaleToTargetToFeatureToValues.entrySet()) { 5194 final GrammarInfo grammarInfo = localeAndGrammar.getValue(); 5195 for (GrammaticalFeature feature : features) { 5196 Collection<String> featureInfo = grammarInfo.get(target, feature, scope); 5197 if (!featureInfo.isEmpty()) { 5198 locales.add(localeAndGrammar.getKey()); 5199 } 5200 } 5201 } 5202 return ImmutableSet.copyOf(locales); 5203 } 5204 5205 /** 5206 * Grammar info for locales, with inheritance 5207 * 5208 * @param locale 5209 * @return 5210 */ getGrammarInfo(String locale)5211 public GrammarInfo getGrammarInfo(String locale) { 5212 return getGrammarInfo(locale, false); 5213 } 5214 5215 /** 5216 * Special hack for v38; should drop seedOnly later. 5217 * 5218 * @param locale 5219 * @param seedOnly 5220 * @return 5221 */ 5222 @Deprecated getGrammarInfo(String locale, boolean seedOnly)5223 public GrammarInfo getGrammarInfo(String locale, boolean seedOnly) { 5224 for (; locale != null; locale = LocaleIDParser.getParent(locale)) { 5225 if (seedOnly && !GrammarInfo.getGrammarLocales().contains(locale)) { 5226 continue; 5227 } 5228 GrammarInfo result = grammarLocaleToTargetToFeatureToValues.get(locale); 5229 if (result != null) { 5230 return result; 5231 } 5232 } 5233 return null; 5234 } 5235 hasGrammarDerivation()5236 public Set<String> hasGrammarDerivation() { 5237 return localeToGrammarDerivation.keySet(); 5238 } 5239 getGrammarDerivation(String locale)5240 public GrammarDerivation getGrammarDerivation(String locale) { 5241 for (; locale != null; locale = LocaleIDParser.getParent(locale)) { 5242 GrammarDerivation result = localeToGrammarDerivation.get(locale); 5243 if (result != null) { 5244 return result; 5245 } 5246 } 5247 return null; 5248 } 5249 getPersonNameOrder()5250 public Multimap<Order, String> getPersonNameOrder() { 5251 return personNameOrder; 5252 } 5253 getUnitPrefixInfo(String prefix)5254 public UnitPrefixInfo getUnitPrefixInfo(String prefix) { 5255 return unitPrefixInfo.get(prefix); 5256 } 5257 getUnitPrefixes()5258 public Set<String> getUnitPrefixes() { 5259 return unitPrefixInfo.keySet(); 5260 } 5261 5262 /** 5263 * Filter out deprecated items. This is more complicated than it seems. The deprecation is in 5264 * timezones.xml, eg: <type name="cathu" description="Thunder Bay, Canada" deprecated="true" 5265 * preferred="cator"/> <type name="cator" description="Toronto, Canada" alias="America/Toronto 5266 * America/Montreal Canada/Eastern America/Nipigon America/Thunder_Bay"/> We need to find the 5267 * short id's that are deprecated, put there is a problem due to 5268 * https://unicode-org.atlassian.net/browse/CLDR-17412. 5269 * 5270 * <p>America/Nipigon, America/Thunder_Bay, America/Rainy_River 5271 */ 5272 Supplier<Set<String>> goodTimezones = 5273 Suppliers.memoize( 5274 new Supplier<Set<String>>() { 5275 5276 @Override 5277 public Set<String> get() { 5278 Set<String> availableLongTz = sc.getAvailableCodes(CodeType.tzid); 5279 Set<String> result = null; 5280 if (true) { // hack for now 5281 final Set<String> hack = 5282 Set.of( 5283 "America/Santa_Isabel", 5284 "Australia/Currie", 5285 "America/Yellowknife", 5286 "America/Rainy_River", 5287 "America/Thunder_Bay", 5288 "America/Nipigon", 5289 "America/Pangnirtung", 5290 "Europe/Uzhgorod", 5291 "Europe/Zaporozhye", 5292 "Pacific/Johnston"); 5293 result = Set.copyOf(Sets.difference(availableLongTz, hack)); 5294 } else { // TODO restore when CLDR-17412 is fixed 5295 Map<String, String> aliasToRegular = 5296 bcp47KeyToAliasToSubtype.get("tz"); 5297 Map<String, Bcp47KeyInfo> subtypeToInfo = 5298 bcp47KeyToSubtypeToInfo.get("tz"); 5299 result = 5300 availableLongTz.stream() 5301 .filter( 5302 x -> { 5303 String shortId = aliasToRegular.get(x); 5304 Bcp47KeyInfo info = 5305 subtypeToInfo.get(shortId); 5306 System.out.println( 5307 String.format( 5308 "%s %s %s", 5309 x, shortId, info)); 5310 return !info.deprecated; 5311 }) 5312 .collect(Collectors.toUnmodifiableSet()); 5313 } 5314 return result; 5315 } 5316 }); 5317 getCLDRTimezoneCodes()5318 public Set<String> getCLDRTimezoneCodes() { 5319 return goodTimezones.get(); 5320 } 5321 } 5322