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 LinkedHashSet<>(); // retain order 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 LinkedHashSet<>(); 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(XPathValue 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 CLDRLanguageCodes = CldrUtility.protectCollection(CLDRLanguageCodes); 1352 languageNonTcLtBasic = CldrUtility.protectCollection(languageNonTcLtBasic); 1353 } 1354 1355 /** 1356 * Core function used to process each of the paths, and add the data to the appropriate data 1357 * member. 1358 */ 1359 class MyHandler extends XMLFileReader.SimpleHandler { 1360 private static final double MAX_POPULATION = 3000000000.0; 1361 1362 LanguageTagParser languageTagParser = 1363 null; // postpone assignment until needed, to avoid re-entrance of 1364 // SupplementalDataInfo.getInstance 1365 1366 /** Finish processing anything left hanging in the file. */ 1367 public void cleanup() { 1368 if (lastPluralMap.size() > 0) { 1369 addPluralInfo(lastPluralWasOrdinal); 1370 } 1371 lastPluralLocales = ""; 1372 } 1373 1374 @Override 1375 public void handlePathValue(String path, String value) { 1376 try { 1377 XPathValue parts = SimpleXPathParts.getFrozenInstance(path); 1378 String level0 = parts.getElement(0); 1379 String level1 = parts.size() < 2 ? null : parts.getElement(1); 1380 String level2 = parts.size() < 3 ? null : parts.getElement(2); 1381 String level3 = parts.size() < 4 ? null : parts.getElement(3); 1382 // String level4 = parts.size() < 5 ? null : parts.getElement(4); 1383 if (level1.equals("generation")) { 1384 // skip 1385 return; 1386 } 1387 if (level1.equals("version")) { 1388 if (cldrVersion == null) { 1389 String version = parts.getAttributeValue(1, "cldrVersion"); 1390 if (version == null) { 1391 // old format 1392 version = parts.getAttributeValue(0, "version"); 1393 } 1394 cldrVersionString = version; 1395 cldrVersion = VersionInfo.getInstance(version); 1396 unicodeVersion = parts.getAttributeValue(1, "unicodeVersion"); 1397 } 1398 return; 1399 } 1400 1401 // copy the rest from ShowLanguages later 1402 if (level0.equals("ldmlBCP47")) { 1403 if (handleBcp47(level1, parts)) { 1404 return; 1405 } 1406 } else if (level1.equals("territoryInfo")) { 1407 if (handleTerritoryInfo(parts)) { 1408 return; 1409 } 1410 } else if (level1.equals("calendarPreferenceData")) { 1411 handleCalendarPreferenceData(parts); 1412 return; 1413 } else if (level1.equals("languageData")) { 1414 handleLanguageData(parts); 1415 return; 1416 } else if (level1.equals("territoryContainment")) { 1417 handleTerritoryContainment(parts); 1418 return; 1419 } else if (level1.equals("subdivisionContainment")) { 1420 handleSubdivisionContainment(parts); 1421 return; 1422 } else if (level1.equals("currencyData")) { 1423 if (handleCurrencyData(level2, parts)) { 1424 return; 1425 } 1426 } else if ("metazoneInfo".equals(level2)) { 1427 if (handleMetazoneInfo(level3, parts)) { 1428 return; 1429 } 1430 } else if ("mapTimezones".equals(level2)) { 1431 if (handleMetazoneData(level3, parts)) { 1432 return; 1433 } 1434 } else if (level1.equals("plurals")) { 1435 if (addPluralPath(parts, value)) { 1436 return; 1437 } 1438 } else if (level1.equals("dayPeriodRuleSet")) { 1439 addDayPeriodPath(parts); 1440 return; 1441 } else if (level1.equals("telephoneCodeData")) { 1442 handleTelephoneCodeData(parts); 1443 return; 1444 } else if (level1.equals("references")) { 1445 String type = parts.getAttributeValue(-1, "type"); 1446 String uri = parts.getAttributeValue(-1, "uri"); 1447 references.put(type, new Pair<>(uri, value).freeze()); 1448 return; 1449 } else if (level1.equals("likelySubtags")) { 1450 handleLikelySubtags(parts); 1451 return; 1452 } else if (level1.equals("numberingSystems")) { 1453 handleNumberingSystems(parts); 1454 return; 1455 } else if (level1.equals("coverageLevels")) { 1456 handleCoverageLevels(parts); 1457 return; 1458 } else if (level1.equals("parentLocales")) { 1459 handleParentLocales(parts); 1460 return; 1461 } else if (level1.equals("metadata")) { 1462 if (handleMetadata(level2, value, parts)) { 1463 return; 1464 } 1465 } else if (level1.equals("codeMappings")) { 1466 if (handleCodeMappings(level2, parts)) { 1467 return; 1468 } 1469 } else if (level1.equals("languageMatching")) { 1470 if (handleLanguageMatcher(parts)) { 1471 return; 1472 } 1473 } else if (level1.equals("measurementData")) { 1474 if (handleMeasurementData(level2, parts)) { 1475 return; 1476 } 1477 } else if (level1.equals("unitIdComponents")) { 1478 if (handleUnitUnitIdComponents(parts)) { 1479 return; 1480 } 1481 } else if (level1.equals("unitPrefixes")) { 1482 if (handleUnitPrefix(parts)) { 1483 return; 1484 } 1485 } else if (level1.equals("unitConstants")) { 1486 if (handleUnitConstants(parts)) { 1487 return; 1488 } 1489 } else if (level1.equals("unitQuantities")) { 1490 if (handleUnitQuantities(parts)) { 1491 return; 1492 } 1493 } else if (level1.equals("convertUnits")) { 1494 if (handleUnitConversion(parts)) { 1495 return; 1496 } 1497 } else if (level1.equals("unitPreferenceData")) { 1498 if (handleUnitPreferences(parts, value)) { 1499 return; 1500 } 1501 } else if (level1.equals("timeData")) { 1502 if (handleTimeData(parts)) { 1503 return; 1504 } 1505 } else if (level1.equals("languageGroups")) { 1506 if (handleLanguageGroups(value, parts)) { 1507 return; 1508 } 1509 } else if (level1.contentEquals("grammaticalData")) { 1510 if (handleGrammaticalData(value, parts)) { 1511 return; 1512 } 1513 } else if (level1.contentEquals("personNamesDefaults")) { 1514 if (handlePersonNamesDefaults(value, parts)) { 1515 return; 1516 } 1517 } 1518 1519 // capture elements we didn't look at, since we should cover everything. 1520 // this helps for updates 1521 1522 final String skipKey = level1 + (level2 == null ? "" : "/" + level2); 1523 if (!skippedElements.contains(skipKey)) { 1524 skippedElements.add(skipKey); 1525 } 1526 // System.out.println("Skipped Element: " + path); 1527 } catch (Exception e) { 1528 throw (IllegalArgumentException) 1529 new IllegalArgumentException( 1530 "Exception while processing path: " 1531 + path 1532 + ",\tvalue: " 1533 + value) 1534 .initCause(e); 1535 } 1536 } 1537 1538 private boolean handleUnitPrefix(XPathValue parts) { 1539 // <unitPrefix type='quecto' symbol='q' power10='-30'/> 1540 String power10 = parts.getAttributeValue(-1, "power10"); 1541 String power2 = parts.getAttributeValue(-1, "power2"); 1542 if ((power10 != null) == (power2 != null)) { 1543 throw new IllegalArgumentException("Must have exactly one @power2 or @power10"); 1544 } 1545 unitPrefixInfo.put( 1546 parts.getAttributeValue(-1, "type"), 1547 new UnitPrefixInfo( 1548 parts.getAttributeValue(-1, "symbol"), 1549 power10 != null ? 10 : 2, 1550 Integer.parseInt(power10 != null ? power10 : power2))); 1551 return true; 1552 } 1553 1554 private boolean handlePersonNamesDefaults(String value, XPathValue parts) { 1555 personNameOrder.putAll( 1556 Order.from(parts.getAttributeValue(-1, "order")), split_space.split(value)); 1557 return true; 1558 } 1559 1560 private boolean handleUnitUnitIdComponents(XPathValue parts) { 1561 // <unitIdComponent type="prefix" values="arc british dessert fluid light 1562 // nautical"/> 1563 UnitIdComponentType type = 1564 UnitIdComponentType.valueOf(parts.getAttributeValue(-1, "type")); 1565 for (String value : split_space.split(parts.getAttributeValue(-1, "values"))) { 1566 UnitIdComponentType old = unitIdComponentType.put(value, type); 1567 if (old != null) { 1568 throw new IllegalArgumentException("Duplicate component: " + value); 1569 } 1570 } 1571 return true; 1572 } 1573 1574 private boolean handleGrammaticalData(String value, XPathValue parts) { 1575 /* 1576 <!ATTLIST grammaticalFeatures targets NMTOKENS #REQUIRED > 1577 <!ATTLIST grammaticalFeatures locales NMTOKENS #REQUIRED > 1578 OR 1579 <!ATTLIST grammaticalDerivations locales NMTOKENS #REQUIRED > 1580 */ 1581 1582 for (String locale : split_space.split(parts.getAttributeValue(2, "locales"))) { 1583 switch (parts.getElement(2)) { 1584 case "grammaticalFeatures": 1585 GrammarInfo targetToFeatureToValues = 1586 grammarLocaleToTargetToFeatureToValues.get(locale); 1587 if (targetToFeatureToValues == null) { 1588 grammarLocaleToTargetToFeatureToValues.put( 1589 locale, targetToFeatureToValues = new GrammarInfo()); 1590 } 1591 final String targets = parts.getAttributeValue(2, "targets"); 1592 if (parts.size() < 4) { 1593 targetToFeatureToValues.add( 1594 targets, null, null, null); // special case "known no features" 1595 } else { 1596 targetToFeatureToValues.add( 1597 targets, 1598 parts.getElement(3), 1599 parts.getAttributeValue(3, "scope"), 1600 parts.getAttributeValue(3, "values")); 1601 } 1602 break; 1603 case "grammaticalDerivations": 1604 String feature = parts.getAttributeValue(3, "feature"); 1605 String structure = parts.getAttributeValue(3, "structure"); 1606 GrammarDerivation grammarCompoundDerivation = 1607 localeToGrammarDerivation.get(locale); 1608 if (grammarCompoundDerivation == null) { 1609 localeToGrammarDerivation.put( 1610 locale, grammarCompoundDerivation = new GrammarDerivation()); 1611 } 1612 1613 switch (parts.getElement(3)) { 1614 case "deriveCompound": 1615 grammarCompoundDerivation.add( 1616 feature, structure, parts.getAttributeValue(3, "value")); 1617 break; 1618 case "deriveComponent": 1619 grammarCompoundDerivation.add( 1620 feature, 1621 structure, 1622 parts.getAttributeValue(3, "value0"), 1623 parts.getAttributeValue(3, "value1")); 1624 break; 1625 default: 1626 throw new IllegalArgumentException( 1627 "Structure not handled: " + parts); 1628 } 1629 break; 1630 default: 1631 throw new IllegalArgumentException("Structure not handled: " + parts); 1632 } 1633 } 1634 return true; 1635 } 1636 1637 /* 1638 * Handles 1639 * <unitPreferences category="area" usage="_default"> 1640 *<unitPreference regions="001" draft="unconfirmed">square-centimeter</unitPreference> 1641 */ 1642 1643 private boolean handleUnitPreferences(XPathValue parts, String value) { 1644 String geq = parts.getAttributeValue(-1, "geq"); 1645 String small = parts.getAttributeValue(-2, "scope"); 1646 if (small != null) { 1647 geq = "0.1234"; 1648 } 1649 unitPreferences.add( 1650 parts.getAttributeValue(-2, "category"), 1651 parts.getAttributeValue(-2, "usage"), 1652 parts.getAttributeValue(-1, "regions"), 1653 geq, 1654 parts.getAttributeValue(-1, "skeleton"), 1655 value); 1656 return true; 1657 } 1658 1659 private boolean handleLanguageGroups(String value, XPathValue parts) { 1660 String parent = parts.getAttributeValue(-1, "parent"); 1661 List<String> children = WHITESPACE_SPLTTER.splitToList(value); 1662 languageGroups.putAll(parent, children); 1663 return true; 1664 } 1665 1666 private boolean handleMeasurementData(String level2, XPathValue parts) { 1667 /** 1668 * <measurementSystem type="US" territories="LR MM US"/> <paperSize type="A4" 1669 * territories="001"/> 1670 */ 1671 MeasurementType measurementType = MeasurementType.valueOf(level2); 1672 String type = parts.getAttributeValue(-1, "type"); 1673 String territories = parts.getAttributeValue(-1, "territories"); 1674 Map<String, String> data = measurementData.get(measurementType); 1675 if (data == null) { 1676 measurementData.put(measurementType, data = new HashMap<>()); 1677 } 1678 for (String territory : territories.trim().split("\\s+")) { 1679 data.put(territory, type); 1680 } 1681 return true; 1682 } 1683 1684 private boolean handleUnitConstants(XPathValue parts) { 1685 // <unitConstant constant="ft2m" value="0.3048"/> 1686 1687 final String constant = parts.getAttributeValue(-1, "constant"); 1688 final String value = parts.getAttributeValue(-1, "value"); 1689 final String status = parts.getAttributeValue(-1, "status"); 1690 rationalParser.addConstant(constant, value, status); 1691 return true; 1692 } 1693 1694 private boolean handleUnitQuantities(XPathValue parts) { 1695 // <unitQuantity quantity='wave-number' baseUnit='reciprocal-meter'/> 1696 1697 final String baseUnit = parts.getAttributeValue(-1, "baseUnit"); 1698 final String quantity = parts.getAttributeValue(-1, "quantity"); 1699 final String status = parts.getAttributeValue(-1, "status"); 1700 if (unitConverter == null) { 1701 unitConverter = 1702 new UnitConverter(rationalParser, validity, x -> getUnitIdComponentType(x)); 1703 } 1704 unitConverter.addQuantityInfo(baseUnit, quantity, status); 1705 return true; 1706 } 1707 1708 private boolean handleUnitConversion(XPathValue parts) { 1709 // <convertUnit source='acre' target='square-meter' factor='ft2m^2 * 43560'/> 1710 1711 final String source = parts.getAttributeValue(-1, "source"); 1712 final String target = parts.getAttributeValue(-1, "baseUnit"); 1713 // if (source.contentEquals(target)) { 1714 // throw new IllegalArgumentException("Cannot convert from something to 1715 // itself " + parts); 1716 // } 1717 String factor = parts.getAttributeValue(-1, "factor"); 1718 String offset = parts.getAttributeValue(-1, "offset"); 1719 String special = parts.getAttributeValue(-1, "special"); 1720 String systems = parts.getAttributeValue(-1, "systems"); 1721 unitConverter.addRaw(source, target, factor, offset, special, systems); 1722 return true; 1723 } 1724 1725 private boolean handleTimeData(XPathValue parts) { 1726 /** <hours preferred="H" allowed="H" regions="IL RU"/> */ 1727 String preferred = parts.getAttributeValue(-1, "preferred"); 1728 PreferredAndAllowedHour preferredAndAllowedHour = 1729 new PreferredAndAllowedHour(preferred, parts.getAttributeValue(-1, "allowed")); 1730 for (String region : parts.getAttributeValue(-1, "regions").trim().split("\\s+")) { 1731 PreferredAndAllowedHour oldValue = timeData.put(region, preferredAndAllowedHour); 1732 if (oldValue != null) { 1733 throw new IllegalArgumentException( 1734 "timeData/hours must not have duplicate regions: " + region); 1735 } 1736 } 1737 return true; 1738 } 1739 1740 private boolean handleBcp47(String level1, XPathValue parts) { 1741 if (level1.equals("version") 1742 || level1.equals("generation") 1743 || level1.equals("cldrVersion")) { 1744 return true; // skip 1745 } 1746 if (!level1.equals("keyword")) { 1747 throw new IllegalArgumentException("Unexpected level1 element: " + level1); 1748 } 1749 1750 String finalElement = parts.getElement(-1); 1751 String key = parts.getAttributeValue(2, "name"); 1752 String extension = parts.getAttributeValue(2, "extension"); 1753 if (extension == null) { 1754 extension = "u"; 1755 } 1756 bcp47Extension2Keys.put(extension, key); 1757 1758 String keyAlias = parts.getAttributeValue(2, "alias"); 1759 String keyDescription = parts.getAttributeValue(2, "description"); 1760 String deprecated = parts.getAttributeValue(2, "deprecated"); 1761 // TODO add preferred, valueType, since 1762 1763 final R2<String, String> key_empty = (R2<String, String>) Row.of(key, "").freeze(); 1764 1765 if (keyAlias != null) { 1766 bcp47Aliases.putAll(key_empty, Arrays.asList(keyAlias.trim().split("\\s+"))); 1767 } 1768 1769 if (keyDescription != null) { 1770 bcp47Descriptions.put(key_empty, keyDescription); 1771 } 1772 if (deprecated != null && deprecated.equals("true")) { 1773 bcp47Deprecated.put(key_empty, deprecated); 1774 } 1775 1776 switch (finalElement) { 1777 case "key": 1778 break; // all actions taken above 1779 1780 case "type": 1781 String subtype = parts.getAttributeValue(3, "name"); 1782 String subtypeAlias = parts.getAttributeValue(3, "alias"); 1783 String desc = parts.getAttributeValue(3, "description"); 1784 String subtypeDescription = desc == null ? null : desc.replaceAll("\\s+", " "); 1785 String subtypeSince = parts.getAttributeValue(3, "since"); 1786 String subtypePreferred = parts.getAttributeValue(3, "preferred"); 1787 String subtypeDeprecated = parts.getAttributeValue(3, "deprecated"); 1788 String valueType = parts.getAttributeValue(3, "deprecated"); 1789 1790 Set<String> set = bcp47Key2Subtypes.get(key); 1791 if (set != null && set.contains(key)) { 1792 throw new IllegalArgumentException( 1793 "Collision with bcp47 key-value: " + key + "," + subtype); 1794 } 1795 bcp47Key2Subtypes.put(key, subtype); 1796 1797 final R2<String, String> key_subtype = 1798 (R2<String, String>) Row.of(key, subtype).freeze(); 1799 1800 if (subtypeAlias != null) { 1801 bcp47Aliases.putAll( 1802 key_subtype, Arrays.asList(subtypeAlias.trim().split("\\s+"))); 1803 } 1804 if (subtypeDescription != null) { 1805 bcp47Descriptions.put( 1806 key_subtype, subtypeDescription.replaceAll("\\s+", " ")); 1807 } 1808 if (subtypeSince != null) { 1809 bcp47Since.put(key_subtype, subtypeSince); 1810 } 1811 if (subtypePreferred != null) { 1812 bcp47Preferred.put(key_subtype, subtypePreferred); 1813 } 1814 if (subtypeDeprecated != null) { 1815 bcp47Deprecated.put(key_subtype, subtypeDeprecated); 1816 } 1817 if (valueType != null) { 1818 bcp47ValueType.put(subtype, valueType); 1819 } 1820 break; 1821 default: 1822 throw new IllegalArgumentException("Unexpected element: " + finalElement); 1823 } 1824 1825 return true; 1826 } 1827 1828 private boolean handleLanguageMatcher(XPathValue parts) { 1829 String type = parts.getAttributeValue(2, "type"); 1830 String alt = parts.getAttributeValue(2, "alt"); 1831 if (alt != null) { 1832 type += "_" + alt; 1833 } 1834 switch (parts.getElement(3)) { 1835 case "paradigmLocales": 1836 List<String> locales = 1837 WHITESPACE_SPLTTER.splitToList(parts.getAttributeValue(3, "locales")); 1838 // TODO 1839 // LanguageMatchData languageMatchData = 1840 // languageMatchData.get(type); 1841 // if (languageMatchData == null) { 1842 // languageMatch.put(type, languageMatchData = new 1843 // LanguageMatchData()); 1844 // } 1845 break; 1846 case "matchVariable": 1847 // String id = parts.getAttributeValue(3, "id"); 1848 // String value = parts.getAttributeValue(3, "value"); 1849 // TODO 1850 break; 1851 case "languageMatch": 1852 List<R4<String, String, Integer, Boolean>> matches = languageMatch.get(type); 1853 if (matches == null) { 1854 languageMatch.put(type, matches = new ArrayList<>()); 1855 } 1856 String percent = parts.getAttributeValue(3, "percent"); 1857 String distance = parts.getAttributeValue(3, "distance"); 1858 matches.add( 1859 Row.of( 1860 parts.getAttributeValue(3, "desired"), 1861 parts.getAttributeValue(3, "supported"), 1862 percent != null 1863 ? Integer.parseInt(percent) 1864 : 100 - Integer.parseInt(distance), 1865 "true".equals(parts.getAttributeValue(3, "oneway")))); 1866 break; 1867 default: 1868 throw new IllegalArgumentException("Unknown element"); 1869 } 1870 return true; 1871 } 1872 1873 private boolean handleCodeMappings(String level2, XPathValue parts) { 1874 if (level2.equals("territoryCodes")) { 1875 // <territoryCodes type="VU" numeric="548" alpha3="VUT"/> 1876 String type = parts.getAttributeValue(-1, "type"); 1877 final String numeric = parts.getAttributeValue(-1, "numeric"); 1878 if (numeric != null) { 1879 numericTerritoryMapping.put(type, Integer.parseInt(numeric)); 1880 } 1881 final String alpha3 = parts.getAttributeValue(-1, "alpha3"); 1882 if (alpha3 != null) { 1883 alpha3TerritoryMapping.put(type, alpha3); 1884 } 1885 return true; 1886 } else if (level2.equals("currencyCodes")) { 1887 // <currencyCodes type="BBD" numeric="52"/> 1888 String type = parts.getAttributeValue(-1, "type"); 1889 final String numeric = parts.getAttributeValue(-1, "numeric"); 1890 if (numeric != null) { 1891 numericCurrencyCodeMapping.put(type, Integer.parseInt(numeric)); 1892 } 1893 return true; 1894 } 1895 return false; 1896 } 1897 1898 private void handleNumberingSystems(XPathValue parts) { 1899 NumberingSystemInfo ns = new NumberingSystemInfo(parts); 1900 numberingSystems.put(ns.name, ns); 1901 if (ns.type == NumberingSystemType.numeric) { 1902 numericSystems.add(ns.name); 1903 } 1904 } 1905 1906 private void handleCoverageLevels(XPathValue parts) { 1907 if (parts.containsElement("approvalRequirement")) { 1908 approvalRequirements.add(parts.toString()); 1909 } else if (parts.containsElement("coverageLevel")) { 1910 String match = 1911 parts.containsAttribute("match") 1912 ? coverageVariables.replace(parts.getAttributeValue(-1, "match")) 1913 : null; 1914 String valueStr = parts.getAttributeValue(-1, "value"); 1915 // Ticket 7125: map the number to English. So switch from English to number for 1916 // construction 1917 valueStr = Integer.toString(Level.get(valueStr).getLevel()); 1918 1919 String inLanguage = 1920 parts.containsAttribute("inLanguage") 1921 ? coverageVariables.replace( 1922 parts.getAttributeValue(-1, "inLanguage")) 1923 : null; 1924 String inScript = 1925 parts.containsAttribute("inScript") 1926 ? coverageVariables.replace(parts.getAttributeValue(-1, "inScript")) 1927 : null; 1928 String inTerritory = 1929 parts.containsAttribute("inTerritory") 1930 ? coverageVariables.replace( 1931 parts.getAttributeValue(-1, "inTerritory")) 1932 : null; 1933 Integer value = 1934 (valueStr != null) ? Integer.valueOf(valueStr) : Integer.valueOf("101"); 1935 if (cldrVersion.getMajor() < 2) { 1936 value = 40; 1937 } 1938 CoverageLevelInfo ci = 1939 new CoverageLevelInfo(match, value, inLanguage, inScript, inTerritory); 1940 coverageLevels.add(ci); 1941 } else if (parts.containsElement("coverageVariable")) { 1942 String key = parts.getAttributeValue(-1, "key"); 1943 String value = parts.getAttributeValue(-1, "value"); 1944 coverageVariables.add(key, value); 1945 } 1946 } 1947 1948 public static final String NONLIKELYSCRIPT = "nonlikelyScript"; 1949 1950 private void handleParentLocales(XPathValue parts) { 1951 // CLDR-16253 added component-specific parents, which we ignore for now. 1952 // TODO(CLDR-16361): Figure out how to handle these in CLDR itself. 1953 String componentsString = parts.getAttributeValue(1, "component"); 1954 Set<ParentLocaleComponent> components; 1955 if (componentsString == null) { 1956 components = ImmutableSet.of(ParentLocaleComponent.main); 1957 } else { 1958 components = 1959 split_space 1960 .splitToStream(componentsString) 1961 .map(x -> ParentLocaleComponent.fromString(x)) 1962 .collect(Collectors.toSet()); 1963 } 1964 if (!parts.getElement(-1).equals("parentLocale")) { 1965 // If there is no parentLocale element , that means we have nothing to add 1966 // Since we have pre-populated the parentLocales with component -> empty map, 1967 // there is nothing more to do, and we can exit. 1968 // We have parsed the components, however, so they are valid 1969 return; 1970 } 1971 String parent = parts.getAttributeValue(-1, "parent"); 1972 String locales = parts.getAttributeValue(-1, "locales"); 1973 String localeRules = parts.getAttributeValue(-1, "localeRules"); 1974 Set<String> localeRuleSet = 1975 localeRules == null 1976 ? Set.of() 1977 : Set.copyOf(split_space.splitToList(localeRules)); 1978 1979 for (ParentLocaleComponent component : components) { 1980 Map<String, String> componentParentLocales = parentLocales.get(component); 1981 if (localeRuleSet.contains(NONLIKELYSCRIPT)) { 1982 // This will need to be modified if we add any other rules, 1983 // particularly if any rules are based on the particular parent 1984 parentLocalesSkipNonLikely.add(component); 1985 continue; 1986 } 1987 for (String childLocale : split_space.split(locales)) { 1988 String old = componentParentLocales.put(childLocale, parent); 1989 if (old != null) { 1990 throw new IllegalArgumentException( 1991 "Locale " 1992 + childLocale 1993 + " cannot have two parents: " 1994 + old 1995 + " and " 1996 + parent); 1997 } 1998 } 1999 } 2000 } 2001 2002 private void handleCalendarPreferenceData(XPathValue parts) { 2003 String territoryString = parts.getAttributeValue(-1, "territories"); 2004 String orderingString = parts.getAttributeValue(-1, "ordering"); 2005 String[] calendars = orderingString.split(" "); 2006 String[] territories = territoryString.split(" "); 2007 List<String> calendarList = Arrays.asList(calendars); 2008 for (int i = 0; i < territories.length; i++) { 2009 calendarPreferences.put(territories[i], calendarList); 2010 } 2011 } 2012 2013 private void handleLikelySubtags(XPathValue parts) { 2014 String from = parts.getAttributeValue(-1, "from"); 2015 String to = parts.getAttributeValue(-1, "to"); 2016 String origin = parts.getAttributeValue(-1, "origin"); 2017 String toOld = likelySubtags.get(from); 2018 if (toOld != null) { 2019 if (to.equals(toOld)) { 2020 System.err.println("Likely subtags repeats from=" + from + " to= " + to); 2021 } else { 2022 throw new IllegalArgumentException( 2023 "Likely subtags duplicate from=" 2024 + from 2025 + ", overrides values: " 2026 + toOld 2027 + " with " 2028 + to); 2029 } 2030 } else { 2031 likelySubtags.put(from, to); 2032 if (origin != null) { 2033 likelyOrigins.put(from, origin); 2034 } 2035 } 2036 } 2037 2038 /** 2039 * Only called if level2 = mapTimezones. Level 1 might be metaZones or might be windowsZones 2040 */ 2041 private boolean handleMetazoneData(String level3, XPathValue parts) { 2042 if (level3.equals("mapZone")) { 2043 String maintype = parts.getAttributeValue(2, "type"); 2044 if (maintype == null) { 2045 maintype = "windows"; 2046 } 2047 String mzone = parts.getAttributeValue(3, "other"); 2048 String region = parts.getAttributeValue(3, "territory"); 2049 String zone = parts.getAttributeValue(3, "type"); 2050 2051 Map<String, Map<String, String>> zoneToRegionToZone = 2052 typeToZoneToRegionToZone.get(maintype); 2053 if (zoneToRegionToZone == null) { 2054 typeToZoneToRegionToZone.put(maintype, zoneToRegionToZone = new TreeMap<>()); 2055 } 2056 Map<String, String> regionToZone = zoneToRegionToZone.get(mzone); 2057 if (regionToZone == null) { 2058 zoneToRegionToZone.put(mzone, regionToZone = new TreeMap<>()); 2059 } 2060 if (region != null) { 2061 regionToZone.put(region, zone); 2062 } 2063 if (maintype.equals("metazones")) { 2064 if (mzone != null && region.equals("001")) { 2065 metazoneContinentMap.put(mzone, zone.substring(0, zone.indexOf("/"))); 2066 } 2067 allMetazones.add(mzone); 2068 } 2069 return true; 2070 } 2071 return false; 2072 } 2073 2074 private Collection<String> getSpaceDelimited( 2075 int index, String attribute, Collection<String> defaultValue, XPathValue parts) { 2076 String temp = parts.getAttributeValue(index, attribute); 2077 Collection<String> elements = 2078 temp == null ? defaultValue : Arrays.asList(temp.split("\\s+")); 2079 return elements; 2080 } 2081 2082 /* 2083 * 2084 * <supplementalData> 2085 * <metaZones> 2086 * <metazoneInfo> 2087 * <timezone type="Asia/Yerevan"> 2088 * <usesMetazone to="1991-09-22 20:00" mzone="Yerevan"/> 2089 * <usesMetazone from="1991-09-22 20:00" mzone="Armenia"/> 2090 */ 2091 2092 private boolean handleMetazoneInfo(String level3, XPathValue parts) { 2093 if (level3.equals("timezone")) { 2094 String zone = parts.getAttributeValue(3, "type"); 2095 String mzone = parts.getAttributeValue(4, "mzone"); 2096 String from = parts.getAttributeValue(4, "from"); 2097 String to = parts.getAttributeValue(4, "to"); 2098 MetaZoneRange mzoneRange = new MetaZoneRange(mzone, from, to); 2099 zoneToMetaZoneRanges.put(zone, mzoneRange); 2100 return true; 2101 } 2102 return false; 2103 } 2104 2105 private boolean handleMetadata(String level2, String value, XPathValue parts) { 2106 if (parts.containsElement("defaultContent")) { 2107 String defContent = parts.getAttributeValue(-1, "locales").trim(); 2108 String[] defLocales = defContent.split("\\s+"); 2109 defaultContentLocales = 2110 Collections.unmodifiableSet(new TreeSet<>(Arrays.asList(defLocales))); 2111 return true; 2112 } 2113 if (level2.equals("alias")) { 2114 // <alias> 2115 // <languageAlias type="art-lojban" replacement="jbo"/> <!-- Lojban --> 2116 String level3 = parts.getElement(3); 2117 if (!level3.endsWith("Alias")) { 2118 throw new IllegalArgumentException(); 2119 } 2120 level3 = level3.substring(0, level3.length() - "Alias".length()); 2121 Map<String, R2<List<String>, String>> tagToReplacement = 2122 typeToTagToReplacement.get(level3); 2123 if (tagToReplacement == null) { 2124 typeToTagToReplacement.put(level3, tagToReplacement = new TreeMap<>()); 2125 } 2126 final String replacement = parts.getAttributeValue(3, "replacement"); 2127 List<String> replacementList = null; 2128 if (replacement != null) { 2129 Set<String> builder = new LinkedHashSet<>(); 2130 for (String item : replacement.split("\\s+")) { 2131 String cleaned = 2132 SubdivisionNames.isOldSubdivisionCode(item) 2133 ? replacement.replace("-", "").toLowerCase(Locale.ROOT) 2134 : item; 2135 builder.add(cleaned); 2136 } 2137 replacementList = ImmutableList.copyOf(builder); 2138 } 2139 final String reason = parts.getAttributeValue(3, "reason"); 2140 String cleanTag = parts.getAttributeValue(3, "type"); 2141 tagToReplacement.put( 2142 cleanTag, 2143 (R2<List<String>, String>) Row.of(replacementList, reason).freeze()); 2144 return true; 2145 } else if (level2.equals("validity")) { 2146 // <variable id="$grandfathered" type="choice"> 2147 String level3 = parts.getElement(3); 2148 if (level3.equals("variable")) { 2149 Map<String, String> attributes = parts.getAttributes(-1); 2150 final String idString = attributes.get("id"); 2151 final String typeString = attributes.get("type"); 2152 validityInfo.put(idString, Row.of(typeString, value)); 2153 if ("choice".equals(typeString)) { 2154 if ("$language".equals(idString) 2155 || "$languageNonTcGeqBasic".equals(idString)) { 2156 String[] validCodeArray = value.trim().split("\\s+"); 2157 CLDRLanguageCodes.addAll(Arrays.asList(validCodeArray)); 2158 } 2159 if ("$languageNonTcLtBasic".equals(idString)) { // not yet basic 2160 String[] validCodeArray = value.trim().split("\\s+"); 2161 final List<String> asList = Arrays.asList(validCodeArray); 2162 languageNonTcLtBasic.addAll(asList); 2163 CLDRLanguageCodes.addAll(asList); 2164 } 2165 } 2166 return true; 2167 } else if (level3.equals("attributeValues")) { 2168 AttributeValidityInfo.add( 2169 parts.getAttributes(-1), value, attributeValidityInfo); 2170 return true; 2171 } 2172 } else if (level2.equals("serialElements")) { 2173 serialElements = Arrays.asList(value.trim().split("\\s+")); 2174 return true; 2175 } else if (level2.equals("distinguishing")) { 2176 String level3 = parts.getElement(3); 2177 if (level3.equals("distinguishingItems")) { 2178 Map<String, String> attributes = parts.getAttributes(-1); 2179 // <distinguishingItems 2180 // attributes="key request id _q registry alt iso4217 iso3166 mzone from to type 2181 // numberSystem"/> 2182 // <distinguishingItems exclude="true" 2183 // elements="default measurementSystem mapping abbreviationFallback 2184 // preferenceOrdering" 2185 // attributes="type"/> 2186 2187 if (attributes.containsKey("exclude") 2188 && "true".equals(attributes.get("exclude"))) { 2189 return false; // don't handle the excludes -yet. 2190 } else { 2191 distinguishingAttributes = 2192 Collections.unmodifiableCollection( 2193 getSpaceDelimited(-1, "attributes", STAR_SET, parts)); 2194 return true; 2195 } 2196 } 2197 } 2198 return false; 2199 } 2200 2201 private boolean handleTerritoryInfo(XPathValue parts) { 2202 2203 // <territoryInfo> 2204 // <territory type="AD" gdp="1840000000" literacyPercent="100" 2205 // population="66000"> <!--Andorra--> 2206 // <languagePopulation type="ca" populationPercent="50"/> 2207 // <!--Catalan--> 2208 2209 Map<String, String> territoryAttributes = parts.getAttributes(2); 2210 String territory = territoryAttributes.get("type"); 2211 double territoryPopulation = parseDouble(territoryAttributes.get("population")); 2212 if (failsRangeCheck("population", territoryPopulation, 0, MAX_POPULATION)) { 2213 return true; 2214 } 2215 2216 double territoryLiteracyPercent = 2217 parseDouble(territoryAttributes.get("literacyPercent")); 2218 double territoryGdp = parseDouble(territoryAttributes.get("gdp")); 2219 if (territoryToPopulationData.get(territory) == null) { 2220 territoryToPopulationData.put( 2221 territory, 2222 new PopulationData() 2223 .setPopulation(territoryPopulation) 2224 .setLiteratePopulation( 2225 territoryLiteracyPercent * territoryPopulation / 100) 2226 .setGdp(territoryGdp)); 2227 } 2228 if (parts.size() > 3) { 2229 2230 Map<String, String> languageInTerritoryAttributes = parts.getAttributes(3); 2231 String language = languageInTerritoryAttributes.get("type"); 2232 double languageLiteracyPercent = 2233 parseDouble(languageInTerritoryAttributes.get("literacyPercent")); 2234 if (Double.isNaN(languageLiteracyPercent)) { 2235 languageLiteracyPercent = territoryLiteracyPercent; 2236 } 2237 double writingPercent = 2238 parseDouble(languageInTerritoryAttributes.get("writingPercent")); 2239 if (Double.isNaN(writingPercent)) { 2240 writingPercent = languageLiteracyPercent; 2241 } 2242 // else { 2243 // System.out.println("writingPercent\t" + languageLiteracyPercent 2244 // + "\tterritory\t" + territory 2245 // + "\tlanguage\t" + language); 2246 // } 2247 double languagePopulationPercent = 2248 parseDouble(languageInTerritoryAttributes.get("populationPercent")); 2249 double languagePopulation = languagePopulationPercent * territoryPopulation / 100; 2250 // double languageGdp = languagePopulationPercent * territoryGdp; 2251 2252 // store 2253 Map<String, PopulationData> territoryLanguageToPopulation = 2254 territoryToLanguageToPopulationData.get(territory); 2255 if (territoryLanguageToPopulation == null) { 2256 territoryToLanguageToPopulationData.put( 2257 territory, territoryLanguageToPopulation = new TreeMap<>()); 2258 } 2259 OfficialStatus officialStatus = OfficialStatus.unknown; 2260 String officialStatusString = languageInTerritoryAttributes.get("officialStatus"); 2261 if (officialStatusString != null) 2262 officialStatus = OfficialStatus.valueOf(officialStatusString); 2263 2264 PopulationData newData = 2265 new PopulationData() 2266 .setPopulation(languagePopulation) 2267 .setLiteratePopulation( 2268 languageLiteracyPercent * languagePopulation / 100) 2269 .setWritingPopulation(writingPercent * languagePopulation / 100) 2270 .setOfficialStatus(officialStatus) 2271 // .setGdp(languageGdp) 2272 ; 2273 newData.freeze(); 2274 if (territoryLanguageToPopulation.get(language) != null) { 2275 System.out.println( 2276 "Internal Problem in supplementalData: multiple data items for " 2277 + language 2278 + ", " 2279 + territory 2280 + "\tSkipping " 2281 + newData); 2282 return true; 2283 } 2284 2285 territoryLanguageToPopulation.put(language, newData); 2286 // add the language, using the Pair fields to get the ordering right 2287 languageToTerritories2.put( 2288 language, 2289 Pair.of( 2290 newData.getOfficialStatus().isMajor() ? false : true, 2291 Pair.of(-newData.getLiteratePopulation(), territory))); 2292 2293 // now collect data for languages globally 2294 PopulationData data = languageToPopulation.get(language); 2295 if (data == null) { 2296 languageToPopulation.put(language, data = new PopulationData().set(newData)); 2297 } else { 2298 data.add(newData); 2299 } 2300 // if (language.equals("en")) { 2301 // System.out.println(territory + "\tnewData:\t" + newData + "\tdata:\t" + data); 2302 // } 2303 2304 if (languageTagParser == null) { 2305 languageTagParser = new LanguageTagParser(); 2306 } 2307 String baseLanguage = languageTagParser.set(language).getLanguage(); 2308 data = baseLanguageToPopulation.get(baseLanguage); 2309 if (data == null) { 2310 baseLanguageToPopulation.put( 2311 baseLanguage, data = new PopulationData().set(newData)); 2312 } else { 2313 data.add(newData); 2314 } 2315 if (!baseLanguage.equals(language)) { 2316 languageToScriptVariants.put(baseLanguage, language); 2317 } 2318 } 2319 return true; 2320 } 2321 2322 private boolean handleCurrencyData(String level2, XPathValue parts) { 2323 if (level2.equals("fractions")) { 2324 // <info iso4217="ADP" digits="0" rounding="0" cashRounding="5"/> 2325 currencyToCurrencyNumberInfo.put( 2326 parts.getAttributeValue(3, "iso4217"), 2327 new CurrencyNumberInfo( 2328 parseIntegerOrNull(parts.getAttributeValue(3, "digits")), 2329 parseIntegerOrNull(parts.getAttributeValue(3, "rounding")), 2330 parseIntegerOrNull(parts.getAttributeValue(3, "cashDigits")), 2331 parseIntegerOrNull(parts.getAttributeValue(3, "cashRounding")))); 2332 return true; 2333 } 2334 /* 2335 * <region iso3166="AD"> 2336 * <currency iso4217="EUR" from="1999-01-01"/> 2337 * <currency iso4217="ESP" from="1873" to="2002-02-28"/> 2338 */ 2339 if (level2.equals("region")) { 2340 territoryToCurrencyDateInfo.put( 2341 parts.getAttributeValue(2, "iso3166"), 2342 new CurrencyDateInfo( 2343 parts.getAttributeValue(3, "iso4217"), 2344 parts.getAttributeValue(3, "from"), 2345 parts.getAttributeValue(3, "to"), 2346 parts.getAttributeValue(3, "tender"))); 2347 return true; 2348 } 2349 2350 return false; 2351 } 2352 2353 private void handleTelephoneCodeData(XPathValue parts) { 2354 // element 2: codesByTerritory territory [draft] [references] 2355 String terr = parts.getAttributeValue(2, "territory"); 2356 // element 3: telephoneCountryCode code [from] [to] [draft] [references] [alt] 2357 TelephoneCodeInfo tcInfo = 2358 new TelephoneCodeInfo( 2359 parts.getAttributeValue(3, "code"), 2360 parts.getAttributeValue(3, "from"), 2361 parts.getAttributeValue(3, "to"), 2362 parts.getAttributeValue(3, "alt")); 2363 2364 Set<TelephoneCodeInfo> tcSet = territoryToTelephoneCodeInfo.get(terr); 2365 if (tcSet == null) { 2366 tcSet = new LinkedHashSet<>(); 2367 territoryToTelephoneCodeInfo.put(terr, tcSet); 2368 } 2369 tcSet.add(tcInfo); 2370 } 2371 2372 private void handleTerritoryContainment(XPathValue parts) { 2373 // <group type="001" contains="002 009 019 142 150"/> 2374 final String container = parts.getAttributeValue(-1, "type"); 2375 final List<String> contained = 2376 Arrays.asList(parts.getAttributeValue(-1, "contains").split("\\s+")); 2377 // everything! 2378 containment.putAll(container, contained); 2379 2380 String status = parts.getAttributeValue(-1, "status"); 2381 String grouping = parts.getAttributeValue(-1, "grouping"); 2382 if (status == null && grouping == null) { 2383 containmentCore.putAll(container, contained); 2384 } 2385 if (status != null && status.equals("deprecated")) { 2386 containmentDeprecated.putAll(container, contained); 2387 } 2388 if (grouping != null) { 2389 containmentGrouping.putAll(container, contained); 2390 } 2391 } 2392 2393 private void handleSubdivisionContainment(XPathValue parts) { 2394 // <subgroup type="AL" subtype="04" contains="FR MK LU"/> 2395 final String country = parts.getAttributeValue(-1, "type"); 2396 final String subtype = parts.getAttributeValue(-1, "subtype"); 2397 final String container = 2398 subtype == null ? country : (country + subtype).toLowerCase(Locale.ROOT); 2399 for (String contained : parts.getAttributeValue(-1, "contains").split("\\s+")) { 2400 String newContained = 2401 contained.charAt(0) >= 'a' 2402 ? contained 2403 : (country + contained).toLowerCase(Locale.ROOT); 2404 containerToSubdivision.put(container, newContained); 2405 } 2406 } 2407 2408 private void handleLanguageData(XPathValue parts) { 2409 // <languageData> 2410 // <language type="aa" scripts="Latn" territories="DJ ER ET"/> <!-- 2411 // Reflecting submitted data, cldrbug #1013 --> 2412 // <language type="ab" scripts="Cyrl" territories="GE" 2413 // alt="secondary"/> 2414 String language = parts.getAttributeValue(2, "type"); 2415 BasicLanguageData languageData = new BasicLanguageData(); 2416 languageData.setType( 2417 parts.getAttributeValue(2, "alt") == null 2418 ? BasicLanguageData.Type.primary 2419 : BasicLanguageData.Type.secondary); 2420 languageData 2421 .setScripts(parts.getAttributeValue(2, "scripts")) 2422 .setTerritories(parts.getAttributeValue(2, "territories")); 2423 Map<Type, BasicLanguageData> map = languageToBasicLanguageData.get(language); 2424 if (map == null) { 2425 languageToBasicLanguageData.put( 2426 language, map = new EnumMap<>(BasicLanguageData.Type.class)); 2427 } 2428 if (map.containsKey(languageData.type)) { 2429 throw new IllegalArgumentException("Duplicate value:\t" + parts); 2430 } 2431 map.put(languageData.type, languageData); 2432 } 2433 2434 private boolean failsRangeCheck(String path, double input, double min, double max) { 2435 if (input >= min && input <= max) { 2436 return false; 2437 } 2438 System.out.println( 2439 "Internal Problem in supplementalData: range check fails for " 2440 + input 2441 + ", min: " 2442 + min 2443 + ", max:" 2444 + max 2445 + "\t" 2446 + path); 2447 2448 return false; 2449 } 2450 2451 private double parseDouble(String literacyString) { 2452 return literacyString == null ? Double.NaN : Double.parseDouble(literacyString); 2453 } 2454 } 2455 2456 public class CoverageVariableInfo { 2457 public Set<String> targetScripts; 2458 public Set<String> targetTerritories; 2459 public Set<String> calendars; 2460 public Set<String> targetCurrencies; 2461 public Set<String> targetTimeZones; 2462 public Set<String> targetPlurals; 2463 } 2464 2465 public static String toRegexString(Set<String> s) { 2466 Iterator<String> it = s.iterator(); 2467 StringBuilder sb = new StringBuilder("("); 2468 int count = 0; 2469 while (it.hasNext()) { 2470 if (count > 0) { 2471 sb.append("|"); 2472 } 2473 sb.append(it.next()); 2474 count++; 2475 } 2476 sb.append(")"); 2477 return sb.toString(); 2478 } 2479 2480 public int parseIntegerOrNull(String attributeValue) { 2481 return attributeValue == null ? -1 : Integer.parseInt(attributeValue); 2482 } 2483 2484 Set<String> skippedElements = new TreeSet<>(); 2485 2486 private Map<String, Pair<String, String>> references = new TreeMap<>(); 2487 private Map<String, String> likelySubtags = new TreeMap<>(); 2488 private Map<String, String> likelyOrigins = new TreeMap<>(); 2489 // make public temporarily until we resolve. 2490 private Set<CoverageLevelInfo> coverageLevels = new LinkedHashSet<>(); 2491 private Map<ParentLocaleComponent, Map<String, String>> parentLocales = new HashMap<>(); 2492 2493 { // Prefill, since we know we will need these 2494 Arrays.stream(ParentLocaleComponent.values()) 2495 .forEach(x -> parentLocales.put(x, new HashMap<>())); 2496 } 2497 2498 private Set<ParentLocaleComponent> parentLocalesSkipNonLikely = 2499 EnumSet.noneOf(ParentLocaleComponent.class); 2500 private Map<String, List<String>> calendarPreferences = new HashMap<>(); 2501 private Map<String, CoverageVariableInfo> localeSpecificVariables = new TreeMap<>(); 2502 private VariableReplacer coverageVariables = new VariableReplacer(); 2503 private Map<String, NumberingSystemInfo> numberingSystems = new HashMap<>(); 2504 private Set<String> numericSystems = new TreeSet<>(); 2505 private Set<String> defaultContentLocales; 2506 public Map<CLDRLocale, CLDRLocale> baseToDefaultContent; // wo -> wo_Arab_SN 2507 public Map<CLDRLocale, CLDRLocale> defaultContentToBase; // wo_Arab_SN -> wo 2508 private Set<String> CLDRLanguageCodes = new TreeSet<>(); 2509 private Set<String> languageNonTcLtBasic = new TreeSet<>(); 2510 2511 private Set<String> CLDRScriptCodes; 2512 2513 /** 2514 * Get the population data for a language. Warning: if the language has script variants, cycle 2515 * on those variants. 2516 * 2517 * @param language 2518 * @return 2519 */ 2520 public PopulationData getLanguagePopulationData(String language) { 2521 return languageToPopulation.get(language); 2522 } 2523 2524 public PopulationData getBaseLanguagePopulationData(String language) { 2525 return baseLanguageToPopulation.get(language); 2526 } 2527 2528 public Set<String> getLanguages() { 2529 return allLanguages; 2530 } 2531 2532 public Set<String> getTerritoryToLanguages(String territory) { 2533 Map<String, PopulationData> result = territoryToLanguageToPopulationData.get(territory); 2534 if (result == null) { 2535 return Collections.emptySet(); 2536 } 2537 return result.keySet(); 2538 } 2539 2540 public PopulationData getLanguageAndTerritoryPopulationData(String language, String territory) { 2541 Map<String, PopulationData> result = territoryToLanguageToPopulationData.get(territory); 2542 if (result == null) { 2543 return null; 2544 } 2545 return result.get(language); 2546 } 2547 2548 public Set<String> getTerritoriesWithPopulationData() { 2549 return territoryToLanguageToPopulationData.keySet(); 2550 } 2551 2552 public Set<String> getLanguagesForTerritoryWithPopulationData(String territory) { 2553 Map<String, PopulationData> languageToPopulationMap = 2554 territoryToLanguageToPopulationData.get(territory); 2555 return languageToPopulationMap == null 2556 ? Collections.emptySet() 2557 : languageToPopulationMap.keySet(); 2558 } 2559 2560 public Set<BasicLanguageData> getBasicLanguageData(String language) { 2561 Map<Type, BasicLanguageData> map = languageToBasicLanguageData.get(language); 2562 if (map == null) { 2563 throw new IllegalArgumentException("Bad language code: " + language); 2564 } 2565 return new LinkedHashSet<>(map.values()); 2566 } 2567 2568 public Map<Type, BasicLanguageData> getBasicLanguageDataMap(String language) { 2569 return languageToBasicLanguageData.get(language); 2570 } 2571 2572 public Set<String> getBasicLanguageDataLanguages() { 2573 return languageToBasicLanguageData.keySet(); 2574 } 2575 2576 public Relation<String, String> getContainmentCore() { 2577 return containmentCore; 2578 } 2579 2580 public Set<String> getContained(String territoryCode) { 2581 return containment.getAll(territoryCode); 2582 } 2583 2584 public Set<String> getContainers() { 2585 return containment.keySet(); 2586 } 2587 2588 public Set<String> getContainedSubdivisions(String territoryOrSubdivisionCode) { 2589 return containerToSubdivision.getAll(territoryOrSubdivisionCode); 2590 } 2591 2592 public Set<String> getContainersForSubdivisions() { 2593 return containerToSubdivision.keySet(); 2594 } 2595 2596 public Relation<String, String> getTerritoryToContained() { 2597 return getTerritoryToContained(ContainmentStyle.all); // true 2598 } 2599 2600 // public Relation<String, String> getTerritoryToContained(boolean allowDeprecated) { 2601 // return allowDeprecated ? containment : containmentNonDeprecated; 2602 // } 2603 // 2604 public enum ContainmentStyle { 2605 all, 2606 core, 2607 grouping, 2608 deprecated 2609 } 2610 2611 public Relation<String, String> getTerritoryToContained(ContainmentStyle containmentStyle) { 2612 switch (containmentStyle) { 2613 case all: 2614 return containment; 2615 case core: 2616 return containmentCore; 2617 case grouping: 2618 return containmentGrouping; 2619 case deprecated: 2620 return containmentDeprecated; 2621 } 2622 throw new IllegalArgumentException("internal error"); 2623 } 2624 2625 public Set<String> getSkippedElements() { 2626 return skippedElements; 2627 } 2628 2629 public Set<String> getZone_aliases(String zone) { 2630 Set<String> result = zone_aliases.getAll(zone); 2631 if (result == null) { 2632 return Collections.emptySet(); 2633 } 2634 return result; 2635 } 2636 2637 public String getZone_territory(String zone) { 2638 return zone_territory.get(zone); 2639 } 2640 2641 public Set<String> getCanonicalZones() { 2642 return zone_territory.keySet(); 2643 } 2644 2645 public Set<String> getTerritoriesForPopulationData(String language) { 2646 return languageToTerritories.getAll(language); 2647 } 2648 2649 public Set<String> getLanguagesForTerritoriesPopulationData() { 2650 return languageToTerritories.keySet(); 2651 } 2652 2653 /** 2654 * Return the list of default content locales. 2655 * 2656 * @return 2657 */ 2658 public Set<String> getDefaultContentLocales() { 2659 return defaultContentLocales; 2660 } 2661 2662 public static Map<String, String> makeLocaleToDefaultContents( 2663 Set<String> defaultContents, Map<String, String> result, Set<String> errors) { 2664 for (String s : defaultContents) { 2665 String simpleParent = LanguageTagParser.getSimpleParent(s); 2666 String oldValue = result.get(simpleParent); 2667 if (oldValue != null) { 2668 errors.add( 2669 "*** Error: Default contents cannot contain two children for the same parent:\t" 2670 + oldValue 2671 + ", " 2672 + s 2673 + "; keeping " 2674 + oldValue); 2675 continue; 2676 } 2677 result.put(simpleParent, s); 2678 } 2679 return result; 2680 } 2681 2682 /** 2683 * Return the list of default content locales. 2684 * 2685 * @return 2686 */ 2687 public Set<CLDRLocale> getDefaultContentCLDRLocales() { 2688 initCLDRLocaleBasedData(); 2689 return defaultContentToBase.keySet(); 2690 } 2691 2692 /** 2693 * Get the default content locale for a specified language 2694 * 2695 * @param language language to search 2696 * @return default content, or null if none 2697 */ 2698 public String getDefaultContentLocale(String locale) { 2699 CLDRLocale cLocale = CLDRLocale.getInstance(locale); 2700 for (String dc : defaultContentLocales) { 2701 if (CLDRLocale.getInstance(dc).getParent() == cLocale) { 2702 return dc; 2703 } 2704 } 2705 return null; 2706 } 2707 2708 /** 2709 * Get the default content locale for a specified language and script. If script is null, 2710 * delegates to {@link #getDefaultContentLocale(String)} 2711 * 2712 * @param language 2713 * @param script if null, delegates to {@link #getDefaultContentLocale(String)} 2714 * @return default content, or null if none 2715 */ 2716 public String getDefaultContentLocale(String language, String script) { 2717 if (script == null) return getDefaultContentLocale(language); 2718 for (String dc : defaultContentLocales) { 2719 if (dc.startsWith(language + "_" + script + "_")) { 2720 return dc; 2721 } 2722 } 2723 return null; 2724 } 2725 2726 /** 2727 * Given a default locale (such as 'wo_Arab_SN') return the base locale (such as 'wo'), or null 2728 * if the input wasn't a default conetnt locale. 2729 * 2730 * @param dcLocale 2731 * @return 2732 */ 2733 public CLDRLocale getBaseFromDefaultContent(CLDRLocale dcLocale) { 2734 initCLDRLocaleBasedData(); 2735 return defaultContentToBase.get(dcLocale); 2736 } 2737 2738 /** 2739 * Given a base locale (such as 'wo') return the default content locale (such as 'wo_Arab_SN'), 2740 * or null. 2741 * 2742 * @param baseLocale 2743 * @return 2744 */ 2745 public CLDRLocale getDefaultContentFromBase(CLDRLocale baseLocale) { 2746 initCLDRLocaleBasedData(); 2747 return baseToDefaultContent.get(baseLocale); 2748 } 2749 2750 /** 2751 * Is this a default content locale? 2752 * 2753 * @param dcLocale 2754 * @return 2755 */ 2756 public boolean isDefaultContent(CLDRLocale dcLocale) { 2757 initCLDRLocaleBasedData(); 2758 if (dcLocale == null) throw new NullPointerException("null locale"); 2759 return (defaultContentToBase.get(dcLocale) != null); 2760 } 2761 2762 public Set<String> getNumberingSystems() { 2763 return numberingSystems.keySet(); 2764 } 2765 2766 public Set<String> getNumericNumberingSystems() { 2767 return Collections.unmodifiableSet(numericSystems); 2768 } 2769 2770 public String getDigits(String numberingSystem) { 2771 try { 2772 return numberingSystems.get(numberingSystem).digits; 2773 } catch (Exception e) { 2774 throw new IllegalArgumentException("Can't get digits for:" + numberingSystem); 2775 } 2776 } 2777 2778 public NumberingSystemType getNumberingSystemType(String numberingSystem) { 2779 return numberingSystems.get(numberingSystem).type; 2780 } 2781 2782 public Set<CoverageLevelInfo> getCoverageLevelInfo() { 2783 return coverageLevels; 2784 } 2785 2786 /** 2787 * Used to get the coverage value for a path. This is generally the most efficient way for tools 2788 * to get coverage. 2789 * 2790 * @param xpath 2791 * @param loc 2792 * @return 2793 */ 2794 public Level getCoverageLevel(String xpath, String loc) { 2795 Level result = null; 2796 result = coverageCache.get(xpath, loc); 2797 if (result == null) { 2798 CoverageLevel2 cov = localeToCoverageLevelInfo.get(loc); 2799 if (cov == null) { 2800 cov = CoverageLevel2.getInstance(this, loc); 2801 localeToCoverageLevelInfo.put(loc, cov); 2802 } 2803 2804 result = cov.getLevel(xpath); 2805 coverageCache.put(xpath, loc, result); 2806 } 2807 return result; 2808 } 2809 2810 /** 2811 * Cache Data structure with object expiry, List that can hold up to MAX_LOCALES caches of 2812 * locales, when one locale hasn't been used for a while it will removed and GC'd 2813 */ 2814 private class CoverageCache { 2815 private final Deque<Node> localeList = new LinkedList<>(); 2816 private final int MAX_LOCALES = 10; 2817 2818 /** Object to sync on for modifying the locale list */ 2819 private final Object LOCALE_LIST_ITER_SYNC = new Object(); 2820 2821 /* 2822 * constructor 2823 */ 2824 public CoverageCache() { 2825 // localeList = new LinkedList<Node>(); 2826 } 2827 2828 /* 2829 * retrieves coverage level associated with two keys if it exists in the cache, otherwise returns null 2830 * @param xpath 2831 * @param loc 2832 * @return the coverage level of the above two keys 2833 */ 2834 public Level get(String xpath, String loc) { 2835 synchronized (LOCALE_LIST_ITER_SYNC) { 2836 Iterator<Node> it = localeList.iterator(); 2837 Node reAddNode = null; 2838 while (it.hasNext()) { 2839 // for (Iterator<Node> it = localeList.iterator(); it.hasNext();) { 2840 Node node = it.next(); 2841 if (node.loc.equals(loc)) { 2842 reAddNode = node; 2843 it.remove(); 2844 break; 2845 } 2846 } 2847 if (reAddNode != null) { 2848 localeList.addFirst(reAddNode); 2849 return reAddNode.map.get(xpath); 2850 } 2851 return null; 2852 } 2853 } 2854 2855 /* 2856 * places a coverage level into the cache, with two keys 2857 * @param xpath 2858 * @param loc 2859 * @param covLevel the coverage level of the above two keys 2860 */ 2861 public void put(String xpath, String loc, Level covLevel) { 2862 synchronized (LOCALE_LIST_ITER_SYNC) { 2863 // if locale's map is already in the cache add to it 2864 // for (Iterator<Node> it = localeList.iterator(); it.hasNext();) { 2865 for (Node node : localeList) { 2866 // Node node = it.next(); 2867 if (node.loc.equals(loc)) { 2868 node.map.put(xpath, covLevel); 2869 return; 2870 } 2871 } 2872 2873 // if it is not, add a new map with the coverage level, and remove the last map in 2874 // the list (used most seldom) if the list is too large 2875 Map<String, Level> newMap = new ConcurrentHashMap<>(); 2876 newMap.put(xpath, covLevel); 2877 localeList.addFirst(new Node(loc, newMap)); 2878 2879 if (localeList.size() > MAX_LOCALES) { 2880 localeList.removeLast(); 2881 } 2882 } 2883 } 2884 2885 /* 2886 * node to hold a location and a Map 2887 */ 2888 private class Node { 2889 // public fields to emulate a C/C++ struct 2890 public String loc; 2891 public Map<String, Level> map; 2892 2893 public Node(String _loc, Map<String, Level> _map) { 2894 loc = _loc; 2895 map = _map; 2896 } 2897 } 2898 } 2899 2900 /** 2901 * Used to get the coverage value for a path. Note, it is more efficient to create a 2902 * CoverageLevel2 for a language, and keep it around. 2903 * 2904 * @param xpath 2905 * @param loc 2906 * @return 2907 */ 2908 public int getCoverageValue(String xpath, String loc) { 2909 return getCoverageLevel(xpath, loc).getLevel(); 2910 } 2911 2912 private RegexLookup<Level> coverageLookup = null; 2913 2914 public synchronized RegexLookup<Level> getCoverageLookup() { 2915 if (coverageLookup == null) { 2916 RegexLookup<Level> lookup = 2917 new RegexLookup<>(RegexLookup.LookupType.STAR_PATTERN_LOOKUP); 2918 2919 Matcher variable = PatternCache.get("\\$\\{[A-Za-z][\\-A-Za-z]*\\}").matcher(""); 2920 2921 for (CoverageLevelInfo ci : getCoverageLevelInfo()) { 2922 String pattern = 2923 ci.match 2924 .replace('\'', '"') 2925 .replace("[@", "\\[@") // make sure that attributes are quoted 2926 .replace("(", "(?:") // make sure that there are no capturing groups 2927 // (beyond what we generate 2928 .replace("(?:?!", "(?!"); // Allow negative lookahead 2929 pattern = "^//ldml/" + pattern + "$"; // for now, force a complete match 2930 String variableType = null; 2931 variable.reset(pattern); 2932 if (variable.find()) { 2933 pattern = 2934 pattern.substring(0, variable.start()) 2935 + "([^\"]*)" 2936 + pattern.substring(variable.end()); 2937 variableType = variable.group(); 2938 if (variable.find()) { 2939 throw new IllegalArgumentException( 2940 "We can only handle a single variable on a line"); 2941 } 2942 } 2943 2944 // .replaceAll("\\]","\\\\]"); 2945 lookup.add(new CoverageLevel2.MyRegexFinder(pattern, variableType, ci), ci.value); 2946 } 2947 coverageLookup = lookup; 2948 } 2949 return coverageLookup; 2950 } 2951 2952 /** 2953 * Older version of code. 2954 * 2955 * @param xpath 2956 * @param loc 2957 * @return 2958 */ 2959 public int getCoverageValueOld(String xpath, ULocale loc) { 2960 String targetLanguage = loc.getLanguage(); 2961 2962 CoverageVariableInfo cvi = getCoverageVariableInfo(targetLanguage); 2963 String targetScriptString = toRegexString(cvi.targetScripts); 2964 String targetTerritoryString = toRegexString(cvi.targetTerritories); 2965 String calendarListString = toRegexString(cvi.calendars); 2966 String targetCurrencyString = toRegexString(cvi.targetCurrencies); 2967 String targetTimeZoneString = toRegexString(cvi.targetTimeZones); 2968 String targetPluralsString = toRegexString(cvi.targetPlurals); 2969 Iterator<CoverageLevelInfo> i = coverageLevels.iterator(); 2970 while (i.hasNext()) { 2971 CoverageLevelInfo ci = i.next(); 2972 String regex = 2973 "//ldml/" 2974 + ci.match 2975 .replace('\'', '"') 2976 .replaceAll("\\[", "\\\\[") 2977 .replaceAll("\\]", "\\\\]") 2978 .replace("${Target-Language}", targetLanguage) 2979 .replace("${Target-Scripts}", targetScriptString) 2980 .replace("${Target-Territories}", targetTerritoryString) 2981 .replace("${Target-TimeZones}", targetTimeZoneString) 2982 .replace("${Target-Currencies}", targetCurrencyString) 2983 .replace("${Target-Plurals}", targetPluralsString) 2984 .replace("${Calendar-List}", calendarListString); 2985 2986 // Special logic added for coverage fields that are only to be applicable 2987 // to certain territories 2988 if (ci.inTerritory != null) { 2989 if (ci.inTerritory.equals("EU")) { 2990 Set<String> containedTerritories = new HashSet<>(); 2991 containedTerritories.addAll(getContained(ci.inTerritory)); 2992 containedTerritories.retainAll(cvi.targetTerritories); 2993 if (containedTerritories.isEmpty()) { 2994 continue; 2995 } 2996 } else { 2997 if (!cvi.targetTerritories.contains(ci.inTerritory)) { 2998 continue; 2999 } 3000 } 3001 } 3002 // Special logic added for coverage fields that are only to be applicable 3003 // to certain languages 3004 if (ci.inLanguage != null && !ci.inLanguage.matcher(targetLanguage).matches()) { 3005 continue; 3006 } 3007 3008 // Special logic added for coverage fields that are only to be applicable 3009 // to certain scripts 3010 if (ci.inScript != null && !cvi.targetScripts.contains(ci.inScript)) { 3011 continue; 3012 } 3013 3014 if (xpath.matches(regex)) { 3015 return ci.value.getLevel(); 3016 } 3017 3018 if (xpath.matches(regex)) { 3019 return ci.value.getLevel(); 3020 } 3021 } 3022 return Level.OPTIONAL.getLevel(); // If no match then return highest possible value 3023 } 3024 3025 // The following is for mapping language to the explicit script and region codes in 3026 // the locale IDs for CLDR locales. We don't need to worry about the unmarked default 3027 // script or region since they are already supplied by the <languageData>. 3028 private Map<String, BasicLanguageData> doMapLanguagesToScriptsRegion() { 3029 Map<String, BasicLanguageData> langToScriptsRegions = new TreeMap<>(); 3030 org.unicode.cldr.util.Factory factory = CLDRConfig.getInstance().getCldrFactory(); 3031 for (CLDRLocale locale : factory.getAvailableCLDRLocales()) { 3032 String language = locale.getLanguage(); 3033 if (language.length() == 0 || language.equals(LocaleNames.ROOT)) { 3034 continue; 3035 } 3036 BasicLanguageData scriptsAndRegions = langToScriptsRegions.get(language); 3037 if (scriptsAndRegions == null) { 3038 scriptsAndRegions = new BasicLanguageData(); 3039 langToScriptsRegions.put(language, scriptsAndRegions); 3040 } 3041 String script = locale.getScript(); 3042 if (script.length() > 0) { 3043 scriptsAndRegions.addScript(script); 3044 } 3045 String region = locale.getCountry(); 3046 if (region.length() > 0 3047 && region.length() < 3) { // per CLDR TC, do not want 001, 419 etc. 3048 scriptsAndRegions.addTerritory(region); 3049 } 3050 } 3051 for (String language : langToScriptsRegions.keySet()) { 3052 BasicLanguageData scriptsAndRegions = langToScriptsRegions.get(language); 3053 langToScriptsRegions.put(language, scriptsAndRegions.freeze()); 3054 } 3055 return Collections.unmodifiableMap(langToScriptsRegions); 3056 } 3057 3058 private static Map<String, BasicLanguageData> languageToScriptsAndRegions = null; 3059 3060 private synchronized Map<String, BasicLanguageData> getLanguageToScriptsAndRegions() { 3061 if (languageToScriptsAndRegions == null) { 3062 languageToScriptsAndRegions = doMapLanguagesToScriptsRegion(); 3063 } 3064 return languageToScriptsAndRegions; 3065 } 3066 3067 public CoverageVariableInfo getCoverageVariableInfo(String targetLanguage) { 3068 CoverageVariableInfo cvi; 3069 if (localeSpecificVariables.containsKey(targetLanguage)) { 3070 cvi = localeSpecificVariables.get(targetLanguage); 3071 } else { 3072 cvi = new CoverageVariableInfo(); 3073 cvi.targetScripts = getTargetScripts(targetLanguage); 3074 cvi.targetTerritories = getTargetTerritories(targetLanguage); 3075 cvi.calendars = getCalendars(cvi.targetTerritories); 3076 cvi.targetCurrencies = getCurrentCurrencies(cvi.targetTerritories); 3077 cvi.targetTimeZones = getCurrentTimeZones(cvi.targetTerritories); 3078 cvi.targetPlurals = getTargetPlurals(targetLanguage); 3079 localeSpecificVariables.put(targetLanguage, cvi); 3080 } 3081 return cvi; 3082 } 3083 3084 private Set<String> getTargetScripts(String language) { 3085 Set<String> targetScripts = new HashSet<>(); 3086 try { 3087 Set<BasicLanguageData> langData = getBasicLanguageData(language); 3088 Iterator<BasicLanguageData> ldi = langData.iterator(); 3089 while (ldi.hasNext()) { 3090 BasicLanguageData bl = ldi.next(); 3091 Set<String> addScripts = bl.scripts; 3092 if (addScripts != null && bl.getType() != BasicLanguageData.Type.secondary) { 3093 targetScripts.addAll(addScripts); 3094 } 3095 } 3096 Map<String, BasicLanguageData> languageToScriptsAndRegions = 3097 getLanguageToScriptsAndRegions(); 3098 if (languageToScriptsAndRegions != null) { 3099 BasicLanguageData scriptsAndRegions = languageToScriptsAndRegions.get(language); 3100 if (scriptsAndRegions != null) { 3101 targetScripts.addAll(scriptsAndRegions.getScripts()); 3102 } 3103 } 3104 } catch (Exception e) { 3105 // fall through 3106 } 3107 3108 if (targetScripts.size() == 0) { 3109 targetScripts.add("Zzzz"); // Unknown Script 3110 } 3111 return targetScripts; 3112 } 3113 3114 private Set<String> getTargetTerritories(String language) { 3115 Set<String> targetTerritories = new HashSet<>(); 3116 Set<String> secondaryTerritories = new HashSet<>(); 3117 try { 3118 Set<BasicLanguageData> langData = getBasicLanguageData(language); 3119 Iterator<BasicLanguageData> ldi = langData.iterator(); 3120 while (ldi.hasNext()) { 3121 BasicLanguageData bl = ldi.next(); 3122 Set<String> addTerritories = bl.territories; 3123 if (addTerritories != null) { 3124 if (bl.getType() == BasicLanguageData.Type.secondary) { 3125 secondaryTerritories.addAll(addTerritories); 3126 } else { 3127 targetTerritories.addAll(addTerritories); 3128 } 3129 } 3130 } 3131 Map<String, BasicLanguageData> languageToScriptsAndRegions = 3132 getLanguageToScriptsAndRegions(); 3133 if (languageToScriptsAndRegions != null) { 3134 BasicLanguageData scriptsAndRegions = languageToScriptsAndRegions.get(language); 3135 if (scriptsAndRegions != null) { 3136 targetTerritories.addAll(scriptsAndRegions.getTerritories()); 3137 } 3138 } 3139 } catch (Exception e) { 3140 // fall through 3141 } 3142 if (targetTerritories.size() == 0) { 3143 getFallbackTargetTerritories(language, targetTerritories, secondaryTerritories); 3144 } 3145 return targetTerritories; 3146 } 3147 3148 private void getFallbackTargetTerritories( 3149 String language, Set<String> targetTerritories, Set<String> secondaryTerritories) { 3150 String region = null; 3151 String maximized = new LikelySubtags().maximize(language); 3152 if (maximized != null) { 3153 CLDRLocale cldrLocale = CLDRLocale.getInstance(maximized); 3154 region = cldrLocale.getCountry(); 3155 } 3156 if (region != null) { 3157 targetTerritories.add(region); 3158 } else if (secondaryTerritories.size() > 0) { 3159 targetTerritories.addAll(secondaryTerritories); 3160 } else { 3161 targetTerritories.add("ZZ"); 3162 } 3163 } 3164 3165 private Set<String> getCalendars(Set<String> territories) { 3166 Set<String> targetCalendars = new HashSet<>(); 3167 Iterator<String> it = territories.iterator(); 3168 while (it.hasNext()) { 3169 List<String> addCalendars = getCalendars(it.next()); 3170 if (addCalendars == null) { 3171 continue; 3172 } 3173 targetCalendars.addAll(addCalendars); 3174 } 3175 return targetCalendars; 3176 } 3177 3178 /** 3179 * @param territory 3180 * @return a list the calendars used in the specified territorys 3181 */ 3182 public List<String> getCalendars(String territory) { 3183 return calendarPreferences.get(territory); 3184 } 3185 3186 private Set<String> getCurrentCurrencies(Set<String> territories) { 3187 Date now = new Date(); 3188 return getCurrentCurrencies(territories, now, now); 3189 } 3190 3191 public Set<String> getCurrentCurrencies( 3192 Set<String> territories, Date startsBefore, Date endsAfter) { 3193 Set<String> targetCurrencies = new HashSet<>(); 3194 Iterator<String> it = territories.iterator(); 3195 while (it.hasNext()) { 3196 Set<CurrencyDateInfo> targetCurrencyInfo = getCurrencyDateInfo(it.next()); 3197 if (targetCurrencyInfo == null) { 3198 continue; 3199 } 3200 Iterator<CurrencyDateInfo> it2 = targetCurrencyInfo.iterator(); 3201 while (it2.hasNext()) { 3202 CurrencyDateInfo cdi = it2.next(); 3203 if (cdi.getStart().before(startsBefore) 3204 && cdi.getEnd().after(endsAfter) 3205 && cdi.isLegalTender()) { 3206 targetCurrencies.add(cdi.getCurrency()); 3207 } 3208 } 3209 } 3210 return targetCurrencies; 3211 } 3212 3213 private Set<String> getCurrentTimeZones(Set<String> territories) { 3214 Set<String> targetTimeZones = new HashSet<>(); 3215 Iterator<String> it = territories.iterator(); 3216 while (it.hasNext()) { 3217 String[] countryIDs = TimeZone.getAvailableIDs(it.next()); 3218 for (int i = 0; i < countryIDs.length; i++) { 3219 targetTimeZones.add(countryIDs[i]); 3220 } 3221 } 3222 return targetTimeZones; 3223 } 3224 3225 private Set<String> getTargetPlurals(String language) { 3226 Set<String> targetPlurals = new HashSet<>(); 3227 targetPlurals.addAll(getPlurals(PluralType.cardinal, language).getCanonicalKeywords()); 3228 // TODO: Kept 0 and 1 specifically until Mark figures out what to do with them. 3229 // They should be removed once this is done. 3230 targetPlurals.add("0"); 3231 targetPlurals.add("1"); 3232 return targetPlurals; 3233 } 3234 3235 public enum ParentLocaleComponent { 3236 main, 3237 collations, 3238 segmentations, 3239 grammaticalFeatures, 3240 plurals; 3241 3242 public static ParentLocaleComponent fromString(String s) { 3243 return s == null 3244 ? ParentLocaleComponent.main // handle empty 3245 : ParentLocaleComponent.valueOf(s); 3246 } 3247 } 3248 3249 public boolean parentLocalesSkipNonLikely(ParentLocaleComponent component) { 3250 return parentLocalesSkipNonLikely.contains(component); 3251 } 3252 3253 public String getExplicitParentLocale(String loc, ParentLocaleComponent component) { 3254 return parentLocales.get(component).get(loc); 3255 } 3256 3257 // These are not (now) used by the current code. 3258 // They should not be used, because the answer is incorrect for parentLocalesSkipNonLikely 3259 // 3260 // public String getExplicitParentLocale(String loc) { 3261 // return getExplicitParentLocale(loc, ParentLocaleComponent.main); 3262 // } 3263 // 3264 3265 // 3266 // public Set<String> getExplicitChildren() { 3267 // return getExplicitChildren(ParentLocaleComponent.main); 3268 // } 3269 // 3270 // public Set<String> getExplicitChildren(ParentLocaleComponent component) { 3271 // return parentLocales.get(component).keySet(); 3272 // } 3273 3274 public Collection<String> getExplicitParents() { 3275 return getExplicitParents(ParentLocaleComponent.main); 3276 } 3277 3278 public Collection<String> getExplicitParents(ParentLocaleComponent component) { 3279 return parentLocales.get(component).values(); 3280 } 3281 3282 public static final class ApprovalRequirementMatcher { 3283 @Override 3284 public String toString() { 3285 return locales + " / " + xpathMatcher + " = " + requiredVotes; 3286 } 3287 3288 ApprovalRequirementMatcher(String xpath) { 3289 XPathValue parts = SimpleXPathParts.getFrozenInstance(xpath); 3290 if (parts.containsElement("approvalRequirement")) { 3291 requiredVotes = getRequiredVotes(parts); 3292 String localeAttrib = parts.getAttributeValue(-1, "locales"); 3293 if (localeAttrib == null || localeAttrib.equals(STAR) || localeAttrib.isEmpty()) { 3294 locales = null; // no locale listed == '*' 3295 } else { 3296 Set<CLDRLocale> localeList = new HashSet<>(); 3297 String[] el = localeAttrib.split(" "); 3298 for (int i = 0; i < el.length; i++) { 3299 if (el[i].indexOf(":") == -1) { // Just a simple locale designation 3300 localeList.add(CLDRLocale.getInstance(el[i])); 3301 } else { // Org:CoverageLevel 3302 String[] coverageLocaleParts = el[i].split(":", 2); 3303 String org = coverageLocaleParts[0]; 3304 String level = coverageLocaleParts[1].toUpperCase(); 3305 Set<String> coverageLocales = 3306 sc.getLocaleCoverageLocales( 3307 Organization.fromString(org), 3308 EnumSet.of(Level.fromString(level))); 3309 for (String cl : coverageLocales) { 3310 localeList.add(CLDRLocale.getInstance(cl)); 3311 } 3312 } 3313 } 3314 locales = Collections.unmodifiableSet(localeList); 3315 } 3316 String xpathMatch = parts.getAttributeValue(-1, "paths"); 3317 if (xpathMatch == null || xpathMatch.isEmpty() || xpathMatch.equals(STAR)) { 3318 xpathMatcher = null; 3319 } else { 3320 xpathMatcher = PatternCache.get(xpathMatch); 3321 } 3322 } else { 3323 throw new RuntimeException("Unknown approval requirement: " + xpath); 3324 } 3325 } 3326 3327 static int getRequiredVotes(XPathValue parts) { 3328 String votesStr = parts.getAttributeValue(-1, "votes"); 3329 if (votesStr.charAt(0) == '=') { 3330 votesStr = votesStr.substring(1); 3331 if (votesStr.equals("HIGH_BAR")) { 3332 return VoteResolver.HIGH_BAR; 3333 } else if (votesStr.equals("LOWER_BAR")) { 3334 return VoteResolver.LOWER_BAR; 3335 } 3336 final VoteResolver.Level l = VoteResolver.Level.valueOf(votesStr); 3337 return l.getVotes(Organization.unaffiliated); // use non-TC vote count 3338 } else { 3339 return Integer.parseInt(votesStr); 3340 } 3341 } 3342 3343 private final Set<CLDRLocale> locales; 3344 private final Pattern xpathMatcher; 3345 final int requiredVotes; 3346 3347 public static List<ApprovalRequirementMatcher> buildAll(List<String> approvalRequirements) { 3348 List<ApprovalRequirementMatcher> newList = new LinkedList<>(); 3349 3350 for (String xpath : approvalRequirements) { 3351 newList.add(new ApprovalRequirementMatcher(xpath)); 3352 } 3353 3354 return Collections.unmodifiableList(newList); 3355 } 3356 3357 public boolean matches(CLDRLocale loc, PathHeader ph) { 3358 if (DEBUG) System.err.println(">> testing " + loc + " / " + ph + " vs " + toString()); 3359 if (locales != null) { 3360 if (!locales.contains(loc)) { 3361 return false; 3362 } 3363 } 3364 if (xpathMatcher != null) { 3365 if (ph != null) { 3366 if (!xpathMatcher.matcher(ph.getOriginalPath()).matches()) { 3367 return false; 3368 } else { 3369 return true; 3370 } 3371 } else { 3372 return false; 3373 } 3374 } 3375 return true; 3376 } 3377 3378 public int getRequiredVotes() { 3379 return requiredVotes; 3380 } 3381 } 3382 3383 // run these from first to last to get the approval info. 3384 volatile List<ApprovalRequirementMatcher> approvalMatchers = null; 3385 3386 /** 3387 * Get the preliminary number of required votes based on the given locale and PathHeader 3388 * 3389 * <p>Important: this number may not agree with VoteResolver.getRequiredVotes since VoteResolver 3390 * also takes the baseline status into account. 3391 * 3392 * <p>Called by VoteResolver, ShowStarredCoverage, TestCoverage, and TestCoverageLevel. 3393 * 3394 * @param loc the CLDRLocale 3395 * @param ph the PathHeader - which path this is applied to, or null if unknown. 3396 * @return a number such as 4 or 8 3397 */ getRequiredVotes(CLDRLocale loc, PathHeader ph)3398 public int getRequiredVotes(CLDRLocale loc, PathHeader ph) { 3399 if (approvalMatchers == null) { 3400 approvalMatchers = ApprovalRequirementMatcher.buildAll(approvalRequirements); 3401 } 3402 3403 for (ApprovalRequirementMatcher m : approvalMatchers) { 3404 if (m.matches(loc, ph)) { 3405 return m.getRequiredVotes(); 3406 } 3407 } 3408 throw new RuntimeException( 3409 "Error: " + loc + " " + ph + " ran off the end of the approvalMatchers."); 3410 } 3411 3412 /** 3413 * Return the canonicalized zone, or null if there is none. 3414 * 3415 * @param alias 3416 * @return 3417 */ getZoneFromAlias(String alias)3418 public String getZoneFromAlias(String alias) { 3419 String zone = alias_zone.get(alias); 3420 if (zone != null) return zone; 3421 if (zone_territory.get(alias) != null) return alias; 3422 return null; 3423 } 3424 isCanonicalZone(String alias)3425 public boolean isCanonicalZone(String alias) { 3426 return zone_territory.get(alias) != null; 3427 } 3428 3429 /** 3430 * Return the approximate economic weight of this language, computed by taking all of the 3431 * languages in each territory, looking at the literate population and dividing up the GDP of 3432 * the territory (in PPP) according to the proportion that language has of the total. This is 3433 * only an approximation, since the language information is not complete, languages may overlap 3434 * (bilingual speakers), the literacy figures may be estimated, and literacy is only a rough 3435 * proxy for weight of each language in the economy of the territory. 3436 * 3437 * @param targetLanguage 3438 * @return 3439 */ getApproximateEconomicWeight(String targetLanguage)3440 public double getApproximateEconomicWeight(String targetLanguage) { 3441 double weight = 0; 3442 Set<String> territories = getTerritoriesForPopulationData(targetLanguage); 3443 if (territories == null) return weight; 3444 for (String territory : territories) { 3445 Set<String> languagesInTerritory = getTerritoryToLanguages(territory); 3446 double totalLiteratePopulation = 0; 3447 double targetLiteratePopulation = 0; 3448 for (String language : languagesInTerritory) { 3449 PopulationData populationData = 3450 getLanguageAndTerritoryPopulationData(language, territory); 3451 totalLiteratePopulation += populationData.getLiteratePopulation(); 3452 if (language.equals(targetLanguage)) { 3453 targetLiteratePopulation = populationData.getLiteratePopulation(); 3454 } 3455 } 3456 PopulationData territoryPopulationData = getPopulationDataForTerritory(territory); 3457 final double gdp = territoryPopulationData.getGdp(); 3458 final double scaledGdp = gdp * targetLiteratePopulation / totalLiteratePopulation; 3459 if (scaledGdp > 0) { 3460 weight += scaledGdp; 3461 } else { 3462 // System.out.println("?\t" + territory + "\t" + targetLanguage); 3463 } 3464 } 3465 return weight; 3466 } 3467 getPopulationDataForTerritory(String territory)3468 public PopulationData getPopulationDataForTerritory(String territory) { 3469 return territoryToPopulationData.get(territory); 3470 } 3471 getScriptVariantsForPopulationData(String language)3472 public Set<String> getScriptVariantsForPopulationData(String language) { 3473 return languageToScriptVariants.getAll(language); 3474 } 3475 getReferences()3476 public Map<String, Pair<String, String>> getReferences() { 3477 return references; 3478 } 3479 getMetazoneToRegionToZone()3480 public Map<String, Map<String, String>> getMetazoneToRegionToZone() { 3481 return typeToZoneToRegionToZone.get("metazones"); 3482 } 3483 getZoneForMetazoneByRegion(String metazone, String region)3484 public String getZoneForMetazoneByRegion(String metazone, String region) { 3485 String result = null; 3486 if (getMetazoneToRegionToZone().containsKey(metazone)) { 3487 Map<String, String> myMap = getMetazoneToRegionToZone().get(metazone); 3488 if (myMap.containsKey(region)) { 3489 result = myMap.get(region); 3490 } else { 3491 result = myMap.get("001"); 3492 } 3493 } 3494 3495 if (result == null) { 3496 result = "Etc/GMT"; 3497 } 3498 3499 return result; 3500 } 3501 getTypeToZoneToRegionToZone()3502 public Map<String, Map<String, Map<String, String>>> getTypeToZoneToRegionToZone() { 3503 return typeToZoneToRegionToZone; 3504 } 3505 3506 /** 3507 * @deprecated, use PathHeader.getMetazonePageTerritory 3508 */ getMetazoneToContinentMap()3509 public Map<String, String> getMetazoneToContinentMap() { 3510 return metazoneContinentMap; 3511 } 3512 getAllMetazones()3513 public Set<String> getAllMetazones() { 3514 return allMetazones; 3515 } 3516 3517 /** 3518 * Is the given metazone outdated? 3519 * 3520 * @param metazone the metazone such as "Liberia" 3521 * @param tzid the timezone id such as "Africa/Monrovia" 3522 * @param timeInMillis the time in milliseconds since 1970 3523 * @return true if the metazone is outdated 3524 */ metazoneIsOutdated(String metazone, String tzid, long timeInMillis)3525 public boolean metazoneIsOutdated(String metazone, String tzid, long timeInMillis) { 3526 final MetaZoneRange range = getMetaZoneRange(tzid, timeInMillis); 3527 // For example, for metazone = "Liberia", if range.metazone = "GMT", 3528 // that implies "GMT" is current and "Liberia" is outdated 3529 if (range == null) { 3530 if (DEBUG) { 3531 System.out.println( 3532 "metazoneIsOutdated: " + metazone + "; tzid = " + tzid + "; range is null"); 3533 } 3534 return true; 3535 } 3536 if (!metazone.equals(range.metazone)) { 3537 if (DEBUG) { 3538 System.out.println( 3539 "metazoneIsOutdated: " 3540 + metazone 3541 + "; tzid = " 3542 + tzid 3543 + "; range.metazone = " 3544 + range.metazone); 3545 } 3546 return true; 3547 } 3548 return false; 3549 } 3550 getLikelySubtags()3551 public Map<String, String> getLikelySubtags() { 3552 return likelySubtags; 3553 } 3554 getLikelyOrigins()3555 public Map<String, String> getLikelyOrigins() { 3556 return likelyOrigins; 3557 } 3558 3559 public enum PluralType { 3560 cardinal(PluralRules.PluralType.CARDINAL), 3561 ordinal(PluralRules.PluralType.ORDINAL); 3562 3563 // add some gorp to interwork until we clean things up 3564 3565 public final PluralRules.PluralType standardType; 3566 PluralType(PluralRules.PluralType standardType)3567 PluralType(PluralRules.PluralType standardType) { 3568 this.standardType = standardType; 3569 } 3570 fromStandardType(PluralRules.PluralType standardType)3571 public static PluralType fromStandardType(PluralRules.PluralType standardType) { 3572 return standardType == null 3573 ? null 3574 : standardType == PluralRules.PluralType.CARDINAL ? cardinal : ordinal; 3575 } 3576 } 3577 3578 private EnumMap<PluralType, Map<String, PluralInfo>> localeToPluralInfo2 = 3579 new EnumMap<>(PluralType.class); 3580 3581 { 3582 localeToPluralInfo2.put(PluralType.cardinal, new LinkedHashMap<String, PluralInfo>()); 3583 localeToPluralInfo2.put(PluralType.ordinal, new LinkedHashMap<String, PluralInfo>()); 3584 } 3585 3586 private Map<String, PluralRanges> localeToPluralRanges = new LinkedHashMap<>(); 3587 3588 private Map<DayPeriodInfo.Type, Map<String, DayPeriodInfo>> typeToLocaleToDayPeriodInfo = 3589 new EnumMap<>(DayPeriodInfo.Type.class); 3590 private Map<String, CoverageLevel2> localeToCoverageLevelInfo = new ConcurrentHashMap<>(); 3591 private CoverageCache coverageCache = new CoverageCache(); 3592 private transient String lastPluralLocales = ""; 3593 private transient PluralType lastPluralWasOrdinal = null; 3594 private transient Map<Count, String> lastPluralMap = new EnumMap<>(Count.class); 3595 private transient String lastDayPeriodLocales = null; 3596 private transient DayPeriodInfo.Type lastDayPeriodType = null; 3597 private transient DayPeriodInfo.Builder dayPeriodBuilder = new DayPeriodInfo.Builder(); 3598 addDayPeriodPath(XPathValue path)3599 private void addDayPeriodPath(XPathValue path) { 3600 // ldml/dates/calendars/calendar[@type="gregorian"]/dayPeriods/dayPeriodContext[@type="format"]/dayPeriodWidth[@type="wide"]/dayPeriod[@type="am"] 3601 /* 3602 * <supplementalData> 3603 * <version number="$Revision$"/> 3604 * <generation date="$D..e... $"/> 3605 * <dayPeriodRuleSet> 3606 * <dayPeriodRules locales = "en"> <!-- default for any locales not listed under other dayPeriods --> 3607 * <dayPeriodRule type = "am" from = "0:00" before="12:00"/> 3608 * <dayPeriodRule type = "pm" from = "12:00" to="24:00"/> 3609 */ 3610 String typeString = path.getAttributeValue(1, "type"); 3611 String locales = path.getAttributeValue(2, "locales").trim(); 3612 DayPeriodInfo.Type type = 3613 typeString == null 3614 ? DayPeriodInfo.Type.format 3615 : DayPeriodInfo.Type.valueOf(typeString.trim()); 3616 if (!locales.equals(lastDayPeriodLocales) || type != lastDayPeriodType) { 3617 if (lastDayPeriodLocales != null) { 3618 addDayPeriodInfo(); 3619 } 3620 lastDayPeriodLocales = locales; 3621 lastDayPeriodType = type; 3622 // System.out.println(type + ", " + locales + ", " + path); 3623 } 3624 if (path.size() != 4) { 3625 if (locales.equals(LocaleNames.ROOT)) return; // we allow root to be empty 3626 throw new IllegalArgumentException(locales + " must have dayPeriodRule elements"); 3627 } 3628 DayPeriod dayPeriod; 3629 try { 3630 dayPeriod = DayPeriod.fromString(path.getAttributeValue(-1, "type")); 3631 } catch (Exception e) { 3632 System.err.println(e.getMessage()); 3633 return; 3634 } 3635 String at = path.getAttributeValue(-1, "at"); 3636 String from = path.getAttributeValue(-1, "from"); 3637 String after = path.getAttributeValue(-1, "after"); 3638 String to = path.getAttributeValue(-1, "to"); 3639 String before = path.getAttributeValue(-1, "before"); 3640 if (at != null) { 3641 if (from != null || after != null || to != null || before != null) { 3642 throw new IllegalArgumentException(); 3643 } 3644 from = at; 3645 to = at; 3646 } else if ((from == null) == (after == null) || (to == null) == (before == null)) { 3647 throw new IllegalArgumentException(); 3648 } 3649 // if (dayPeriodBuilder.contains(dayPeriod)) { // disallow multiple rules with same 3650 // dayperiod 3651 // throw new IllegalArgumentException("Multiple rules with same dayperiod are 3652 // disallowed: " 3653 // + lastDayPeriodLocales + ", " + lastDayPeriodType + ", " + dayPeriod); 3654 // } 3655 boolean includesStart = from != null; 3656 boolean includesEnd = to != null; 3657 int start = parseTime(includesStart ? from : after); 3658 int end = parseTime(includesEnd ? to : before); 3659 // Check if any periods contain 0, e.g. 1700 - 300 3660 if (start > end) { 3661 // System.out.println("start " + start + " end " + end); 3662 dayPeriodBuilder.add(dayPeriod, start, includesStart, parseTime("24:00"), includesEnd); 3663 dayPeriodBuilder.add(dayPeriod, parseTime("0:00"), includesStart, end, includesEnd); 3664 } else { 3665 dayPeriodBuilder.add(dayPeriod, start, includesStart, end, includesEnd); 3666 } 3667 } 3668 3669 static Pattern PARSE_TIME = PatternCache.get("(\\d\\d?):(\\d\\d)"); 3670 parseTime(String string)3671 private int parseTime(String string) { 3672 Matcher matcher = PARSE_TIME.matcher(string); 3673 if (!matcher.matches()) { 3674 throw new IllegalArgumentException(); 3675 } 3676 return (Integer.parseInt(matcher.group(1)) * 60 + Integer.parseInt(matcher.group(2))) 3677 * 60 3678 * 1000; 3679 } 3680 addDayPeriodInfo()3681 private void addDayPeriodInfo() { 3682 String[] locales = lastDayPeriodLocales.split("\\s+"); 3683 DayPeriodInfo temp = dayPeriodBuilder.finish(locales); 3684 Map<String, DayPeriodInfo> locale2DPI = typeToLocaleToDayPeriodInfo.get(lastDayPeriodType); 3685 if (locale2DPI == null) { 3686 typeToLocaleToDayPeriodInfo.put(lastDayPeriodType, locale2DPI = new LinkedHashMap<>()); 3687 // System.out.println(lastDayPeriodType + ", " + locale2DPI); 3688 } 3689 for (String locale : locales) { 3690 locale2DPI.put(locale, temp); 3691 } 3692 } 3693 3694 static String lastPluralRangesLocales = null; 3695 static PluralRanges lastPluralRanges = null; 3696 addPluralPath(XPathValue path, String value)3697 private boolean addPluralPath(XPathValue path, String value) { 3698 /* 3699 * Adding 3700 <pluralRanges locales="am"> 3701 <pluralRange start="one" end="one" result="one" /> 3702 </pluralRanges> 3703 */ 3704 String locales = path.getAttributeValue(2, "locales").trim(); 3705 String element = path.getElement(2); 3706 if ("pluralRanges".equals(element)) { 3707 if (!locales.equals(lastPluralRangesLocales)) { 3708 addPluralRanges(locales); 3709 } 3710 if (path.size() == 3) { 3711 // ok for ranges to be empty 3712 return true; 3713 } 3714 String rangeStart = path.getAttributeValue(-1, "start"); 3715 String rangeEnd = path.getAttributeValue(-1, "end"); 3716 String result = path.getAttributeValue(-1, "result"); 3717 lastPluralRanges.add( 3718 rangeStart == null ? null : Count.valueOf(rangeStart), 3719 rangeEnd == null ? null : Count.valueOf(rangeEnd), 3720 Count.valueOf(result)); 3721 return true; 3722 } else if ("pluralRules".equals(element)) { 3723 3724 String type = path.getAttributeValue(1, "type"); 3725 PluralType pluralType = type == null ? PluralType.cardinal : PluralType.valueOf(type); 3726 if (!lastPluralLocales.equals(locales)) { 3727 addPluralInfo(pluralType); 3728 lastPluralLocales = locales; 3729 } 3730 final String countString = path.getAttributeValue(-1, "count"); 3731 if (countString == null) { 3732 return false; 3733 } 3734 Count count = Count.valueOf(countString); 3735 if (lastPluralMap.containsKey(count)) { 3736 throw new IllegalArgumentException( 3737 "Duplicate plural count: " + count + " in " + locales); 3738 } 3739 lastPluralMap.put(count, value); 3740 lastPluralWasOrdinal = pluralType; 3741 return true; 3742 } else { 3743 return false; 3744 } 3745 } 3746 addPluralRanges(String localesString)3747 private void addPluralRanges(String localesString) { 3748 final String[] locales = localesString.split("\\s+"); 3749 lastPluralRanges = new PluralRanges(); 3750 for (String locale : locales) { 3751 if (localeToPluralRanges.containsKey(locale)) { 3752 throw new IllegalArgumentException("Duplicate plural locale: " + locale); 3753 } 3754 localeToPluralRanges.put(locale, lastPluralRanges); 3755 } 3756 lastPluralRangesLocales = localesString; 3757 } 3758 addPluralInfo(PluralType pluralType)3759 private void addPluralInfo(PluralType pluralType) { 3760 final String[] locales = lastPluralLocales.split("\\s+"); 3761 PluralInfo info = new PluralInfo(lastPluralMap, pluralType); 3762 Map<String, PluralInfo> localeToInfo = localeToPluralInfo2.get(pluralType); 3763 for (String locale : locales) { 3764 if (localeToInfo.containsKey(locale)) { 3765 throw new IllegalArgumentException("Duplicate plural locale: " + locale); 3766 } else if (!locale.isEmpty()) { 3767 localeToInfo.put(locale, info); 3768 } 3769 } 3770 lastPluralMap.clear(); 3771 } 3772 3773 public static class SampleList { 3774 public static final SampleList EMPTY = new SampleList().freeze(); 3775 3776 private UnicodeSet uset = new UnicodeSet(); 3777 private List<FixedDecimal> fractions = new ArrayList<>(0); 3778 3779 @Override toString()3780 public String toString() { 3781 return toString(6, 3); 3782 } 3783 toString(int intLimit, int fractionLimit)3784 public String toString(int intLimit, int fractionLimit) { 3785 StringBuilder b = new StringBuilder(); 3786 int intCount = 0; 3787 int fractionCount = 0; 3788 int limit = uset.getRangeCount(); 3789 for (int i = 0; i < limit; ++i) { 3790 if (intCount >= intLimit) { 3791 b.append(", …"); 3792 break; 3793 } 3794 if (b.length() != 0) { 3795 b.append(", "); 3796 } 3797 int start = uset.getRangeStart(i); 3798 int end = uset.getRangeEnd(i); 3799 if (start == end) { 3800 b.append(start); 3801 ++intCount; 3802 } else if (start + 1 == end) { 3803 b.append(start).append(", ").append(end); 3804 intCount += 2; 3805 } else { 3806 b.append(start).append('-').append(end); 3807 intCount += 2; 3808 } 3809 } 3810 if (fractions.size() > 0) { 3811 for (int i = 0; i < fractions.size(); ++i) { 3812 if (fractionCount >= fractionLimit) { 3813 break; 3814 } 3815 if (b.length() != 0) { 3816 b.append(", "); 3817 } 3818 FixedDecimal fraction = fractions.get(i); 3819 String formatted = 3820 String.format( 3821 Locale.ROOT, 3822 "%." + fraction.getVisibleDecimalDigitCount() + "f", 3823 fraction.getSource()); 3824 b.append(formatted); 3825 ++fractionCount; 3826 } 3827 b.append(", …"); 3828 } 3829 return b.toString(); 3830 } 3831 getRangeCount()3832 public int getRangeCount() { 3833 return uset.getRangeCount(); 3834 } 3835 getRangeStart(int index)3836 public int getRangeStart(int index) { 3837 return uset.getRangeStart(index); 3838 } 3839 getRangeEnd(int index)3840 public int getRangeEnd(int index) { 3841 return uset.getRangeEnd(index); 3842 } 3843 getFractions()3844 public List<FixedDecimal> getFractions() { 3845 return fractions; 3846 } 3847 intSize()3848 public int intSize() { 3849 return uset.size(); 3850 } 3851 remove(int i)3852 public SampleList remove(int i) { 3853 uset.remove(i); 3854 return this; 3855 } 3856 add(int i)3857 public SampleList add(int i) { 3858 uset.add(i); 3859 return this; 3860 } 3861 freeze()3862 public SampleList freeze() { 3863 uset.freeze(); 3864 if (fractions instanceof ArrayList) { 3865 fractions = Collections.unmodifiableList(fractions); 3866 } 3867 return this; 3868 } 3869 add(FixedDecimal i)3870 public void add(FixedDecimal i) { 3871 fractions.add(i); 3872 } 3873 fractionSize()3874 public int fractionSize() { 3875 return fractions.size(); 3876 } 3877 } 3878 3879 public static class CountSampleList { 3880 private final Map<Count, SampleList> countToIntegerSamples9999; 3881 private final Map<Count, SampleList[]> countToDigitToIntegerSamples9999; 3882 CountSampleList(PluralRules pluralRules, Set<Count> keywords, PluralType pluralType)3883 CountSampleList(PluralRules pluralRules, Set<Count> keywords, PluralType pluralType) { 3884 // Create the integer counts 3885 countToIntegerSamples9999 = new EnumMap<>(Count.class); 3886 countToDigitToIntegerSamples9999 = new EnumMap<>(Count.class); 3887 for (Count c : keywords) { 3888 countToIntegerSamples9999.put(c, new SampleList()); 3889 SampleList[] row = new SampleList[5]; 3890 countToDigitToIntegerSamples9999.put(c, row); 3891 for (int i = 1; i < 5; ++i) { 3892 row[i] = new SampleList(); 3893 } 3894 } 3895 for (int ii = 0; ii < 10000; ++ii) { 3896 int i = ii; 3897 int digit; 3898 if (i > 999) { 3899 digit = 4; 3900 } else if (i > 99) { 3901 digit = 3; 3902 } else if (i > 9) { 3903 digit = 2; 3904 } else { 3905 digit = 1; 3906 } 3907 Count count = Count.valueOf(pluralRules.select(i)); 3908 addSimple(countToIntegerSamples9999, i, count); 3909 addDigit(countToDigitToIntegerSamples9999, i, count, digit); 3910 if (haveFractions(keywords, digit)) { 3911 continue; 3912 } 3913 if (pluralType == PluralType.cardinal) { 3914 for (int f = 0; f < 30; ++f) { 3915 FixedDecimal ni = new FixedDecimal(i + f / 10.0d, f < 10 ? 1 : 2, f); 3916 count = Count.valueOf(pluralRules.select(ni)); 3917 addSimple(countToIntegerSamples9999, ni, count); 3918 addDigit(countToDigitToIntegerSamples9999, ni, count, digit); 3919 } 3920 } 3921 } 3922 // HACK for Breton 3923 addSimple( 3924 countToIntegerSamples9999, 1000000, Count.valueOf(pluralRules.select(1000000))); 3925 3926 for (Count count : keywords) { 3927 SampleList uset = countToIntegerSamples9999.get(count); 3928 uset.freeze(); 3929 SampleList[] map = countToDigitToIntegerSamples9999.get(count); 3930 for (int i = 1; i < map.length; ++i) { 3931 map[i].freeze(); 3932 } 3933 } 3934 } 3935 haveFractions(Set<Count> keywords, int digit)3936 private boolean haveFractions(Set<Count> keywords, int digit) { 3937 for (Count c : keywords) { 3938 int size = countToDigitToIntegerSamples9999.get(c)[digit].fractionSize(); 3939 if (size < MAX_COLLECTED_FRACTION) { 3940 return false; 3941 } 3942 } 3943 return true; 3944 } 3945 3946 static final int MAX_COLLECTED_FRACTION = 5; 3947 addDigit( Map<Count, SampleList[]> countToDigitToIntegerSamples9999, FixedDecimal i, Count count, int digit)3948 private boolean addDigit( 3949 Map<Count, SampleList[]> countToDigitToIntegerSamples9999, 3950 FixedDecimal i, 3951 Count count, 3952 int digit) { 3953 return addFraction(i, countToDigitToIntegerSamples9999.get(count)[digit]); 3954 } 3955 addFraction(FixedDecimal i, SampleList sampleList)3956 private boolean addFraction(FixedDecimal i, SampleList sampleList) { 3957 if (sampleList.fractionSize() < MAX_COLLECTED_FRACTION) { 3958 sampleList.add(i); 3959 return true; 3960 } else { 3961 return false; 3962 } 3963 } 3964 addSimple( Map<Count, SampleList> countToIntegerSamples9999, FixedDecimal i, Count count)3965 private boolean addSimple( 3966 Map<Count, SampleList> countToIntegerSamples9999, FixedDecimal i, Count count) { 3967 return addFraction(i, countToIntegerSamples9999.get(count)); 3968 } 3969 addDigit( Map<Count, SampleList[]> countToDigitToIntegerSamples9999, int i, Count count, int digit)3970 private void addDigit( 3971 Map<Count, SampleList[]> countToDigitToIntegerSamples9999, 3972 int i, 3973 Count count, 3974 int digit) { 3975 countToDigitToIntegerSamples9999.get(count)[digit].add(i); 3976 } 3977 addSimple( Map<Count, SampleList> countToIntegerSamples9999, int i, Count count)3978 private void addSimple( 3979 Map<Count, SampleList> countToIntegerSamples9999, int i, Count count) { 3980 countToIntegerSamples9999.get(count).add(i); 3981 } 3982 get(Count type)3983 public SampleList get(Count type) { 3984 return countToIntegerSamples9999.get(type); 3985 } 3986 get(Count c, int digit)3987 public SampleList get(Count c, int digit) { 3988 SampleList[] sampleLists = countToDigitToIntegerSamples9999.get(c); 3989 return sampleLists == null ? null : sampleLists[digit]; 3990 } 3991 } 3992 3993 /** 3994 * Immutable class with plural info for different locales 3995 * 3996 * @author markdavis 3997 */ 3998 public static class PluralInfo implements Comparable<PluralInfo> { 3999 static final Set<Double> explicits = new HashSet<>(); 4000 4001 static { 4002 explicits.add(0.0d); 4003 explicits.add(1.0d); 4004 } 4005 4006 public enum Count { 4007 zero, 4008 one, 4009 two, 4010 few, 4011 many, 4012 other; 4013 public static final int LENGTH = Count.values().length; 4014 public static final List<Count> VALUES = 4015 Collections.unmodifiableList(Arrays.asList(values())); 4016 } 4017 4018 static final Pattern pluralPaths = PatternCache.get(".*pluralRule.*"); 4019 static final int fractDecrement = 13; 4020 static final int fractStart = 20; 4021 4022 private final Map<Count, Set<Double>> countToExampleSet; 4023 private final Map<Count, String> countToStringExample; 4024 private final Map<Integer, Count> exampleToCount; 4025 private final PluralRules pluralRules; 4026 private final String pluralRulesString; 4027 private final Set<String> canonicalKeywords; 4028 private final Set<Count> keywords; 4029 private final Set<Count> integerKeywords; 4030 private final Set<Count> decimalKeywords; 4031 private final CountSampleList countSampleList; 4032 private final Map<Count, String> countToRule; 4033 private final Set<Count> adjustedCounts; 4034 private final Set<String> adjustedCountStrings; 4035 4036 // e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5 4037 static final Pattern hasE = Pattern.compile("e\\s*!?="); 4038 PluralInfo(Map<Count, String> countToRule, PluralType pluralType)4039 private PluralInfo(Map<Count, String> countToRule, PluralType pluralType) { 4040 EnumMap<Count, String> tempCountToRule = new EnumMap<>(Count.class); 4041 tempCountToRule.putAll(countToRule); 4042 this.countToRule = Collections.unmodifiableMap(tempCountToRule); 4043 4044 // now build rules 4045 NumberFormat nf = NumberFormat.getNumberInstance(ULocale.ENGLISH); 4046 nf.setMaximumFractionDigits(2); 4047 StringBuilder pluralRuleBuilder = new StringBuilder(); 4048 for (Count count : countToRule.keySet()) { 4049 if (pluralRuleBuilder.length() != 0) { 4050 pluralRuleBuilder.append(';'); 4051 } 4052 pluralRuleBuilder.append(count).append(':').append(countToRule.get(count)); 4053 } 4054 pluralRulesString = pluralRuleBuilder.toString(); 4055 try { 4056 pluralRules = PluralRules.parseDescription(pluralRulesString); 4057 } catch (ParseException e) { 4058 throw new IllegalArgumentException( 4059 "Can't create plurals from <" + pluralRulesString + ">", e); 4060 } 4061 EnumSet<Count> _keywords = EnumSet.noneOf(Count.class); 4062 EnumSet<Count> _integerKeywords = EnumSet.noneOf(Count.class); 4063 EnumSet<Count> _decimalKeywords = EnumSet.noneOf(Count.class); 4064 Matcher hasEMatcher = hasE.matcher(""); 4065 Set<Count> _adjustedCounts = null; 4066 Set<String> _adjustedCountStrings = null; 4067 4068 for (String s : pluralRules.getKeywords()) { 4069 Count c = Count.valueOf(s); 4070 _keywords.add(c); 4071 if (pluralRules.getDecimalSamples(s, SampleType.DECIMAL) != null) { 4072 _decimalKeywords.add(c); 4073 } else { 4074 int debug = 1; 4075 } 4076 if (pluralRules.getDecimalSamples(s, SampleType.INTEGER) != null) { 4077 _integerKeywords.add(c); 4078 } else { 4079 int debug = 1; 4080 } 4081 String parsedRules = pluralRules.getRules(s); 4082 if (!hasEMatcher.reset(parsedRules).find()) { 4083 if (_adjustedCounts == null) { 4084 _adjustedCounts = new TreeSet<>(); 4085 _adjustedCountStrings = new TreeSet<>(); 4086 } 4087 _adjustedCounts.add(c); 4088 _adjustedCountStrings.add(s); 4089 } 4090 } 4091 adjustedCounts = 4092 _adjustedCounts == null 4093 ? Collections.emptySet() 4094 : ImmutableSet.copyOf(_adjustedCounts); 4095 adjustedCountStrings = 4096 _adjustedCounts == null 4097 ? Collections.emptySet() 4098 : ImmutableSet.copyOf(_adjustedCountStrings); 4099 4100 keywords = Collections.unmodifiableSet(_keywords); 4101 decimalKeywords = Collections.unmodifiableSet(_decimalKeywords); 4102 integerKeywords = Collections.unmodifiableSet(_integerKeywords); 4103 4104 countSampleList = new CountSampleList(pluralRules, keywords, pluralType); 4105 4106 Map<Count, Set<Double>> countToExampleSetRaw = new TreeMap<>(); 4107 Map<Integer, Count> exampleToCountRaw = new TreeMap<>(); 4108 4109 Output<Map<Count, SampleList[]>> output = new Output(); 4110 4111 // double check 4112 // if (!targetKeywords.equals(typeToExamples2.keySet())) { 4113 // throw new IllegalArgumentException ("Problem in plurals " + targetKeywords + ", " + 4114 // this); 4115 // } 4116 // now fix the longer examples 4117 String otherFractionalExamples = ""; 4118 List<Double> otherFractions = new ArrayList<>(0); 4119 4120 // add fractional samples 4121 Map<Count, String> countToStringExampleRaw = new TreeMap<>(); 4122 for (Count type : keywords) { 4123 SampleList uset = countSampleList.get(type); 4124 countToStringExampleRaw.put(type, uset.toString(5, 5)); 4125 } 4126 final String baseOtherExamples = countToStringExampleRaw.get(Count.other); 4127 String otherExamples = 4128 (baseOtherExamples == null ? "" : baseOtherExamples + "; ") 4129 + otherFractionalExamples 4130 + "..."; 4131 countToStringExampleRaw.put(Count.other, otherExamples); 4132 4133 // Now do double examples (previously unused & not working). 4134 // Currently a bit of a hack, we should enhance SampleList to make this easier 4135 // and then use SampleList directly, see http://unicode.org/cldr/trac/ticket/9813 4136 for (Count type : countToStringExampleRaw.keySet()) { 4137 Set<Double> doublesSet = new LinkedHashSet<>(0); 4138 String examples = countToStringExampleRaw.get(type); 4139 if (examples == null) { 4140 examples = ""; 4141 } 4142 String strippedExamples = examples.replaceAll("(, …)|(; ...)", ""); 4143 String[] exampleArray = strippedExamples.split("(, )|(-)"); 4144 for (String example : exampleArray) { 4145 if (example == null || example.length() == 0) { 4146 continue; 4147 } 4148 Double doubleValue = Double.valueOf(example); 4149 doublesSet.add(doubleValue); 4150 } 4151 doublesSet = Collections.unmodifiableSet(doublesSet); 4152 countToExampleSetRaw.put(type, doublesSet); 4153 } 4154 4155 countToExampleSet = Collections.unmodifiableMap(countToExampleSetRaw); 4156 countToStringExample = Collections.unmodifiableMap(countToStringExampleRaw); 4157 exampleToCount = Collections.unmodifiableMap(exampleToCountRaw); 4158 Set<String> temp = new LinkedHashSet<>(); 4159 // String keyword = pluralRules.select(0.0d); 4160 // double value = pluralRules.getUniqueKeywordValue(keyword); 4161 // if (value == pluralRules.NO_UNIQUE_VALUE) { 4162 // temp.add("0"); 4163 // } 4164 // keyword = pluralRules.select(1.0d); 4165 // value = pluralRules.getUniqueKeywordValue(keyword); 4166 // if (value == pluralRules.NO_UNIQUE_VALUE) { 4167 // temp.add("1"); 4168 // } 4169 Set<String> keywords = pluralRules.getKeywords(); 4170 for (Count count : Count.values()) { 4171 String keyword = count.toString(); 4172 if (keywords.contains(keyword)) { 4173 temp.add(keyword); 4174 } 4175 } 4176 // if (false) { 4177 // change to this after rationalizing 0/1 4178 // temp.add("0"); 4179 // temp.add("1"); 4180 // for (Count count : Count.values()) { 4181 // temp.add(count.toString()); 4182 // KeywordStatus status = 4183 // org.unicode.cldr.util.PluralRulesUtil.getKeywordStatus(pluralRules, 4184 // count.toString(), 0, explicits, true); 4185 // if (status != KeywordStatus.SUPPRESSED && status != KeywordStatus.INVALID) { 4186 // temp.add(count.toString()); 4187 // } 4188 // } 4189 // } 4190 canonicalKeywords = Collections.unmodifiableSet(temp); 4191 } 4192 4193 @Override toString()4194 public String toString() { 4195 return countToExampleSet + "; " + exampleToCount + "; " + pluralRules; 4196 } 4197 getCountToExamplesMap()4198 public Map<Count, Set<Double>> getCountToExamplesMap() { 4199 return countToExampleSet; 4200 } 4201 getCountToStringExamplesMap()4202 public Map<Count, String> getCountToStringExamplesMap() { 4203 return countToStringExample; 4204 } 4205 getCount(double exampleCount)4206 public Count getCount(double exampleCount) { 4207 return Count.valueOf(pluralRules.select(exampleCount)); 4208 } 4209 getCount(DecimalQuantity exampleCount)4210 public Count getCount(DecimalQuantity exampleCount) { 4211 return Count.valueOf(pluralRules.select(exampleCount)); 4212 } 4213 getPluralRules()4214 public PluralRules getPluralRules() { 4215 return pluralRules; 4216 } 4217 getRules()4218 public String getRules() { 4219 return pluralRulesString; 4220 } 4221 getDefault()4222 public Count getDefault() { 4223 return null; 4224 } 4225 getCanonicalKeywords()4226 public Set<String> getCanonicalKeywords() { 4227 return canonicalKeywords; 4228 } 4229 getCounts()4230 public Set<Count> getCounts() { 4231 return keywords; 4232 } 4233 4234 /** 4235 * Return the counts returned by the plural rules, adjusted to remove values that are not 4236 * used in collecting data. 4237 */ getAdjustedCounts()4238 public Set<Count> getAdjustedCounts() { 4239 return adjustedCounts; 4240 } 4241 4242 /** 4243 * Return the counts returned by the plural rules, adjusted to remove values that are not 4244 * used in collecting data. 4245 */ getAdjustedCountStrings()4246 public Set<String> getAdjustedCountStrings() { 4247 return adjustedCountStrings; 4248 } 4249 getCounts(SampleType sampleType)4250 public Set<Count> getCounts(SampleType sampleType) { 4251 return sampleType == SampleType.DECIMAL ? decimalKeywords : integerKeywords; 4252 } 4253 4254 /** 4255 * Return the integer samples from 0 to 9999. For simplicity and compactness, this is a 4256 * UnicodeSet, but the interpretation is simply as a list of integers. UnicodeSet.EMPTY is 4257 * returned if there are none. 4258 * 4259 * @param c 4260 * @return 4261 */ getSamples9999(Count c)4262 public SampleList getSamples9999(Count c) { 4263 return countSampleList.get(c); 4264 } 4265 4266 /** 4267 * Return the integer samples for the specified digit, eg 1 => 0..9. For simplicity and 4268 * compactness, this is a UnicodeSet, but the interpretation is simply as a list of 4269 * integers. 4270 * 4271 * @param c 4272 * @return 4273 */ getSamples9999(Count c, int digit)4274 public SampleList getSamples9999(Count c, int digit) { 4275 return countSampleList.get(c, digit); 4276 } 4277 hasSamples(Count c, int digits)4278 public boolean hasSamples(Count c, int digits) { 4279 SampleList samples = countSampleList.get(c, digits); 4280 return samples != null && (samples.fractionSize() > 0 || samples.intSize() > 0); 4281 } 4282 getRule(Count keyword)4283 public String getRule(Count keyword) { 4284 return countToRule.get(keyword); 4285 } 4286 4287 @Override compareTo(PluralInfo other)4288 public int compareTo(PluralInfo other) { 4289 int size1 = this.countToRule.size(); 4290 int size2 = other.countToRule.size(); 4291 int diff = size1 - size2; 4292 if (diff != 0) { 4293 return diff; 4294 } 4295 Iterator<Count> it1 = countToRule.keySet().iterator(); 4296 Iterator<Count> it2 = other.countToRule.keySet().iterator(); 4297 while (it1.hasNext()) { 4298 Count a1 = it1.next(); 4299 Count a2 = it2.next(); 4300 diff = a1.ordinal() - a2.ordinal(); 4301 if (diff != 0) { 4302 return diff; 4303 } 4304 } 4305 return pluralRules.compareTo(other.pluralRules); 4306 } 4307 4308 enum MinMax { 4309 MIN, 4310 MAX 4311 } 4312 4313 public static final DecimalQuantity NEGATIVE_INFINITY = 4314 new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY); 4315 public static final DecimalQuantity POSITIVE_INFINITY = 4316 new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY); 4317 doubleValue(DecimalQuantity a)4318 static double doubleValue(DecimalQuantity a) { 4319 return a.toDouble(); 4320 } 4321 rangeExists( Count s, Count e, Output<DecimalQuantity> minSample, Output<DecimalQuantity> maxSample)4322 public boolean rangeExists( 4323 Count s, 4324 Count e, 4325 Output<DecimalQuantity> minSample, 4326 Output<DecimalQuantity> maxSample) { 4327 if (!getCounts().contains(s) || !getCounts().contains(e)) { 4328 return false; 4329 } 4330 DecimalQuantity temp; 4331 minSample.value = 4332 getLeastIn(s, SampleType.INTEGER, NEGATIVE_INFINITY, POSITIVE_INFINITY); 4333 temp = getLeastIn(s, SampleType.DECIMAL, NEGATIVE_INFINITY, POSITIVE_INFINITY); 4334 if (lessOrFewerDecimals(temp, minSample.value)) { 4335 minSample.value = temp; 4336 } 4337 maxSample.value = 4338 getGreatestIn(e, SampleType.INTEGER, NEGATIVE_INFINITY, POSITIVE_INFINITY); 4339 temp = getGreatestIn(e, SampleType.DECIMAL, NEGATIVE_INFINITY, POSITIVE_INFINITY); 4340 if (greaterOrFewerDecimals(temp, maxSample.value)) { 4341 maxSample.value = temp; 4342 } 4343 // if there is no range, just return 4344 if (doubleValue(minSample.value) >= doubleValue(maxSample.value)) { 4345 return false; 4346 } 4347 // see if we can get a better range, with not such a large end range 4348 4349 DecimalQuantity lowestMax = 4350 new DecimalQuantity_DualStorageBCD( 4351 minSample 4352 .value 4353 .toBigDecimal() 4354 .add(new java.math.BigDecimal("0.00001"))); 4355 lowestMax.setMinFraction(5); 4356 SampleType bestType = 4357 getCounts(SampleType.INTEGER).contains(e) 4358 ? SampleType.INTEGER 4359 : SampleType.DECIMAL; 4360 temp = getLeastIn(e, bestType, lowestMax, POSITIVE_INFINITY); 4361 if (lessOrFewerDecimals(temp, maxSample.value)) { 4362 maxSample.value = temp; 4363 } 4364 if (maxSample.value.toDouble() > 100000) { 4365 temp = getLeastIn(e, bestType, lowestMax, POSITIVE_INFINITY); 4366 if (lessOrFewerDecimals(temp, maxSample.value)) { 4367 maxSample.value = temp; 4368 } 4369 } 4370 4371 return true; 4372 } 4373 greaterOrFewerDecimals(DecimalQuantity a, DecimalQuantity b)4374 public boolean greaterOrFewerDecimals(DecimalQuantity a, DecimalQuantity b) { 4375 return doubleValue(a) > doubleValue(b) 4376 || doubleValue(b) == doubleValue(a) 4377 && b.getPluralOperand(Operand.f) > a.getPluralOperand(Operand.f); 4378 } 4379 lessOrFewerDecimals(DecimalQuantity a, DecimalQuantity b)4380 public boolean lessOrFewerDecimals(DecimalQuantity a, DecimalQuantity b) { 4381 return doubleValue(a) < doubleValue(b) 4382 || doubleValue(b) == doubleValue(a) 4383 && b.getPluralOperand(Operand.f) > a.getPluralOperand(Operand.f); 4384 } 4385 getLeastIn( Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max)4386 private DecimalQuantity getLeastIn( 4387 Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max) { 4388 DecimalQuantity result = POSITIVE_INFINITY; 4389 DecimalQuantitySamples sSamples1 = 4390 pluralRules.getDecimalSamples(s.toString(), sampleType); 4391 if (sSamples1 != null) { 4392 for (DecimalQuantitySamplesRange x : sSamples1.samples) { 4393 // overlap in ranges?? 4394 if (doubleValue(x.start) > doubleValue(max) 4395 || doubleValue(x.end) < doubleValue(min)) { 4396 continue; // no, continue 4397 } 4398 // get restricted range 4399 DecimalQuantity minOverlap = 4400 greaterOrFewerDecimals(min, x.start) ? max : x.start; 4401 // DecimalQuantity maxOverlap = lessOrFewerDecimals(max, x.end) ? max : x.end; 4402 4403 // replace if better 4404 if (lessOrFewerDecimals(minOverlap, result)) { 4405 result = minOverlap; 4406 } 4407 } 4408 } 4409 return result; 4410 } 4411 getGreatestIn( Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max)4412 private DecimalQuantity getGreatestIn( 4413 Count s, SampleType sampleType, DecimalQuantity min, DecimalQuantity max) { 4414 DecimalQuantity result = NEGATIVE_INFINITY; 4415 DecimalQuantitySamples sSamples1 = 4416 pluralRules.getDecimalSamples(s.toString(), sampleType); 4417 if (sSamples1 != null) { 4418 for (DecimalQuantitySamplesRange x : sSamples1.getSamples()) { 4419 // overlap in ranges?? 4420 if (doubleValue(x.start) > doubleValue(max) 4421 || doubleValue(x.end) < doubleValue(min)) { 4422 continue; // no, continue 4423 } 4424 // get restricted range 4425 // DecimalQuantity minOverlap = greaterOrFewerDecimals(min, x.start) ? max : 4426 // x.start; 4427 DecimalQuantity maxOverlap = lessOrFewerDecimals(max, x.end) ? max : x.end; 4428 4429 // replace if better 4430 if (greaterOrFewerDecimals(maxOverlap, result)) { 4431 result = maxOverlap; 4432 } 4433 } 4434 } 4435 return result; 4436 } 4437 getNonZeroSampleIfPossible( DecimalQuantitySamples exampleList)4438 public static DecimalQuantity getNonZeroSampleIfPossible( 4439 DecimalQuantitySamples exampleList) { 4440 Set<DecimalQuantitySamplesRange> sampleSet = exampleList.getSamples(); 4441 DecimalQuantity sampleDecimal = null; 4442 // skip 0 if possible 4443 for (DecimalQuantitySamplesRange range : sampleSet) { 4444 sampleDecimal = range.start; 4445 if (sampleDecimal.toDouble() != 0.0) { 4446 break; 4447 } 4448 sampleDecimal = range.end; 4449 if (sampleDecimal.toDouble() != 0.0) { 4450 break; 4451 } 4452 } 4453 return sampleDecimal; 4454 } 4455 } 4456 4457 /** 4458 * @deprecated use {@link #getPlurals(PluralType)} instead 4459 */ 4460 @Deprecated getPluralLocales()4461 public Set<String> getPluralLocales() { 4462 return getPluralLocales(PluralType.cardinal); 4463 } 4464 4465 /** 4466 * @param type 4467 * @return the set of locales that have rules for the specified plural type 4468 */ getPluralLocales(PluralType type)4469 public Set<String> getPluralLocales(PluralType type) { 4470 return localeToPluralInfo2.get(type).keySet(); 4471 } 4472 getPluralRangesLocales()4473 public Set<String> getPluralRangesLocales() { 4474 return localeToPluralRanges.keySet(); 4475 } 4476 getPluralRanges(String locale)4477 public PluralRanges getPluralRanges(String locale) { 4478 return localeToPluralRanges.get(locale); 4479 } 4480 4481 /** 4482 * @deprecated use {@link #getPlurals(PluralType, String)} instead 4483 */ 4484 @Deprecated getPlurals(String locale)4485 public PluralInfo getPlurals(String locale) { 4486 return getPlurals(locale, true); 4487 } 4488 4489 /** 4490 * Returns the plural info for a given locale. 4491 * 4492 * @param locale 4493 * @return 4494 */ getPlurals(PluralType type, String locale)4495 public PluralInfo getPlurals(PluralType type, String locale) { 4496 return getPlurals(type, locale, true); 4497 } 4498 4499 /** 4500 * @deprecated use {@link #getPlurals(PluralType, String, boolean)} instead. 4501 */ 4502 @Deprecated getPlurals(String locale, boolean allowRoot)4503 public PluralInfo getPlurals(String locale, boolean allowRoot) { 4504 return getPlurals(PluralType.cardinal, locale, allowRoot); 4505 } 4506 4507 /** 4508 * Returns the plural info for a given locale. 4509 * 4510 * @param locale 4511 * @param allowRoot 4512 * @param type 4513 * @return 4514 */ getPlurals(PluralType type, String locale, boolean allowRoot)4515 public PluralInfo getPlurals(PluralType type, String locale, boolean allowRoot) { 4516 Map<String, PluralInfo> infoMap = localeToPluralInfo2.get(type); 4517 while (locale != null) { 4518 if (!allowRoot && locale.equals(LocaleNames.ROOT)) { 4519 break; 4520 } 4521 PluralInfo result = infoMap.get(locale); 4522 if (result != null) { 4523 return result; 4524 } 4525 locale = LocaleIDParser.getSimpleParent(locale); 4526 } 4527 return null; 4528 } 4529 4530 /** 4531 * CLDR equivalent of com.ibm.icu.text.PluralRules.forLocale() 4532 * 4533 * @param loc 4534 * @param type 4535 * @return an ICU PluralRules, from CLDR data 4536 */ getPluralRules(ULocale loc, PluralRules.PluralType type)4537 public PluralRules getPluralRules(ULocale loc, PluralRules.PluralType type) { 4538 return getPluralRules(loc.getBaseName(), type); 4539 } 4540 4541 /** 4542 * CLDR equivalent of com.ibm.icu.text.PluralRules.forLocale() 4543 * 4544 * @param loc 4545 * @param type 4546 * @return an ICU PluralRules, from CLDR data 4547 */ getPluralRules(String loc, PluralRules.PluralType type)4548 public PluralRules getPluralRules(String loc, PluralRules.PluralType type) { 4549 return getPlurals(PluralType.fromStandardType(type), loc).getPluralRules(); 4550 } 4551 getDayPeriods(DayPeriodInfo.Type type, String locale)4552 public DayPeriodInfo getDayPeriods(DayPeriodInfo.Type type, String locale) { 4553 Map<String, DayPeriodInfo> map1 = typeToLocaleToDayPeriodInfo.get(type); 4554 while (locale != null) { 4555 DayPeriodInfo result = map1.get(locale); 4556 if (result != null) { 4557 return result; 4558 } 4559 locale = LocaleIDParser.getSimpleParent(locale); 4560 } 4561 return null; 4562 } 4563 getDayPeriodLocales(DayPeriodInfo.Type type)4564 public Set<String> getDayPeriodLocales(DayPeriodInfo.Type type) { 4565 return typeToLocaleToDayPeriodInfo.get(type).keySet(); 4566 } 4567 4568 private static CurrencyNumberInfo DEFAULT_NUMBER_INFO = new CurrencyNumberInfo(2, -1, -1, -1); 4569 getCurrencyNumberInfo(String currency)4570 public CurrencyNumberInfo getCurrencyNumberInfo(String currency) { 4571 CurrencyNumberInfo result = currencyToCurrencyNumberInfo.get(currency); 4572 if (result == null) { 4573 result = DEFAULT_NUMBER_INFO; 4574 } 4575 return result; 4576 } 4577 4578 /** 4579 * Returns ordered set of currency data information 4580 * 4581 * @param territory 4582 * @return 4583 */ getCurrencyDateInfo(String territory)4584 public Set<CurrencyDateInfo> getCurrencyDateInfo(String territory) { 4585 return territoryToCurrencyDateInfo.getAll(territory); 4586 } 4587 4588 /** 4589 * Returns ordered set of currency data information 4590 * 4591 * @return 4592 */ getCurrencyTerritories()4593 public Set<String> getCurrencyTerritories() { 4594 return territoryToCurrencyDateInfo.keySet(); 4595 } 4596 4597 /** 4598 * Returns the ISO4217 currency code of the default currency for a given territory. The default 4599 * currency is the first one listed which is legal tender at the present moment. 4600 * 4601 * @param territory 4602 * @return 4603 */ getDefaultCurrency(String territory)4604 public String getDefaultCurrency(String territory) { 4605 4606 String result = "XXX"; 4607 Set<CurrencyDateInfo> targetCurrencyInfo = getCurrencyDateInfo(territory); 4608 if (targetCurrencyInfo == null) { 4609 /* 4610 * This happens during ConsoleCheckCLDR 4611 * territory = "419" 4612 * path = //ldml/numbers/currencyFormats[@numberSystem="latn"]/currencyFormatLength/currencyFormat[@type="accounting"]/pattern[@type="standard"] 4613 * value = ¤#,##0.00 4614 * Prevent NullPointerException 4615 */ 4616 return result; 4617 } 4618 Date now = new Date(); 4619 for (CurrencyDateInfo cdi : targetCurrencyInfo) { 4620 if (cdi.getStart().before(now) && cdi.getEnd().after(now) && cdi.isLegalTender()) { 4621 result = cdi.getCurrency(); 4622 break; 4623 } 4624 } 4625 return result; 4626 } 4627 4628 /** 4629 * Returns the ISO4217 currency code of the default currency for a given CLDRLocale. The default 4630 * currency is the first one listed which is legal tender at the present moment. 4631 * 4632 * @param loc 4633 * @return 4634 */ getDefaultCurrency(CLDRLocale loc)4635 public String getDefaultCurrency(CLDRLocale loc) { 4636 return getDefaultCurrency(loc.getCountry()); 4637 } 4638 getTerritoryToTelephoneCodeInfo()4639 public Map<String, Set<TelephoneCodeInfo>> getTerritoryToTelephoneCodeInfo() { 4640 return territoryToTelephoneCodeInfo; 4641 } 4642 getTelephoneCodeInfoForTerritory(String territory)4643 public Set<TelephoneCodeInfo> getTelephoneCodeInfoForTerritory(String territory) { 4644 return territoryToTelephoneCodeInfo.get(territory); 4645 } 4646 getTerritoriesForTelephoneCodeInfo()4647 public Set<String> getTerritoriesForTelephoneCodeInfo() { 4648 return territoryToTelephoneCodeInfo.keySet(); 4649 } 4650 4651 private List<String> serialElements; 4652 private Collection<String> distinguishingAttributes; 4653 4654 // @Deprecated 4655 // public List<String> getSerialElements() { 4656 // return serialElements; 4657 // } 4658 4659 // @Deprecated 4660 // public Collection<String> getDistinguishingAttributes() { 4661 // return distinguishingAttributes; 4662 // } 4663 4664 /** 4665 * The Row is: desired, supported, percent, oneway 4666 * 4667 * @param string the type (written-new, for new format) 4668 * @return 4669 */ getLanguageMatcherData(String string)4670 public List<R4<String, String, Integer, Boolean>> getLanguageMatcherData(String string) { 4671 return languageMatch.get(string); 4672 } 4673 getLanguageMatcherKeys()4674 public Set<String> getLanguageMatcherKeys() { 4675 return languageMatch.keySet(); 4676 } 4677 4678 /** Return mapping from type to territory to data. 001 is the default. */ getTerritoryMeasurementData()4679 public Map<MeasurementType, Map<String, String>> getTerritoryMeasurementData() { 4680 return measurementData; 4681 } 4682 4683 /** Return mapping from keys to subtypes */ getBcp47Keys()4684 public Relation<String, String> getBcp47Keys() { 4685 return bcp47Key2Subtypes; 4686 } 4687 4688 /** Return mapping from extensions to keys */ getBcp47Extension2Keys()4689 public Relation<String, String> getBcp47Extension2Keys() { 4690 return bcp47Extension2Keys; 4691 } 4692 4693 /** Return mapping from <key,subtype> to aliases */ getBcp47Aliases()4694 public Relation<R2<String, String>, String> getBcp47Aliases() { 4695 return bcp47Aliases; 4696 } 4697 4698 /** Return mapping from <key,subtype> to description */ getBcp47Descriptions()4699 public Map<R2<String, String>, String> getBcp47Descriptions() { 4700 return bcp47Descriptions; 4701 } 4702 4703 /** Return mapping from <key,subtype> to since */ getBcp47Since()4704 public Map<R2<String, String>, String> getBcp47Since() { 4705 return bcp47Since; 4706 } 4707 4708 /** Return mapping from <key,subtype> to preferred */ getBcp47Preferred()4709 public Map<R2<String, String>, String> getBcp47Preferred() { 4710 return bcp47Preferred; 4711 } 4712 4713 /** Return mapping from <key,subtype> to deprecated */ getBcp47Deprecated()4714 public Map<R2<String, String>, String> getBcp47Deprecated() { 4715 return bcp47Deprecated; 4716 } 4717 4718 /** Return mapping from subtype to deprecated */ getBcp47ValueType()4719 public Map<String, String> getBcp47ValueType() { 4720 return bcp47ValueType; 4721 } 4722 4723 static Set<String> MainTimeZones; 4724 4725 /** 4726 * Return canonical timezones 4727 * 4728 * @return 4729 */ getCanonicalTimeZones()4730 public Set<String> getCanonicalTimeZones() { 4731 synchronized (SupplementalDataInfo.class) { 4732 if (MainTimeZones == null) { 4733 MainTimeZones = new TreeSet<>(); 4734 SupplementalDataInfo info = SupplementalDataInfo.getInstance(); 4735 for (Entry<R2<String, String>, Set<String>> entry : 4736 info.getBcp47Aliases().keyValuesSet()) { 4737 R2<String, String> subtype_aliases = entry.getKey(); 4738 if (!subtype_aliases.get0().equals("timezone")) { 4739 continue; 4740 } 4741 MainTimeZones.add(entry.getValue().iterator().next()); 4742 } 4743 MainTimeZones = Collections.unmodifiableSet(MainTimeZones); 4744 } 4745 return MainTimeZones; 4746 } 4747 } 4748 getMetaZoneRanges(String zone)4749 public Set<MetaZoneRange> getMetaZoneRanges(String zone) { 4750 return zoneToMetaZoneRanges.get(zone); 4751 } 4752 4753 /** 4754 * Return the metazone containing this zone at this date 4755 * 4756 * @param zone 4757 * @param date 4758 * @return 4759 */ getMetaZoneRange(String zone, long date)4760 public MetaZoneRange getMetaZoneRange(String zone, long date) { 4761 Set<MetaZoneRange> metazoneRanges = zoneToMetaZoneRanges.get(zone); 4762 if (metazoneRanges != null) { 4763 for (MetaZoneRange metazoneRange : metazoneRanges) { 4764 if (metazoneRange.dateRange.getFrom() <= date 4765 && date < metazoneRange.dateRange.getTo()) { 4766 return metazoneRange; 4767 } 4768 } 4769 } 4770 return null; 4771 } 4772 isDeprecated(DtdType type, String element, String attribute, String value)4773 public boolean isDeprecated(DtdType type, String element, String attribute, String value) { 4774 if (type.getStatus() == DtdStatus.removed) { 4775 // if the DTD was removed, skip 4776 return true; 4777 } 4778 return DtdData.getInstance(type).isDeprecated(element, attribute, value); 4779 } 4780 isDeprecated(DtdType type, String path)4781 public boolean isDeprecated(DtdType type, String path) { 4782 4783 XPathValue parts = SimpleXPathParts.getFrozenInstance(path); 4784 for (int i = 0; i < parts.size(); ++i) { 4785 String element = parts.getElement(i); 4786 if (isDeprecated(type, element, "*", "*")) { 4787 return true; 4788 } 4789 for (Entry<String, String> entry : parts.getAttributes(i).entrySet()) { 4790 String attribute = entry.getKey(); 4791 String value = entry.getValue(); 4792 if (isDeprecated(type, element, attribute, value)) { 4793 return true; 4794 } 4795 } 4796 } 4797 return false; 4798 } 4799 4800 /** 4801 * Returns map of ID/Type/Value, such as id="$integer" type="regex" value=[0-9]+ 4802 * 4803 * @return 4804 */ getValidityInfo()4805 public Map<String, R2<String, String>> getValidityInfo() { 4806 return validityInfo; 4807 } 4808 4809 /** TC languages or at Basic or better, or worse than Basic (grandfathered or Core). */ getCLDRLanguageCodes()4810 public Set<String> getCLDRLanguageCodes() { 4811 return CLDRLanguageCodes; 4812 } 4813 4814 /** TC languages or those at Basic or better. */ getLanguageTcOrBasic()4815 public Set<String> getLanguageTcOrBasic() { 4816 return Sets.difference(CLDRLanguageCodes, languageNonTcLtBasic); 4817 } 4818 4819 /** Non TC languages that are worse than Basic (grandfathered or Core) */ getLanguageNonTcLtBasic()4820 public Set<String> getLanguageNonTcLtBasic() { 4821 return languageNonTcLtBasic; 4822 } 4823 isCLDRLanguageCode(String code)4824 public boolean isCLDRLanguageCode(String code) { 4825 return CLDRLanguageCodes.contains(code); 4826 } 4827 getCLDRScriptCodes()4828 public Set<String> getCLDRScriptCodes() { 4829 return CLDRScriptCodes; 4830 } 4831 isCLDRScriptCode(String code)4832 public boolean isCLDRScriptCode(String code) { 4833 return CLDRScriptCodes.contains(code); 4834 } 4835 initCLDRLocaleBasedData()4836 private synchronized void initCLDRLocaleBasedData() throws InternalError { 4837 // This initialization depends on SDI being initialized. 4838 if (defaultContentToBase == null) { 4839 Map<CLDRLocale, CLDRLocale> p2c = new TreeMap<>(); 4840 Map<CLDRLocale, CLDRLocale> c2p = new TreeMap<>(); 4841 TreeSet<CLDRLocale> tmpAllLocales = new TreeSet<>(); 4842 // copied from SupplementalData.java - CLDRLocale based 4843 for (String l : defaultContentLocales) { 4844 CLDRLocale child = CLDRLocale.getInstance(l); 4845 tmpAllLocales.add(child); 4846 } 4847 4848 for (CLDRLocale child : tmpAllLocales) { 4849 // Find a parent of this locale which is NOT itself also a defaultContent 4850 CLDRLocale nextParent = child.getParent(); 4851 // /System.err.println(">> considering " + child + " with parent " + nextParent); 4852 while (nextParent != null) { 4853 if (!tmpAllLocales.contains( 4854 nextParent)) { // Did we find a parent that's also not itself a 4855 // defaultContent? 4856 // /System.err.println(">>>> Got 1? considering " + child + " with parent " 4857 // + nextParent); 4858 break; 4859 } 4860 // /System.err.println(">>>>> considering " + child + " with parent " + 4861 // nextParent); 4862 nextParent = nextParent.getParent(); 4863 } 4864 // parent 4865 if (nextParent == null) { 4866 throw new InternalError( 4867 "SupplementalDataInfo.defaultContentToChild(): No valid parent for " 4868 + child); 4869 } else if (nextParent == CLDRLocale.ROOT 4870 || nextParent == CLDRLocale.getInstance(LocaleNames.ROOT)) { 4871 throw new InternalError( 4872 "SupplementalDataInfo.defaultContentToChild(): Parent is root for default content locale " 4873 + child); 4874 } else { 4875 c2p.put(child, nextParent); // wo_Arab_SN -> wo 4876 CLDRLocale oldChild = p2c.get(nextParent); 4877 if (oldChild != null) { 4878 CLDRLocale childParent = child.getParent(); 4879 if (!childParent.equals(oldChild)) { 4880 throw new InternalError( 4881 "SupplementalData.defaultContentToChild(): defaultContent list in wrong order? Tried to map " 4882 + nextParent 4883 + " -> " 4884 + child 4885 + ", replacing " 4886 + oldChild 4887 + " (should have been " 4888 + childParent 4889 + ")"); 4890 } 4891 } 4892 p2c.put(nextParent, child); // wo -> wo_Arab_SN 4893 } 4894 } 4895 4896 // done, save the hashtables.. 4897 baseToDefaultContent = Collections.unmodifiableMap(p2c); // wo -> wo_Arab_SN 4898 defaultContentToBase = Collections.unmodifiableMap(c2p); // wo_Arab_SN -> wo 4899 } 4900 } 4901 getTimeData()4902 public Map<String, PreferredAndAllowedHour> getTimeData() { 4903 return timeData; 4904 } 4905 getDefaultScript(String baseLanguage)4906 public String getDefaultScript(String baseLanguage) { 4907 String ls = likelySubtags.get(baseLanguage); 4908 if (ls == null) { 4909 return UNKNOWN_SCRIPT; 4910 } 4911 LocaleIDParser lp = new LocaleIDParser().set(ls); 4912 String defaultScript = lp.getScript(); 4913 if (defaultScript.length() > 0) { 4914 return defaultScript; 4915 } else { 4916 return UNKNOWN_SCRIPT; 4917 } 4918 } 4919 4920 private XEquivalenceClass<String, String> equivalentLocales = null; 4921 getEquivalentsForLocale(String localeId)4922 public Set<String> getEquivalentsForLocale(String localeId) { 4923 if (equivalentLocales == null) { 4924 equivalentLocales = getEquivalentsForLocale(); 4925 } 4926 Set<String> result = new TreeSet(LENGTH_FIRST); 4927 result.add(localeId); 4928 Set<String> equiv = equivalentLocales.getEquivalences(localeId); 4929 // if (equiv == null) { 4930 // result.add(localeId); 4931 // return result; 4932 // } 4933 if (equiv != null) { 4934 result.addAll(equivalentLocales.getEquivalences(localeId)); 4935 } 4936 Map<String, String> likely = getLikelySubtags(); 4937 String newMax = LikelySubtags.maximize(localeId, likely); 4938 if (newMax != null) { 4939 result.add(newMax); 4940 newMax = LikelySubtags.minimize(localeId, likely, true); 4941 if (newMax != null) { 4942 result.add(newMax); 4943 } 4944 newMax = LikelySubtags.minimize(localeId, likely, false); 4945 if (newMax != null) { 4946 result.add(newMax); 4947 } 4948 } 4949 4950 // if (result.size() == 1) { 4951 // LanguageTagParser ltp = new LanguageTagParser().set(localeId); 4952 // if (ltp.getScript().isEmpty()) { 4953 // String ds = getDefaultScript(ltp.getLanguage()); 4954 // if (ds != null) { 4955 // ltp.setScript(ds); 4956 // result.add(ltp.toString()); 4957 // } 4958 // } 4959 // } 4960 return result; 4961 } 4962 4963 public static final class LengthFirstComparator<T> implements Comparator<T> { 4964 @Override compare(T a, T b)4965 public int compare(T a, T b) { 4966 String as = a.toString(); 4967 String bs = b.toString(); 4968 if (as.length() < bs.length()) return -1; 4969 if (as.length() > bs.length()) return 1; 4970 return as.compareTo(bs); 4971 } 4972 } 4973 4974 public static final LengthFirstComparator LENGTH_FIRST = new LengthFirstComparator(); 4975 getEquivalentsForLocale()4976 private synchronized XEquivalenceClass<String, String> getEquivalentsForLocale() { 4977 SupplementalDataInfo sdi = this; 4978 Relation<String, String> localeToDefaultContents = 4979 Relation.of(new HashMap<String, Set<String>>(), LinkedHashSet.class); 4980 4981 Set<String> dcl = sdi.getDefaultContentLocales(); 4982 Map<String, String> likely = sdi.getLikelySubtags(); 4983 XEquivalenceClass<String, String> locales = new XEquivalenceClass<>(); 4984 LanguageTagParser ltp = new LanguageTagParser(); 4985 Set<String> temp = new HashSet<>(); 4986 for (Entry<String, String> entry : likely.entrySet()) { 4987 String source = entry.getKey(); 4988 if (source.startsWith(LocaleNames.UND)) { 4989 continue; 4990 } 4991 for (String s : getCombinations(source, ltp, likely, temp)) { 4992 locales.add(source, s); 4993 } 4994 for (String s : getCombinations(entry.getValue(), ltp, likely, temp)) { 4995 locales.add(source, s); 4996 } 4997 } 4998 // Set<String> sorted = new TreeSet(locales.getExplicitItems()); 4999 // for (String s : sorted) { 5000 // System.out.println(locales.getEquivalences(s)); 5001 // } 5002 for (String defaultContentLocale : dcl) { 5003 if (defaultContentLocale.startsWith("zh")) { 5004 int x = 0; 5005 } 5006 Set<String> set = locales.getEquivalences(defaultContentLocale); 5007 5008 String parent = LocaleIDParser.getSimpleParent(defaultContentLocale); 5009 if (!set.contains(parent)) { 5010 localeToDefaultContents.put(parent, defaultContentLocale); 5011 // System.out.println("Mismatch " + parent + ", " + set); 5012 } 5013 if (parent.contains("_")) { 5014 continue; 5015 } 5016 // only base locales after this point 5017 String ds = sdi.getDefaultScript(parent); 5018 if (ds != null) { 5019 ltp.set(parent); 5020 ltp.setScript(ds); 5021 String trial = ltp.toString(); 5022 if (!set.contains(trial)) { 5023 // System.out.println("Mismatch " + trial + ", " + set); 5024 localeToDefaultContents.put(parent, trial); 5025 } 5026 } 5027 } 5028 return locales; 5029 } 5030 getCombinations( String source, LanguageTagParser ltp, Map<String, String> likely, Set<String> locales)5031 private Set<String> getCombinations( 5032 String source, LanguageTagParser ltp, Map<String, String> likely, Set<String> locales) { 5033 locales.clear(); 5034 5035 String max = LikelySubtags.maximize(source, likely); 5036 locales.add(max); 5037 5038 ltp.set(source); 5039 ltp.setScript(""); 5040 String trial = ltp.toString(); 5041 String newMax = LikelySubtags.maximize(trial, likely); 5042 if (Objects.equals(newMax, max)) { 5043 locales.add(trial); 5044 } 5045 5046 ltp.set(source); 5047 ltp.setRegion(""); 5048 trial = ltp.toString(); 5049 newMax = LikelySubtags.maximize(trial, likely); 5050 if (Objects.equals(newMax, max)) { 5051 locales.add(trial); 5052 } 5053 5054 return locales; 5055 } 5056 getCldrVersion()5057 public VersionInfo getCldrVersion() { 5058 return cldrVersion; 5059 } 5060 getUnicodeVersionString()5061 public String getUnicodeVersionString() { 5062 return unicodeVersion; 5063 } 5064 getUnicodeVersion()5065 public VersionInfo getUnicodeVersion() { 5066 return VersionInfo.getInstance(getUnicodeVersionString()); 5067 } 5068 getCldrVersionString()5069 public String getCldrVersionString() { 5070 return cldrVersionString; 5071 } 5072 getDirectory()5073 public File getDirectory() { 5074 return directory; 5075 } 5076 5077 public static final Splitter WHITESPACE_SPLTTER = 5078 Splitter.on(PatternCache.get("\\s+")).omitEmptyStrings(); 5079 5080 public static final class AttributeValidityInfo { 5081 // <attributeValues elements="alias" attributes="path" 5082 // type="path">notDoneYet</attributeValues> 5083 5084 final String type; 5085 final Set<DtdType> dtds; 5086 final Set<String> elements; 5087 final Set<String> attributes; 5088 final String order; 5089 5090 @Override toString()5091 public String toString() { 5092 return "type:" 5093 + type 5094 + ", elements:" 5095 + elements 5096 + ", attributes:" 5097 + attributes 5098 + ", order:" 5099 + order; 5100 } 5101 add( Map<String, String> inputAttibutes, String inputValue, Map<AttributeValidityInfo, String> data)5102 static void add( 5103 Map<String, String> inputAttibutes, 5104 String inputValue, 5105 Map<AttributeValidityInfo, String> data) { 5106 final AttributeValidityInfo key = 5107 new AttributeValidityInfo( 5108 inputAttibutes.get("dtds"), 5109 inputAttibutes.get("type"), 5110 inputAttibutes.get("attributes"), 5111 inputAttibutes.get("elements"), 5112 inputAttibutes.get("order")); 5113 if (data.containsKey(key)) { 5114 throw new IllegalArgumentException(key + " declared twice"); 5115 } 5116 data.put(key, inputValue); 5117 } 5118 AttributeValidityInfo( String dtds, String type, String attributes, String elements, String order)5119 public AttributeValidityInfo( 5120 String dtds, String type, String attributes, String elements, String order) { 5121 if (dtds == null) { 5122 this.dtds = Collections.singleton(DtdType.ldml); 5123 } else { 5124 Set<DtdType> temp = EnumSet.noneOf(DtdType.class); 5125 for (String s : WHITESPACE_SPLTTER.split(dtds)) { 5126 temp.add(DtdType.fromElement(s)); 5127 } 5128 this.dtds = Collections.unmodifiableSet(temp); 5129 } 5130 this.type = type != null ? type : order != null ? "choice" : null; 5131 this.elements = 5132 elements == null 5133 ? Collections.EMPTY_SET 5134 : With.in(WHITESPACE_SPLTTER.split(elements)) 5135 .toUnmodifiableCollection(new HashSet<String>()); 5136 this.attributes = 5137 With.in(WHITESPACE_SPLTTER.split(attributes)) 5138 .toUnmodifiableCollection(new HashSet<String>()); 5139 this.order = order; 5140 } 5141 getType()5142 public String getType() { 5143 return type; 5144 } 5145 getDtds()5146 public Set<DtdType> getDtds() { 5147 return dtds; 5148 } 5149 getElements()5150 public Set<String> getElements() { 5151 return elements; 5152 } 5153 getAttributes()5154 public Set<String> getAttributes() { 5155 return attributes; 5156 } 5157 getOrder()5158 public String getOrder() { 5159 return order; 5160 } 5161 5162 @Override equals(Object obj)5163 public boolean equals(Object obj) { 5164 AttributeValidityInfo other = (AttributeValidityInfo) obj; 5165 return CldrUtility.deepEquals( 5166 type, other.type, 5167 dtds, other.dtds, 5168 elements, other.elements, 5169 attributes, other.attributes, 5170 order, other.order); 5171 } 5172 5173 @Override hashCode()5174 public int hashCode() { 5175 return Objects.hash(type, dtds, elements, attributes, order); 5176 } 5177 } 5178 getAttributeValidity()5179 public Map<AttributeValidityInfo, String> getAttributeValidity() { 5180 return attributeValidityInfo; 5181 } 5182 getLanguageGroups()5183 public Multimap<String, String> getLanguageGroups() { 5184 return languageGroups; 5185 } 5186 getUnitConverter()5187 public UnitConverter getUnitConverter() { 5188 return unitConverter; 5189 } 5190 getRationalParser()5191 public RationalParser getRationalParser() { 5192 return rationalParser; 5193 } 5194 getUnitPreferences()5195 public UnitPreferences getUnitPreferences() { 5196 return unitPreferences; 5197 } 5198 getUnitIdComponentType(String component)5199 public UnitIdComponentType getUnitIdComponentType(String component) { 5200 UnitIdComponentType result = unitIdComponentType.get(component); 5201 return result == null ? UnitIdComponentType.base : result; 5202 } 5203 5204 /** Locales that have grammar info */ hasGrammarInfo()5205 public Set<String> hasGrammarInfo() { 5206 return grammarLocaleToTargetToFeatureToValues.keySet(); 5207 } 5208 5209 /** 5210 * Locales that have grammar info for at least one of the features (with the given target and 5211 * scope). 5212 */ getLocalesWithFeatures( GrammaticalTarget target, GrammaticalScope scope, GrammaticalFeature... features)5213 public Set<String> getLocalesWithFeatures( 5214 GrammaticalTarget target, GrammaticalScope scope, GrammaticalFeature... features) { 5215 Set<String> locales = new TreeSet<>(); 5216 for (Entry<String, GrammarInfo> localeAndGrammar : 5217 grammarLocaleToTargetToFeatureToValues.entrySet()) { 5218 final GrammarInfo grammarInfo = localeAndGrammar.getValue(); 5219 for (GrammaticalFeature feature : features) { 5220 Collection<String> featureInfo = grammarInfo.get(target, feature, scope); 5221 if (!featureInfo.isEmpty()) { 5222 locales.add(localeAndGrammar.getKey()); 5223 } 5224 } 5225 } 5226 return ImmutableSet.copyOf(locales); 5227 } 5228 5229 /** 5230 * Grammar info for locales, with inheritance 5231 * 5232 * @param locale 5233 * @return 5234 */ getGrammarInfo(String locale)5235 public GrammarInfo getGrammarInfo(String locale) { 5236 return getGrammarInfo(locale, false); 5237 } 5238 5239 /** 5240 * Special hack for v38; should drop seedOnly later. 5241 * 5242 * @param locale 5243 * @param seedOnly 5244 * @return 5245 */ 5246 @Deprecated getGrammarInfo(String locale, boolean seedOnly)5247 public GrammarInfo getGrammarInfo(String locale, boolean seedOnly) { 5248 for (; locale != null; locale = LocaleIDParser.getParent(locale)) { 5249 if (seedOnly && !GrammarInfo.getGrammarLocales().contains(locale)) { 5250 continue; 5251 } 5252 GrammarInfo result = grammarLocaleToTargetToFeatureToValues.get(locale); 5253 if (result != null) { 5254 return result; 5255 } 5256 } 5257 return null; 5258 } 5259 hasGrammarDerivation()5260 public Set<String> hasGrammarDerivation() { 5261 return localeToGrammarDerivation.keySet(); 5262 } 5263 getGrammarDerivation(String locale)5264 public GrammarDerivation getGrammarDerivation(String locale) { 5265 for (; locale != null; locale = LocaleIDParser.getParent(locale)) { 5266 GrammarDerivation result = localeToGrammarDerivation.get(locale); 5267 if (result != null) { 5268 return result; 5269 } 5270 } 5271 return null; 5272 } 5273 getPersonNameOrder()5274 public Multimap<Order, String> getPersonNameOrder() { 5275 return personNameOrder; 5276 } 5277 getUnitPrefixInfo(String prefix)5278 public UnitPrefixInfo getUnitPrefixInfo(String prefix) { 5279 return unitPrefixInfo.get(prefix); 5280 } 5281 getUnitPrefixes()5282 public Set<String> getUnitPrefixes() { 5283 return unitPrefixInfo.keySet(); 5284 } 5285 5286 /** 5287 * Filter out deprecated items. This is more complicated than it seems. The deprecation is in 5288 * timezones.xml, eg: <type name="cathu" description="Thunder Bay, Canada" deprecated="true" 5289 * preferred="cator"/> <type name="cator" description="Toronto, Canada" alias="America/Toronto 5290 * America/Montreal Canada/Eastern America/Nipigon America/Thunder_Bay"/> We need to find the 5291 * short id's that are deprecated, put there is a problem due to 5292 * https://unicode-org.atlassian.net/browse/CLDR-17412. 5293 * 5294 * <p>America/Nipigon, America/Thunder_Bay, America/Rainy_River 5295 */ 5296 Supplier<Set<String>> goodTimezones = 5297 Suppliers.memoize( 5298 new Supplier<Set<String>>() { 5299 5300 @Override 5301 public Set<String> get() { 5302 Set<String> availableLongTz = sc.getAvailableCodes(CodeType.tzid); 5303 Set<String> result = null; 5304 if (true) { // hack for now 5305 final Set<String> hack = 5306 Set.of( 5307 "America/Santa_Isabel", 5308 "Australia/Currie", 5309 "America/Yellowknife", 5310 "America/Rainy_River", 5311 "America/Thunder_Bay", 5312 "America/Nipigon", 5313 "America/Pangnirtung", 5314 "Europe/Uzhgorod", 5315 "Europe/Zaporozhye", 5316 "Pacific/Johnston"); 5317 result = Set.copyOf(Sets.difference(availableLongTz, hack)); 5318 } else { // TODO restore when CLDR-17412 is fixed 5319 Map<String, String> aliasToRegular = 5320 bcp47KeyToAliasToSubtype.get("tz"); 5321 Map<String, Bcp47KeyInfo> subtypeToInfo = 5322 bcp47KeyToSubtypeToInfo.get("tz"); 5323 result = 5324 availableLongTz.stream() 5325 .filter( 5326 x -> { 5327 String shortId = aliasToRegular.get(x); 5328 Bcp47KeyInfo info = 5329 subtypeToInfo.get(shortId); 5330 System.out.println( 5331 String.format( 5332 "%s %s %s", 5333 x, shortId, info)); 5334 return !info.deprecated; 5335 }) 5336 .collect(Collectors.toUnmodifiableSet()); 5337 } 5338 return result; 5339 } 5340 }); 5341 getCLDRTimezoneCodes()5342 public Set<String> getCLDRTimezoneCodes() { 5343 return goodTimezones.get(); 5344 } 5345 } 5346