• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.util;
2 
3 import com.google.common.collect.ImmutableList;
4 import com.google.common.collect.ImmutableSet;
5 import com.ibm.icu.text.PluralRules;
6 import com.ibm.icu.util.Output;
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.HashSet;
10 import java.util.List;
11 import java.util.Set;
12 import java.util.TreeSet;
13 import java.util.concurrent.ConcurrentHashMap;
14 import org.unicode.cldr.util.DayPeriodInfo.DayPeriod;
15 import org.unicode.cldr.util.GrammarInfo.GrammaticalFeature;
16 import org.unicode.cldr.util.GrammarInfo.GrammaticalScope;
17 import org.unicode.cldr.util.GrammarInfo.GrammaticalTarget;
18 import org.unicode.cldr.util.PluralRulesUtil.KeywordStatus;
19 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
20 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
21 import org.unicode.cldr.util.SupplementalDataInfo.PluralType;
22 
23 public class LogicalGrouping {
24 
25     static final SupplementalDataInfo supplementalData =
26             CLDRConfig.getInstance().getSupplementalDataInfo();
27 
28     public static final ImmutableSet<String> metazonesDSTSet =
29             ImmutableSet.of(
30                     "Acre",
31                     "Africa_Western",
32                     "Alaska",
33                     "Almaty",
34                     "Amazon",
35                     "America_Central",
36                     "America_Eastern",
37                     "America_Mountain",
38                     "America_Pacific",
39                     "Anadyr",
40                     "Apia",
41                     "Aqtau",
42                     "Aqtobe",
43                     "Arabian",
44                     "Argentina",
45                     "Argentina_Western",
46                     "Armenia",
47                     "Atlantic",
48                     "Australia_Central",
49                     "Australia_CentralWestern",
50                     "Australia_Eastern",
51                     "Australia_Western",
52                     "Azerbaijan",
53                     "Azores",
54                     "Bangladesh",
55                     "Brasilia",
56                     "Cape_Verde",
57                     "Chatham",
58                     "Chile",
59                     "China",
60                     "Colombia",
61                     "Cook",
62                     "Cuba",
63                     "Easter",
64                     "Europe_Central",
65                     "Europe_Eastern",
66                     "Europe_Western",
67                     "Falkland",
68                     "Fiji",
69                     "Georgia",
70                     "Greenland",
71                     "Greenland_Eastern",
72                     "Greenland_Western",
73                     "Hawaii_Aleutian",
74                     "Hong_Kong",
75                     "Hovd",
76                     "Iran",
77                     "Irkutsk",
78                     "Israel",
79                     "Japan",
80                     "Kamchatka",
81                     "Korea",
82                     "Krasnoyarsk",
83                     "Lord_Howe",
84                     "Macau",
85                     "Magadan",
86                     "Mauritius",
87                     "Mexico_Northwest",
88                     "Mexico_Pacific",
89                     "Mongolia",
90                     "Moscow",
91                     "New_Caledonia",
92                     "New_Zealand",
93                     "Newfoundland",
94                     "Norfolk",
95                     "Noronha",
96                     "Novosibirsk",
97                     "Omsk",
98                     "Pakistan",
99                     "Paraguay",
100                     "Peru",
101                     "Philippines",
102                     "Pierre_Miquelon",
103                     "Qyzylorda",
104                     "Sakhalin",
105                     "Samara",
106                     "Samoa",
107                     "Taipei",
108                     "Tonga",
109                     "Turkmenistan",
110                     "Uruguay",
111                     "Uzbekistan",
112                     "Vanuatu",
113                     "Vladivostok",
114                     "Volgograd",
115                     "Yakutsk",
116                     "Yekaterinburg");
117 
118     public static final ImmutableList<String> days =
119             ImmutableList.of("sun", "mon", "tue", "wed", "thu", "fri", "sat");
120 
121     public static final ImmutableSet<String> calendarsWith13Months =
122             ImmutableSet.of("coptic", "ethiopic", "hebrew");
123     public static final ImmutableSet<String> compactDecimalFormatLengths =
124             ImmutableSet.of("short", "long");
125     private static final ImmutableSet<String> ampm = ImmutableSet.of("am", "pm");
126     private static final ImmutableSet<String> nowUnits =
127             ImmutableSet.of(
128                     "second",
129                     "second-short",
130                     "second-narrow",
131                     "minute",
132                     "minute-short",
133                     "minute-narrow",
134                     "hour",
135                     "hour-short",
136                     "hour-narrow");
137 
138     /** Cache from path (String) to logical group (Set<String>) */
139     private static final ConcurrentHashMap<String, Set<String>> cachePathToLogicalGroup =
140             new ConcurrentHashMap<>();
141 
142     /** Cache from locale and path (<Pair<String, String>), to logical group (Set<String>) */
143     private static ConcurrentHashMap<Pair<String, String>, Set<String>>
144             cacheLocaleAndPathToLogicalGroup = new ConcurrentHashMap<>();
145 
146     /**
147      * Statistics on occurrences of types of logical groups, for performance testing, debugging.
148      * GET_TYPE_COUNTS should be false for production to maximize performance.
149      */
150     public static final boolean GET_TYPE_COUNTS = false;
151 
152     public static final ConcurrentHashMap<String, Long> typeCount =
153             GET_TYPE_COUNTS ? new ConcurrentHashMap<>() : null;
154 
155     /**
156      * GET_TYPE_FROM_PARTS is more elegant when true, but performance is a little faster when it's
157      * false. This might change if XPathParts.getInstance and/or XPathParts.set are made faster.
158      */
159     private static final boolean GET_TYPE_FROM_PARTS = false;
160 
161     /**
162      * Return a sorted set of paths that are in the same logical set as the given path
163      *
164      * @param cldrFile the CLDRFile
165      * @param path the distinguishing xpath
166      * @param pathTypeOut if not null, gets filled in with the PathType
167      * @return the set of paths, or null (to be treated as equivalent to empty set)
168      *     <p>For example, given the path
169      *     <p>//ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="1"]
170      *     <p>return the set of four paths
171      *     <p>//ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="1"]
172      *     //ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="2"]
173      *     //ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="3"]
174      *     //ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="4"]
175      *     <p>Caches: Most of the calculations are independent of the locale, and can be cached on a
176      *     static basis. The paths that are locale-dependent are /dayPeriods and @count. Those can
177      *     be computed on a per-locale basis; and cached (they are shared across a number of
178      *     locales).
179      */
getPaths( CLDRFile cldrFile, String path, Output<PathType> pathTypeOut)180     public static Set<String> getPaths(
181             CLDRFile cldrFile, String path, Output<PathType> pathTypeOut) {
182         if (path == null) {
183             return null; // return null for null path
184         }
185         XPathParts parts = null;
186         PathType pathType = null;
187         if (GET_TYPE_FROM_PARTS) {
188             parts = XPathParts.getFrozenInstance(path);
189             pathType = PathType.getPathTypeFromParts(parts);
190         } else {
191             /*
192              * XPathParts.set is expensive, so avoid it (not needed for singletons) if !GET_TYPE_FROM_PARTS
193              */
194             pathType = PathType.getPathTypeFromPath(path);
195         }
196         if (pathTypeOut != null) {
197             pathTypeOut.value = pathType;
198         }
199 
200         if (GET_TYPE_COUNTS) {
201             typeCount.compute(pathType.toString(), (k, v) -> (v == null) ? 1 : v + 1);
202         }
203 
204         if (pathType == PathType.SINGLETON) {
205             /*
206              * Skip cache for PathType.SINGLETON and simply return a set of one.
207              */
208             Set<String> set = new TreeSet<>();
209             set.add(path);
210             return set;
211         }
212 
213         if (!GET_TYPE_FROM_PARTS) {
214             parts = XPathParts.getFrozenInstance(path).cloneAsThawed();
215         } else {
216             parts = parts.cloneAsThawed();
217         }
218 
219         if (PathType.isLocaleDependent(pathType)) {
220             String locale = cldrFile.getLocaleID();
221             Pair<String, String> key = new Pair<>(locale, path);
222             if (cacheLocaleAndPathToLogicalGroup.containsKey(key)) {
223                 return new TreeSet<>(cacheLocaleAndPathToLogicalGroup.get(key));
224             }
225             Set<String> set = new TreeSet<>();
226             pathType.addPaths(set, cldrFile, path, parts);
227             cacheLocaleAndPathToLogicalGroup.put(key, set);
228             return set;
229         } else {
230             /*
231              * All other paths are locale-independent.
232              */
233             if (cachePathToLogicalGroup.containsKey(path)) {
234                 return new TreeSet<>(cachePathToLogicalGroup.get(path));
235             }
236             Set<String> set = new TreeSet<>();
237             pathType.addPaths(set, cldrFile, path, parts);
238             cachePathToLogicalGroup.compute(
239                     path,
240                     (pathKey, cachedPaths) -> {
241                         if (cachedPaths == null) {
242                             return Collections.synchronizedSet(new HashSet<>(set));
243                         } else {
244                             cachedPaths.addAll(set);
245                             return cachedPaths;
246                         }
247                     });
248             return set;
249         }
250     }
251 
getPaths(CLDRFile cldrFile, String path)252     public static Set<String> getPaths(CLDRFile cldrFile, String path) {
253         return getPaths(cldrFile, path, null);
254     }
255 
256     /** Returns the plural info for a given locale. */
getPluralInfo(CLDRFile cldrFile)257     private static PluralInfo getPluralInfo(CLDRFile cldrFile) {
258         return supplementalData.getPlurals(PluralType.cardinal, cldrFile.getLocaleID());
259     }
260 
261     /**
262      * @param cldrFile
263      * @param path
264      * @return true if the specified path is optional in the logical grouping that it belongs to.
265      */
isOptional(CLDRFile cldrFile, String path)266     public static boolean isOptional(CLDRFile cldrFile, String path) {
267         XPathParts parts = XPathParts.getFrozenInstance(path);
268 
269         if (parts.containsElement("relative")) {
270             String fieldType = parts.findAttributeValue("field", "type");
271             String relativeType = parts.findAttributeValue("relative", "type");
272             Integer relativeValue = relativeType == null ? 999 : Integer.parseInt(relativeType);
273             if (fieldType != null
274                     && fieldType.startsWith("day")
275                     && Math.abs(relativeValue.intValue()) >= 2) {
276                 return true; // relative days +2 +3 -2 -3 are optional in a logical group.
277             }
278         }
279         // Paths with count="(zero|one)" are optional if their usage is covered
280         // fully by paths with count="(0|1)", which are always optional themselves.
281         if (!path.contains("[@count=")) return false;
282         String pluralType = parts.getAttributeValue(-1, "count");
283         switch (pluralType) {
284             case "0":
285             case "1":
286                 return true;
287             case "zero":
288             case "one":
289                 break; // continue
290             default:
291                 return false;
292         }
293 
294         parts = parts.cloneAsThawed();
295         PluralRules pluralRules = getPluralInfo(cldrFile).getPluralRules();
296         parts.setAttribute(-1, "count", "0");
297         Set<Double> explicits = new HashSet<>();
298         if (cldrFile.isHere(parts.toString())) {
299             explicits.add(0.0);
300         }
301         parts.setAttribute(-1, "count", "1");
302         if (cldrFile.isHere(parts.toString())) {
303             explicits.add(1.0);
304         }
305         if (!explicits.isEmpty()) {
306             // HACK: The com.ibm.icu.text prefix is needed so that ST can find it
307             // (no idea why).
308             KeywordStatus status =
309                     org.unicode.cldr.util.PluralRulesUtil.getKeywordStatus(
310                             pluralRules, pluralType, 0, explicits, true);
311             if (status == KeywordStatus.SUPPRESSED) {
312                 return true;
313             }
314         }
315         return false;
316     }
317 
removeOptionalPaths(Set<String> grouping, CLDRFile cldrFile)318     public static void removeOptionalPaths(Set<String> grouping, CLDRFile cldrFile) {
319         Set<String> grouping2 = new HashSet<>(grouping);
320         for (String p : grouping2) {
321             if (LogicalGrouping.isOptional(cldrFile, p)) {
322                 grouping.remove(p);
323             }
324         }
325     }
326 
327     /** Path types for logical groupings */
328     public enum PathType {
329         SINGLETON { // no logical groups for singleton paths
330             @Override
331             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)332             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
333                 // Do nothing. This function won't be called.
334             }
335         },
336         METAZONE {
337             @Override
338             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)339             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
340                 String metazoneName = parts.getAttributeValue(3, "type");
341                 if (metazonesDSTSet.contains(metazoneName)) {
342                     for (String str : ImmutableSet.of("generic", "standard", "daylight")) {
343                         set.add(path.substring(0, path.lastIndexOf('/') + 1) + str);
344                     }
345                 }
346             }
347         },
348         DAYS {
349             @Override
350             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)351             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
352                 String dayName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
353                 // This is just a quick check to make sure the path is good.
354                 if (dayName != null && days.contains(dayName)) {
355                     for (String str : days) {
356                         parts.setAttribute("day", "type", str);
357                         set.add(parts.toString());
358                     }
359                 }
360             }
361         },
362         DAY_PERIODS {
363             @Override
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)364             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
365                 if (parts.containsElement("alias")) {
366                     set.add(path);
367                 } else {
368                     String dayPeriodType = parts.findAttributeValue("dayPeriod", "type");
369                     if (ampm.contains(dayPeriodType)) {
370                         for (String s : ampm) {
371                             parts.setAttribute("dayPeriod", "type", s);
372                             set.add(parts.toString());
373                         }
374                     } else {
375                         DayPeriodInfo.Type dayPeriodContext =
376                                 DayPeriodInfo.Type.fromString(
377                                         parts.findAttributeValue("dayPeriodContext", "type"));
378                         DayPeriodInfo dpi =
379                                 supplementalData.getDayPeriods(
380                                         dayPeriodContext, cldrFile.getLocaleID());
381                         List<DayPeriod> dayPeriods = dpi.getPeriods();
382                         DayPeriod thisDayPeriod = DayPeriod.fromString(dayPeriodType);
383                         if (dayPeriods.contains(thisDayPeriod)) {
384                             for (DayPeriod d : dayPeriods) {
385                                 parts.setAttribute("dayPeriod", "type", d.name());
386                                 set.add(parts.toString());
387                             }
388                         }
389                     }
390                 }
391             }
392         },
393         QUARTERS {
394             @Override
395             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)396             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
397                 String quarterName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
398                 Integer quarter = quarterName == null ? 0 : Integer.parseInt(quarterName);
399                 if (quarter > 0
400                         && quarter
401                                 <= 4) { // This is just a quick check to make sure the path is good.
402                     for (Integer i = 1; i <= 4; i++) {
403                         parts.setAttribute("quarter", "type", i.toString());
404                         set.add(parts.toString());
405                     }
406                 }
407             }
408         },
409         MONTHS {
410             @Override
411             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)412             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
413                 String calType = parts.size() > 3 ? parts.getAttributeValue(3, "type") : null;
414                 String monthName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
415                 Integer month = monthName == null ? 0 : Integer.parseInt(monthName);
416                 int calendarMonthMax = calendarsWith13Months.contains(calType) ? 13 : 12;
417                 if (month > 0
418                         && month <= calendarMonthMax) { // This is just a quick check to make sure
419                     // the path is good.
420                     for (Integer i = 1; i <= calendarMonthMax; i++) {
421                         parts.setAttribute("month", "type", i.toString());
422                         if ("hebrew".equals(calType)) {
423                             parts.removeAttribute("month", "yeartype");
424                         }
425                         set.add(parts.toString());
426                     }
427                     if ("hebrew".equals(calType)) { // Add extra hebrew calendar leap month
428                         parts.setAttribute("month", "type", Integer.toString(7));
429                         parts.setAttribute("month", "yeartype", "leap");
430                         set.add(parts.toString());
431                     }
432                 }
433             }
434         },
435         RELATIVE {
436             @Override
437             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)438             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
439                 String fieldType = parts.findAttributeValue("field", "type");
440                 String relativeType = parts.findAttributeValue("relative", "type");
441                 Integer relativeValue = relativeType == null ? 999 : Integer.parseInt(relativeType);
442                 if (relativeValue >= -3
443                         && relativeValue
444                                 <= 3) { // This is just a quick check to make sure the path is good.
445                     if (!(nowUnits.contains(fieldType)
446                             && relativeValue
447                                     == 0)) { // Workaround for "now", "this hour", "this minute"
448                         int limit = 1;
449                         if (fieldType != null && fieldType.startsWith("day")) {
450                             limit = 3;
451                         }
452                         for (Integer i = -1 * limit; i <= limit; i++) {
453                             parts.setAttribute("relative", "type", i.toString());
454                             set.add(parts.toString());
455                         }
456                     }
457                 }
458             }
459         },
460         DECIMAL_FORMAT_LENGTH {
461             @Override
462             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)463             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
464                 PluralInfo pluralInfo = getPluralInfo(cldrFile);
465                 Set<Count> pluralTypes = pluralInfo.getCounts();
466                 String decimalFormatLengthType =
467                         parts.size() > 3 ? parts.getAttributeValue(3, "type") : null;
468                 String decimalFormatPatternType =
469                         parts.size() > 5 ? parts.getAttributeValue(5, "type") : null;
470                 if (decimalFormatLengthType != null
471                         && decimalFormatPatternType != null
472                         && compactDecimalFormatLengths.contains(decimalFormatLengthType)) {
473                     int numZeroes = decimalFormatPatternType.length() - 1;
474                     int baseZeroes = (numZeroes / 3) * 3;
475                     for (int i = 0; i < 3; i++) {
476                         // This gives us "baseZeroes+i" zeroes at the end.
477                         String patType =
478                                 "1" + String.format(String.format("%%0%dd", baseZeroes + i), 0);
479                         parts.setAttribute(5, "type", patType);
480                         for (Count count : pluralTypes) {
481                             parts.setAttribute(5, "count", count.toString());
482                             set.add(parts.toString());
483                         }
484                     }
485                 }
486             }
487         },
488         COUNT {
489             @Override
490             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)491             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
492                 addCaseOnly(set, cldrFile, parts);
493             }
494         },
495         COUNT_CASE {
496             @Override
497             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)498             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
499                 if (!GrammarInfo.getGrammarLocales().contains(cldrFile.getLocaleID())) {
500                     addCaseOnly(set, cldrFile, parts);
501                     return;
502                 }
503                 GrammarInfo grammarInfo = supplementalData.getGrammarInfo(cldrFile.getLocaleID());
504                 if (grammarInfo == null
505                         || (parts.getElement(3).equals("unitLength")
506                                 && GrammarInfo.getUnitsToAddGrammar()
507                                         .contains(parts.getAttributeValue(3, "type")))) {
508                     addCaseOnly(set, cldrFile, parts);
509                     return;
510                 }
511                 Set<Count> pluralTypes = getPluralInfo(cldrFile).getCounts();
512                 Collection<String> rawCases =
513                         grammarInfo.get(
514                                 GrammaticalTarget.nominal,
515                                 GrammaticalFeature.grammaticalCase,
516                                 GrammaticalScope.units);
517                 setGrammarAttributes(set, parts, pluralTypes, rawCases, null);
518             }
519         },
520         COUNT_CASE_GENDER {
521             @Override
522             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)523             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
524                 if (!GrammarInfo.getGrammarLocales().contains(cldrFile.getLocaleID())) {
525                     addCaseOnly(set, cldrFile, parts);
526                     return;
527                 }
528                 GrammarInfo grammarInfo = supplementalData.getGrammarInfo(cldrFile.getLocaleID());
529                 if (grammarInfo == null) {
530                     addCaseOnly(set, cldrFile, parts);
531                     return;
532                 }
533                 Set<Count> pluralTypes = getPluralInfo(cldrFile).getCounts();
534                 Collection<String> rawCases =
535                         grammarInfo.get(
536                                 GrammaticalTarget.nominal,
537                                 GrammaticalFeature.grammaticalCase,
538                                 GrammaticalScope.units);
539                 Collection<String> rawGenders =
540                         grammarInfo.get(
541                                 GrammaticalTarget.nominal,
542                                 GrammaticalFeature.grammaticalGender,
543                                 GrammaticalScope.units);
544                 setGrammarAttributes(set, parts, pluralTypes, rawCases, rawGenders);
545             }
546         };
547 
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)548         abstract void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts);
549 
addCaseOnly(Set<String> set, CLDRFile cldrFile, XPathParts parts)550         public void addCaseOnly(Set<String> set, CLDRFile cldrFile, XPathParts parts) {
551             Set<Count> pluralTypes = getPluralInfo(cldrFile).getCounts();
552             for (Count count : pluralTypes) {
553                 parts.setAttribute(-1, "count", count.toString());
554                 set.add(parts.toString());
555             }
556         }
557 
setGrammarAttributes( Set<String> set, XPathParts parts, Set<Count> pluralTypes, Collection<String> rawCases, Collection<String> rawGenders)558         public void setGrammarAttributes(
559                 Set<String> set,
560                 XPathParts parts,
561                 Set<Count> pluralTypes,
562                 Collection<String> rawCases,
563                 Collection<String> rawGenders) {
564             final String defaultGender =
565                     GrammaticalFeature.grammaticalGender.getDefault(rawGenders);
566             final String defaultCase = GrammaticalFeature.grammaticalCase.getDefault(rawCases);
567 
568             if (rawCases == null || rawCases.isEmpty()) {
569                 rawCases = Collections.singleton(defaultCase);
570             }
571             if (rawGenders == null || rawGenders.isEmpty()) {
572                 rawGenders = Collections.singleton(defaultGender);
573             }
574             for (String gender : rawGenders) {
575                 if (gender.equals(defaultGender)) {
576                     gender = null;
577                 }
578                 for (String case1 : rawCases) {
579                     if (case1.equals(defaultCase)) {
580                         case1 = null;
581                     }
582                     for (Count count : pluralTypes) {
583                         parts.setAttribute(-1, "gender", gender);
584                         parts.setAttribute(-1, "count", count.toString());
585                         parts.setAttribute(-1, "case", case1);
586                         set.add(parts.toString());
587                     }
588                 }
589             }
590         }
591 
592         /**
593          * Is the given PathType locale-dependent (for caching)?
594          *
595          * @param pathType the PathType
596          * @return the boolean
597          */
isLocaleDependent(PathType pathType)598         private static boolean isLocaleDependent(PathType pathType) {
599             /*
600              * The paths that are locale-dependent are @count and /dayPeriods.
601              */
602             return (pathType == COUNT
603                     || pathType == DAY_PERIODS
604                     || pathType.equals(COUNT_CASE)
605                     || pathType.equals(COUNT_CASE_GENDER));
606         }
607 
608         /**
609          * Get the PathType from the given path
610          *
611          * @param path the path
612          * @return the PathType
613          *     <p>Note: it would be more elegant and cleaner, but slower, if we used XPathParts to
614          *     determine the PathType. We avoid that since XPathParts.set is a performance hot spot.
615          *     (NOTE: don't know if the preceding is true anymore.)
616          */
getPathTypeFromPath(String path)617         public static PathType getPathTypeFromPath(String path) {
618             /*
619              * Would changing the order of these tests ever change the return value?
620              * Assume it could if in doubt.
621              */
622             if (path.indexOf("/metazone") > 0) {
623                 return PathType.METAZONE;
624             }
625             if (path.indexOf("/days") > 0) {
626                 return PathType.DAYS;
627             }
628             if (path.indexOf("/dayPeriods") > 0) {
629                 return PathType.DAY_PERIODS;
630             }
631             if (path.indexOf("/quarters") > 0) {
632                 return PathType.QUARTERS;
633             }
634             if (path.indexOf("/months") > 0) {
635                 return PathType.MONTHS;
636             }
637             if (path.indexOf("/relative[") > 0) {
638                 /*
639                  * include "[" in "/relative[" to avoid matching "/relativeTime" or "/relativeTimePattern".
640                  */
641                 return PathType.RELATIVE;
642             }
643             if (path.indexOf("/decimalFormatLength") > 0) {
644                 return PathType.DECIMAL_FORMAT_LENGTH;
645             }
646             if (path.indexOf("/unitLength[@type=\"long\"]") > 0) {
647                 if (path.indexOf("compoundUnitPattern1") > 0) {
648                     return PathType.COUNT_CASE_GENDER;
649                 }
650                 if (path.indexOf("/unitPattern[") > 0) {
651                     return PathType.COUNT_CASE;
652                 }
653             }
654             if (path.indexOf("[@count=") > 0) {
655                 return PathType.COUNT;
656             }
657             return PathType.SINGLETON;
658         }
659 
660         /**
661          * Get the PathType from the given XPathParts
662          *
663          * @param parts the XPathParts
664          * @return the PathType
665          * @deprecated
666          */
667         @Deprecated
getPathTypeFromParts(XPathParts parts)668         private static PathType getPathTypeFromParts(XPathParts parts) {
669             if (true) {
670                 throw new UnsupportedOperationException(
671                         "Code not updated. We may want to try using XPathParts in a future optimization, so leaving for now.");
672             }
673             /*
674              * Would changing the order of these tests ever change the return value?
675              * Assume it could if in doubt.
676              */
677             if (parts.containsElement("metazone")) {
678                 return PathType.METAZONE;
679             }
680             if (parts.containsElement("days")) {
681                 return PathType.DAYS;
682             }
683             if (parts.containsElement("dayPeriods")) {
684                 return PathType.DAY_PERIODS;
685             }
686             if (parts.containsElement("quarters")) {
687                 return PathType.QUARTERS;
688             }
689             if (parts.containsElement("months")) {
690                 return PathType.MONTHS;
691             }
692             if (parts.containsElement("relative")) {
693                 return PathType.RELATIVE;
694             }
695             if (parts.containsElement("decimalFormatLength")) {
696                 return PathType.DECIMAL_FORMAT_LENGTH;
697             }
698             if (parts.containsAttribute("count")) { // containsAttribute not containsElement
699                 return PathType.COUNT;
700             }
701             return PathType.SINGLETON;
702         }
703     }
704 }
705