1 package org.unicode.cldr.util; 2 3 import com.google.common.base.Joiner; 4 import com.google.common.collect.ImmutableMap; 5 import com.google.common.collect.ImmutableSet; 6 import com.google.common.collect.ImmutableSortedSet; 7 import com.ibm.icu.util.Freezable; 8 import com.ibm.icu.util.Output; 9 import java.util.Arrays; 10 import java.util.Collection; 11 import java.util.Collections; 12 import java.util.Comparator; 13 import java.util.List; 14 import java.util.Map; 15 import java.util.Map.Entry; 16 import java.util.Set; 17 import java.util.TreeMap; 18 import java.util.TreeSet; 19 import java.util.regex.Matcher; 20 import java.util.regex.Pattern; 21 import java.util.stream.Collectors; 22 import org.unicode.cldr.util.UnitConverter.UnitSystem; 23 24 /** 25 * Get the info from supplemental data, eg 26 * CLDRConfig.getInstance().getSupplementalDataInfo().getGrammarInfo("fr"); Use hasGrammarInfo() to 27 * see which locales have it. 28 * 29 * @author markdavis 30 */ 31 public class GrammarInfo implements Freezable<GrammarInfo> { 32 33 public enum GrammaticalTarget { 34 nominal 35 } 36 37 /** 38 * There is no standard order of grammatical case values across languages. This ordering is 39 * based on the French order for Indo-European cases, then interleaving others (Finnish) where 40 * clear, then adding the rest in alphabetical order. Note that any given language will only see 41 * the values that their language uses. We may refine this order over time. 42 */ 43 public enum CaseValues { 44 nominative, 45 vocative, 46 accusative, 47 oblique, 48 genitive, 49 partitive, 50 locative, 51 dative, 52 instrumental, 53 prepositional, 54 ablative, 55 inessive, 56 elative, 57 illative, 58 adessive, 59 allative, 60 essive, 61 translative, 62 abessive, 63 comitative, 64 causal, 65 delative, 66 ergative, 67 locativecopulative, 68 sociative, 69 sublative, 70 superessive, 71 terminative; 72 public static Comparator<String> COMPARATOR = EnumComparator.create(CaseValues.class); 73 } 74 /** 75 * There is no standard order of grammatical gender values across languages. The ordering uses 76 * Polish order https://en.wikipedia.org/wiki/Polish_grammar#Gender as a base (since it has most 77 * of the values), then interleaves common (a merger of masculine and feminine) into the 78 * ordering. Note that any given language will only see the values that their language uses. We 79 * may refine this order over time. 80 */ 81 public enum GenderValues { 82 personal, 83 animate, 84 inanimate, 85 masculine, 86 feminine, 87 common, 88 neuter; 89 public static Comparator<String> COMPARATOR = EnumComparator.create(GenderValues.class); 90 } 91 92 public enum DefinitenessValues { 93 unspecified, 94 indefinite, 95 definite, 96 construct; 97 public static Comparator<String> COMPARATOR = 98 EnumComparator.create(DefinitenessValues.class); 99 } 100 101 public enum PluralValues { 102 zero, 103 one, 104 two, 105 few, 106 many, 107 other; 108 public static Comparator<String> COMPARATOR = EnumComparator.create(PluralValues.class); 109 } 110 111 public enum GrammaticalFeature { 112 grammaticalNumber("plural", "Ⓟ", "other", PluralValues.COMPARATOR), 113 grammaticalCase("case", "Ⓒ", "nominative", CaseValues.COMPARATOR), 114 grammaticalDefiniteness("definiteness", "Ⓓ", "indefinite", DefinitenessValues.COMPARATOR), 115 grammaticalGender("gender", "Ⓖ", "neuter", GenderValues.COMPARATOR); 116 117 private final String shortName; 118 private final String symbol; 119 private final String defaultValue; 120 private final Comparator<String> comparator; 121 122 public static final Pattern PATH_HAS_FEATURE = 123 Pattern.compile("\\[@(count|case|gender|definiteness)="); 124 GrammaticalFeature( String shortName, String symbol, String defaultValue, Comparator<String> comparator)125 GrammaticalFeature( 126 String shortName, 127 String symbol, 128 String defaultValue, 129 Comparator<String> comparator) { 130 this.shortName = shortName; 131 this.symbol = symbol; 132 this.defaultValue = defaultValue; 133 this.comparator = comparator; 134 } 135 getShortName()136 public String getShortName() { 137 return shortName; 138 } 139 getSymbol()140 public CharSequence getSymbol() { 141 return symbol; 142 } 143 /** Gets the default value. The parameter only needs to be set for grammaticalGender */ getDefault(Collection<String> featureValuesFromGrammaticalInfo)144 public String getDefault(Collection<String> featureValuesFromGrammaticalInfo) { 145 return this == grammaticalGender 146 && featureValuesFromGrammaticalInfo != null 147 && !featureValuesFromGrammaticalInfo.contains("neuter") 148 ? "masculine" 149 : defaultValue; 150 } 151 pathHasFeature(String path)152 public static Matcher pathHasFeature(String path) { 153 Matcher result = PATH_HAS_FEATURE.matcher(path); 154 return result.find() ? result : null; 155 } 156 157 static final Map<String, GrammaticalFeature> shortNameToEnum = 158 ImmutableMap.copyOf( 159 Arrays.asList(GrammaticalFeature.values()).stream() 160 .collect(Collectors.toMap(e -> e.shortName, e -> e))); 161 fromName(String name)162 public static GrammaticalFeature fromName(String name) { 163 GrammaticalFeature result = shortNameToEnum.get(name); 164 return result != null ? result : valueOf(name); 165 } 166 getValueComparator()167 public Comparator getValueComparator() { 168 return comparator; 169 } 170 } 171 172 public enum GrammaticalScope { 173 general, 174 units, 175 personNames 176 } 177 178 private Map<GrammaticalTarget, Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>>> 179 targetToFeatureToUsageToValues = new TreeMap<>(); 180 private boolean frozen = false; 181 182 /** Only internal */ 183 @Deprecated add( GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage, String value)184 public void add( 185 GrammaticalTarget target, 186 GrammaticalFeature feature, 187 GrammaticalScope usage, 188 String value) { 189 Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>> featureToUsageToValues = 190 targetToFeatureToUsageToValues.get(target); 191 if (featureToUsageToValues == null) { 192 targetToFeatureToUsageToValues.put(target, featureToUsageToValues = new TreeMap<>()); 193 } 194 if (feature != null) { 195 Map<GrammaticalScope, Set<String>> usageToValues = featureToUsageToValues.get(feature); 196 if (usageToValues == null) { 197 featureToUsageToValues.put(feature, usageToValues = new TreeMap<>()); 198 } 199 Set<String> values = usageToValues.get(usage); 200 if (values == null) { 201 usageToValues.put(usage, values = new TreeSet<>()); 202 } 203 if (value != null) { 204 values.add(value); 205 } else { 206 int debug = 0; 207 } 208 } 209 } 210 211 /** Only internal */ 212 @Deprecated add( GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage, Collection<String> valueSet)213 public void add( 214 GrammaticalTarget target, 215 GrammaticalFeature feature, 216 GrammaticalScope usage, 217 Collection<String> valueSet) { 218 Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>> featureToUsageToValues = 219 targetToFeatureToUsageToValues.get(target); 220 if (featureToUsageToValues == null) { 221 targetToFeatureToUsageToValues.put(target, featureToUsageToValues = new TreeMap<>()); 222 } 223 if (feature != null) { 224 Map<GrammaticalScope, Set<String>> usageToValues = featureToUsageToValues.get(feature); 225 if (usageToValues == null) { 226 featureToUsageToValues.put(feature, usageToValues = new TreeMap<>()); 227 } 228 Set<String> values = usageToValues.get(usage); 229 if (values == null) { 230 usageToValues.put(usage, values = new TreeSet<>()); 231 } 232 validate(feature, valueSet); 233 values.addAll(valueSet); 234 } 235 } 236 validate(GrammaticalFeature feature, Collection<String> valueSet)237 private void validate(GrammaticalFeature feature, Collection<String> valueSet) { 238 for (String value : valueSet) { 239 validate(feature, value); 240 } 241 } 242 validate(GrammaticalFeature feature, String value)243 private void validate(GrammaticalFeature feature, String value) { 244 switch (feature) { 245 case grammaticalCase: 246 CaseValues.valueOf(value); 247 break; 248 case grammaticalDefiniteness: 249 DefinitenessValues.valueOf(value); 250 break; 251 case grammaticalGender: 252 GenderValues.valueOf(value); 253 break; 254 case grammaticalNumber: 255 PluralValues.valueOf(value); 256 break; 257 } 258 } 259 260 /** Note: when there is known to be no features, the featureRaw will be null Only internal */ 261 @Deprecated add(String targetsRaw, String featureRaw, String usagesRaw, String valuesRaw)262 public void add(String targetsRaw, String featureRaw, String usagesRaw, String valuesRaw) { 263 for (String targetString : SupplementalDataInfo.split_space.split(targetsRaw)) { 264 GrammaticalTarget target = GrammaticalTarget.valueOf(targetString); 265 if (featureRaw == null) { 266 add(target, null, null, (String) null); 267 } else { 268 final GrammaticalFeature feature = GrammaticalFeature.valueOf(featureRaw); 269 270 List<String> usages = 271 usagesRaw == null 272 ? Collections.singletonList(GrammaticalScope.general.toString()) 273 : SupplementalDataInfo.split_space.splitToList(usagesRaw); 274 275 List<String> values = 276 valuesRaw == null 277 ? Collections.emptyList() 278 : SupplementalDataInfo.split_space.splitToList(valuesRaw); 279 for (String usageRaw : usages) { 280 GrammaticalScope usage = GrammaticalScope.valueOf(usageRaw); 281 add(target, feature, usage, values); 282 } 283 } 284 } 285 } 286 287 @Override isFrozen()288 public boolean isFrozen() { 289 return frozen; 290 } 291 292 @Override freeze()293 public GrammarInfo freeze() { 294 if (!frozen) { 295 Map<GrammaticalTarget, Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>>> 296 temp = CldrUtility.protectCollection(targetToFeatureToUsageToValues); 297 if (!temp.equals(targetToFeatureToUsageToValues)) { 298 throw new IllegalArgumentException(); 299 } 300 targetToFeatureToUsageToValues = temp; 301 frozen = true; 302 } 303 return this; 304 } 305 306 @Override cloneAsThawed()307 public GrammarInfo cloneAsThawed() { 308 GrammarInfo result = new GrammarInfo(); 309 this.forEach3((t, f, u, v) -> result.add(t, f, u, v)); 310 return result; 311 } 312 313 static interface Handler4<T, F, U, V> { apply(T t, F f, U u, V v)314 void apply(T t, F f, U u, V v); 315 } 316 forEach( Handler4<GrammaticalTarget, GrammaticalFeature, GrammaticalScope, String> handler)317 public void forEach( 318 Handler4<GrammaticalTarget, GrammaticalFeature, GrammaticalScope, String> handler) { 319 for (Entry<GrammaticalTarget, Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>>> 320 entry1 : targetToFeatureToUsageToValues.entrySet()) { 321 GrammaticalTarget target = entry1.getKey(); 322 final Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>> 323 featureToUsageToValues = entry1.getValue(); 324 if (featureToUsageToValues.isEmpty()) { 325 handler.apply(target, null, null, null); 326 } else 327 for (Entry<GrammaticalFeature, Map<GrammaticalScope, Set<String>>> entry2 : 328 featureToUsageToValues.entrySet()) { 329 GrammaticalFeature feature = entry2.getKey(); 330 for (Entry<GrammaticalScope, Set<String>> entry3 : 331 entry2.getValue().entrySet()) { 332 final GrammaticalScope usage = entry3.getKey(); 333 for (String value : entry3.getValue()) { 334 handler.apply(target, feature, usage, value); 335 } 336 } 337 } 338 } 339 } 340 341 static interface Handler3<T, F, U, V> { apply(T t, F f, U u, V v)342 void apply(T t, F f, U u, V v); 343 } 344 forEach3( Handler3<GrammaticalTarget, GrammaticalFeature, GrammaticalScope, Collection<String>> handler)345 public void forEach3( 346 Handler3<GrammaticalTarget, GrammaticalFeature, GrammaticalScope, Collection<String>> 347 handler) { 348 for (Entry<GrammaticalTarget, Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>>> 349 entry1 : targetToFeatureToUsageToValues.entrySet()) { 350 GrammaticalTarget target = entry1.getKey(); 351 final Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>> 352 featureToUsageToValues = entry1.getValue(); 353 if (featureToUsageToValues.isEmpty()) { 354 handler.apply(target, null, null, null); 355 } else 356 for (Entry<GrammaticalFeature, Map<GrammaticalScope, Set<String>>> entry2 : 357 featureToUsageToValues.entrySet()) { 358 GrammaticalFeature feature = entry2.getKey(); 359 for (Entry<GrammaticalScope, Set<String>> entry3 : 360 entry2.getValue().entrySet()) { 361 final GrammaticalScope usage = entry3.getKey(); 362 final Collection<String> values = entry3.getValue(); 363 handler.apply(target, feature, usage, values); 364 } 365 } 366 } 367 } 368 369 /** 370 * Returns null if there is no known information. Otherwise returns the information for the 371 * locale (which may be empty if there are no variants) 372 */ get( GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage)373 public Collection<String> get( 374 GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage) { 375 Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>> featureToUsageToValues = 376 targetToFeatureToUsageToValues.get(target); 377 if (featureToUsageToValues == null) { 378 return Collections.emptySet(); 379 } 380 Map<GrammaticalScope, Set<String>> usageToValues = featureToUsageToValues.get(feature); 381 if (usageToValues == null) { 382 return Collections.emptySet(); 383 } 384 Collection<String> result = usageToValues.get(usage); 385 return result == null ? usageToValues.get(GrammaticalScope.general) : result; 386 } 387 get( GrammaticalTarget target, GrammaticalFeature feature)388 public Map<GrammaticalScope, Set<String>> get( 389 GrammaticalTarget target, GrammaticalFeature feature) { 390 Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>> featureToUsageToValues = 391 targetToFeatureToUsageToValues.get(target); 392 if (featureToUsageToValues == null) { 393 return Collections.emptyMap(); 394 } 395 Map<GrammaticalScope, Set<String>> usageToValues = featureToUsageToValues.get(feature); 396 if (usageToValues == null) { 397 return Collections.emptyMap(); 398 } 399 return usageToValues; 400 } 401 hasInfo(GrammaticalTarget target)402 public boolean hasInfo(GrammaticalTarget target) { 403 return targetToFeatureToUsageToValues.containsKey(target); 404 } 405 406 @Override toString()407 public String toString() { 408 return toString("\n"); 409 } 410 toString(String lineSep)411 public String toString(String lineSep) { 412 StringBuilder result = new StringBuilder(); 413 this.forEach3( 414 (t, f, u, v) -> { 415 result.append(lineSep); 416 result.append( 417 "{" 418 + (t == null ? "" : t.toString()) 419 + "}" 420 + "\t{" 421 + (f == null ? "" : f.toString()) 422 + "}" 423 + "\t{" 424 + (u == null ? "" : u.toString()) 425 + "}" 426 + "\t{" 427 + (v == null ? "" : Joiner.on(' ').join(v)) 428 + "}"); 429 }); 430 return result.toString(); 431 } 432 getGrammaticalInfoAttributes( GrammarInfo grammarInfo, UnitPathType pathType, String plural, String gender, String caseVariant)433 public static String getGrammaticalInfoAttributes( 434 GrammarInfo grammarInfo, 435 UnitPathType pathType, 436 String plural, 437 String gender, 438 String caseVariant) { 439 String grammaticalAttributes = ""; 440 if (pathType.features.contains(GrammaticalFeature.grammaticalNumber)) { // count is special 441 grammaticalAttributes += "[@count=\"" + (plural == null ? "other" : plural) + "\"]"; 442 } 443 if (grammarInfo != null 444 && gender != null 445 && pathType.features.contains(GrammaticalFeature.grammaticalGender)) { 446 Collection<String> genders = 447 grammarInfo.get( 448 GrammaticalTarget.nominal, 449 GrammaticalFeature.grammaticalGender, 450 GrammaticalScope.units); 451 if (!gender.equals(GrammaticalFeature.grammaticalGender.getDefault(genders))) { 452 grammaticalAttributes += "[@gender=\"" + gender + "\"]"; 453 } 454 } 455 if (grammarInfo != null 456 && caseVariant != null 457 && pathType.features.contains(GrammaticalFeature.grammaticalCase) 458 && !caseVariant.equals(GrammaticalFeature.grammaticalCase.getDefault(null))) { 459 grammaticalAttributes += "[@case=\"" + caseVariant + "\"]"; 460 } 461 return grammaticalAttributes; 462 } 463 464 /** TODO: change this to be data-file driven */ 465 private static final Set<String> CORE_UNITS_NEEDING_GRAMMAR = 466 ImmutableSet.of( 467 // new in v38 468 "mass-grain", 469 "volume-dessert-spoon", 470 "volume-dessert-spoon-imperial", 471 "volume-drop", 472 "volume-dram", 473 "volume-jigger", 474 "volume-pinch", 475 "volume-quart-imperial", 476 // "volume-pint-imperial", 477 478 "acceleration-meter-per-square-second", 479 "area-acre", 480 "area-hectare", 481 "area-square-centimeter", 482 "area-square-foot", 483 "area-square-kilometer", 484 "area-square-mile", 485 "concentr-percent", 486 "consumption-mile-per-gallon", 487 "consumption-mile-per-gallon-imperial", 488 "duration-day", 489 "duration-hour", 490 "duration-minute", 491 "duration-month", 492 "duration-second", 493 "duration-week", 494 "duration-year", 495 "energy-foodcalorie", 496 "energy-kilocalorie", 497 "length-centimeter", 498 "length-foot", 499 "length-inch", 500 "length-kilometer", 501 "length-meter", 502 "length-mile", 503 "length-millimeter", 504 "length-parsec", 505 "length-picometer", 506 "length-solar-radius", 507 "length-yard", 508 "light-solar-luminosity", 509 "mass-dalton", 510 "mass-earth-mass", 511 "mass-milligram", 512 "mass-solar-mass", 513 "pressure-kilopascal", 514 "speed-kilometer-per-hour", 515 "speed-meter-per-second", 516 "speed-mile-per-hour", 517 "temperature-celsius", 518 "temperature-fahrenheit", 519 "temperature-generic", 520 "temperature-kelvin", 521 "acceleration-g-force", 522 "consumption-liter-per-100-kilometer", 523 "mass-gram", 524 "mass-kilogram", 525 "mass-ounce", 526 "mass-pound", 527 "volume-centiliter", 528 "volume-cubic-centimeter", 529 "volume-cubic-foot", 530 "volume-cubic-mile", 531 "volume-cup", 532 "volume-deciliter", 533 "volume-fluid-ounce", 534 "volume-fluid-ounce-imperial", 535 "volume-gallon", 536 "volume-gallon", 537 "volume-gallon-imperial", 538 "volume-liter", 539 "volume-milliliter", 540 "volume-pint", 541 "volume-quart", 542 "volume-tablespoon", 543 "volume-teaspoon"); 544 // compounds 545 // "kilogram-per-cubic-meter", "kilometer-per-liter", "concentr-gram-per-mole", 546 // "speed-mile-per-second", "volumetricflow-cubic-foot-per-second", 547 // "volumetricflow-cubic-meter-per-second", "gram-per-cubic-centimeter", 548 getSourceCaseAndPlural( String locale, String gender, String value, String desiredCase, String desiredPlural, Output<String> sourceCase, Output<String> sourcePlural)549 public void getSourceCaseAndPlural( 550 String locale, 551 String gender, 552 String value, 553 String desiredCase, 554 String desiredPlural, 555 Output<String> sourceCase, 556 Output<String> sourcePlural) { 557 switch (locale) { 558 case "pl": 559 getSourceCaseAndPluralPolish( 560 gender, value, desiredCase, desiredPlural, sourceCase, sourcePlural); 561 break; 562 case "ru": 563 getSourceCaseAndPluralRussian( 564 gender, value, desiredCase, desiredPlural, sourceCase, sourcePlural); 565 break; 566 default: 567 throw new UnsupportedOperationException(locale); 568 } 569 } 570 571 /** 572 * Russian rules for paucal (few) and fractional (other) 573 * 574 * <pre> 575 * plural = other 576 * Nominative ⇒ genitive singular 577 * Accusative + masculine ⇒ genitive singular 578 * All other combinations of gender + case ⇒ same-case, plural 579 * 580 * Other 581 * genitive singular 582 * 583 * Plurals: 584 * one, 585 * few (2~4), 586 * many, = plural 587 * other (where other is 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0) 588 * </pre> 589 */ getSourceCaseAndPluralRussian( String gender, String value, String desiredCase, String desiredPlural, Output<String> sourceCase, Output<String> sourcePlural)590 private void getSourceCaseAndPluralRussian( 591 String gender, 592 String value, 593 String desiredCase, 594 String desiredPlural, 595 Output<String> sourceCase, 596 Output<String> sourcePlural) { 597 switch (desiredPlural) { 598 case "few": 599 // default source 600 sourceCase.value = desiredCase; 601 sourcePlural.value = "many"; 602 // special cases 603 switch (desiredCase) { 604 case "nominative": 605 sourceCase.value = "genitive"; 606 sourcePlural.value = "one"; 607 break; 608 case "accusative": 609 switch (gender) { 610 case "masculine": 611 sourceCase.value = "genitive"; 612 sourcePlural.value = "one"; 613 break; 614 } 615 break; 616 } 617 case "other": 618 sourceCase.value = "genitive"; 619 sourcePlural.value = "one"; 620 return; 621 } 622 } 623 624 /** 625 * Polish rules 626 * 627 * <pre> 628 * plural = few 629 * 630 * neuter + ending in -um + (nominative, accusative) ⇒ vocative plural 631 * Feminine||neuter + (nominative, accusative) ⇒ genitive singular 632 * Animate||inanimate + (nominative, accusative) ⇒ vocative plural 633 * Personal + nominative ⇒ vocative plural 634 * Personal + accusative ⇒ genitive plural 635 * All other combinations of gender + case ⇒ same-case, plural 636 * 637 * plural = other 638 * genitive singular 639 * 640 * Plurals: 641 * one, 642 * few (2~4), 643 * many, = plural 644 * other (where other is 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0) 645 * </pre> 646 */ getSourceCaseAndPluralPolish( String gender, String value, String desiredCase, String desiredPlural, Output<String> sourceCase, Output<String> sourcePlural)647 private void getSourceCaseAndPluralPolish( 648 String gender, 649 String value, 650 String desiredCase, 651 String desiredPlural, 652 Output<String> sourceCase, 653 Output<String> sourcePlural) { 654 switch (desiredPlural) { 655 case "few": 656 // default 657 sourceCase.value = desiredCase; 658 sourcePlural.value = "many"; 659 // special cases 660 boolean isNominative = false; 661 switch (desiredCase) { 662 case "nominative": 663 isNominative = true; 664 case "vocative": 665 case "accusative": 666 switch (gender) { 667 case "neuter": 668 if (value.endsWith("um")) { 669 sourceCase.value = "vocative"; 670 break; 671 } 672 // otherwise fall thorugh to feminine 673 case "feminine": 674 sourceCase.value = "nominative"; 675 sourcePlural.value = "few"; 676 break; 677 case "animate": 678 case "inanimate": 679 sourceCase.value = "vocative"; 680 break; 681 case "personal": 682 sourceCase.value = isNominative ? "vocative" : "genitive"; 683 break; 684 } 685 break; 686 } 687 return; 688 case "other": 689 sourceCase.value = "genitive"; 690 sourcePlural.value = "one"; 691 return; 692 } 693 } 694 695 /** Internal class for thread-safety */ 696 static class GrammarLocales { 697 static final Set<String> data = 698 ImmutableSortedSet.copyOf( 699 ImmutableSet.<String>builder() 700 .addAll( 701 CLDRConfig.getInstance() 702 .getSupplementalDataInfo() 703 .getLocalesWithFeatures( 704 GrammaticalTarget.nominal, 705 GrammaticalScope.units, 706 GrammaticalFeature.grammaticalCase)) 707 .addAll( 708 CLDRConfig.getInstance() 709 .getSupplementalDataInfo() 710 .getLocalesWithFeatures( 711 GrammaticalTarget.nominal, 712 GrammaticalScope.units, 713 GrammaticalFeature.grammaticalGender)) 714 .build()); 715 } 716 717 /** Return the locales that have either case or gender info for units (or both). */ getGrammarLocales()718 public static Set<String> getGrammarLocales() { 719 return GrammarLocales.data; 720 } 721 722 /** There is a BRS item to adjust the following for each release! */ 723 static final Set<String> INCLUDE_OTHER = 724 ImmutableSet.of( 725 "g-force", 726 "arc-minute", 727 "arc-second", 728 "degree", 729 "revolution", 730 "bit", 731 "byte", 732 "week", 733 "calorie", 734 "pixel", 735 "generic", 736 "karat", 737 "percent", 738 "permille", 739 "permillion", 740 "permyriad", 741 "atmosphere", 742 "em", 743 "century", 744 "decade", 745 "month", 746 "year"); 747 748 static final Set<String> EXCLUDE_GRAMMAR = 749 Set.of( 750 "point", 751 "dunam", 752 "dot", 753 "astronomical-unit", 754 "nautical-mile", 755 "knot", 756 "dalton", 757 "kilocalorie", 758 "electronvolt", 759 // The following may be reinstated after 45. 760 "dot-per-centimeter", 761 "millimeter-ofhg", 762 "milligram-ofglucose-per-deciliter"); 763 getSpecialsToTranslate()764 public static Set<String> getSpecialsToTranslate() { 765 return INCLUDE_OTHER; 766 } 767 768 public static final boolean DEBUG = false; 769 /** Internal class for thread-safety */ 770 static class UnitsToAddGrammar { 771 static final Set<String> data; 772 773 static { 774 final CLDRConfig config = CLDRConfig.getInstance(); 775 final UnitConverter converter = config.getSupplementalDataInfo().getUnitConverter(); 776 Set<String> missing = new TreeSet<>(); 777 Set<String> _data = new TreeSet<>(); 778 for (String path : 779 With.in( 780 config.getRoot() 781 .iterator("//ldml/units/unitLength[@type=\"short\"]/unit"))) { 782 XPathParts parts = XPathParts.getFrozenInstance(path); 783 String unit = parts.getAttributeValue(3, "type"); 784 // Add simple units 785 String shortUnit = converter.getShortId(unit); 786 if (INCLUDE_OTHER.contains(shortUnit)) { 787 _data.add(unit); 788 continue; 789 } 790 if (!EXCLUDE_GRAMMAR.contains(shortUnit)) { 791 Set<UnitSystem> systems = converter.getSystemsEnum(shortUnit); 792 // we now add all SI and metric and si_acceptable and metric_adjacent 793 if (!Collections.disjoint(systems, UnitSystem.SiOrMetric)) { 794 _data.add(unit); 795 continue; 796 } 797 } 798 missing.add(unit); 799 } 800 if (DEBUG) 801 for (String unit : missing) { 802 String shortUnit = converter.getShortId(unit); 803 System.out.println( 804 "*Skipping\t" 805 + unit 806 + "\t" 807 + converter.getQuantityFromUnit(shortUnit, false) 808 + "\t" 809 + converter.getSystemsEnum(shortUnit) 810 + "\t" 811 + (converter.isSimple(shortUnit) ? "SIMPLE" : "")); 812 } 813 data = ImmutableSet.copyOf(_data); 814 } 815 } 816 817 /** Return the units that we should get grammar information for. */ getUnitsToAddGrammar()818 public static Set<String> getUnitsToAddGrammar() { 819 return UnitsToAddGrammar.data; 820 } 821 } 822