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