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