xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/GrammarInfo.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
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