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