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