1 package org.unicode.cldr.util; 2 3 import java.util.Arrays; 4 import java.util.Collection; 5 import java.util.Collections; 6 import java.util.Comparator; 7 import java.util.List; 8 import java.util.Map; 9 import java.util.Map.Entry; 10 import java.util.Set; 11 import java.util.TreeMap; 12 import java.util.TreeSet; 13 import java.util.regex.Matcher; 14 import java.util.regex.Pattern; 15 import java.util.stream.Collectors; 16 17 import org.unicode.cldr.util.UnitConverter.UnitSystem; 18 19 import com.google.common.base.Joiner; 20 import com.google.common.collect.ImmutableMap; 21 import com.google.common.collect.ImmutableSet; 22 import com.google.common.collect.ImmutableSortedSet; 23 import com.ibm.icu.util.Freezable; 24 import com.ibm.icu.util.Output; 25 26 /** 27 * Get the info from supplemental data, eg CLDRConfig.getInstance().getSupplementalDataInfo().getGrammarInfo("fr"); Use hasGrammarInfo() to see which locales have it. 28 * @author markdavis 29 * 30 */ 31 public class GrammarInfo implements Freezable<GrammarInfo>{ 32 33 public enum GrammaticalTarget {nominal} 34 35 /** 36 * The ordering of these values is intended to put the default values first, and to group values together that tend to have similar forms for the most common cases, 37 * then have the rest in alphabetical order. 38 */ 39 public enum CaseValues {nominative, vocative, accusative, oblique, genitive, dative, locative, instrumental, prepositional, ablative, 40 abessive, adessive, allative, causal, comitative, delative, elative, ergative, essive, illative, inessive, locativecopulative, partitive, sociative, sublative, superessive, terminative, translative; 41 public static Comparator<String> COMPARATOR = EnumComparator.create(CaseValues.class); 42 } 43 public enum GenderValues {neuter, masculine, inanimate, animate, common, personal, feminine; 44 public static Comparator<String> COMPARATOR = EnumComparator.create(GenderValues.class); 45 } 46 public enum DefinitenessValues {unspecified, indefinite, definite, construct; 47 public static Comparator<String> COMPARATOR = EnumComparator.create(DefinitenessValues.class); 48 } 49 public enum PluralValues {zero, one, two, few, many, other; 50 public static Comparator<String> COMPARATOR = EnumComparator.create(PluralValues.class); 51 } 52 53 public enum GrammaticalFeature { 54 grammaticalNumber("plural", "Ⓟ", "other", PluralValues.COMPARATOR), 55 grammaticalCase("case", "Ⓒ", "nominative", CaseValues.COMPARATOR), 56 grammaticalDefiniteness("definiteness", "Ⓓ", "indefinite", DefinitenessValues.COMPARATOR), 57 grammaticalGender("gender", "Ⓖ", "neuter", GenderValues.COMPARATOR); 58 59 private final String shortName; 60 private final String symbol; 61 private final String defaultValue; 62 private final Comparator<String> comparator; 63 64 public static final Pattern PATH_HAS_FEATURE = Pattern.compile("\\[@(count|case|gender|definiteness)="); 65 GrammaticalFeature(String shortName, String symbol, String defaultValue, Comparator<String> comparator)66 GrammaticalFeature(String shortName, String symbol, String defaultValue, Comparator<String> comparator) { 67 this.shortName = shortName; 68 this.symbol = symbol; 69 this.defaultValue = defaultValue; 70 this.comparator = comparator; 71 } getShortName()72 public String getShortName() { 73 return shortName; 74 } getSymbol()75 public CharSequence getSymbol() { 76 return symbol; 77 } 78 /** 79 * Gets the default value. The parameter only needs to be set for grammaticalGender 80 */ getDefault(Collection<String> featureValuesFromGrammaticalInfo)81 public String getDefault(Collection<String> featureValuesFromGrammaticalInfo) { 82 return this == grammaticalGender 83 && featureValuesFromGrammaticalInfo != null 84 && !featureValuesFromGrammaticalInfo.contains("neuter") 85 ? "masculine" 86 : defaultValue; 87 } pathHasFeature(String path)88 public static Matcher pathHasFeature(String path) { 89 Matcher result = PATH_HAS_FEATURE.matcher(path); 90 return result.find() ? result : null; 91 } 92 static final Map<String, GrammaticalFeature> shortNameToEnum = 93 ImmutableMap.copyOf(Arrays.asList(GrammaticalFeature.values()) 94 .stream() 95 .collect(Collectors.toMap(e -> e.shortName, e -> e))); 96 fromName(String name)97 public static GrammaticalFeature fromName(String name) { 98 GrammaticalFeature result = shortNameToEnum.get(name); 99 return result != null ? result : valueOf(name); 100 } getValueComparator()101 public Comparator getValueComparator() { 102 return comparator; 103 } 104 } 105 106 public enum GrammaticalScope {general, units} 107 108 private Map<GrammaticalTarget, Map<GrammaticalFeature, Map<GrammaticalScope,Set<String>>>> targetToFeatureToUsageToValues = new TreeMap<>(); 109 private boolean frozen = false; 110 111 /** Only internal */ 112 @Deprecated add(GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage, String value)113 public void add(GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage, String value) { 114 Map<GrammaticalFeature, Map<GrammaticalScope,Set<String>>> featureToUsageToValues = targetToFeatureToUsageToValues.get(target); 115 if (featureToUsageToValues == null) { 116 targetToFeatureToUsageToValues.put(target, featureToUsageToValues = new TreeMap<>()); 117 } 118 if (feature != null) { 119 Map<GrammaticalScope,Set<String>> usageToValues = featureToUsageToValues.get(feature); 120 if (usageToValues == null) { 121 featureToUsageToValues.put(feature, usageToValues = new TreeMap<>()); 122 } 123 Set<String> values = usageToValues.get(usage); 124 if (values == null) { 125 usageToValues.put(usage, values = new TreeSet<>()); 126 } 127 if (value != null) { 128 values.add(value); 129 } else { 130 int debug = 0; 131 } 132 } 133 } 134 135 /** Only internal */ 136 @Deprecated add(GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage, Collection<String> valueSet)137 public void add(GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage, Collection<String> valueSet) { 138 Map<GrammaticalFeature, Map<GrammaticalScope,Set<String>>> featureToUsageToValues = targetToFeatureToUsageToValues.get(target); 139 if (featureToUsageToValues == null) { 140 targetToFeatureToUsageToValues.put(target, featureToUsageToValues = new TreeMap<>()); 141 } 142 if (feature != null) { 143 Map<GrammaticalScope,Set<String>> usageToValues = featureToUsageToValues.get(feature); 144 if (usageToValues == null) { 145 featureToUsageToValues.put(feature, usageToValues = new TreeMap<>()); 146 } 147 Set<String> values = usageToValues.get(usage); 148 if (values == null) { 149 usageToValues.put(usage, values = new TreeSet<>()); 150 } 151 validate(feature, valueSet); 152 values.addAll(valueSet); 153 } 154 } 155 156 validate(GrammaticalFeature feature, Collection<String> valueSet)157 private void validate(GrammaticalFeature feature, Collection<String> valueSet) { 158 for (String value : valueSet) { 159 validate(feature, value); 160 } 161 } 162 validate(GrammaticalFeature feature, String value)163 private void validate(GrammaticalFeature feature, String value) { 164 switch (feature) { 165 case grammaticalCase: CaseValues.valueOf(value); break; 166 case grammaticalDefiniteness: DefinitenessValues.valueOf(value); break; 167 case grammaticalGender: GenderValues.valueOf(value); break; 168 case grammaticalNumber: PluralValues.valueOf(value); break; 169 } 170 } 171 172 /** 173 * Note: when there is known to be no features, the featureRaw will be null 174 * Only internal */ 175 @Deprecated add(String targetsRaw, String featureRaw, String usagesRaw, String valuesRaw)176 public void add(String targetsRaw, String featureRaw, String usagesRaw, String valuesRaw) { 177 for (String targetString : SupplementalDataInfo.split_space.split(targetsRaw)) { 178 GrammaticalTarget target = GrammaticalTarget.valueOf(targetString); 179 if (featureRaw == null) { 180 add(target, null, null, (String)null); 181 } else { 182 final GrammaticalFeature feature = GrammaticalFeature.valueOf(featureRaw); 183 184 List<String> usages = usagesRaw == null ? Collections.singletonList(GrammaticalScope.general.toString()) : SupplementalDataInfo.split_space.splitToList(usagesRaw); 185 186 List<String> values = valuesRaw == null ? Collections.emptyList() : SupplementalDataInfo.split_space.splitToList(valuesRaw); 187 for (String usageRaw : usages) { 188 GrammaticalScope usage = GrammaticalScope.valueOf(usageRaw); 189 add(target, feature, usage, values); 190 } 191 } 192 } 193 } 194 195 @Override isFrozen()196 public boolean isFrozen() { 197 return frozen; 198 } 199 200 @Override freeze()201 public GrammarInfo freeze() { 202 if (!frozen) { 203 Map<GrammaticalTarget, Map<GrammaticalFeature, Map<GrammaticalScope, Set<String>>>> temp = CldrUtility.protectCollection(targetToFeatureToUsageToValues); 204 if (!temp.equals(targetToFeatureToUsageToValues)) { 205 throw new IllegalArgumentException(); 206 } 207 targetToFeatureToUsageToValues = temp; 208 frozen = true; 209 } 210 return this; 211 } 212 213 @Override cloneAsThawed()214 public GrammarInfo cloneAsThawed() { 215 GrammarInfo result = new GrammarInfo(); 216 this.forEach3((t,f,u,v) -> result.add(t,f,u,v)); 217 return result; 218 } 219 220 static interface Handler4<T,F,U,V> { apply(T t, F f, U u, V v)221 void apply(T t, F f, U u, V v); 222 } 223 forEach(Handler4<GrammaticalTarget, GrammaticalFeature, GrammaticalScope, String> handler)224 public void forEach(Handler4<GrammaticalTarget, GrammaticalFeature, GrammaticalScope, String> handler) { 225 for (Entry<GrammaticalTarget, Map<GrammaticalFeature, Map<GrammaticalScope,Set<String>>>> entry1 : targetToFeatureToUsageToValues.entrySet()) { 226 GrammaticalTarget target = entry1.getKey(); 227 final Map<GrammaticalFeature, Map<GrammaticalScope,Set<String>>> featureToUsageToValues = entry1.getValue(); 228 if (featureToUsageToValues.isEmpty()) { 229 handler.apply(target, null, null, null); 230 } else 231 for (Entry<GrammaticalFeature, Map<GrammaticalScope,Set<String>>> entry2 : featureToUsageToValues.entrySet()) { 232 GrammaticalFeature feature = entry2.getKey(); 233 for (Entry<GrammaticalScope, Set<String>> entry3 : entry2.getValue().entrySet()) { 234 final GrammaticalScope usage = entry3.getKey(); 235 for (String value : entry3.getValue()) { 236 handler.apply(target, feature, usage, value); 237 } 238 } 239 } 240 } 241 } 242 243 static interface Handler3<T,F,U, V> { apply(T t, F f, U u, V v)244 void apply(T t, F f, U u, V v); 245 } 246 forEach3(Handler3<GrammaticalTarget, GrammaticalFeature, GrammaticalScope, Collection<String>> handler)247 public void forEach3(Handler3<GrammaticalTarget, GrammaticalFeature, GrammaticalScope, Collection<String>> handler) { 248 for (Entry<GrammaticalTarget, Map<GrammaticalFeature, Map<GrammaticalScope,Set<String>>>> entry1 : targetToFeatureToUsageToValues.entrySet()) { 249 GrammaticalTarget target = entry1.getKey(); 250 final Map<GrammaticalFeature, Map<GrammaticalScope,Set<String>>> featureToUsageToValues = entry1.getValue(); 251 if (featureToUsageToValues.isEmpty()) { 252 handler.apply(target, null, null, null); 253 } else 254 for (Entry<GrammaticalFeature, Map<GrammaticalScope,Set<String>>> entry2 : featureToUsageToValues.entrySet()) { 255 GrammaticalFeature feature = entry2.getKey(); 256 for (Entry<GrammaticalScope, Set<String>> entry3 : entry2.getValue().entrySet()) { 257 final GrammaticalScope usage = entry3.getKey(); 258 final Collection<String> values = entry3.getValue(); 259 handler.apply(target, feature, usage, values); 260 } 261 } 262 } 263 } 264 265 /** Returns null if there is no known information. Otherwise returns the information for the locale (which may be empty if there are no variants) */ get(GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage)266 public Collection<String> get(GrammaticalTarget target, GrammaticalFeature feature, GrammaticalScope usage) { 267 Map<GrammaticalFeature, Map<GrammaticalScope,Set<String>>> featureToUsageToValues = targetToFeatureToUsageToValues.get(target); 268 if (featureToUsageToValues == null) { 269 return Collections.emptySet(); 270 } 271 Map<GrammaticalScope,Set<String>> usageToValues = featureToUsageToValues.get(feature); 272 if (usageToValues == null) { 273 return Collections.emptySet(); 274 } 275 Collection<String> result = usageToValues.get(usage); 276 return result == null 277 ? usageToValues.get(GrammaticalScope.general) 278 : result; 279 } 280 get(GrammaticalTarget target, GrammaticalFeature feature)281 public Map<GrammaticalScope, Set<String>> get(GrammaticalTarget target, GrammaticalFeature feature) { 282 Map<GrammaticalFeature, Map<GrammaticalScope,Set<String>>> featureToUsageToValues = targetToFeatureToUsageToValues.get(target); 283 if (featureToUsageToValues == null) { 284 return Collections.emptyMap(); 285 } 286 Map<GrammaticalScope,Set<String>> usageToValues = featureToUsageToValues.get(feature); 287 if (usageToValues == null) { 288 return Collections.emptyMap(); 289 } 290 return usageToValues; 291 } 292 293 hasInfo(GrammaticalTarget target)294 public boolean hasInfo(GrammaticalTarget target) { 295 return targetToFeatureToUsageToValues.containsKey(target); 296 } 297 298 @Override toString()299 public String toString() { 300 return toString("\n"); 301 } toString(String lineSep)302 public String toString(String lineSep) { 303 StringBuilder result = new StringBuilder(); 304 this.forEach3((t,f,u, v) -> 305 { 306 result.append(lineSep); 307 result.append("{" + (t == null ? "" : t.toString()) + "}" 308 + "\t{" + (f == null ? "" : f.toString()) + "}" 309 + "\t{" + (u == null ? "" : u.toString()) + "}" 310 + "\t{" + (v == null ? "" : Joiner.on(' ').join(v)) + "}"); 311 }); 312 return result.toString(); 313 } 314 getGrammaticalInfoAttributes(GrammarInfo grammarInfo, UnitPathType pathType, String plural, String gender, String caseVariant)315 static public String getGrammaticalInfoAttributes(GrammarInfo grammarInfo, UnitPathType pathType, String plural, String gender, String caseVariant) { 316 String grammaticalAttributes = ""; 317 if (pathType.features.contains(GrammaticalFeature.grammaticalNumber)) { // count is special 318 grammaticalAttributes += "[@count=\"" + (plural == null ? "other" : plural) + "\"]"; 319 } 320 if (grammarInfo != null && gender != null 321 && pathType.features.contains(GrammaticalFeature.grammaticalGender) 322 ) { 323 Collection<String> genders = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalGender, GrammaticalScope.units); 324 if (!gender.equals(GrammaticalFeature.grammaticalGender.getDefault(genders))) { 325 grammaticalAttributes += "[@gender=\"" + gender + "\"]"; 326 } 327 } 328 if (grammarInfo != null && caseVariant != null 329 && pathType.features.contains(GrammaticalFeature.grammaticalCase) 330 && !caseVariant.equals(GrammaticalFeature.grammaticalCase.getDefault(null))) { 331 grammaticalAttributes += "[@case=\"" + caseVariant + "\"]"; 332 } 333 return grammaticalAttributes; 334 } 335 336 /** 337 * TODO: change this to be data-file driven 338 */ 339 private static final Set<String> CORE_UNITS_NEEDING_GRAMMAR = ImmutableSet.of( 340 // new in v38 341 "mass-grain", 342 "volume-dessert-spoon", 343 "volume-dessert-spoon-imperial", 344 "volume-drop", 345 "volume-dram", 346 "volume-jigger", 347 "volume-pinch", 348 "volume-quart-imperial", 349 // "volume-pint-imperial", 350 351 "acceleration-meter-per-square-second", "area-acre", "area-hectare", 352 "area-square-centimeter", "area-square-foot", "area-square-kilometer", "area-square-mile", "concentr-percent", "consumption-mile-per-gallon", 353 "consumption-mile-per-gallon-imperial", "duration-day", "duration-hour", "duration-minute", "duration-month", "duration-second", "duration-week", 354 "duration-year", "energy-foodcalorie", "energy-kilocalorie", "length-centimeter", "length-foot", "length-inch", "length-kilometer", "length-meter", 355 "length-mile", "length-millimeter", "length-parsec", "length-picometer", "length-solar-radius", "length-yard", "light-solar-luminosity", "mass-dalton", 356 "mass-earth-mass", "mass-milligram", "mass-solar-mass", "pressure-kilopascal", "speed-kilometer-per-hour", "speed-meter-per-second", "speed-mile-per-hour", 357 "temperature-celsius", "temperature-fahrenheit", "temperature-generic", "temperature-kelvin", "acceleration-g-force", "consumption-liter-per-100-kilometer", 358 "mass-gram", "mass-kilogram", "mass-ounce", "mass-pound", "volume-centiliter", "volume-cubic-centimeter", "volume-cubic-foot", "volume-cubic-mile", 359 "volume-cup", "volume-deciliter", "volume-fluid-ounce", "volume-fluid-ounce-imperial", "volume-gallon", "volume-gallon", "volume-gallon-imperial", 360 "volume-liter", "volume-milliliter", "volume-pint", "volume-quart", "volume-tablespoon", "volume-teaspoon"); 361 // compounds 362 // "kilogram-per-cubic-meter", "kilometer-per-liter", "concentr-gram-per-mole", "speed-mile-per-second", "volumetricflow-cubic-foot-per-second", 363 // "volumetricflow-cubic-meter-per-second", "gram-per-cubic-centimeter", 364 365 getSourceCaseAndPlural(String locale, String gender, String value, String desiredCase, String desiredPlural, Output<String> sourceCase, Output<String> sourcePlural)366 public void getSourceCaseAndPlural(String locale, String gender, String value, String desiredCase, String desiredPlural, 367 Output<String> sourceCase, Output<String> sourcePlural) { 368 switch(locale) { 369 case "pl": 370 getSourceCaseAndPluralPolish(gender, value, desiredCase, desiredPlural, sourceCase, sourcePlural); 371 break; 372 case "ru": 373 getSourceCaseAndPluralRussian(gender, value, desiredCase, desiredPlural, sourceCase, sourcePlural); 374 break; 375 default: 376 throw new UnsupportedOperationException(locale); 377 } 378 } 379 380 /** Russian rules for paucal (few) and fractional (other) 381 * <pre> 382 * plural = other 383 * Nominative ⇒ genitive singular 384 * Accusative + masculine ⇒ genitive singular 385 * All other combinations of gender + case ⇒ same-case, plural 386 * 387 * Other 388 * genitive singular 389 * 390 * Plurals: 391 * one, 392 * few (2~4), 393 * many, = plural 394 * other (where other is 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0) 395 * </pre> 396 */ getSourceCaseAndPluralRussian(String gender, String value, String desiredCase, String desiredPlural, Output<String> sourceCase, Output<String> sourcePlural)397 private void getSourceCaseAndPluralRussian(String gender, String value, 398 String desiredCase, String desiredPlural, 399 Output<String> sourceCase, Output<String> sourcePlural) { 400 switch (desiredPlural) { 401 case "few": 402 // default source 403 sourceCase.value = desiredCase; 404 sourcePlural.value = "many"; 405 // special cases 406 switch (desiredCase) { 407 case "nominative": 408 sourceCase.value = "genitive"; 409 sourcePlural.value = "one"; 410 break; 411 case "accusative": 412 switch (gender) { 413 case "masculine": 414 sourceCase.value = "genitive"; 415 sourcePlural.value = "one"; 416 break; 417 } 418 break; 419 } 420 case "other": 421 sourceCase.value = "genitive"; 422 sourcePlural.value = "one"; 423 return; 424 } 425 } 426 427 /** Polish rules 428 * <pre> 429 * plural = few 430 * 431 * neuter + ending in -um + (nominative, accusative) ⇒ vocative plural 432 * Feminine||neuter + (nominative, accusative) ⇒ genitive singular 433 * Animate||inanimate + (nominative, accusative) ⇒ vocative plural 434 * Personal + nominative ⇒ vocative plural 435 * Personal + accusative ⇒ genitive plural 436 * All other combinations of gender + case ⇒ same-case, plural 437 * 438 * plural = other 439 * genitive singular 440 * 441 * Plurals: 442 * one, 443 * few (2~4), 444 * many, = plural 445 * other (where other is 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0) 446 * </pre> 447 */ getSourceCaseAndPluralPolish(String gender, String value, String desiredCase, String desiredPlural, Output<String> sourceCase, Output<String> sourcePlural)448 private void getSourceCaseAndPluralPolish(String gender, String value, 449 String desiredCase, String desiredPlural, 450 Output<String> sourceCase, Output<String> sourcePlural) { 451 switch (desiredPlural) { 452 case "few": 453 // default 454 sourceCase.value = desiredCase; 455 sourcePlural.value = "many"; 456 // special cases 457 boolean isNominative = false; 458 switch (desiredCase) { 459 case "nominative": 460 isNominative = true; 461 case "vocative": 462 case "accusative": 463 switch (gender) { 464 case "neuter": 465 if (value.endsWith("um")) { 466 sourceCase.value = "vocative"; 467 break; 468 } 469 // otherwise fall thorugh to feminine 470 case "feminine": 471 sourceCase.value = "nominative"; 472 sourcePlural.value = "few"; 473 break; 474 case "animate": 475 case "inanimate": 476 sourceCase.value = "vocative"; 477 break; 478 case "personal": 479 sourceCase.value = isNominative ? "vocative" : "genitive"; 480 break; 481 } 482 break; 483 } 484 return; 485 case "other": 486 sourceCase.value = "genitive"; 487 sourcePlural.value = "one"; 488 return; 489 } 490 } 491 492 /** 493 * Internal class for thread-safety 494 */ 495 static class GrammarLocales { 496 static final Set<String> data = ImmutableSortedSet.copyOf(ImmutableSet.<String>builder() 497 .addAll( 498 CLDRConfig.getInstance().getSupplementalDataInfo() 499 .getLocalesWithFeatures(GrammaticalTarget.nominal, GrammaticalScope.units, GrammaticalFeature.grammaticalCase)) 500 .addAll( 501 CLDRConfig.getInstance().getSupplementalDataInfo() 502 .getLocalesWithFeatures(GrammaticalTarget.nominal, GrammaticalScope.units, GrammaticalFeature.grammaticalGender) 503 ).build()); 504 } 505 506 /** 507 * Return the locales that have either case or gender info for units (or both). 508 */ getGrammarLocales()509 public static Set<String> getGrammarLocales() { 510 return GrammarLocales.data; 511 } 512 513 static final Set<String> INCLUDE_OTHER = ImmutableSet.of( 514 "g-force", 515 "arc-minute", 516 "arc-second", 517 "degree", 518 "revolution", 519 "bit", 520 "byte", 521 "week", 522 "calorie", 523 "pixel", 524 "generic", 525 "karat", 526 "percent", 527 "permille", 528 "permillion", 529 "permyriad", 530 "atmosphere", 531 "em", 532 "century", 533 "decade", 534 "month", 535 "year" 536 ); 537 public static final boolean DEBUG = false; 538 /** 539 * Internal class for thread-safety 540 */ 541 static class UnitsToAddGrammar { 542 static final Set<String> data; 543 static { 544 final CLDRConfig config = CLDRConfig.getInstance(); 545 final UnitConverter converter = config.getSupplementalDataInfo().getUnitConverter(); 546 Set<String> missing = new TreeSet<>(); 547 Set<String> _data = new TreeSet<>(); 548 for (String path : With.in(config.getRoot().iterator("//ldml/units/unitLength[@type=\"short\"]/unit"))) { 549 XPathParts parts = XPathParts.getFrozenInstance(path); 550 String unit = parts.getAttributeValue(3, "type"); 551 // Add simple units 552 String shortUnit = converter.getShortId(unit); 553 if (INCLUDE_OTHER.contains(shortUnit)) { 554 _data.add(unit); 555 continue; 556 } 557 Set<UnitSystem> systems = converter.getSystemsEnum(shortUnit); 558 if (converter.isSimple(shortUnit) 559 && !Collections.disjoint(systems, UnitSystem.SiOrMetric)) { 560 _data.add(unit); 561 continue; 562 } 563 missing.add(unit); 564 } 565 if (DEBUG) for (String unit : missing) { 566 String shortUnit = converter.getShortId(unit); 567 System.out.println("*Skipping\t" + unit 568 + "\t" + converter.getQuantityFromUnit(shortUnit, false) 569 + "\t" + converter.getSystemsEnum(shortUnit) 570 + "\t" + (converter.isSimple(shortUnit) ? "SIMPLE" : "")); 571 } 572 data = ImmutableSet.copyOf(_data); 573 } 574 } 575 576 /** 577 * Return the units that we should get grammar information for. 578 */ getUnitsToAddGrammar()579 public static Set<String> getUnitsToAddGrammar() { 580 return UnitsToAddGrammar.data; 581 } 582 }