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