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.Rational.RationalParser; 42 import org.unicode.cldr.util.StandardCodes.LstrType; 43 import org.unicode.cldr.util.SupplementalDataInfo.BasicLanguageData.Type; 44 import org.unicode.cldr.util.SupplementalDataInfo.NumberingSystemInfo.NumberingSystemType; 45 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count; 46 import org.unicode.cldr.util.Validity.Status; 47 48 import com.google.common.base.Joiner; 49 import com.google.common.base.Splitter; 50 import com.google.common.collect.ImmutableList; 51 import com.google.common.collect.ImmutableSet; 52 import com.google.common.collect.ImmutableSetMultimap; 53 import com.google.common.collect.Multimap; 54 import com.google.common.collect.TreeMultimap; 55 import com.ibm.icu.impl.IterableComparator; 56 import com.ibm.icu.impl.Relation; 57 import com.ibm.icu.impl.Row; 58 import com.ibm.icu.impl.Row.R2; 59 import com.ibm.icu.impl.Row.R4; 60 import com.ibm.icu.text.DateFormat; 61 import com.ibm.icu.text.MessageFormat; 62 import com.ibm.icu.text.NumberFormat; 63 import com.ibm.icu.text.PluralRules; 64 import com.ibm.icu.text.PluralRules.FixedDecimal; 65 import com.ibm.icu.text.PluralRules.FixedDecimalRange; 66 import com.ibm.icu.text.PluralRules.FixedDecimalSamples; 67 import com.ibm.icu.text.PluralRules.SampleType; 68 import com.ibm.icu.text.SimpleDateFormat; 69 import com.ibm.icu.text.UnicodeSet; 70 import com.ibm.icu.util.Freezable; 71 import com.ibm.icu.util.ICUUncheckedIOException; 72 import com.ibm.icu.util.Output; 73 import com.ibm.icu.util.TimeZone; 74 import com.ibm.icu.util.ULocale; 75 import com.ibm.icu.util.VersionInfo; 76 77 /** 78 * Singleton class to provide API access to supplemental data -- in all the supplemental data files. 79 * <p> 80 * To create, use SupplementalDataInfo.getInstance 81 * <p> 82 * To add API for new structure, you will generally: 83 * <ul> 84 * <li>add a Map or Relation as a data member, 85 * <li>put a check and handler in MyHandler for the paths that you consume, 86 * <li>make the data member immutable in makeStuffSave, and 87 * <li>add a getter for the data member 88 * </ul> 89 * 90 * @author markdavis 91 */ 92 93 public class SupplementalDataInfo { 94 private static final boolean DEBUG = false; 95 private static final StandardCodes sc = StandardCodes.make(); 96 private static final String UNKNOWN_SCRIPT = "Zzzz"; 97 98 public static final Splitter split_space = Splitter.on(' ').omitEmptyStrings(); 99 100 // TODO add structure for items shown by TestSupplementalData to be missing 101 /* 102 * [calendarData/calendar, 103 * characters/character-fallback, 104 * measurementData/measurementSystem, measurementData/paperSize, 105 * metadata/attributeOrder, metadata/blocking, metadata/deprecated, 106 * metadata/distinguishing, metadata/elementOrder, metadata/serialElements, metadata/skipDefaultLocale, 107 * metadata/suppress, metadata/validity, metazoneInfo/timezone, 108 * timezoneData/mapTimezones, 109 * weekData/firstDay, weekData/minDays, weekData/weekendEnd, weekData/weekendStart] 110 */ 111 // TODO: verify that we get everything by writing the files solely from the API, and verifying identity. 112 113 /** 114 * Official status of languages 115 */ 116 public enum OfficialStatus { 117 unknown("U", 1), recognized("R", 1), official_minority("OM", 2), official_regional("OR", 3), de_facto_official("OD", 10), official("O", 10); 118 119 private final String shortName; 120 private final int weight; 121 OfficialStatus(String shortName, int weight)122 private OfficialStatus(String shortName, int weight) { 123 this.shortName = shortName; 124 this.weight = weight; 125 } 126 toShortString()127 public String toShortString() { 128 return shortName; 129 } 130 getWeight()131 public int getWeight() { 132 return weight; 133 } 134 isMajor()135 public boolean isMajor() { 136 return compareTo(OfficialStatus.de_facto_official) >= 0; 137 } 138 isOfficial()139 public boolean isOfficial() { 140 return compareTo(OfficialStatus.official_regional) >= 0; 141 } 142 } 143 144 /** 145 * Population data for different languages. 146 */ 147 public static final class PopulationData implements Freezable<PopulationData> { 148 private double population = Double.NaN; 149 150 private double literatePopulation = Double.NaN; 151 152 private double writingPopulation = Double.NaN; 153 154 private double gdp = Double.NaN; 155 156 private OfficialStatus officialStatus = OfficialStatus.unknown; 157 getGdp()158 public double getGdp() { 159 return gdp; 160 } 161 getLiteratePopulation()162 public double getLiteratePopulation() { 163 return literatePopulation; 164 } 165 getLiteratePopulationPercent()166 public double getLiteratePopulationPercent() { 167 return 100 * literatePopulation / population; 168 } 169 getWritingPopulation()170 public double getWritingPopulation() { 171 return writingPopulation; 172 } 173 getWritingPercent()174 public double getWritingPercent() { 175 return 100 * writingPopulation / population; 176 } 177 getPopulation()178 public double getPopulation() { 179 return population; 180 } 181 setGdp(double gdp)182 public PopulationData setGdp(double gdp) { 183 if (frozen) { 184 throw new UnsupportedOperationException( 185 "Attempt to modify frozen object"); 186 } 187 this.gdp = gdp; 188 return this; 189 } 190 setLiteratePopulation(double literatePopulation)191 public PopulationData setLiteratePopulation(double literatePopulation) { 192 if (frozen) { 193 throw new UnsupportedOperationException( 194 "Attempt to modify frozen object"); 195 } 196 this.literatePopulation = literatePopulation; 197 return this; 198 } 199 setPopulation(double population)200 public PopulationData setPopulation(double population) { 201 if (frozen) { 202 throw new UnsupportedOperationException( 203 "Attempt to modify frozen object"); 204 } 205 this.population = population; 206 return this; 207 } 208 set(PopulationData other)209 public PopulationData set(PopulationData other) { 210 if (frozen) { 211 throw new UnsupportedOperationException( 212 "Attempt to modify frozen object"); 213 } 214 if (other == null) { 215 population = literatePopulation = gdp = Double.NaN; 216 } else { 217 population = other.population; 218 literatePopulation = other.literatePopulation; 219 writingPopulation = other.writingPopulation; 220 gdp = other.gdp; 221 } 222 return this; 223 } 224 add(PopulationData other)225 public void add(PopulationData other) { 226 if (frozen) { 227 throw new UnsupportedOperationException( 228 "Attempt to modify frozen object"); 229 } 230 population += other.population; 231 literatePopulation += other.literatePopulation; 232 writingPopulation += other.writingPopulation; 233 gdp += other.gdp; 234 } 235 236 @Override toString()237 public String toString() { 238 return MessageFormat 239 .format( 240 "[pop: {0,number,#,##0},\t lit: {1,number,#,##0.00},\t gdp: {2,number,#,##0},\t status: {3}]", 241 new Object[] { population, literatePopulation, gdp, officialStatus }); 242 } 243 244 private boolean frozen; 245 246 @Override isFrozen()247 public boolean isFrozen() { 248 return frozen; 249 } 250 251 @Override freeze()252 public PopulationData freeze() { 253 frozen = true; 254 return this; 255 } 256 257 @Override cloneAsThawed()258 public PopulationData cloneAsThawed() { 259 throw new UnsupportedOperationException("not yet implemented"); 260 } 261 getOfficialStatus()262 public OfficialStatus getOfficialStatus() { 263 return officialStatus; 264 } 265 setOfficialStatus(OfficialStatus officialStatus)266 public PopulationData setOfficialStatus(OfficialStatus officialStatus) { 267 if (frozen) { 268 throw new UnsupportedOperationException( 269 "Attempt to modify frozen object"); 270 } 271 this.officialStatus = officialStatus; 272 return this; 273 } 274 setWritingPopulation(double writingPopulation)275 public PopulationData setWritingPopulation(double writingPopulation) { 276 if (frozen) { 277 throw new UnsupportedOperationException( 278 "Attempt to modify frozen object"); 279 } 280 this.writingPopulation = writingPopulation; 281 return this; 282 } 283 } 284 285 static final Pattern WHITESPACE_PATTERN = PatternCache.get("\\s+"); 286 287 /** 288 * Simple language/script/region information 289 */ 290 public static class BasicLanguageData implements Comparable<BasicLanguageData>, 291 com.ibm.icu.util.Freezable<BasicLanguageData> { 292 public enum Type { 293 primary, secondary 294 } 295 296 private Type type = Type.primary; 297 298 private Set<String> scripts = Collections.emptySet(); 299 300 private Set<String> territories = Collections.emptySet(); 301 getType()302 public Type getType() { 303 return type; 304 } 305 setType(Type type)306 public BasicLanguageData setType(Type type) { 307 this.type = type; 308 return this; 309 } 310 setScripts(String scriptTokens)311 public BasicLanguageData setScripts(String scriptTokens) { 312 return setScripts(scriptTokens == null ? null : Arrays 313 .asList(WHITESPACE_PATTERN.split(scriptTokens))); 314 } 315 setTerritories(String territoryTokens)316 public BasicLanguageData setTerritories(String territoryTokens) { 317 return setTerritories(territoryTokens == null ? null : Arrays 318 .asList(WHITESPACE_PATTERN.split(territoryTokens))); 319 } 320 setScripts(Collection<String> scriptTokens)321 public BasicLanguageData setScripts(Collection<String> scriptTokens) { 322 if (frozen) { 323 throw new UnsupportedOperationException(); 324 } 325 // TODO add error checking 326 scripts = Collections.emptySet(); 327 if (scriptTokens != null) { 328 for (String script : scriptTokens) { 329 addScript(script); 330 } 331 } 332 return this; 333 } 334 setTerritories(Collection<String> territoryTokens)335 public BasicLanguageData setTerritories(Collection<String> territoryTokens) { 336 if (frozen) { 337 throw new UnsupportedOperationException(); 338 } 339 territories = Collections.emptySet(); 340 if (territoryTokens != null) { 341 for (String territory : territoryTokens) { 342 addTerritory(territory); 343 } 344 } 345 return this; 346 } 347 set(BasicLanguageData other)348 public BasicLanguageData set(BasicLanguageData other) { 349 scripts = other.scripts; 350 territories = other.territories; 351 return this; 352 } 353 getScripts()354 public Set<String> getScripts() { 355 return scripts; 356 } 357 getTerritories()358 public Set<String> getTerritories() { 359 return territories; 360 } 361 toString(String languageSubtag)362 public String toString(String languageSubtag) { 363 if (scripts.size() == 0 && territories.size() == 0) 364 return ""; 365 return "\t\t<language type=\"" 366 + languageSubtag 367 + "\"" 368 + (scripts.size() == 0 ? "" : " scripts=\"" 369 + CldrUtility.join(scripts, " ") + "\"") 370 + (territories.size() == 0 ? "" : " territories=\"" 371 + CldrUtility.join(territories, " ") + "\"") 372 + (type == Type.primary ? "" : " alt=\"" + type + "\"") + "/>"; 373 } 374 375 @Override toString()376 public String toString() { 377 return "[" + type 378 + (scripts.isEmpty() ? "" : "; scripts=" + Joiner.on(" ").join(scripts)) 379 + (scripts.isEmpty() ? "" : "; territories=" + Joiner.on(" ").join(territories)) 380 + "]"; 381 } 382 383 @Override compareTo(BasicLanguageData o)384 public int compareTo(BasicLanguageData o) { 385 int result; 386 if (0 != (result = type.compareTo(o.type))) 387 return result; 388 if (0 != (result = IterableComparator.compareIterables(scripts, o.scripts))) 389 return result; 390 if (0 != (result = IterableComparator.compareIterables(territories, o.territories))) 391 return result; 392 return 0; 393 } 394 395 @Override equals(Object input)396 public boolean equals(Object input) { 397 return compareTo((BasicLanguageData) input) == 0; 398 } 399 400 @Override hashCode()401 public int hashCode() { 402 // TODO Auto-generated method stub 403 return ((type.ordinal() * 37 + scripts.hashCode()) * 37) + territories.hashCode(); 404 } 405 addScript(String script)406 public BasicLanguageData addScript(String script) { 407 // simple error checking 408 if (script.length() != 4) { 409 throw new IllegalArgumentException("Illegal Script: " + script); 410 } 411 if (scripts == Collections.EMPTY_SET) { 412 scripts = new TreeSet<>(); 413 } 414 scripts.add(script); 415 return this; 416 } 417 addTerritory(String territory)418 public BasicLanguageData addTerritory(String territory) { 419 // simple error checking 420 if (territory.length() != 2) { 421 throw new IllegalArgumentException("Illegal Territory: " + territory); 422 } 423 if (territories == Collections.EMPTY_SET) { 424 territories = new TreeSet<>(); 425 } 426 territories.add(territory); 427 return this; 428 } 429 430 boolean frozen = false; 431 432 @Override isFrozen()433 public boolean isFrozen() { 434 return frozen; 435 } 436 437 @Override freeze()438 public BasicLanguageData freeze() { 439 frozen = true; 440 if (scripts != Collections.EMPTY_SET) { 441 scripts = Collections.unmodifiableSet(scripts); 442 } 443 if (territories != Collections.EMPTY_SET) { 444 territories = Collections.unmodifiableSet(territories); 445 } 446 return this; 447 } 448 449 @Override cloneAsThawed()450 public BasicLanguageData cloneAsThawed() { 451 BasicLanguageData result = new BasicLanguageData(); 452 result.scripts = new TreeSet<>(scripts); 453 result.territories = new TreeSet<>(territories); 454 return this; 455 } 456 addScripts(Set<String> scripts2)457 public void addScripts(Set<String> scripts2) { 458 for (String script : scripts2) { 459 addScript(script); 460 } 461 } 462 } 463 464 /** 465 * Information about currency digits and rounding. 466 */ 467 public static class CurrencyNumberInfo { 468 public final int digits; 469 public final int rounding; 470 public final double roundingIncrement; 471 public final int cashDigits; 472 public final int cashRounding; 473 public final double cashRoundingIncrement; 474 getDigits()475 public int getDigits() { 476 return digits; 477 } 478 getRounding()479 public int getRounding() { 480 return rounding; 481 } 482 getRoundingIncrement()483 public double getRoundingIncrement() { 484 return roundingIncrement; 485 } 486 CurrencyNumberInfo(int _digits, int _rounding, int _cashDigits, int _cashRounding)487 public CurrencyNumberInfo(int _digits, int _rounding, int _cashDigits, int _cashRounding) { 488 digits = _digits; 489 rounding = _rounding < 0 ? 0 : _rounding; 490 roundingIncrement = rounding * Math.pow(10.0, -digits); 491 // if the values are not set, use the above values 492 cashDigits = _cashDigits < 0 ? digits : _cashDigits; 493 cashRounding = _cashRounding < 0 ? rounding : _cashRounding; 494 cashRoundingIncrement = this.cashRounding * Math.pow(10.0, -digits); 495 } 496 } 497 498 public static class NumberingSystemInfo { 499 public enum NumberingSystemType { 500 algorithmic, numeric, unknown 501 } 502 503 public final String name; 504 public final NumberingSystemType type; 505 public final String digits; 506 public final String rules; 507 508 public NumberingSystemInfo(XPathParts parts) { 509 name = parts.getAttributeValue(-1, "id"); 510 digits = parts.getAttributeValue(-1, "digits"); 511 rules = parts.getAttributeValue(-1, "rules"); 512 type = NumberingSystemType.valueOf(parts.getAttributeValue(-1, "type")); 513 } 514 515 } 516 517 /** 518 * Class for a range of two dates, refactored to share code. 519 * 520 * @author markdavis 521 */ 522 public static final class DateRange implements Comparable<DateRange> { 523 public static final long START_OF_TIME = Long.MIN_VALUE; 524 public static final long END_OF_TIME = Long.MAX_VALUE; 525 public final long from; 526 public final long to; 527 528 public DateRange(String fromString, String toString) { 529 from = parseDate(fromString, START_OF_TIME); 530 to = parseDate(toString, END_OF_TIME); 531 } 532 533 public long getFrom() { 534 return from; 535 } 536 537 public long getTo() { 538 return to; 539 } 540 541 static final DateFormat[] simpleFormats = { 542 new SimpleDateFormat("yyyy-MM-dd HH:mm"), 543 new SimpleDateFormat("yyyy-MM-dd"), 544 new SimpleDateFormat("yyyy-MM"), 545 new SimpleDateFormat("yyyy"), 546 }; 547 static { 548 TimeZone gmt = TimeZone.getTimeZone("GMT"); 549 for (DateFormat format : simpleFormats) { 550 format.setTimeZone(gmt); 551 } 552 } 553 554 long parseDate(String dateString, long defaultDate) { 555 if (dateString == null) { 556 return defaultDate; 557 } 558 ParseException e2 = null; 559 for (int i = 0; i < simpleFormats.length; ++i) { 560 try { 561 synchronized (simpleFormats[i]) { 562 Date result = simpleFormats[i].parse(dateString); 563 return result.getTime(); 564 } 565 } catch (ParseException e) { 566 if (e2 == null) { 567 e2 = e; 568 } 569 } 570 } 571 throw new IllegalArgumentException(e2); 572 } 573 574 @Override 575 public String toString() { 576 return "{" + formatDate(from) 577 + ", " 578 + formatDate(to) + "}"; 579 } 580 581 public static String formatDate(long date) { 582 if (date == START_OF_TIME) { 583 return "-∞"; 584 } 585 if (date == END_OF_TIME) { 586 return "∞"; 587 } 588 synchronized (simpleFormats[0]) { 589 return simpleFormats[0].format(date); 590 } 591 } 592 593 @Override 594 public int compareTo(DateRange arg0) { 595 return to > arg0.to ? 1 : to < arg0.to ? -1 : from > arg0.from ? 1 : from < arg0.from ? -1 : 0; 596 } 597 } 598 599 /** 600 * Information about when currencies are in use in territories 601 */ 602 public static class CurrencyDateInfo implements Comparable<CurrencyDateInfo> { 603 604 public static final Date END_OF_TIME = new Date(DateRange.END_OF_TIME); 605 public static final Date START_OF_TIME = new Date(DateRange.START_OF_TIME); 606 607 private String currency; 608 private DateRange dateRange; 609 private boolean isLegalTender; 610 private String errors = ""; 611 612 public CurrencyDateInfo(String currency, String startDate, String endDate, String tender) { 613 this.currency = currency; 614 this.dateRange = new DateRange(startDate, endDate); 615 this.isLegalTender = (tender == null || !tender.equals("false")); 616 } 617 618 public String getCurrency() { 619 return currency; 620 } 621 622 public Date getStart() { 623 return new Date(dateRange.getFrom()); 624 } 625 626 public Date getEnd() { 627 return new Date(dateRange.getTo()); 628 } 629 630 public String getErrors() { 631 return errors; 632 } 633 634 public boolean isLegalTender() { 635 return isLegalTender; 636 } 637 638 @Override 639 public int compareTo(CurrencyDateInfo o) { 640 int result = dateRange.compareTo(o.dateRange); 641 if (result != 0) return result; 642 return currency.compareTo(o.currency); 643 } 644 645 @Override 646 public String toString() { 647 return "{" + dateRange + ", " + currency + "}"; 648 } 649 650 public static String formatDate(Date date) { 651 return DateRange.formatDate(date.getTime()); 652 } 653 654 } 655 656 public static final class MetaZoneRange implements Comparable<MetaZoneRange> { 657 public final DateRange dateRange; 658 public final String metazone; 659 660 /** 661 * @param metazone 662 * @param from 663 * @param to 664 */ 665 public MetaZoneRange(String metazone, String fromString, String toString) { 666 super(); 667 this.metazone = metazone; 668 dateRange = new DateRange(fromString, toString); 669 } 670 671 @Override 672 public int compareTo(MetaZoneRange arg0) { 673 int result; 674 if (0 != (result = dateRange.compareTo(arg0.dateRange))) { 675 return result; 676 } 677 return metazone.compareTo(arg0.metazone); 678 } 679 680 @Override 681 public String toString() { 682 return "{" + dateRange + ", " + metazone + "}"; 683 } 684 } 685 686 /** 687 * Information about telephone code(s) for a given territory 688 */ 689 public static class TelephoneCodeInfo implements Comparable<TelephoneCodeInfo> { 690 public static final Date END_OF_TIME = new Date(Long.MAX_VALUE); 691 public static final Date START_OF_TIME = new Date(Long.MIN_VALUE); 692 private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 693 694 private String code; 695 private Date start; 696 private Date end; 697 private String alt; 698 private String errors = ""; 699 700 // code must not be null, the others can be 701 public TelephoneCodeInfo(String code, String startDate, String endDate, String alt) { 702 if (code == null) 703 throw new NullPointerException(); 704 this.code = code; // code will not be null 705 this.start = parseDate(startDate, START_OF_TIME); // start will not be null 706 this.end = parseDate(endDate, END_OF_TIME); // end willl not be null 707 this.alt = (alt == null) ? "" : alt; // alt will not be null 708 } 709 710 static DateFormat[] simpleFormats = { 711 new SimpleDateFormat("yyyy-MM-dd"), 712 new SimpleDateFormat("yyyy-MM"), 713 new SimpleDateFormat("yyyy"), }; 714 715 Date parseDate(String dateString, Date defaultDate) { 716 if (dateString == null) { 717 return defaultDate; 718 } 719 ParseException e2 = null; 720 for (int i = 0; i < simpleFormats.length; ++i) { 721 try { 722 Date result = simpleFormats[i].parse(dateString); 723 return result; 724 } catch (ParseException e) { 725 if (i == 0) { 726 errors += dateString + " "; 727 } 728 if (e2 == null) { 729 e2 = e; 730 } 731 } 732 } 733 throw (IllegalArgumentException) new IllegalArgumentException().initCause(e2); 734 } 735 736 public String getCode() { 737 return code; 738 } 739 740 public Date getStart() { 741 return start; 742 } 743 744 public Date getEnd() { 745 return end; 746 } 747 748 public String getAlt() { 749 return alt; // may return null 750 } 751 752 public String getErrors() { 753 return errors; 754 } 755 756 @Override 757 public boolean equals(Object o) { 758 if (!(o instanceof TelephoneCodeInfo)) 759 return false; 760 TelephoneCodeInfo tc = (TelephoneCodeInfo) o; 761 return tc.code.equals(code) && tc.start.equals(start) && tc.end.equals(end) && tc.alt.equals(alt); 762 } 763 764 @Override 765 public int hashCode() { 766 return 31 * code.hashCode() + start.hashCode() + end.hashCode() + alt.hashCode(); 767 } 768 769 @Override 770 public int compareTo(TelephoneCodeInfo o) { 771 int result = code.compareTo(o.code); 772 if (result != 0) return result; 773 result = start.compareTo(o.start); 774 if (result != 0) return result; 775 result = end.compareTo(o.end); 776 if (result != 0) return result; 777 return alt.compareTo(o.alt); 778 } 779 780 @Override 781 public String toString() { 782 return "{" + code + ", " + formatDate(start) + ", " + formatDate(end) + ", " + alt + "}"; 783 } 784 785 public static String formatDate(Date date) { 786 if (date.equals(START_OF_TIME)) return "-∞"; 787 if (date.equals(END_OF_TIME)) return "∞"; 788 return dateFormat.format(date); 789 } 790 } 791 792 public static class CoverageLevelInfo implements Comparable<CoverageLevelInfo> { 793 public final String match; 794 public final Level value; 795 public final Pattern inLanguage; 796 public final String inScript; 797 public final Set<String> inScriptSet; 798 public final String inTerritory; 799 public final Set<String> inTerritorySet; 800 private Set<String> inTerritorySetInternal; 801 802 public CoverageLevelInfo(String match, int value, String language, String script, String territory) { 803 this.inLanguage = language != null ? PatternCache.get(language) : null; 804 this.inScript = script; 805 this.inTerritory = territory; 806 this.inScriptSet = toSet(script); 807 this.inTerritorySet = toSet(territory); // MUST BE LAST, sets inTerritorySetInternal 808 this.match = match; 809 this.value = Level.fromLevel(value); 810 } 811 812 public static final Pattern NON_ASCII_LETTER = PatternCache.get("[^A-Za-z]+"); 813 814 private Set<String> toSet(String source) { 815 if (source == null) { 816 return null; 817 } 818 Set<String> result = new HashSet<>(Arrays.asList(NON_ASCII_LETTER.split(source))); 819 result.remove(""); 820 inTerritorySetInternal = result; 821 return Collections.unmodifiableSet(result); 822 } 823 824 @Override 825 public int compareTo(CoverageLevelInfo o) { 826 if (value == o.value) { 827 return match.compareTo(o.match); 828 } else { 829 return value.compareTo(o.value); 830 } 831 } 832 833 static void fixEU(Collection<CoverageLevelInfo> targets, SupplementalDataInfo info) { 834 Set<String> euCountries = info.getContained("EU"); 835 for (CoverageLevelInfo item : targets) { 836 if (item.inTerritorySet != null 837 && item.inTerritorySet.contains("EU")) { 838 item.inTerritorySetInternal.addAll(euCountries); 839 } 840 } 841 } 842 } 843 844 public enum RBNFGroup {SpelloutRules, OrdinalRules, NumberingSystemRules} 845 846 public static final String STAR = "*"; 847 public static final Set<String> STAR_SET = Builder.with(new HashSet<String>()).add("*").freeze(); 848 849 private VersionInfo cldrVersion; 850 851 private Map<String, PopulationData> territoryToPopulationData = new TreeMap<>(); 852 853 private Map<String, Map<String, PopulationData>> territoryToLanguageToPopulationData = new TreeMap<>(); 854 855 private Map<String, PopulationData> languageToPopulation = new TreeMap<>(); 856 857 private Map<String, PopulationData> baseLanguageToPopulation = new TreeMap<>(); 858 859 private Relation<String, String> languageToScriptVariants = Relation.of(new TreeMap<String, Set<String>>(), 860 TreeSet.class); 861 862 private Relation<String, String> languageToTerritories = Relation.of(new TreeMap<String, Set<String>>(), 863 LinkedHashSet.class); 864 865 transient private Relation<String, Pair<Boolean, Pair<Double, String>>> languageToTerritories2 = Relation 866 .of(new TreeMap<String, Set<Pair<Boolean, Pair<Double, String>>>>(), TreeSet.class); 867 868 private Map<String, Map<BasicLanguageData.Type, BasicLanguageData>> languageToBasicLanguageData = new TreeMap<>(); 869 870 private Set<String> allLanguages = new TreeSet<>(); 871 final private List<String> approvalRequirements = new LinkedList<>(); // xpath array 872 873 private Relation<String, String> containment = Relation.of(new LinkedHashMap<String, Set<String>>(), 874 LinkedHashSet.class); 875 private Relation<String, String> containmentCore = Relation.of(new LinkedHashMap<String, Set<String>>(), 876 LinkedHashSet.class); 877 private Relation<String, String> containmentGrouping = Relation.of(new LinkedHashMap<String, Set<String>>(), 878 LinkedHashSet.class); 879 private Relation<String, String> containmentDeprecated = Relation.of(new LinkedHashMap<String, Set<String>>(), 880 LinkedHashSet.class); 881 private Relation<String, String> containerToSubdivision = Relation.of(new LinkedHashMap<String, Set<String>>(), 882 LinkedHashSet.class); 883 884 private Map<String, CurrencyNumberInfo> currencyToCurrencyNumberInfo = new TreeMap<>(); 885 886 private Relation<String, CurrencyDateInfo> territoryToCurrencyDateInfo = Relation.of( 887 new TreeMap<String, Set<CurrencyDateInfo>>(), LinkedHashSet.class); 888 889 private Map<String, Set<TelephoneCodeInfo>> territoryToTelephoneCodeInfo = new TreeMap<>(); 890 891 private Set<String> multizone = new TreeSet<>(); 892 893 private Map<String, String> zone_territory = new TreeMap<>(); 894 895 private Relation<String, String> zone_aliases = Relation 896 .of(new TreeMap<String, Set<String>>(), LinkedHashSet.class); 897 898 private Map<String, Map<String, Map<String, String>>> typeToZoneToRegionToZone = new TreeMap<>(); 899 private Relation<String, MetaZoneRange> zoneToMetaZoneRanges = Relation.of( 900 new TreeMap<String, Set<MetaZoneRange>>(), TreeSet.class); 901 902 private Map<String, String> metazoneContinentMap = new HashMap<>(); 903 private Set<String> allMetazones = new TreeSet<>(); 904 905 private Map<String, String> alias_zone = new TreeMap<>(); 906 907 public Relation<String, Integer> numericTerritoryMapping = Relation.of(new HashMap<String, Set<Integer>>(), 908 HashSet.class); 909 910 public Relation<String, String> alpha3TerritoryMapping = Relation.of(new HashMap<String, Set<String>>(), 911 HashSet.class); 912 913 public Relation<String, Integer> numericCurrencyCodeMapping = Relation.of(new HashMap<String, Set<Integer>>(), 914 HashSet.class); 915 916 static Map<String, SupplementalDataInfo> directory_instance = new HashMap<>(); 917 918 public Map<String, Map<String, Row.R2<List<String>, String>>> typeToTagToReplacement = new TreeMap<>(); 919 920 Map<String, List<Row.R4<String, String, Integer, Boolean>>> languageMatch = new HashMap<>(); 921 922 public Relation<String, String> bcp47Key2Subtypes = Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class); 923 public Relation<String, String> bcp47Extension2Keys = Relation 924 .of(new TreeMap<String, Set<String>>(), TreeSet.class); 925 public Relation<Row.R2<String, String>, String> bcp47Aliases = Relation.of( 926 new TreeMap<Row.R2<String, String>, Set<String>>(), LinkedHashSet.class); 927 public Map<Row.R2<String, String>, String> bcp47Descriptions = new TreeMap<>(); 928 public Map<Row.R2<String, String>, String> bcp47Since = new TreeMap<>(); 929 public Map<Row.R2<String, String>, String> bcp47Preferred = new TreeMap<>(); 930 public Map<Row.R2<String, String>, String> bcp47Deprecated = new TreeMap<>(); 931 public Map<String, String> bcp47ValueType = new TreeMap<>(); 932 933 934 public Map<String, Row.R2<String, String>> validityInfo = new LinkedHashMap<>(); 935 public Map<AttributeValidityInfo, String> attributeValidityInfo = new LinkedHashMap<>(); 936 937 public Multimap<String, String> languageGroups = TreeMultimap.create(); 938 939 public RationalParser rationalParser = new RationalParser(); 940 941 private UnitConverter unitConverter = null; 942 943 private final UnitPreferences unitPreferences = new UnitPreferences(); 944 945 public Map<String, GrammarInfo> grammarLocaleToTargetToFeatureToValues = new TreeMap<>(); 946 public Map<String, GrammarDerivation> localeToGrammarDerivation = new TreeMap<>(); 947 948 public enum MeasurementType { 949 measurementSystem, paperSize 950 } 951 952 Map<MeasurementType, Map<String, String>> measurementData = new HashMap<>(); 953 Map<String, PreferredAndAllowedHour> timeData = new HashMap<>(); 954 955 public Relation<String, String> getAlpha3TerritoryMapping() { 956 return alpha3TerritoryMapping; 957 } 958 959 public Relation<String, Integer> getNumericTerritoryMapping() { 960 return numericTerritoryMapping; 961 } 962 963 public Relation<String, Integer> getNumericCurrencyCodeMapping() { 964 return numericCurrencyCodeMapping; 965 } 966 967 /** 968 * Returns type -> tag -> <replacementList, reason>, like "language" -> "sh" -> <{"sr_Latn"}, reason> 969 * 970 * @return 971 */ 972 public Map<String, Map<String, R2<List<String>, String>>> getLocaleAliasInfo() { 973 return typeToTagToReplacement; 974 } 975 976 public R2<List<String>, String> getDeprecatedInfo(String type, String code) { 977 return typeToTagToReplacement.get(type).get(code); 978 } 979 980 public static SupplementalDataInfo getInstance(File supplementalDirectory) { 981 return getInstance(getNormalizedPathString(supplementalDirectory)); 982 } 983 984 static private SupplementalDataInfo defaultInstance = null; 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 if (defaultInstance != null) { 999 return defaultInstance; 1000 } 1001 return CLDRConfig.getInstance().getSupplementalDataInfo(); 1002 // return getInstance(CldrUtility.SUPPLEMENTAL_DIRECTORY); 1003 } 1004 1005 /** 1006 * Mark this as the default instance to be returned by getInstance() 1007 */ 1008 public void setAsDefaultInstance() { 1009 defaultInstance = this; 1010 } 1011 1012 public static SupplementalDataInfo getInstance(String supplementalDirectory) { 1013 synchronized (SupplementalDataInfo.class) { 1014 // Sanity checks - not null, not empty 1015 if (supplementalDirectory == null) { 1016 throw new IllegalArgumentException("Error: null supplemental directory."); 1017 } 1018 if (supplementalDirectory.isEmpty()) { 1019 throw new IllegalArgumentException("Error: The string passed as a parameter resolves to the empty string."); 1020 } 1021 // canonicalize path 1022 String normalizedPath = getNormalizedPathString(supplementalDirectory); 1023 SupplementalDataInfo instance = directory_instance.get(normalizedPath); 1024 if (instance != null) { 1025 return instance; 1026 } 1027 // reaching here means we have not cached the entry 1028 File directory = new File(normalizedPath); 1029 instance = new SupplementalDataInfo(directory); 1030 MyHandler myHandler = instance.new MyHandler(); 1031 XMLFileReader xfr = new XMLFileReader().setHandler(myHandler); 1032 File files1[] = directory.listFiles(); 1033 if (files1 == null || files1.length == 0) { 1034 throw new ICUUncheckedIOException("Error: Supplemental files missing from " + directory.getAbsolutePath()); 1035 } 1036 // get bcp47 files also 1037 File bcp47dir = instance.getBcp47Directory(); 1038 if (!bcp47dir.isDirectory()) { 1039 throw new ICUUncheckedIOException("Error: BCP47 dir is not a directory: " + bcp47dir.getAbsolutePath()); 1040 } 1041 File files2[] = bcp47dir.listFiles(); 1042 if (files2 == null || files2.length == 0) { 1043 throw new ICUUncheckedIOException("Error: BCP47 files missing from " + bcp47dir.getAbsolutePath()); 1044 } 1045 1046 CBuilder<File, ArrayList<File>> builder = Builder.with(new ArrayList<File>()); 1047 builder.addAll(files1); 1048 builder.addAll(files2); 1049 for (File file : builder.get()) { 1050 if (DEBUG) { 1051 System.out.println(getNormalizedPathString(file)); 1052 } 1053 String name = file.toString(); 1054 String shortName = file.getName(); 1055 if (!shortName.endsWith(".xml") || // skip non-XML 1056 shortName.startsWith("#") || // skip other junk files 1057 shortName.startsWith(".")) continue; // skip dot files (backups, etc) 1058 xfr.read(name, -1, true); 1059 myHandler.cleanup(); 1060 } 1061 1062 // xfr = new XMLFileReader().setHandler(instance.new MyHandler()); 1063 // .xfr.read(normalizedPath + "/supplementalMetadata.xml", -1, true); 1064 1065 instance.makeStuffSafe(); 1066 // cache 1067 // directory_instance.put(supplementalDirectory, instance); 1068 directory_instance.put(normalizedPath, instance); 1069 // if (!normalizedPath.equals(supplementalDirectory)) { 1070 // directory_instance.put(normalizedPath, instance); 1071 // } 1072 return instance; 1073 } 1074 } 1075 1076 private File getBcp47Directory() { 1077 return new File(getDirectory().getParent(), "bcp47"); 1078 } 1079 1080 private SupplementalDataInfo(File directory) { 1081 this.directory = directory; 1082 this.validity = Validity.getInstance(directory.toString() + "/../validity/"); 1083 } // hide 1084 1085 private void makeStuffSafe() { 1086 // now make stuff safe 1087 allLanguages.addAll(languageToPopulation.keySet()); 1088 allLanguages.addAll(baseLanguageToPopulation.keySet()); 1089 allLanguages = Collections.unmodifiableSet(allLanguages); 1090 skippedElements = Collections.unmodifiableSet(skippedElements); 1091 multizone = Collections.unmodifiableSet(multizone); 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 /** 2277 * Return the multizone countries (should change name). 2278 * 2279 * @return 2280 */ 2281 public Set<String> getMultizones() { 2282 // TODO Auto-generated method stub 2283 return multizone; 2284 } 2285 2286 private Set<String> singleRegionZones; 2287 2288 public Set<String> getSingleRegionZones() { 2289 synchronized (this) { 2290 if (singleRegionZones == null) { 2291 singleRegionZones = new HashSet<>(); 2292 SupplementalDataInfo supplementalData = this; // TODO: this? 2293 Set<String> multizoneCountries = supplementalData.getMultizones(); 2294 for (String zone : supplementalData.getCanonicalZones()) { 2295 String region = supplementalData.getZone_territory(zone); 2296 if (!multizoneCountries.contains(region) || zone.startsWith("Etc/")) { 2297 singleRegionZones.add(zone); 2298 } 2299 } 2300 singleRegionZones.remove("Etc/Unknown"); // remove special case 2301 singleRegionZones = Collections.unmodifiableSet(singleRegionZones); 2302 } 2303 } 2304 return singleRegionZones; 2305 } 2306 2307 public Set<String> getTerritoriesForPopulationData(String language) { 2308 return languageToTerritories.getAll(language); 2309 } 2310 2311 public Set<String> getLanguagesForTerritoriesPopulationData() { 2312 return languageToTerritories.keySet(); 2313 } 2314 2315 /** 2316 * Return the list of default content locales. 2317 * 2318 * @return 2319 */ 2320 public Set<String> getDefaultContentLocales() { 2321 return defaultContentLocales; 2322 } 2323 2324 public static Map<String, String> makeLocaleToDefaultContents(Set<String> defaultContents, 2325 Map<String, String> result, Set<String> errors) { 2326 for (String s : defaultContents) { 2327 String simpleParent = LanguageTagParser.getSimpleParent(s); 2328 String oldValue = result.get(simpleParent); 2329 if (oldValue != null) { 2330 errors.add("*** Error: Default contents cannot contain two children for the same parent:\t" 2331 + oldValue + ", " + s + "; keeping " + oldValue); 2332 continue; 2333 } 2334 result.put(simpleParent, s); 2335 } 2336 return result; 2337 } 2338 2339 /** 2340 * Return the list of default content locales. 2341 * 2342 * @return 2343 */ 2344 public Set<CLDRLocale> getDefaultContentCLDRLocales() { 2345 initCLDRLocaleBasedData(); 2346 return defaultContentToBase.keySet(); 2347 } 2348 2349 /** 2350 * Get the default content locale for a specified language 2351 * 2352 * @param language 2353 * language to search 2354 * @return default content, or null if none 2355 */ 2356 public String getDefaultContentLocale(String language) { 2357 for (String dc : defaultContentLocales) { 2358 if (dc.startsWith(language + "_")) { 2359 return dc; 2360 } 2361 } 2362 return null; 2363 } 2364 2365 /** 2366 * Get the default content locale for a specified language and script. 2367 * If script is null, delegates to {@link #getDefaultContentLocale(String)} 2368 * 2369 * @param language 2370 * @param script 2371 * if null, delegates to {@link #getDefaultContentLocale(String)} 2372 * @return default content, or null if none 2373 */ 2374 public String getDefaultContentLocale(String language, String script) { 2375 if (script == null) return getDefaultContentLocale(language); 2376 for (String dc : defaultContentLocales) { 2377 if (dc.startsWith(language + "_" + script + "_")) { 2378 return dc; 2379 } 2380 } 2381 return null; 2382 } 2383 2384 /** 2385 * Given a default locale (such as 'wo_Arab_SN') return the base locale (such as 'wo'), or null if the input wasn't 2386 * a default conetnt locale. 2387 * 2388 * @param baseLocale 2389 * @return 2390 */ 2391 public CLDRLocale getBaseFromDefaultContent(CLDRLocale dcLocale) { 2392 initCLDRLocaleBasedData(); 2393 return defaultContentToBase.get(dcLocale); 2394 } 2395 2396 /** 2397 * Given a base locale (such as 'wo') return the default content locale (such as 'wo_Arab_SN'), or null. 2398 * 2399 * @param baseLocale 2400 * @return 2401 */ 2402 public CLDRLocale getDefaultContentFromBase(CLDRLocale baseLocale) { 2403 initCLDRLocaleBasedData(); 2404 return baseToDefaultContent.get(baseLocale); 2405 } 2406 2407 /** 2408 * Is this a default content locale? 2409 * 2410 * @param dcLocale 2411 * @return 2412 */ 2413 public boolean isDefaultContent(CLDRLocale dcLocale) { 2414 initCLDRLocaleBasedData(); 2415 if (dcLocale == null) throw new NullPointerException("null locale"); 2416 return (defaultContentToBase.get(dcLocale) != null); 2417 } 2418 2419 public Set<String> getNumberingSystems() { 2420 return numberingSystems.keySet(); 2421 } 2422 2423 public Set<String> getNumericNumberingSystems() { 2424 return Collections.unmodifiableSet(numericSystems); 2425 } 2426 2427 public String getDigits(String numberingSystem) { 2428 try { 2429 return numberingSystems.get(numberingSystem).digits; 2430 } catch (Exception e) { 2431 throw new IllegalArgumentException("Can't get digits for:" + numberingSystem); 2432 } 2433 } 2434 2435 public NumberingSystemType getNumberingSystemType(String numberingSystem) { 2436 return numberingSystems.get(numberingSystem).type; 2437 } 2438 2439 public SortedSet<CoverageLevelInfo> getCoverageLevelInfo() { 2440 return coverageLevels; 2441 } 2442 2443 /** 2444 * Used to get the coverage value for a path. This is generally the most 2445 * efficient way for tools to get coverage. 2446 * 2447 * @param xpath 2448 * @param loc 2449 * @return 2450 */ 2451 public Level getCoverageLevel(String xpath, String loc) { 2452 Level result = null; 2453 result = coverageCache.get(xpath, loc); 2454 if (result == null) { 2455 CoverageLevel2 cov = localeToCoverageLevelInfo.get(loc); 2456 if (cov == null) { 2457 cov = CoverageLevel2.getInstance(this, loc); 2458 localeToCoverageLevelInfo.put(loc, cov); 2459 } 2460 2461 result = cov.getLevel(xpath); 2462 coverageCache.put(xpath, loc, result); 2463 } 2464 return result; 2465 } 2466 2467 /** 2468 * Cache Data structure with object expiry, 2469 * 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 2470 */ 2471 private class CoverageCache { 2472 private final Deque<Node> localeList = new LinkedList<>(); 2473 private final int MAX_LOCALES = 10; 2474 2475 /** 2476 * Object to sync on for modifying the locale list 2477 */ 2478 private final Object LOCALE_LIST_ITER_SYNC = new Object(); 2479 2480 /* 2481 * constructor 2482 */ 2483 public CoverageCache() { 2484 // localeList = new LinkedList<Node>(); 2485 } 2486 2487 /* 2488 * retrieves coverage level associated with two keys if it exists in the cache, otherwise returns null 2489 * @param xpath 2490 * @param loc 2491 * @return the coverage level of the above two keys 2492 */ 2493 public Level get(String xpath, String loc) { 2494 synchronized (LOCALE_LIST_ITER_SYNC) { 2495 Iterator<Node> it = localeList.iterator(); 2496 Node reAddNode = null; 2497 while (it.hasNext()) { 2498 // for (Iterator<Node> it = localeList.iterator(); it.hasNext();) { 2499 Node node = it.next(); 2500 if (node.loc.equals(loc)) { 2501 reAddNode = node; 2502 it.remove(); 2503 break; 2504 2505 } 2506 } 2507 if (reAddNode != null) { 2508 localeList.addFirst(reAddNode); 2509 return reAddNode.map.get(xpath); 2510 } 2511 return null; 2512 } 2513 } 2514 2515 /* 2516 * places a coverage level into the cache, with two keys 2517 * @param xpath 2518 * @param loc 2519 * @param covLevel the coverage level of the above two keys 2520 */ 2521 public void put(String xpath, String loc, Level covLevel) { 2522 synchronized (LOCALE_LIST_ITER_SYNC) { 2523 //if locale's map is already in the cache add to it 2524 // for (Iterator<Node> it = localeList.iterator(); it.hasNext();) { 2525 for (Node node : localeList) { 2526 // Node node = it.next(); 2527 if (node.loc.equals(loc)) { 2528 node.map.put(xpath, covLevel); 2529 return; 2530 } 2531 } 2532 2533 //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 2534 Map<String, Level> newMap = new ConcurrentHashMap<>(); 2535 newMap.put(xpath, covLevel); 2536 localeList.addFirst(new Node(loc, newMap)); 2537 2538 if (localeList.size() > MAX_LOCALES) { 2539 localeList.removeLast(); 2540 } 2541 } 2542 } 2543 2544 /* 2545 * node to hold a location and a Map 2546 */ 2547 private class Node { 2548 //public fields to emulate a C/C++ struct 2549 public String loc; 2550 public Map<String, Level> map; 2551 2552 public Node(String _loc, Map<String, Level> _map) { 2553 loc = _loc; 2554 map = _map; 2555 } 2556 } 2557 } 2558 2559 /** 2560 * Used to get the coverage value for a path. Note, it is more efficient to create 2561 * a CoverageLevel2 for a language, and keep it around. 2562 * 2563 * @param xpath 2564 * @param loc 2565 * @return 2566 */ 2567 public int getCoverageValue(String xpath, String loc) { 2568 return getCoverageLevel(xpath, loc).getLevel(); 2569 } 2570 2571 private RegexLookup<Level> coverageLookup = null; 2572 2573 public synchronized RegexLookup<Level> getCoverageLookup() { 2574 if (coverageLookup == null) { 2575 RegexLookup<Level> lookup = new RegexLookup<>(RegexLookup.LookupType.STAR_PATTERN_LOOKUP); 2576 2577 Matcher variable = PatternCache.get("\\$\\{[A-Za-z][\\-A-Za-z]*\\}").matcher(""); 2578 2579 for (CoverageLevelInfo ci : getCoverageLevelInfo()) { 2580 String pattern = ci.match.replace('\'', '"') 2581 .replace("[@", "\\[@") // make sure that attributes are quoted 2582 .replace("(", "(?:") // make sure that there are no capturing groups (beyond what we generate 2583 .replace("(?:?!", "(?!"); // Allow negative lookahead 2584 pattern = "^//ldml/" + pattern + "$"; // for now, force a complete match 2585 String variableType = null; 2586 variable.reset(pattern); 2587 if (variable.find()) { 2588 pattern = pattern.substring(0, variable.start()) + "([^\"]*)" + pattern.substring(variable.end()); 2589 variableType = variable.group(); 2590 if (variable.find()) { 2591 throw new IllegalArgumentException("We can only handle a single variable on a line"); 2592 } 2593 } 2594 2595 // .replaceAll("\\]","\\\\]"); 2596 lookup.add(new CoverageLevel2.MyRegexFinder(pattern, variableType, ci), ci.value); 2597 } 2598 coverageLookup = lookup; 2599 } 2600 return coverageLookup; 2601 } 2602 2603 /** 2604 * This appears to be unused, so didn't provide new version. 2605 * 2606 * @param xpath 2607 * @return 2608 */ 2609 public int getCoverageValueOld(String xpath) { 2610 ULocale loc = new ULocale("und"); 2611 return getCoverageValueOld(xpath, loc); 2612 } 2613 2614 /** 2615 * Older version of code. 2616 * 2617 * @param xpath 2618 * @param loc 2619 * @return 2620 */ 2621 public int getCoverageValueOld(String xpath, ULocale loc) { 2622 String targetLanguage = loc.getLanguage(); 2623 2624 CoverageVariableInfo cvi = getCoverageVariableInfo(targetLanguage); 2625 String targetScriptString = toRegexString(cvi.targetScripts); 2626 String targetTerritoryString = toRegexString(cvi.targetTerritories); 2627 String calendarListString = toRegexString(cvi.calendars); 2628 String targetCurrencyString = toRegexString(cvi.targetCurrencies); 2629 String targetTimeZoneString = toRegexString(cvi.targetTimeZones); 2630 String targetPluralsString = toRegexString(cvi.targetPlurals); 2631 Iterator<CoverageLevelInfo> i = coverageLevels.iterator(); 2632 while (i.hasNext()) { 2633 CoverageLevelInfo ci = i.next(); 2634 String regex = "//ldml/" + ci.match.replace('\'', '"') 2635 .replaceAll("\\[", "\\\\[") 2636 .replaceAll("\\]", "\\\\]") 2637 .replace("${Target-Language}", targetLanguage) 2638 .replace("${Target-Scripts}", targetScriptString) 2639 .replace("${Target-Territories}", targetTerritoryString) 2640 .replace("${Target-TimeZones}", targetTimeZoneString) 2641 .replace("${Target-Currencies}", targetCurrencyString) 2642 .replace("${Target-Plurals}", targetPluralsString) 2643 .replace("${Calendar-List}", calendarListString); 2644 2645 // Special logic added for coverage fields that are only to be applicable 2646 // to certain territories 2647 if (ci.inTerritory != null) { 2648 if (ci.inTerritory.equals("EU")) { 2649 Set<String> containedTerritories = new HashSet<>(); 2650 containedTerritories.addAll(getContained(ci.inTerritory)); 2651 containedTerritories.retainAll(cvi.targetTerritories); 2652 if (containedTerritories.isEmpty()) { 2653 continue; 2654 } 2655 } else { 2656 if (!cvi.targetTerritories.contains(ci.inTerritory)) { 2657 continue; 2658 } 2659 } 2660 } 2661 // Special logic added for coverage fields that are only to be applicable 2662 // to certain languages 2663 if (ci.inLanguage != null && !ci.inLanguage.matcher(targetLanguage).matches()) { 2664 continue; 2665 } 2666 2667 // Special logic added for coverage fields that are only to be applicable 2668 // to certain scripts 2669 if (ci.inScript != null && !cvi.targetScripts.contains(ci.inScript)) { 2670 continue; 2671 } 2672 2673 if (xpath.matches(regex)) { 2674 return ci.value.getLevel(); 2675 } 2676 2677 if (xpath.matches(regex)) { 2678 return ci.value.getLevel(); 2679 } 2680 } 2681 return Level.OPTIONAL.getLevel(); // If no match then return highest possible value 2682 } 2683 2684 public CoverageVariableInfo getCoverageVariableInfo(String targetLanguage) { 2685 CoverageVariableInfo cvi; 2686 if (localeSpecificVariables.containsKey(targetLanguage)) { 2687 cvi = localeSpecificVariables.get(targetLanguage); 2688 } else { 2689 cvi = new CoverageVariableInfo(); 2690 cvi.targetScripts = getTargetScripts(targetLanguage); 2691 cvi.targetTerritories = getTargetTerritories(targetLanguage); 2692 cvi.calendars = getCalendars(cvi.targetTerritories); 2693 cvi.targetCurrencies = getCurrentCurrencies(cvi.targetTerritories); 2694 cvi.targetTimeZones = getCurrentTimeZones(cvi.targetTerritories); 2695 cvi.targetPlurals = getTargetPlurals(targetLanguage); 2696 localeSpecificVariables.put(targetLanguage, cvi); 2697 } 2698 return cvi; 2699 } 2700 2701 private Set<String> getTargetScripts(String language) { 2702 Set<String> targetScripts = new HashSet<>(); 2703 try { 2704 Set<BasicLanguageData> langData = getBasicLanguageData(language); 2705 Iterator<BasicLanguageData> ldi = langData.iterator(); 2706 while (ldi.hasNext()) { 2707 BasicLanguageData bl = ldi.next(); 2708 Set<String> addScripts = bl.scripts; 2709 if (addScripts != null && bl.getType() != BasicLanguageData.Type.secondary) { 2710 targetScripts.addAll(addScripts); 2711 } 2712 } 2713 } catch (Exception e) { 2714 // fall through 2715 } 2716 2717 if (targetScripts.size() == 0) { 2718 targetScripts.add("Zzzz"); // Unknown Script 2719 } 2720 return targetScripts; 2721 } 2722 2723 private Set<String> getTargetTerritories(String language) { 2724 Set<String> targetTerritories = new HashSet<>(); 2725 try { 2726 Set<BasicLanguageData> langData = getBasicLanguageData(language); 2727 Iterator<BasicLanguageData> ldi = langData.iterator(); 2728 while (ldi.hasNext()) { 2729 BasicLanguageData bl = ldi.next(); 2730 Set<String> addTerritories = bl.territories; 2731 if (addTerritories != null && bl.getType() != BasicLanguageData.Type.secondary) { 2732 targetTerritories.addAll(addTerritories); 2733 } 2734 } 2735 } catch (Exception e) { 2736 // fall through 2737 } 2738 if (targetTerritories.size() == 0) { 2739 targetTerritories.add("ZZ"); 2740 } 2741 return targetTerritories; 2742 } 2743 2744 private Set<String> getCalendars(Set<String> territories) { 2745 Set<String> targetCalendars = new HashSet<>(); 2746 Iterator<String> it = territories.iterator(); 2747 while (it.hasNext()) { 2748 List<String> addCalendars = getCalendars(it.next()); 2749 if (addCalendars == null) { 2750 continue; 2751 } 2752 targetCalendars.addAll(addCalendars); 2753 } 2754 return targetCalendars; 2755 } 2756 2757 /** 2758 * @param territory 2759 * @return a list the calendars used in the specified territorys 2760 */ 2761 public List<String> getCalendars(String territory) { 2762 return calendarPreferences.get(territory); 2763 } 2764 2765 private Set<String> getCurrentCurrencies(Set<String> territories) { 2766 Date now = new Date(); 2767 return getCurrentCurrencies(territories, now, now); 2768 } 2769 2770 public Set<String> getCurrentCurrencies(Set<String> territories, Date startsBefore, Date endsAfter) { 2771 Set<String> targetCurrencies = new HashSet<>(); 2772 Iterator<String> it = territories.iterator(); 2773 while (it.hasNext()) { 2774 Set<CurrencyDateInfo> targetCurrencyInfo = getCurrencyDateInfo(it.next()); 2775 if (targetCurrencyInfo == null) { 2776 continue; 2777 } 2778 Iterator<CurrencyDateInfo> it2 = targetCurrencyInfo.iterator(); 2779 while (it2.hasNext()) { 2780 CurrencyDateInfo cdi = it2.next(); 2781 if (cdi.getStart().before(startsBefore) && cdi.getEnd().after(endsAfter) && cdi.isLegalTender()) { 2782 targetCurrencies.add(cdi.getCurrency()); 2783 } 2784 } 2785 } 2786 return targetCurrencies; 2787 } 2788 2789 private Set<String> getCurrentTimeZones(Set<String> territories) { 2790 Set<String> targetTimeZones = new HashSet<>(); 2791 Iterator<String> it = territories.iterator(); 2792 while (it.hasNext()) { 2793 String[] countryIDs = TimeZone.getAvailableIDs(it.next()); 2794 for (int i = 0; i < countryIDs.length; i++) { 2795 targetTimeZones.add(countryIDs[i]); 2796 } 2797 } 2798 return targetTimeZones; 2799 } 2800 2801 private Set<String> getTargetPlurals(String language) { 2802 Set<String> targetPlurals = new HashSet<>(); 2803 targetPlurals.addAll(getPlurals(PluralType.cardinal, language).getCanonicalKeywords()); 2804 // TODO: Kept 0 and 1 specifically until Mark figures out what to do with them. 2805 // They should be removed once this is done. 2806 targetPlurals.add("0"); 2807 targetPlurals.add("1"); 2808 return targetPlurals; 2809 } 2810 2811 public String getExplicitParentLocale(String loc) { 2812 return parentLocales.get(loc); 2813 } 2814 2815 public Set<String> getExplicitChildren() { 2816 return parentLocales.keySet(); 2817 } 2818 2819 public Collection<String> getExplicitParents() { 2820 return parentLocales.values(); 2821 } 2822 2823 private final static class ApprovalRequirementMatcher { 2824 @Override 2825 public String toString() { 2826 return locales + " / " + xpathMatcher + " = " + requiredVotes; 2827 } 2828 2829 ApprovalRequirementMatcher(String xpath) { 2830 XPathParts parts = XPathParts.getFrozenInstance(xpath); 2831 if (parts.containsElement("approvalRequirement")) { 2832 requiredVotes = Integer.parseInt(parts.getAttributeValue(-1, "votes")); 2833 String localeAttrib = parts.getAttributeValue(-1, "locales"); 2834 if (localeAttrib == null || localeAttrib.equals(STAR) || localeAttrib.isEmpty()) { 2835 locales = null; // no locale listed == '*' 2836 } else { 2837 Set<CLDRLocale> localeList = new HashSet<>(); 2838 String[] el = localeAttrib.split(" "); 2839 for (int i = 0; i < el.length; i++) { 2840 if (el[i].indexOf(":") == -1) { // Just a simple locale designation 2841 localeList.add(CLDRLocale.getInstance(el[i])); 2842 } else { // Org:CoverageLevel 2843 String[] coverageLocaleParts = el[i].split(":", 2); 2844 String org = coverageLocaleParts[0]; 2845 String level = coverageLocaleParts[1].toUpperCase(); 2846 Set<String> coverageLocales = sc.getLocaleCoverageLocales(Organization.fromString(org), EnumSet.of(Level.fromString(level))); 2847 for (String cl : coverageLocales) { 2848 localeList.add(CLDRLocale.getInstance(cl)); 2849 } 2850 } 2851 } 2852 locales = Collections.unmodifiableSet(localeList); 2853 } 2854 String xpathMatch = parts.getAttributeValue(-1, "paths"); 2855 if (xpathMatch == null || xpathMatch.isEmpty() || xpathMatch.equals(STAR)) { 2856 xpathMatcher = null; 2857 } else { 2858 xpathMatcher = PatternCache.get(xpathMatch); 2859 } 2860 } else { 2861 throw new RuntimeException("Unknown approval requirement: " + xpath); 2862 } 2863 } 2864 2865 final private Set<CLDRLocale> locales; 2866 final private Pattern xpathMatcher; 2867 final int requiredVotes; 2868 2869 public static List<ApprovalRequirementMatcher> buildAll(List<String> approvalRequirements) { 2870 List<ApprovalRequirementMatcher> newList = new LinkedList<>(); 2871 2872 for (String xpath : approvalRequirements) { 2873 newList.add(new ApprovalRequirementMatcher(xpath)); 2874 } 2875 2876 return Collections.unmodifiableList(newList); 2877 } 2878 2879 public boolean matches(CLDRLocale loc, PathHeader ph) { 2880 if (DEBUG) System.err.println(">> testing " + loc + " / " + ph + " vs " + toString()); 2881 if (locales != null) { 2882 if (!locales.contains(loc)) { 2883 return false; 2884 } 2885 } 2886 if (xpathMatcher != null) { 2887 if (ph != null) { 2888 if (!xpathMatcher.matcher(ph.getOriginalPath()).matches()) { 2889 return false; 2890 } else { 2891 return true; 2892 } 2893 } else { 2894 return false; 2895 } 2896 } 2897 return true; 2898 } 2899 2900 public int getRequiredVotes() { 2901 return requiredVotes; 2902 } 2903 } 2904 2905 // run these from first to last to get the approval info. 2906 volatile List<ApprovalRequirementMatcher> approvalMatchers = null; 2907 2908 /** 2909 * Only called by VoteResolver. 2910 * @param loc 2911 * @param PathHeader - which path this is applied to, or null if unknown. 2912 * @return 2913 */ 2914 public int getRequiredVotes(CLDRLocale loc, PathHeader ph) { 2915 if (approvalMatchers == null) { 2916 approvalMatchers = ApprovalRequirementMatcher.buildAll(approvalRequirements); 2917 } 2918 2919 for (ApprovalRequirementMatcher m : approvalMatchers) { 2920 if (m.matches(loc, ph)) { 2921 return m.getRequiredVotes(); 2922 } 2923 } 2924 throw new RuntimeException("Error: " + loc + " " + ph + " ran off the end of the approvalMatchers."); 2925 } 2926 2927 /** 2928 * Return the canonicalized zone, or null if there is none. 2929 * 2930 * @param alias 2931 * @return 2932 */ 2933 public String getZoneFromAlias(String alias) { 2934 String zone = alias_zone.get(alias); 2935 if (zone != null) 2936 return zone; 2937 if (zone_territory.get(alias) != null) 2938 return alias; 2939 return null; 2940 } 2941 2942 public boolean isCanonicalZone(String alias) { 2943 return zone_territory.get(alias) != null; 2944 } 2945 2946 /** 2947 * Return the approximate economic weight of this language, computed by taking 2948 * all of the languages in each territory, looking at the literate population 2949 * and dividing up the GDP of the territory (in PPP) according to the 2950 * proportion that language has of the total. This is only an approximation, 2951 * since the language information is not complete, languages may overlap 2952 * (bilingual speakers), the literacy figures may be estimated, and literacy 2953 * is only a rough proxy for weight of each language in the economy of the 2954 * territory. 2955 * 2956 * @param languageId 2957 * @return 2958 */ 2959 public double getApproximateEconomicWeight(String targetLanguage) { 2960 double weight = 0; 2961 Set<String> territories = getTerritoriesForPopulationData(targetLanguage); 2962 if (territories == null) return weight; 2963 for (String territory : territories) { 2964 Set<String> languagesInTerritory = getTerritoryToLanguages(territory); 2965 double totalLiteratePopulation = 0; 2966 double targetLiteratePopulation = 0; 2967 for (String language : languagesInTerritory) { 2968 PopulationData populationData = getLanguageAndTerritoryPopulationData( 2969 language, territory); 2970 totalLiteratePopulation += populationData.getLiteratePopulation(); 2971 if (language.equals(targetLanguage)) { 2972 targetLiteratePopulation = populationData.getLiteratePopulation(); 2973 } 2974 } 2975 PopulationData territoryPopulationData = getPopulationDataForTerritory(territory); 2976 final double gdp = territoryPopulationData.getGdp(); 2977 final double scaledGdp = gdp * targetLiteratePopulation / totalLiteratePopulation; 2978 if (scaledGdp > 0) { 2979 weight += scaledGdp; 2980 } else { 2981 // System.out.println("?\t" + territory + "\t" + targetLanguage); 2982 } 2983 } 2984 return weight; 2985 } 2986 2987 public PopulationData getPopulationDataForTerritory(String territory) { 2988 return territoryToPopulationData.get(territory); 2989 } 2990 2991 public Set<String> getScriptVariantsForPopulationData(String language) { 2992 return languageToScriptVariants.getAll(language); 2993 } 2994 2995 public Map<String, Pair<String, String>> getReferences() { 2996 return references; 2997 } 2998 2999 public Map<String, Map<String, String>> getMetazoneToRegionToZone() { 3000 return typeToZoneToRegionToZone.get("metazones"); 3001 } 3002 3003 public String getZoneForMetazoneByRegion(String metazone, String region) { 3004 String result = null; 3005 if (getMetazoneToRegionToZone().containsKey(metazone)) { 3006 Map<String, String> myMap = getMetazoneToRegionToZone().get(metazone); 3007 if (myMap.containsKey(region)) { 3008 result = myMap.get(region); 3009 } else { 3010 result = myMap.get("001"); 3011 } 3012 } 3013 3014 if (result == null) { 3015 result = "Etc/GMT"; 3016 } 3017 3018 return result; 3019 } 3020 3021 public Map<String, Map<String, Map<String, String>>> getTypeToZoneToRegionToZone() { 3022 return typeToZoneToRegionToZone; 3023 } 3024 3025 /** 3026 * @deprecated, use PathHeader.getMetazonePageTerritory 3027 */ 3028 public Map<String, String> getMetazoneToContinentMap() { 3029 return metazoneContinentMap; 3030 } 3031 3032 public Set<String> getAllMetazones() { 3033 return allMetazones; 3034 } 3035 3036 public Map<String, String> getLikelySubtags() { 3037 return likelySubtags; 3038 } 3039 3040 public enum PluralType { 3041 cardinal(PluralRules.PluralType.CARDINAL), ordinal(PluralRules.PluralType.ORDINAL); 3042 3043 // add some gorp to interwork until we clean things up 3044 3045 public final PluralRules.PluralType standardType; 3046 3047 PluralType(PluralRules.PluralType standardType) { 3048 this.standardType = standardType; 3049 } 3050 3051 public static PluralType fromStandardType(PluralRules.PluralType standardType) { 3052 return standardType == null ? null 3053 : standardType == PluralRules.PluralType.CARDINAL ? cardinal 3054 : ordinal; 3055 } 3056 } 3057 3058 private EnumMap<PluralType, Map<String, PluralInfo>> localeToPluralInfo2 = new EnumMap<>(PluralType.class); 3059 { 3060 localeToPluralInfo2.put(PluralType.cardinal, new LinkedHashMap<String, PluralInfo>()); 3061 localeToPluralInfo2.put(PluralType.ordinal, new LinkedHashMap<String, PluralInfo>()); 3062 } 3063 private Map<String, PluralRanges> localeToPluralRanges = new LinkedHashMap<>(); 3064 3065 private Map<DayPeriodInfo.Type, Map<String, DayPeriodInfo>> typeToLocaleToDayPeriodInfo = new EnumMap<>( 3066 DayPeriodInfo.Type.class); 3067 private Map<String, CoverageLevel2> localeToCoverageLevelInfo = new ConcurrentHashMap<>(); 3068 private CoverageCache coverageCache = new CoverageCache(); 3069 private transient String lastPluralLocales = ""; 3070 private transient PluralType lastPluralWasOrdinal = null; 3071 private transient Map<Count, String> lastPluralMap = new EnumMap<>(Count.class); 3072 private transient String lastDayPeriodLocales = null; 3073 private transient DayPeriodInfo.Type lastDayPeriodType = null; 3074 private transient DayPeriodInfo.Builder dayPeriodBuilder = new DayPeriodInfo.Builder(); 3075 3076 private void addDayPeriodPath(XPathParts path) { 3077 // ldml/dates/calendars/calendar[@type="gregorian"]/dayPeriods/dayPeriodContext[@type="format"]/dayPeriodWidth[@type="wide"]/dayPeriod[@type="am"] 3078 /* 3079 * <supplementalData> 3080 * <version number="$Revision$"/> 3081 * <generation date="$D..e... $"/> 3082 * <dayPeriodRuleSet> 3083 * <dayPeriodRules locales = "en"> <!-- default for any locales not listed under other dayPeriods --> 3084 * <dayPeriodRule type = "am" from = "0:00" before="12:00"/> 3085 * <dayPeriodRule type = "pm" from = "12:00" to="24:00"/> 3086 */ 3087 String typeString = path.getAttributeValue(1, "type"); 3088 String locales = path.getAttributeValue(2, "locales").trim(); 3089 DayPeriodInfo.Type type = typeString == null 3090 ? DayPeriodInfo.Type.format 3091 : DayPeriodInfo.Type.valueOf(typeString.trim()); 3092 if (!locales.equals(lastDayPeriodLocales) || type != lastDayPeriodType) { 3093 if (lastDayPeriodLocales != null) { 3094 addDayPeriodInfo(); 3095 } 3096 lastDayPeriodLocales = locales; 3097 lastDayPeriodType = type; 3098 // System.out.println(type + ", " + locales + ", " + path); 3099 } 3100 if (path.size() != 4) { 3101 if (locales.equals("root")) return; // we allow root to be empty 3102 throw new IllegalArgumentException(locales + " must have dayPeriodRule elements"); 3103 } 3104 DayPeriod dayPeriod; 3105 try { 3106 dayPeriod = DayPeriod.fromString(path.getAttributeValue(-1, "type")); 3107 } catch (Exception e) { 3108 System.err.println(e.getMessage()); 3109 return; 3110 } 3111 String at = path.getAttributeValue(-1, "at"); 3112 String from = path.getAttributeValue(-1, "from"); 3113 String after = path.getAttributeValue(-1, "after"); 3114 String to = path.getAttributeValue(-1, "to"); 3115 String before = path.getAttributeValue(-1, "before"); 3116 if (at != null) { 3117 if (from != null || after != null || to != null || before != null) { 3118 throw new IllegalArgumentException(); 3119 } 3120 from = at; 3121 to = at; 3122 } else if ((from == null) == (after == null) || (to == null) == (before == null)) { 3123 throw new IllegalArgumentException(); 3124 } 3125 // if (dayPeriodBuilder.contains(dayPeriod)) { // disallow multiple rules with same dayperiod 3126 // throw new IllegalArgumentException("Multiple rules with same dayperiod are disallowed: " 3127 // + lastDayPeriodLocales + ", " + lastDayPeriodType + ", " + dayPeriod); 3128 // } 3129 boolean includesStart = from != null; 3130 boolean includesEnd = to != null; 3131 int start = parseTime(includesStart ? from : after); 3132 int end = parseTime(includesEnd ? to : before); 3133 // Check if any periods contain 0, e.g. 1700 - 300 3134 if (start > end) { 3135 // System.out.println("start " + start + " end " + end); 3136 dayPeriodBuilder.add(dayPeriod, start, includesStart, parseTime("24:00"), includesEnd); 3137 dayPeriodBuilder.add(dayPeriod, parseTime("0:00"), includesStart, end, includesEnd); 3138 } else { 3139 dayPeriodBuilder.add(dayPeriod, start, includesStart, end, includesEnd); 3140 } 3141 } 3142 3143 static Pattern PARSE_TIME = PatternCache.get("(\\d\\d?):(\\d\\d)"); 3144 3145 private int parseTime(String string) { 3146 Matcher matcher = PARSE_TIME.matcher(string); 3147 if (!matcher.matches()) { 3148 throw new IllegalArgumentException(); 3149 } 3150 return (Integer.parseInt(matcher.group(1)) * 60 + Integer.parseInt(matcher.group(2))) * 60 * 1000; 3151 } 3152 3153 private void addDayPeriodInfo() { 3154 String[] locales = lastDayPeriodLocales.split("\\s+"); 3155 DayPeriodInfo temp = dayPeriodBuilder.finish(locales); 3156 Map<String, DayPeriodInfo> locale2DPI = typeToLocaleToDayPeriodInfo.get(lastDayPeriodType); 3157 if (locale2DPI == null) { 3158 typeToLocaleToDayPeriodInfo.put(lastDayPeriodType, locale2DPI = new LinkedHashMap<>()); 3159 //System.out.println(lastDayPeriodType + ", " + locale2DPI); 3160 } 3161 for (String locale : locales) { 3162 locale2DPI.put(locale, temp); 3163 } 3164 } 3165 3166 static String lastPluralRangesLocales = null; 3167 static PluralRanges lastPluralRanges = null; 3168 3169 private boolean addPluralPath(XPathParts path, String value) { 3170 /* 3171 * Adding 3172 <pluralRanges locales="am"> 3173 <pluralRange start="one" end="one" result="one" /> 3174 </pluralRanges> 3175 */ 3176 String locales = path.getAttributeValue(2, "locales").trim(); 3177 String element = path.getElement(2); 3178 if ("pluralRanges".equals(element)) { 3179 if (!locales.equals(lastPluralRangesLocales)) { 3180 addPluralRanges(locales); 3181 } 3182 if (path.size() == 3) { 3183 // ok for ranges to be empty 3184 return true; 3185 } 3186 String rangeStart = path.getAttributeValue(-1, "start"); 3187 String rangeEnd = path.getAttributeValue(-1, "end"); 3188 String result = path.getAttributeValue(-1, "result"); 3189 lastPluralRanges.add(rangeStart == null ? null : Count.valueOf(rangeStart), 3190 rangeEnd == null ? null : Count.valueOf(rangeEnd), 3191 Count.valueOf(result)); 3192 return true; 3193 } else if ("pluralRules".equals(element)) { 3194 3195 String type = path.getAttributeValue(1, "type"); 3196 PluralType pluralType = type == null ? PluralType.cardinal : PluralType.valueOf(type); 3197 if (!lastPluralLocales.equals(locales)) { 3198 addPluralInfo(pluralType); 3199 lastPluralLocales = locales; 3200 } 3201 final String countString = path.getAttributeValue(-1, "count"); 3202 if (countString == null) { 3203 return false; 3204 } 3205 Count count = Count.valueOf(countString); 3206 if (lastPluralMap.containsKey(count)) { 3207 throw new IllegalArgumentException("Duplicate plural count: " + count + " in " + locales); 3208 } 3209 lastPluralMap.put(count, value); 3210 lastPluralWasOrdinal = pluralType; 3211 return true; 3212 } else { 3213 return false; 3214 } 3215 } 3216 3217 private void addPluralRanges(String localesString) { 3218 final String[] locales = localesString.split("\\s+"); 3219 lastPluralRanges = new PluralRanges(); 3220 for (String locale : locales) { 3221 if (localeToPluralRanges.containsKey(locale)) { 3222 throw new IllegalArgumentException("Duplicate plural locale: " + locale); 3223 } 3224 localeToPluralRanges.put(locale, lastPluralRanges); 3225 } 3226 lastPluralRangesLocales = localesString; 3227 } 3228 3229 private void addPluralInfo(PluralType pluralType) { 3230 final String[] locales = lastPluralLocales.split("\\s+"); 3231 PluralInfo info = new PluralInfo(lastPluralMap, pluralType); 3232 Map<String, PluralInfo> localeToInfo = localeToPluralInfo2.get(pluralType); 3233 for (String locale : locales) { 3234 if (localeToInfo.containsKey(locale)) { 3235 throw new IllegalArgumentException("Duplicate plural locale: " + locale); 3236 } else if (!locale.isEmpty()) { 3237 localeToInfo.put(locale, info); 3238 } 3239 } 3240 lastPluralMap.clear(); 3241 } 3242 3243 public static class SampleList { 3244 public static final SampleList EMPTY = new SampleList().freeze(); 3245 3246 private UnicodeSet uset = new UnicodeSet(); 3247 private List<FixedDecimal> fractions = new ArrayList<>(0); 3248 3249 @Override 3250 public String toString() { 3251 return toString(6, 3); 3252 } 3253 3254 public String toString(int intLimit, int fractionLimit) { 3255 StringBuilder b = new StringBuilder(); 3256 int intCount = 0; 3257 int fractionCount = 0; 3258 int limit = uset.getRangeCount(); 3259 for (int i = 0; i < limit; ++i) { 3260 if (intCount >= intLimit) { 3261 b.append(", …"); 3262 break; 3263 } 3264 if (b.length() != 0) { 3265 b.append(", "); 3266 } 3267 int start = uset.getRangeStart(i); 3268 int end = uset.getRangeEnd(i); 3269 if (start == end) { 3270 b.append(start); 3271 ++intCount; 3272 } else if (start + 1 == end) { 3273 b.append(start).append(", ").append(end); 3274 intCount += 2; 3275 } else { 3276 b.append(start).append('-').append(end); 3277 intCount += 2; 3278 } 3279 } 3280 if (fractions.size() > 0) { 3281 for (int i = 0; i < fractions.size(); ++i) { 3282 if (fractionCount >= fractionLimit) { 3283 break; 3284 } 3285 if (b.length() != 0) { 3286 b.append(", "); 3287 } 3288 FixedDecimal fraction = fractions.get(i); 3289 String formatted = String.format( 3290 Locale.ROOT, 3291 "%." + fraction.getVisibleDecimalDigitCount() + "f", 3292 fraction.getSource()); 3293 b.append(formatted); 3294 ++fractionCount; 3295 } 3296 b.append(", …"); 3297 } 3298 return b.toString(); 3299 } 3300 3301 public int getRangeCount() { 3302 return uset.getRangeCount(); 3303 } 3304 3305 public int getRangeStart(int index) { 3306 return uset.getRangeStart(index); 3307 } 3308 3309 public int getRangeEnd(int index) { 3310 return uset.getRangeEnd(index); 3311 } 3312 3313 public List<FixedDecimal> getFractions() { 3314 return fractions; 3315 } 3316 3317 public int intSize() { 3318 return uset.size(); 3319 } 3320 3321 public SampleList remove(int i) { 3322 uset.remove(i); 3323 return this; 3324 } 3325 3326 public SampleList add(int i) { 3327 uset.add(i); 3328 return this; 3329 } 3330 3331 public SampleList freeze() { 3332 uset.freeze(); 3333 if (fractions instanceof ArrayList) { 3334 fractions = Collections.unmodifiableList(fractions); 3335 } 3336 return this; 3337 } 3338 3339 public void add(FixedDecimal i) { 3340 fractions.add(i); 3341 } 3342 3343 public int fractionSize() { 3344 return fractions.size(); 3345 } 3346 } 3347 3348 public static class CountSampleList { 3349 private final Map<Count, SampleList> countToIntegerSamples9999; 3350 private final Map<Count, SampleList[]> countToDigitToIntegerSamples9999; 3351 3352 CountSampleList(PluralRules pluralRules, Set<Count> keywords, PluralType pluralType) { 3353 // Create the integer counts 3354 countToIntegerSamples9999 = new EnumMap<>(Count.class); 3355 countToDigitToIntegerSamples9999 = new EnumMap<>(Count.class); 3356 for (Count c : keywords) { 3357 countToIntegerSamples9999.put(c, new SampleList()); 3358 SampleList[] row = new SampleList[5]; 3359 countToDigitToIntegerSamples9999.put(c, row); 3360 for (int i = 1; i < 5; ++i) { 3361 row[i] = new SampleList(); 3362 } 3363 } 3364 for (int ii = 0; ii < 10000; ++ii) { 3365 int i = ii; 3366 int digit; 3367 if (i > 999) { 3368 digit = 4; 3369 } else if (i > 99) { 3370 digit = 3; 3371 } else if (i > 9) { 3372 digit = 2; 3373 } else { 3374 digit = 1; 3375 } 3376 Count count = Count.valueOf(pluralRules.select(i)); 3377 addSimple(countToIntegerSamples9999, i, count); 3378 addDigit(countToDigitToIntegerSamples9999, i, count, digit); 3379 if (haveFractions(keywords, digit)) { 3380 continue; 3381 } 3382 if (pluralType == PluralType.cardinal) { 3383 for (int f = 0; f < 30; ++f) { 3384 FixedDecimal ni = new FixedDecimal(i + f / 10.0d, f < 10 ? 1 : 2, f); 3385 count = Count.valueOf(pluralRules.select(ni)); 3386 addSimple(countToIntegerSamples9999, ni, count); 3387 addDigit(countToDigitToIntegerSamples9999, ni, count, digit); 3388 } 3389 } 3390 } 3391 // HACK for Breton 3392 addSimple(countToIntegerSamples9999, 1000000, Count.valueOf(pluralRules.select(1000000))); 3393 3394 for (Count count : keywords) { 3395 SampleList uset = countToIntegerSamples9999.get(count); 3396 uset.freeze(); 3397 SampleList[] map = countToDigitToIntegerSamples9999.get(count); 3398 for (int i = 1; i < map.length; ++i) { 3399 map[i].freeze(); 3400 } 3401 } 3402 } 3403 3404 private boolean haveFractions(Set<Count> keywords, int digit) { 3405 for (Count c : keywords) { 3406 int size = countToDigitToIntegerSamples9999.get(c)[digit].fractionSize(); 3407 if (size < MAX_COLLECTED_FRACTION) { 3408 return false; 3409 } 3410 } 3411 return true; 3412 } 3413 3414 static final int MAX_COLLECTED_FRACTION = 5; 3415 3416 private boolean addDigit(Map<Count, SampleList[]> countToDigitToIntegerSamples9999, FixedDecimal i, Count count, int digit) { 3417 return addFraction(i, countToDigitToIntegerSamples9999.get(count)[digit]); 3418 } 3419 3420 private boolean addFraction(FixedDecimal i, SampleList sampleList) { 3421 if (sampleList.fractionSize() < MAX_COLLECTED_FRACTION) { 3422 sampleList.add(i); 3423 return true; 3424 } else { 3425 return false; 3426 } 3427 } 3428 3429 private boolean addSimple(Map<Count, SampleList> countToIntegerSamples9999, FixedDecimal i, Count count) { 3430 return addFraction(i, countToIntegerSamples9999.get(count)); 3431 } 3432 3433 private void addDigit(Map<Count, SampleList[]> countToDigitToIntegerSamples9999, int i, Count count, int digit) { 3434 countToDigitToIntegerSamples9999.get(count)[digit].add(i); 3435 } 3436 3437 private void addSimple(Map<Count, SampleList> countToIntegerSamples9999, int i, Count count) { 3438 countToIntegerSamples9999.get(count).add(i); 3439 } 3440 3441 public SampleList get(Count type) { 3442 return countToIntegerSamples9999.get(type); 3443 } 3444 3445 public SampleList get(Count c, int digit) { 3446 SampleList[] sampleLists = countToDigitToIntegerSamples9999.get(c); 3447 return sampleLists == null ? null : sampleLists[digit]; 3448 } 3449 } 3450 3451 /** 3452 * Immutable class with plural info for different locales 3453 * 3454 * @author markdavis 3455 */ 3456 public static class PluralInfo implements Comparable<PluralInfo> { 3457 static final Set<Double> explicits = new HashSet<>(); 3458 static { 3459 explicits.add(0.0d); 3460 explicits.add(1.0d); 3461 } 3462 3463 public enum Count { 3464 zero, one, two, few, many, other; 3465 public static final int LENGTH = Count.values().length; 3466 public static final List<Count> VALUES = Collections.unmodifiableList(Arrays.asList(values())); 3467 } 3468 3469 static final Pattern pluralPaths = PatternCache.get(".*pluralRule.*"); 3470 static final int fractDecrement = 13; 3471 static final int fractStart = 20; 3472 3473 private final Map<Count, Set<Double>> countToExampleSet; 3474 private final Map<Count, String> countToStringExample; 3475 private final Map<Integer, Count> exampleToCount; 3476 private final PluralRules pluralRules; 3477 private final String pluralRulesString; 3478 private final Set<String> canonicalKeywords; 3479 private final Set<Count> keywords; 3480 private final Set<Count> integerKeywords; 3481 private final Set<Count> decimalKeywords; 3482 private final CountSampleList countSampleList; 3483 private final Map<Count, String> countToRule; 3484 3485 private PluralInfo(Map<Count, String> countToRule, PluralType pluralType) { 3486 EnumMap<Count, String> tempCountToRule = new EnumMap<>(Count.class); 3487 tempCountToRule.putAll(countToRule); 3488 this.countToRule = Collections.unmodifiableMap(tempCountToRule); 3489 3490 // now build rules 3491 NumberFormat nf = NumberFormat.getNumberInstance(ULocale.ENGLISH); 3492 nf.setMaximumFractionDigits(2); 3493 StringBuilder pluralRuleBuilder = new StringBuilder(); 3494 for (Count count : countToRule.keySet()) { 3495 if (pluralRuleBuilder.length() != 0) { 3496 pluralRuleBuilder.append(';'); 3497 } 3498 pluralRuleBuilder.append(count).append(':').append(countToRule.get(count)); 3499 } 3500 pluralRulesString = pluralRuleBuilder.toString(); 3501 try { 3502 pluralRules = PluralRules.parseDescription(pluralRulesString); 3503 } catch (ParseException e) { 3504 throw new IllegalArgumentException("Can't create plurals from <" + pluralRulesString + ">", e); 3505 } 3506 EnumSet<Count> _keywords = EnumSet.noneOf(Count.class); 3507 EnumSet<Count> _integerKeywords = EnumSet.noneOf(Count.class); 3508 EnumSet<Count> _decimalKeywords = EnumSet.noneOf(Count.class); 3509 for (String s : pluralRules.getKeywords()) { 3510 Count c = Count.valueOf(s); 3511 _keywords.add(c); 3512 if (pluralRules.getDecimalSamples(s, SampleType.DECIMAL) != null) { 3513 _decimalKeywords.add(c); 3514 } else { 3515 int debug = 1; 3516 } 3517 if (pluralRules.getDecimalSamples(s, SampleType.INTEGER) != null) { 3518 _integerKeywords.add(c); 3519 } else { 3520 int debug = 1; 3521 } 3522 } 3523 keywords = Collections.unmodifiableSet(_keywords); 3524 decimalKeywords = Collections.unmodifiableSet(_decimalKeywords); 3525 integerKeywords = Collections.unmodifiableSet(_integerKeywords); 3526 3527 countSampleList = new CountSampleList(pluralRules, keywords, pluralType); 3528 3529 Map<Count, Set<Double>> countToExampleSetRaw = new TreeMap<>(); 3530 Map<Integer, Count> exampleToCountRaw = new TreeMap<>(); 3531 3532 Output<Map<Count, SampleList[]>> output = new Output(); 3533 3534 // double check 3535 // if (!targetKeywords.equals(typeToExamples2.keySet())) { 3536 // throw new IllegalArgumentException ("Problem in plurals " + targetKeywords + ", " + this); 3537 // } 3538 // now fix the longer examples 3539 String otherFractionalExamples = ""; 3540 List<Double> otherFractions = new ArrayList<>(0); 3541 3542 // add fractional samples 3543 Map<Count, String> countToStringExampleRaw = new TreeMap<>(); 3544 for (Count type : keywords) { 3545 SampleList uset = countSampleList.get(type); 3546 countToStringExampleRaw.put(type, uset.toString(5, 5)); 3547 } 3548 final String baseOtherExamples = countToStringExampleRaw.get(Count.other); 3549 String otherExamples = (baseOtherExamples == null ? "" : baseOtherExamples + "; ") 3550 + otherFractionalExamples + "..."; 3551 countToStringExampleRaw.put(Count.other, otherExamples); 3552 3553 // Now do double examples (previously unused & not working). 3554 // Currently a bit of a hack, we should enhance SampleList to make this easier 3555 // and then use SampleList directly, see http://unicode.org/cldr/trac/ticket/9813 3556 for (Count type : countToStringExampleRaw.keySet()) { 3557 Set<Double> doublesSet = new LinkedHashSet<>(0); 3558 String examples = countToStringExampleRaw.get(type); 3559 if (examples == null) { 3560 examples = ""; 3561 } 3562 String strippedExamples = examples.replaceAll("(, …)|(; ...)", ""); 3563 String[] exampleArray = strippedExamples.split("(, )|(-)"); 3564 for (String example : exampleArray) { 3565 if (example == null || example.length() == 0) { 3566 continue; 3567 } 3568 Double doubleValue = Double.valueOf(example); 3569 doublesSet.add(doubleValue); 3570 } 3571 doublesSet = Collections.unmodifiableSet(doublesSet); 3572 countToExampleSetRaw.put(type, doublesSet); 3573 } 3574 3575 countToExampleSet = Collections.unmodifiableMap(countToExampleSetRaw); 3576 countToStringExample = Collections.unmodifiableMap(countToStringExampleRaw); 3577 exampleToCount = Collections.unmodifiableMap(exampleToCountRaw); 3578 Set<String> temp = new LinkedHashSet<>(); 3579 // String keyword = pluralRules.select(0.0d); 3580 // double value = pluralRules.getUniqueKeywordValue(keyword); 3581 // if (value == pluralRules.NO_UNIQUE_VALUE) { 3582 // temp.add("0"); 3583 // } 3584 // keyword = pluralRules.select(1.0d); 3585 // value = pluralRules.getUniqueKeywordValue(keyword); 3586 // if (value == pluralRules.NO_UNIQUE_VALUE) { 3587 // temp.add("1"); 3588 // } 3589 Set<String> keywords = pluralRules.getKeywords(); 3590 for (Count count : Count.values()) { 3591 String keyword = count.toString(); 3592 if (keywords.contains(keyword)) { 3593 temp.add(keyword); 3594 } 3595 } 3596 // if (false) { 3597 // change to this after rationalizing 0/1 3598 // temp.add("0"); 3599 // temp.add("1"); 3600 // for (Count count : Count.values()) { 3601 // temp.add(count.toString()); 3602 // KeywordStatus status = org.unicode.cldr.util.PluralRulesUtil.getKeywordStatus(pluralRules, 3603 // count.toString(), 0, explicits, true); 3604 // if (status != KeywordStatus.SUPPRESSED && status != KeywordStatus.INVALID) { 3605 // temp.add(count.toString()); 3606 // } 3607 // } 3608 // } 3609 canonicalKeywords = Collections.unmodifiableSet(temp); 3610 } 3611 3612 @Override 3613 public String toString() { 3614 return countToExampleSet + "; " + exampleToCount + "; " + pluralRules; 3615 } 3616 3617 public Map<Count, Set<Double>> getCountToExamplesMap() { 3618 return countToExampleSet; 3619 } 3620 3621 public Map<Count, String> getCountToStringExamplesMap() { 3622 return countToStringExample; 3623 } 3624 3625 public Count getCount(double exampleCount) { 3626 return Count.valueOf(pluralRules.select(exampleCount)); 3627 } 3628 3629 public Count getCount(PluralRules.FixedDecimal exampleCount) { 3630 return Count.valueOf(pluralRules.select(exampleCount)); 3631 } 3632 3633 public PluralRules getPluralRules() { 3634 return pluralRules; 3635 } 3636 3637 public String getRules() { 3638 return pluralRulesString; 3639 } 3640 3641 public Count getDefault() { 3642 return null; 3643 } 3644 3645 public Set<String> getCanonicalKeywords() { 3646 return canonicalKeywords; 3647 } 3648 3649 public Set<Count> getCounts() { 3650 return keywords; 3651 } 3652 3653 public Set<Count> getCounts(SampleType sampleType) { 3654 return sampleType == SampleType.DECIMAL ? decimalKeywords : integerKeywords; 3655 } 3656 3657 /** 3658 * Return the integer samples from 0 to 9999. For simplicity and compactness, this is a UnicodeSet, but 3659 * the interpretation is simply as a list of integers. UnicodeSet.EMPTY is returned if there are none. 3660 * @param c 3661 * @return 3662 */ 3663 public SampleList getSamples9999(Count c) { 3664 return countSampleList.get(c); 3665 } 3666 3667 /** 3668 * Return the integer samples for the specified digit, eg 1 => 0..9. For simplicity and compactness, this is a UnicodeSet, but 3669 * the interpretation is simply as a list of integers. 3670 * @param c 3671 * @return 3672 */ 3673 public SampleList getSamples9999(Count c, int digit) { 3674 return countSampleList.get(c, digit); 3675 } 3676 3677 public boolean hasSamples(Count c, int digits) { 3678 SampleList samples = countSampleList.get(c, digits); 3679 return samples != null && (samples.fractionSize() > 0 || samples.intSize() > 0); 3680 } 3681 3682 public String getRule(Count keyword) { 3683 return countToRule.get(keyword); 3684 } 3685 3686 @Override 3687 public int compareTo(PluralInfo other) { 3688 int size1 = this.countToRule.size(); 3689 int size2 = other.countToRule.size(); 3690 int diff = size1 - size2; 3691 if (diff != 0) { 3692 return diff; 3693 } 3694 Iterator<Count> it1 = countToRule.keySet().iterator(); 3695 Iterator<Count> it2 = other.countToRule.keySet().iterator(); 3696 while (it1.hasNext()) { 3697 Count a1 = it1.next(); 3698 Count a2 = it2.next(); 3699 diff = a1.ordinal() - a2.ordinal(); 3700 if (diff != 0) { 3701 return diff; 3702 } 3703 } 3704 return pluralRules.compareTo(other.pluralRules); 3705 } 3706 3707 enum MinMax { 3708 MIN, MAX 3709 } 3710 3711 public static final FixedDecimal NEGATIVE_INFINITY = new FixedDecimal(Double.NEGATIVE_INFINITY, 0, 0); 3712 public static final FixedDecimal POSITIVE_INFINITY = new FixedDecimal(Double.POSITIVE_INFINITY, 0, 0); 3713 3714 static double doubleValue(FixedDecimal a) { 3715 return a.doubleValue(); 3716 } 3717 3718 public boolean rangeExists(Count s, Count e, Output<FixedDecimal> minSample, Output<FixedDecimal> maxSample) { 3719 if (!getCounts().contains(s) || !getCounts().contains(e)) { 3720 return false; 3721 } 3722 FixedDecimal temp; 3723 minSample.value = getLeastIn(s, SampleType.INTEGER, NEGATIVE_INFINITY, POSITIVE_INFINITY); 3724 temp = getLeastIn(s, SampleType.DECIMAL, NEGATIVE_INFINITY, POSITIVE_INFINITY); 3725 if (lessOrFewerDecimals(temp, minSample.value)) { 3726 minSample.value = temp; 3727 } 3728 maxSample.value = getGreatestIn(e, SampleType.INTEGER, NEGATIVE_INFINITY, POSITIVE_INFINITY); 3729 temp = getGreatestIn(e, SampleType.DECIMAL, NEGATIVE_INFINITY, POSITIVE_INFINITY); 3730 if (greaterOrFewerDecimals(temp, maxSample.value)) { 3731 maxSample.value = temp; 3732 } 3733 // if there is no range, just return 3734 if (doubleValue(minSample.value) >= doubleValue(maxSample.value)) { 3735 return false; 3736 } 3737 // see if we can get a better range, with not such a large end range 3738 3739 FixedDecimal lowestMax = new FixedDecimal(doubleValue(minSample.value) + 0.00001, 5); 3740 SampleType bestType = getCounts(SampleType.INTEGER).contains(e) ? SampleType.INTEGER : SampleType.DECIMAL; 3741 temp = getLeastIn(e, bestType, lowestMax, POSITIVE_INFINITY); 3742 if (lessOrFewerDecimals(temp, maxSample.value)) { 3743 maxSample.value = temp; 3744 } 3745 if (maxSample.value.getSource() > 100000) { 3746 temp = getLeastIn(e, bestType, lowestMax, POSITIVE_INFINITY); 3747 if (lessOrFewerDecimals(temp, maxSample.value)) { 3748 maxSample.value = temp; 3749 } 3750 } 3751 3752 return true; 3753 } 3754 3755 public boolean greaterOrFewerDecimals(FixedDecimal a, FixedDecimal b) { 3756 return doubleValue(a) > doubleValue(b) 3757 || doubleValue(b) == doubleValue(a) && b.getDecimalDigits() > a.getDecimalDigits(); 3758 } 3759 3760 public boolean lessOrFewerDecimals(FixedDecimal a, FixedDecimal b) { 3761 return doubleValue(a) < doubleValue(b) 3762 || doubleValue(b) == doubleValue(a) && b.getDecimalDigits() > a.getDecimalDigits(); 3763 } 3764 3765 private FixedDecimal getLeastIn(Count s, SampleType sampleType, FixedDecimal min, FixedDecimal max) { 3766 FixedDecimal result = POSITIVE_INFINITY; 3767 FixedDecimalSamples sSamples1 = pluralRules.getDecimalSamples(s.toString(), sampleType); 3768 if (sSamples1 != null) { 3769 for (FixedDecimalRange x : sSamples1.samples) { 3770 // overlap in ranges?? 3771 if (doubleValue(x.start) > doubleValue(max) 3772 || doubleValue(x.end) < doubleValue(min)) { 3773 continue; // no, continue 3774 } 3775 // get restricted range 3776 FixedDecimal minOverlap = greaterOrFewerDecimals(min, x.start) ? max : x.start; 3777 //FixedDecimal maxOverlap = lessOrFewerDecimals(max, x.end) ? max : x.end; 3778 3779 // replace if better 3780 if (lessOrFewerDecimals(minOverlap, result)) { 3781 result = minOverlap; 3782 } 3783 } 3784 } 3785 return result; 3786 } 3787 3788 private FixedDecimal getGreatestIn(Count s, SampleType sampleType, FixedDecimal min, FixedDecimal max) { 3789 FixedDecimal result = NEGATIVE_INFINITY; 3790 FixedDecimalSamples sSamples1 = pluralRules.getDecimalSamples(s.toString(), sampleType); 3791 if (sSamples1 != null) { 3792 for (FixedDecimalRange x : sSamples1.samples) { 3793 // overlap in ranges?? 3794 if (doubleValue(x.start) > doubleValue(max) 3795 || doubleValue(x.end) < doubleValue(min)) { 3796 continue; // no, continue 3797 } 3798 // get restricted range 3799 //FixedDecimal minOverlap = greaterOrFewerDecimals(min, x.start) ? max : x.start; 3800 FixedDecimal maxOverlap = lessOrFewerDecimals(max, x.end) ? max : x.end; 3801 3802 // replace if better 3803 if (greaterOrFewerDecimals(maxOverlap, result)) { 3804 result = maxOverlap; 3805 } 3806 } 3807 } 3808 return result; 3809 } 3810 3811 public static FixedDecimal getNonZeroSampleIfPossible(FixedDecimalSamples exampleList) { 3812 Set<FixedDecimalRange> sampleSet = exampleList.getSamples(); 3813 FixedDecimal sampleDecimal = null; 3814 // skip 0 if possible 3815 for (FixedDecimalRange range : sampleSet) { 3816 sampleDecimal = range.start; 3817 if (sampleDecimal.getSource() != 0.0) { 3818 break; 3819 } 3820 sampleDecimal = range.end; 3821 if (sampleDecimal.getSource() != 0.0) { 3822 break; 3823 } 3824 } 3825 return sampleDecimal; 3826 } 3827 } 3828 3829 /** 3830 * @deprecated use {@link #getPlurals(PluralType)} instead 3831 */ 3832 @Deprecated 3833 public Set<String> getPluralLocales() { 3834 return getPluralLocales(PluralType.cardinal); 3835 } 3836 3837 /** 3838 * @param type 3839 * @return the set of locales that have rules for the specified plural type 3840 */ 3841 public Set<String> getPluralLocales(PluralType type) { 3842 return localeToPluralInfo2.get(type).keySet(); 3843 } 3844 3845 public Set<String> getPluralRangesLocales() { 3846 return localeToPluralRanges.keySet(); 3847 } 3848 3849 public PluralRanges getPluralRanges(String locale) { 3850 return localeToPluralRanges.get(locale); 3851 } 3852 3853 /** 3854 * @deprecated use {@link #getPlurals(PluralType, String)} instead 3855 */ 3856 @Deprecated 3857 public PluralInfo getPlurals(String locale) { 3858 return getPlurals(locale, true); 3859 } 3860 3861 /** 3862 * Returns the plural info for a given locale. 3863 * 3864 * @param locale 3865 * @return 3866 */ 3867 public PluralInfo getPlurals(PluralType type, String locale) { 3868 return getPlurals(type, locale, true); 3869 } 3870 3871 /** 3872 * @deprecated use {@link #getPlurals(PluralType, String, boolean)} instead. 3873 */ 3874 @Deprecated 3875 public PluralInfo getPlurals(String locale, boolean allowRoot) { 3876 return getPlurals(PluralType.cardinal, locale, allowRoot); 3877 } 3878 3879 /** 3880 * Returns the plural info for a given locale. 3881 * 3882 * @param locale 3883 * @param allowRoot 3884 * @param type 3885 * @return 3886 */ 3887 public PluralInfo getPlurals(PluralType type, String locale, boolean allowRoot) { 3888 Map<String, PluralInfo> infoMap = localeToPluralInfo2.get(type); 3889 while (locale != null) { 3890 if (!allowRoot && locale.equals("root")) { 3891 break; 3892 } 3893 PluralInfo result = infoMap.get(locale); 3894 if (result != null) { 3895 return result; 3896 } 3897 locale = LocaleIDParser.getSimpleParent(locale); 3898 } 3899 return null; 3900 } 3901 3902 public DayPeriodInfo getDayPeriods(DayPeriodInfo.Type type, String locale) { 3903 Map<String, DayPeriodInfo> map1 = typeToLocaleToDayPeriodInfo.get(type); 3904 while (locale != null) { 3905 DayPeriodInfo result = map1.get(locale); 3906 if (result != null) { 3907 return result; 3908 } 3909 locale = LocaleIDParser.getSimpleParent(locale); 3910 } 3911 return null; 3912 } 3913 3914 public Set<String> getDayPeriodLocales(DayPeriodInfo.Type type) { 3915 return typeToLocaleToDayPeriodInfo.get(type).keySet(); 3916 } 3917 3918 private static CurrencyNumberInfo DEFAULT_NUMBER_INFO = new CurrencyNumberInfo(2, -1, -1, -1); 3919 3920 public CurrencyNumberInfo getCurrencyNumberInfo(String currency) { 3921 CurrencyNumberInfo result = currencyToCurrencyNumberInfo.get(currency); 3922 if (result == null) { 3923 result = DEFAULT_NUMBER_INFO; 3924 } 3925 return result; 3926 } 3927 3928 /** 3929 * Returns ordered set of currency data information 3930 * 3931 * @param territory 3932 * @return 3933 */ 3934 public Set<CurrencyDateInfo> getCurrencyDateInfo(String territory) { 3935 return territoryToCurrencyDateInfo.getAll(territory); 3936 } 3937 3938 /** 3939 * Returns ordered set of currency data information 3940 * 3941 * @param territory 3942 * @return 3943 */ 3944 public Set<String> getCurrencyTerritories() { 3945 return territoryToCurrencyDateInfo.keySet(); 3946 } 3947 3948 /** 3949 * Returns the ISO4217 currency code of the default currency for a given 3950 * territory. The default currency is the first one listed which is legal 3951 * tender at the present moment. 3952 * 3953 * @param territory 3954 * @return 3955 */ 3956 public String getDefaultCurrency(String territory) { 3957 3958 String result = "XXX"; 3959 Set<CurrencyDateInfo> targetCurrencyInfo = getCurrencyDateInfo(territory); 3960 if (targetCurrencyInfo == null) { 3961 /* 3962 * This happens during ConsoleCheckCLDR 3963 * territory = "419" 3964 * path = //ldml/numbers/currencyFormats[@numberSystem="latn"]/currencyFormatLength/currencyFormat[@type="accounting"]/pattern[@type="standard"] 3965 * value = ¤#,##0.00 3966 * Prevent NullPointerException 3967 */ 3968 return result; 3969 } 3970 Date now = new Date(); 3971 for (CurrencyDateInfo cdi : targetCurrencyInfo) { 3972 if (cdi.getStart().before(now) && cdi.getEnd().after(now) && cdi.isLegalTender()) { 3973 result = cdi.getCurrency(); 3974 break; 3975 } 3976 } 3977 return result; 3978 } 3979 3980 /** 3981 * Returns the ISO4217 currency code of the default currency for a given 3982 * CLDRLocale. The default currency is the first one listed which is legal 3983 * tender at the present moment. 3984 * 3985 * @param territory 3986 * @return 3987 */ 3988 public String getDefaultCurrency(CLDRLocale loc) { 3989 return getDefaultCurrency(loc.getCountry()); 3990 } 3991 3992 public Map<String, Set<TelephoneCodeInfo>> getTerritoryToTelephoneCodeInfo() { 3993 return territoryToTelephoneCodeInfo; 3994 } 3995 3996 public Set<TelephoneCodeInfo> getTelephoneCodeInfoForTerritory(String territory) { 3997 return territoryToTelephoneCodeInfo.get(territory); 3998 } 3999 4000 public Set<String> getTerritoriesForTelephoneCodeInfo() { 4001 return territoryToTelephoneCodeInfo.keySet(); 4002 } 4003 4004 private List<String> serialElements; 4005 private Collection<String> distinguishingAttributes; 4006 4007 // @Deprecated 4008 // public List<String> getSerialElements() { 4009 // return serialElements; 4010 // } 4011 4012 // @Deprecated 4013 // public Collection<String> getDistinguishingAttributes() { 4014 // return distinguishingAttributes; 4015 // } 4016 4017 /** 4018 * The Row is: desired, supported, percent, oneway 4019 * @param string the type (written-new, for new format) 4020 * @return 4021 */ 4022 public List<R4<String, String, Integer, Boolean>> getLanguageMatcherData(String string) { 4023 return languageMatch.get(string); 4024 } 4025 4026 public Set<String> getLanguageMatcherKeys() { 4027 return languageMatch.keySet(); 4028 } 4029 4030 /** 4031 * Return mapping from type to territory to data. 001 is the default. 4032 */ 4033 public Map<MeasurementType, Map<String, String>> getTerritoryMeasurementData() { 4034 return measurementData; 4035 } 4036 4037 /** 4038 * Return mapping from keys to subtypes 4039 */ 4040 public Relation<String, String> getBcp47Keys() { 4041 return bcp47Key2Subtypes; 4042 } 4043 4044 /** 4045 * Return mapping from extensions to keys 4046 */ 4047 public Relation<String, String> getBcp47Extension2Keys() { 4048 return bcp47Extension2Keys; 4049 } 4050 4051 /** 4052 * Return mapping from <key,subtype> to aliases 4053 */ 4054 public Relation<R2<String, String>, String> getBcp47Aliases() { 4055 return bcp47Aliases; 4056 } 4057 4058 /** 4059 * Return mapping from <key,subtype> to description 4060 */ 4061 public Map<R2<String, String>, String> getBcp47Descriptions() { 4062 return bcp47Descriptions; 4063 } 4064 4065 /** 4066 * Return mapping from <key,subtype> to since 4067 */ 4068 public Map<R2<String, String>, String> getBcp47Since() { 4069 return bcp47Since; 4070 } 4071 4072 /** 4073 * Return mapping from <key,subtype> to preferred 4074 */ 4075 public Map<R2<String, String>, String> getBcp47Preferred() { 4076 return bcp47Preferred; 4077 } 4078 4079 /** 4080 * Return mapping from <key,subtype> to deprecated 4081 */ 4082 public Map<R2<String, String>, String> getBcp47Deprecated() { 4083 return bcp47Deprecated; 4084 } 4085 4086 /** 4087 * Return mapping from subtype to deprecated 4088 */ 4089 public Map<String, String> getBcp47ValueType() { 4090 return bcp47ValueType; 4091 } 4092 4093 4094 static Set<String> MainTimeZones; 4095 4096 /** 4097 * Return canonical timezones 4098 * 4099 * @return 4100 */ 4101 public Set<String> getCanonicalTimeZones() { 4102 synchronized (SupplementalDataInfo.class) { 4103 if (MainTimeZones == null) { 4104 MainTimeZones = new TreeSet<>(); 4105 SupplementalDataInfo info = SupplementalDataInfo.getInstance(); 4106 for (Entry<R2<String, String>, Set<String>> entry : info.getBcp47Aliases().keyValuesSet()) { 4107 R2<String, String> subtype_aliases = entry.getKey(); 4108 if (!subtype_aliases.get0().equals("timezone")) { 4109 continue; 4110 } 4111 MainTimeZones.add(entry.getValue().iterator().next()); 4112 } 4113 MainTimeZones = Collections.unmodifiableSet(MainTimeZones); 4114 } 4115 return MainTimeZones; 4116 } 4117 } 4118 4119 public Set<MetaZoneRange> getMetaZoneRanges(String zone) { 4120 return zoneToMetaZoneRanges.get(zone); 4121 } 4122 4123 /** 4124 * Return the metazone containing this zone at this date 4125 * 4126 * @param zone 4127 * @param date 4128 * @return 4129 */ 4130 public MetaZoneRange getMetaZoneRange(String zone, long date) { 4131 Set<MetaZoneRange> metazoneRanges = zoneToMetaZoneRanges.get(zone); 4132 if (metazoneRanges != null) { 4133 for (MetaZoneRange metazoneRange : metazoneRanges) { 4134 if (metazoneRange.dateRange.getFrom() <= date && date < metazoneRange.dateRange.getTo()) { 4135 return metazoneRange; 4136 } 4137 } 4138 } 4139 return null; 4140 } 4141 4142 public boolean isDeprecated(DtdType type, String element, String attribute, String value) { 4143 return DtdData.getInstance(type).isDeprecated(element, attribute, value); 4144 } 4145 4146 public boolean isDeprecated(DtdType type, String path) { 4147 4148 XPathParts parts = XPathParts.getFrozenInstance(path); 4149 for (int i = 0; i < parts.size(); ++i) { 4150 String element = parts.getElement(i); 4151 if (isDeprecated(type, element, "*", "*")) { 4152 return true; 4153 } 4154 for (Entry<String, String> entry : parts.getAttributes(i).entrySet()) { 4155 String attribute = entry.getKey(); 4156 String value = entry.getValue(); 4157 if (isDeprecated(type, element, attribute, value)) { 4158 return true; 4159 } 4160 } 4161 } 4162 return false; 4163 } 4164 4165 /** 4166 * Returns map of ID/Type/Value, such as id="$integer" type="regex" value=[0-9]+ 4167 * @return 4168 */ 4169 public Map<String, R2<String, String>> getValidityInfo() { 4170 return validityInfo; 4171 } 4172 4173 public Set<String> getCLDRLanguageCodes() { 4174 return CLDRLanguageCodes; 4175 } 4176 4177 public boolean isCLDRLanguageCode(String code) { 4178 return CLDRLanguageCodes.contains(code); 4179 } 4180 4181 public Set<String> getCLDRScriptCodes() { 4182 return CLDRScriptCodes; 4183 } 4184 4185 public boolean isCLDRScriptCode(String code) { 4186 return CLDRScriptCodes.contains(code); 4187 } 4188 4189 private synchronized void initCLDRLocaleBasedData() throws InternalError { 4190 // This initialization depends on SDI being initialized. 4191 if (defaultContentToBase == null) { 4192 Map<CLDRLocale, CLDRLocale> p2c = new TreeMap<>(); 4193 Map<CLDRLocale, CLDRLocale> c2p = new TreeMap<>(); 4194 TreeSet<CLDRLocale> tmpAllLocales = new TreeSet<>(); 4195 // copied from SupplementalData.java - CLDRLocale based 4196 for (String l : defaultContentLocales) { 4197 CLDRLocale child = CLDRLocale.getInstance(l); 4198 tmpAllLocales.add(child); 4199 } 4200 4201 for (CLDRLocale child : tmpAllLocales) { 4202 // Find a parent of this locale which is NOT itself also a defaultContent 4203 CLDRLocale nextParent = child.getParent(); 4204 // /System.err.println(">> considering " + child + " with parent " + nextParent); 4205 while (nextParent != null) { 4206 if (!tmpAllLocales.contains(nextParent)) { // Did we find a parent that's also not itself a 4207 // defaultContent? 4208 // /System.err.println(">>>> Got 1? considering " + child + " with parent " + nextParent); 4209 break; 4210 } 4211 // /System.err.println(">>>>> considering " + child + " with parent " + nextParent); 4212 nextParent = nextParent.getParent(); 4213 } 4214 // parent 4215 if (nextParent == null) { 4216 throw new InternalError("SupplementalDataInfo.defaultContentToChild(): No valid parent for " 4217 + child); 4218 } else if (nextParent == CLDRLocale.ROOT || nextParent == CLDRLocale.getInstance("root")) { 4219 throw new InternalError( 4220 "SupplementalDataInfo.defaultContentToChild(): Parent is root for default content locale " 4221 + child); 4222 } else { 4223 c2p.put(child, nextParent); // wo_Arab_SN -> wo 4224 CLDRLocale oldChild = p2c.get(nextParent); 4225 if (oldChild != null) { 4226 CLDRLocale childParent = child.getParent(); 4227 if (!childParent.equals(oldChild)) { 4228 throw new InternalError( 4229 "SupplementalData.defaultContentToChild(): defaultContent list in wrong order? Tried to map " 4230 + nextParent + " -> " + child + ", replacing " + oldChild + " (should have been " 4231 + childParent + ")"); 4232 } 4233 } 4234 p2c.put(nextParent, child); // wo -> wo_Arab_SN 4235 } 4236 } 4237 4238 // done, save the hashtables.. 4239 baseToDefaultContent = Collections.unmodifiableMap(p2c); // wo -> wo_Arab_SN 4240 defaultContentToBase = Collections.unmodifiableMap(c2p); // wo_Arab_SN -> wo 4241 } 4242 } 4243 4244 public Map<String, PreferredAndAllowedHour> getTimeData() { 4245 return timeData; 4246 } 4247 4248 public String getDefaultScript(String baseLanguage) { 4249 String ls = likelySubtags.get(baseLanguage); 4250 if (ls == null) { 4251 return UNKNOWN_SCRIPT; 4252 } 4253 LocaleIDParser lp = new LocaleIDParser().set(ls); 4254 String defaultScript = lp.getScript(); 4255 if (defaultScript.length() > 0) { 4256 return defaultScript; 4257 } else { 4258 return UNKNOWN_SCRIPT; 4259 } 4260 } 4261 4262 private XEquivalenceClass<String, String> equivalentLocales = null; 4263 4264 public Set<String> getEquivalentsForLocale(String localeId) { 4265 if (equivalentLocales == null) { 4266 equivalentLocales = getEquivalentsForLocale(); 4267 } 4268 Set<String> result = new TreeSet(LENGTH_FIRST); 4269 result.add(localeId); 4270 Set<String> equiv = equivalentLocales.getEquivalences(localeId); 4271 // if (equiv == null) { 4272 // result.add(localeId); 4273 // return result; 4274 // } 4275 if (equiv != null) { 4276 result.addAll(equivalentLocales.getEquivalences(localeId)); 4277 } 4278 Map<String, String> likely = getLikelySubtags(); 4279 String newMax = LikelySubtags.maximize(localeId, likely); 4280 if (newMax != null) { 4281 result.add(newMax); 4282 newMax = LikelySubtags.minimize(localeId, likely, true); 4283 if (newMax != null) { 4284 result.add(newMax); 4285 } 4286 newMax = LikelySubtags.minimize(localeId, likely, false); 4287 if (newMax != null) { 4288 result.add(newMax); 4289 } 4290 } 4291 4292 // if (result.size() == 1) { 4293 // LanguageTagParser ltp = new LanguageTagParser().set(localeId); 4294 // if (ltp.getScript().isEmpty()) { 4295 // String ds = getDefaultScript(ltp.getLanguage()); 4296 // if (ds != null) { 4297 // ltp.setScript(ds); 4298 // result.add(ltp.toString()); 4299 // } 4300 // } 4301 // } 4302 return result; 4303 } 4304 4305 public final static class LengthFirstComparator<T> implements Comparator<T> { 4306 @Override 4307 public int compare(T a, T b) { 4308 String as = a.toString(); 4309 String bs = b.toString(); 4310 if (as.length() < bs.length()) 4311 return -1; 4312 if (as.length() > bs.length()) 4313 return 1; 4314 return as.compareTo(bs); 4315 } 4316 } 4317 4318 public static final LengthFirstComparator LENGTH_FIRST = new LengthFirstComparator(); 4319 4320 private synchronized XEquivalenceClass<String, String> getEquivalentsForLocale() { 4321 SupplementalDataInfo sdi = this; 4322 Relation<String, String> localeToDefaultContents = Relation.of(new HashMap<String, Set<String>>(), 4323 LinkedHashSet.class); 4324 4325 Set<String> dcl = sdi.getDefaultContentLocales(); 4326 Map<String, String> likely = sdi.getLikelySubtags(); 4327 XEquivalenceClass<String, String> locales = new XEquivalenceClass<>(); 4328 LanguageTagParser ltp = new LanguageTagParser(); 4329 Set<String> temp = new HashSet<>(); 4330 for (Entry<String, String> entry : likely.entrySet()) { 4331 String source = entry.getKey(); 4332 if (source.startsWith("und")) { 4333 continue; 4334 } 4335 for (String s : getCombinations(source, ltp, likely, temp)) { 4336 locales.add(source, s); 4337 } 4338 for (String s : getCombinations(entry.getValue(), ltp, likely, temp)) { 4339 locales.add(source, s); 4340 } 4341 } 4342 // Set<String> sorted = new TreeSet(locales.getExplicitItems()); 4343 // for (String s : sorted) { 4344 // System.out.println(locales.getEquivalences(s)); 4345 // } 4346 for (String defaultContentLocale : dcl) { 4347 if (defaultContentLocale.startsWith("zh")) { 4348 int x = 0; 4349 } 4350 Set<String> set = locales.getEquivalences(defaultContentLocale); 4351 4352 String parent = LocaleIDParser.getSimpleParent(defaultContentLocale); 4353 if (!set.contains(parent)) { 4354 localeToDefaultContents.put(parent, defaultContentLocale); 4355 //System.out.println("Mismatch " + parent + ", " + set); 4356 } 4357 if (parent.contains("_")) { 4358 continue; 4359 } 4360 // only base locales after this point 4361 String ds = sdi.getDefaultScript(parent); 4362 if (ds != null) { 4363 ltp.set(parent); 4364 ltp.setScript(ds); 4365 String trial = ltp.toString(); 4366 if (!set.contains(trial)) { 4367 //System.out.println("Mismatch " + trial + ", " + set); 4368 localeToDefaultContents.put(parent, trial); 4369 } 4370 } 4371 } 4372 return locales; 4373 } 4374 4375 private Set<String> getCombinations(String source, LanguageTagParser ltp, Map<String, String> likely, 4376 Set<String> locales) { 4377 locales.clear(); 4378 4379 String max = LikelySubtags.maximize(source, likely); 4380 locales.add(max); 4381 4382 ltp.set(source); 4383 ltp.setScript(""); 4384 String trial = ltp.toString(); 4385 String newMax = LikelySubtags.maximize(trial, likely); 4386 if (Objects.equals(newMax, max)) { 4387 locales.add(trial); 4388 } 4389 4390 ltp.set(source); 4391 ltp.setRegion(""); 4392 trial = ltp.toString(); 4393 newMax = LikelySubtags.maximize(trial, likely); 4394 if (Objects.equals(newMax, max)) { 4395 locales.add(trial); 4396 } 4397 4398 return locales; 4399 } 4400 4401 public VersionInfo getCldrVersion() { 4402 return cldrVersion; 4403 } 4404 4405 public File getDirectory() { 4406 return directory; 4407 } 4408 4409 public final static Splitter WHITESPACE_SPLTTER = Splitter.on(PatternCache.get("\\s+")).omitEmptyStrings(); 4410 4411 public static final class AttributeValidityInfo { 4412 //<attributeValues elements="alias" attributes="path" type="path">notDoneYet</attributeValues> 4413 4414 final String type; 4415 final Set<DtdType> dtds; 4416 final Set<String> elements; 4417 final Set<String> attributes; 4418 final String order; 4419 4420 @Override 4421 public String toString() { 4422 return "type:" + type 4423 + ", elements:" + elements 4424 + ", attributes:" + attributes 4425 + ", order:" + order; 4426 } 4427 4428 static void add(Map<String, String> inputAttibutes, String inputValue, Map<AttributeValidityInfo, String> data) { 4429 final AttributeValidityInfo key = new AttributeValidityInfo( 4430 inputAttibutes.get("dtds"), 4431 inputAttibutes.get("type"), 4432 inputAttibutes.get("attributes"), 4433 inputAttibutes.get("elements"), 4434 inputAttibutes.get("order")); 4435 if (data.containsKey(key)) { 4436 throw new IllegalArgumentException(key + " declared twice"); 4437 } 4438 data.put(key, inputValue); 4439 } 4440 4441 public AttributeValidityInfo(String dtds, String type, String attributes, String elements, String order) { 4442 if (dtds == null) { 4443 this.dtds = Collections.singleton(DtdType.ldml); 4444 } else { 4445 Set<DtdType> temp = EnumSet.noneOf(DtdType.class); 4446 for (String s : WHITESPACE_SPLTTER.split(dtds)) { 4447 temp.add(DtdType.valueOf(s)); 4448 } 4449 this.dtds = Collections.unmodifiableSet(temp); 4450 } 4451 this.type = type != null ? type : order != null ? "choice" : null; 4452 this.elements = elements == null ? Collections.EMPTY_SET 4453 : With.in(WHITESPACE_SPLTTER.split(elements)).toUnmodifiableCollection(new HashSet<String>()); 4454 this.attributes = With.in(WHITESPACE_SPLTTER.split(attributes)).toUnmodifiableCollection(new HashSet<String>()); 4455 this.order = order; 4456 } 4457 4458 public String getType() { 4459 return type; 4460 } 4461 4462 public Set<DtdType> getDtds() { 4463 return dtds; 4464 } 4465 4466 public Set<String> getElements() { 4467 return elements; 4468 } 4469 4470 public Set<String> getAttributes() { 4471 return attributes; 4472 } 4473 4474 public String getOrder() { 4475 return order; 4476 } 4477 4478 @Override 4479 public boolean equals(Object obj) { 4480 AttributeValidityInfo other = (AttributeValidityInfo) obj; 4481 return CldrUtility.deepEquals( 4482 type, other.type, 4483 dtds, other.dtds, 4484 elements, other.elements, 4485 attributes, other.attributes, 4486 order, other.order); 4487 } 4488 4489 @Override 4490 public int hashCode() { 4491 return Objects.hash(type, dtds, elements, attributes, order); 4492 } 4493 } 4494 4495 public Map<AttributeValidityInfo, String> getAttributeValidity() { 4496 return attributeValidityInfo; 4497 } 4498 4499 public Multimap<String, String> getLanguageGroups() { 4500 return languageGroups; 4501 } 4502 4503 public UnitConverter getUnitConverter() { 4504 return unitConverter; 4505 } 4506 4507 public RationalParser getRationalParser() { 4508 return rationalParser; 4509 } 4510 4511 public UnitPreferences getUnitPreferences() { 4512 return unitPreferences; 4513 } 4514 4515 /** 4516 * locales that have grammar info 4517 */ 4518 public Set<String> hasGrammarInfo() { 4519 return grammarLocaleToTargetToFeatureToValues.keySet(); 4520 } 4521 4522 /** 4523 * Grammar info for locales, with inheritance 4524 * @param seedOnly 4525 * @return 4526 */ 4527 public GrammarInfo getGrammarInfo(String locale) { 4528 return getGrammarInfo(locale, false); 4529 } 4530 4531 /** 4532 * Special hack for v38; should drop seedOnly later. 4533 * @param locale 4534 * @param seedOnly 4535 * @return 4536 */ 4537 @Deprecated 4538 public GrammarInfo getGrammarInfo(String locale, boolean seedOnly) { 4539 for (;locale != null; locale = LocaleIDParser.getParent(locale)) { 4540 if (seedOnly && !GrammarInfo.SEED_LOCALES.contains(locale)) { 4541 continue; 4542 } 4543 GrammarInfo result = grammarLocaleToTargetToFeatureToValues.get(locale); 4544 if (result != null) { 4545 return result; 4546 } 4547 } 4548 return null; 4549 } 4550 4551 public Set<String> hasGrammarDerivation() { 4552 return localeToGrammarDerivation.keySet(); 4553 } 4554 4555 4556 public GrammarDerivation getGrammarDerivation(String locale) { 4557 for (;locale != null; locale = LocaleIDParser.getParent(locale)) { 4558 GrammarDerivation result = localeToGrammarDerivation.get(locale); 4559 if (result != null) { 4560 return result; 4561 } 4562 } 4563 return null; 4564 } 4565 } 4566