• 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 Multimap<String, String> cachePathToLogicalGroup = ArrayListMultimap.create();
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      * @param path the distinguishing xpath
81      * @param pathType TODO
82      *
83      * @return the set of paths, or null (to be treated as equivalent to empty set)
84      *
85      * For example, given the path
86      *
87      * //ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="1"]
88      *
89      * return the set of four paths
90      *
91      * //ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="1"]
92      * //ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="2"]
93      * //ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="3"]
94      * //ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="format"]/quarterWidth[@type="abbreviated"]/quarter[@type="4"]
95      *
96      * Caches: Most of the calculations are independent of the locale, and can be cached on a static basis.
97      * The paths that are locale-dependent are /dayPeriods and @count. Those can be computed on a per-locale basis;
98      * and cached (they are shared across a number of locales).
99      *
100      * Reference: https://unicode.org/cldr/trac/ticket/11854
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             // return new TreeSet<String>(); // return empty set for null path
106         }
107         XPathParts parts = null;
108         PathType pathType = null;
109         if (GET_TYPE_FROM_PARTS) {
110             parts = XPathParts.getFrozenInstance(path);
111             pathType = PathType.getPathTypeFromParts(parts);
112         } else {
113             /*
114              * XPathParts.set is expensive, so avoid it (not needed for singletons) if !GET_TYPE_FROM_PARTS
115              */
116             pathType = PathType.getPathTypeFromPath(path);
117         }
118         if (pathTypeOut != null) {
119             pathTypeOut.value = pathType;
120         }
121 
122         if (GET_TYPE_COUNTS) {
123             typeCount.compute(pathType.toString(), (k, v) -> (v == null) ? 1 : v + 1);
124         }
125 
126         if (pathType == PathType.SINGLETON) {
127             /*
128              * Skip cache for PathType.SINGLETON and simply return a set of one.
129              * TODO: should we ever return null instead of singleton here?
130              */
131             Set<String> set = new TreeSet<>();
132             set.add(path);
133             return set;
134         }
135 
136         if (!GET_TYPE_FROM_PARTS) {
137             parts = XPathParts.getFrozenInstance(path).cloneAsThawed();
138         } else {
139             parts = parts.cloneAsThawed();
140         }
141 
142         if (PathType.isLocaleDependent(pathType)) {
143             String locale = cldrFile.getLocaleID();
144             Pair<String, String> key = new Pair<>(locale, path);
145             if (cacheLocaleAndPathToLogicalGroup.containsKey(key)) {
146                 return new TreeSet<>(cacheLocaleAndPathToLogicalGroup.get(key));
147             }
148             Set<String> set = new TreeSet<>();
149             pathType.addPaths(set, cldrFile, path, parts);
150             cacheLocaleAndPathToLogicalGroup.put(key, set);
151             return set;
152         } else {
153             /*
154              * All other paths are locale-independent.
155              */
156             if (cachePathToLogicalGroup.containsKey(path)) {
157                 return new TreeSet<>(cachePathToLogicalGroup.get(path));
158             }
159             Set<String> set = new TreeSet<>();
160             pathType.addPaths(set, cldrFile, path, parts);
161             cachePathToLogicalGroup.putAll(path, set);
162             return set;
163         }
164     }
165 
getPaths(CLDRFile cldrFile, String path)166     public static Set<String> getPaths(CLDRFile cldrFile, String path) {
167         return getPaths(cldrFile, path, null);
168     }
169 
170     /**
171      * Returns the plural info for a given locale.
172      */
getPluralInfo(CLDRFile cldrFile)173     private static PluralInfo getPluralInfo(CLDRFile cldrFile) {
174         return supplementalData.getPlurals(PluralType.cardinal,
175             cldrFile.getLocaleID());
176     }
177 
178     /**
179      * @param cldrFile
180      * @param path
181      * @return true if the specified path is optional in the logical grouping
182      *         that it belongs to.
183      */
isOptional(CLDRFile cldrFile, String path)184     public static boolean isOptional(CLDRFile cldrFile, String path) {
185         XPathParts parts = XPathParts.getFrozenInstance(path);
186 
187         if (parts.containsElement("relative")) {
188             String fieldType = parts.findAttributeValue("field", "type");
189             String relativeType = parts.findAttributeValue("relative", "type");
190             Integer relativeValue = relativeType == null ? 999 : Integer.valueOf(relativeType);
191             if (fieldType != null && fieldType.startsWith("day") && Math.abs(relativeValue.intValue()) >= 2) {
192                 return true; // relative days +2 +3 -2 -3 are optional in a logical group.
193             }
194         }
195         // Paths with count="(zero|one)" are optional if their usage is covered
196         // fully by paths with count="(0|1)", which are always optional themselves.
197         if (!path.contains("[@count=")) return false;
198         String pluralType = parts.getAttributeValue(-1, "count");
199         switch (pluralType) {
200         case "0": case "1":
201             return true;
202         case "zero": case "one":
203             break; // continue
204         case "many": // special case for french
205             String localeId = cldrFile.getLocaleID();
206             if (localeId.startsWith("fr")
207                 && (localeId.length() == 2 || localeId.charAt(2) == '_')) {
208                 return true;
209             }
210             return false;
211         default:
212             return false;
213         }
214 
215         parts = parts.cloneAsThawed();
216         PluralRules pluralRules = getPluralInfo(cldrFile).getPluralRules();
217         parts.setAttribute(-1, "count", "0");
218         Set<Double> explicits = new HashSet<>();
219         if (cldrFile.isHere(parts.toString())) {
220             explicits.add(0.0);
221         }
222         parts.setAttribute(-1, "count", "1");
223         if (cldrFile.isHere(parts.toString())) {
224             explicits.add(1.0);
225         }
226         if (!explicits.isEmpty()) {
227             // HACK: The com.ibm.icu.text prefix is needed so that ST can find it
228             // (no idea why).
229             KeywordStatus status = org.unicode.cldr.util.PluralRulesUtil.getKeywordStatus(
230                 pluralRules, pluralType, 0, explicits, true);
231             if (status == KeywordStatus.SUPPRESSED) {
232                 return true;
233             }
234         }
235         return false;
236     }
237 
238     /**
239      * Path types for logical groupings
240      */
241     public enum PathType {
242         SINGLETON { // no logical groups for singleton paths
243             @Override
244             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)245             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
246                 // Do nothing. This function won't be called.
247             }
248         },
249         METAZONE {
250             @Override
251             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)252             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
253                 String metazoneName = parts.getAttributeValue(3, "type");
254                 if (metazonesDSTSet.contains(metazoneName)) {
255                     for (String str : ImmutableSet.of("generic", "standard", "daylight")) {
256                         set.add(path.substring(0, path.lastIndexOf('/') + 1) + str);
257                     }
258                 }
259             }
260         },
261         DAYS {
262             @Override
263             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)264             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
265                 String dayName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
266                 // This is just a quick check to make sure the path is good.
267                 if (dayName != null && days.contains(dayName)) {
268                     for (String str : days) {
269                         parts.setAttribute("day", "type", str);
270                         set.add(parts.toString());
271                     }
272                 }
273             }
274         },
275         DAY_PERIODS {
276             @Override
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)277             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
278                 if (parts.containsElement("alias")) {
279                     set.add(path);
280                 } else {
281                     String dayPeriodType = parts.findAttributeValue("dayPeriod", "type");
282                     if (ampm.contains(dayPeriodType)) {
283                         for (String s : ampm) {
284                             parts.setAttribute("dayPeriod", "type", s);
285                             set.add(parts.toString());
286                         }
287                     } else {
288                         DayPeriodInfo.Type dayPeriodContext = DayPeriodInfo.Type.fromString(parts.findAttributeValue("dayPeriodContext", "type"));
289                         DayPeriodInfo dpi = supplementalData.getDayPeriods(dayPeriodContext, cldrFile.getLocaleID());
290                         List<DayPeriod> dayPeriods = dpi.getPeriods();
291                         DayPeriod thisDayPeriod = DayPeriod.fromString(dayPeriodType);
292                         if (dayPeriods.contains(thisDayPeriod)) {
293                             for (DayPeriod d : dayPeriods) {
294                                 parts.setAttribute("dayPeriod", "type", d.name());
295                                 set.add(parts.toString());
296                             }
297                         }
298                     }
299                 }
300             }
301         },
302         QUARTERS {
303             @Override
304             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)305             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
306                 String quarterName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
307                 Integer quarter = quarterName == null ? 0 : Integer.valueOf(quarterName);
308                 if (quarter > 0 && quarter <= 4) { // This is just a quick check to make sure the path is good.
309                     for (Integer i = 1; i <= 4; i++) {
310                         parts.setAttribute("quarter", "type", i.toString());
311                         set.add(parts.toString());
312                     }
313                 }
314             }
315         },
316         MONTHS {
317             @Override
318             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)319             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
320                 String calType = parts.size() > 3 ? parts.getAttributeValue(3, "type") : null;
321                 String monthName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
322                 Integer month = monthName == null ? 0 : Integer.valueOf(monthName);
323                 int calendarMonthMax = calendarsWith13Months.contains(calType) ? 13 : 12;
324                 if (month > 0 && month <= calendarMonthMax) { // This is just a quick check to make sure the path is good.
325                     for (Integer i = 1; i <= calendarMonthMax; i++) {
326                         parts.setAttribute("month", "type", i.toString());
327                         if ("hebrew".equals(calType)) {
328                             parts.removeAttribute("month", "yeartype");
329                         }
330                         set.add(parts.toString());
331                     }
332                     if ("hebrew".equals(calType)) { // Add extra hebrew calendar leap month
333                         parts.setAttribute("month", "type", Integer.toString(7));
334                         parts.setAttribute("month", "yeartype", "leap");
335                         set.add(parts.toString());
336                     }
337                 }
338             }
339         },
340         RELATIVE {
341             @Override
342             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)343             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
344                 String fieldType = parts.findAttributeValue("field", "type");
345                 String relativeType = parts.findAttributeValue("relative", "type");
346                 Integer relativeValue = relativeType == null ? 999 : Integer.valueOf(relativeType);
347                 if (relativeValue >= -3 && relativeValue <= 3) { // This is just a quick check to make sure the path is good.
348                     if (!(nowUnits.contains(fieldType) && relativeValue == 0)) { // Workaround for "now", "this hour", "this minute"
349                         int limit = 1;
350                         if (fieldType != null && fieldType.startsWith("day")) {
351                             limit = 3;
352                         }
353                         for (Integer i = -1 * limit; i <= limit; i++) {
354                             parts.setAttribute("relative", "type", i.toString());
355                             set.add(parts.toString());
356                         }
357                     }
358                 }
359             }
360         },
361         DECIMAL_FORMAT_LENGTH {
362             @Override
363             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)364             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
365                 PluralInfo pluralInfo = getPluralInfo(cldrFile);
366                 Set<Count> pluralTypes = pluralInfo.getCounts();
367                 String decimalFormatLengthType = parts.size() > 3 ? parts.getAttributeValue(3, "type") : null;
368                 String decimalFormatPatternType = parts.size() > 5 ? parts.getAttributeValue(5, "type") : null;
369                 if (decimalFormatLengthType != null && decimalFormatPatternType != null &&
370                     compactDecimalFormatLengths.contains(decimalFormatLengthType)) {
371                     int numZeroes = decimalFormatPatternType.length() - 1;
372                     int baseZeroes = (numZeroes / 3) * 3;
373                     for (int i = 0; i < 3; i++) {
374                         // This gives us "baseZeroes+i" zeroes at the end.
375                         String patType = "1" + String.format(String.format("%%0%dd", baseZeroes + i), 0);
376                         parts.setAttribute(5, "type", patType);
377                         for (Count count : pluralTypes) {
378                             parts.setAttribute(5, "count", count.toString());
379                             set.add(parts.toString());
380                         }
381                     }
382                 }
383             }
384         },
385         COUNT {
386             @Override
387             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)388             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
389                 addCaseOnly(set, cldrFile, parts);
390             }
391         },
392         COUNT_CASE {
393             @Override
394             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)395             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
396                 if (!GrammarInfo.SEED_LOCALES.contains(cldrFile.getLocaleID())) {
397                     addCaseOnly(set, cldrFile, parts);
398                     return;
399                 }
400                 GrammarInfo grammarInfo = supplementalData.getGrammarInfo(cldrFile.getLocaleID());
401                 if (grammarInfo == null
402                     || (parts.getElement(3).equals("unitLength")
403                         && GrammarInfo.SPECIAL_TRANSLATION_UNITS.contains(parts.getAttributeValue(3, "type")))) {
404                     addCaseOnly(set, cldrFile, parts);
405                     return;
406                 }
407                 Set<Count> pluralTypes = getPluralInfo(cldrFile).getCounts();
408                 Collection<String> rawCases = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.units);
409                 setGrammarAttributes(set, parts, pluralTypes, rawCases, null);
410 
411             }
412         },
413         COUNT_CASE_GENDER {
414             @Override
415             @SuppressWarnings("unused")
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)416             void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts) {
417                 if (!GrammarInfo.SEED_LOCALES.contains(cldrFile.getLocaleID())) {
418                     addCaseOnly(set, cldrFile, parts);
419                     return;
420                 }
421                 GrammarInfo grammarInfo = supplementalData.getGrammarInfo(cldrFile.getLocaleID());
422                 if (grammarInfo == null) {
423                     addCaseOnly(set, cldrFile, parts);
424                     return;
425                 }
426                 Set<Count> pluralTypes = getPluralInfo(cldrFile).getCounts();
427                 Collection<String> rawCases = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.units);
428                 Collection<String> rawGenders = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalGender, GrammaticalScope.units);
429                 setGrammarAttributes(set, parts, pluralTypes, rawCases, rawGenders);
430             }
431         }
432 
433         ;
434 
addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts)435         abstract void addPaths(Set<String> set, CLDRFile cldrFile, String path, XPathParts parts);
436 
addCaseOnly(Set<String> set, CLDRFile cldrFile, XPathParts parts)437         public void addCaseOnly(Set<String> set, CLDRFile cldrFile, XPathParts parts) {
438             Set<Count> pluralTypes = getPluralInfo(cldrFile).getCounts();
439             for (Count count : pluralTypes) {
440                 parts.setAttribute(-1, "count", count.toString());
441                 set.add(parts.toString());
442             }
443         }
444 
setGrammarAttributes(Set<String> set, XPathParts parts, Set<Count> pluralTypes, Collection<String> rawCases, Collection<String> rawGenders)445         public void setGrammarAttributes(Set<String> set, XPathParts parts, Set<Count> pluralTypes, Collection<String> rawCases, Collection<String> rawGenders) {
446             final String defaultGender = GrammaticalFeature.grammaticalGender.getDefault(rawGenders);
447             final String defaultCase = GrammaticalFeature.grammaticalCase.getDefault(rawCases);
448 
449             if (rawCases == null || rawCases.isEmpty()) {
450                 rawCases = Collections.singleton(defaultCase);
451             }
452             if (rawGenders == null || rawGenders.isEmpty()) {
453                 rawGenders = Collections.singleton(defaultGender);
454             }
455             for (String gender : rawGenders) {
456                 if (gender.equals(defaultGender)) {
457                     gender = null;
458                 }
459                 for (String case1 : rawCases) {
460                     if (case1.equals(defaultCase)) {
461                         case1 = null;
462                     }
463                     for (Count count : pluralTypes) {
464                         parts.setAttribute(-1, "gender", gender);
465                         parts.setAttribute(-1, "count", count.toString());
466                         parts.setAttribute(-1, "case", case1);
467                         set.add(parts.toString());
468                     }
469                 }
470             }
471         }
472 
473         /**
474          * Is the given PathType locale-dependent (for caching)?
475          *
476          * @param pathType the PathType
477          * @return the boolean
478          */
isLocaleDependent(PathType pathType)479         private static boolean isLocaleDependent(PathType pathType) {
480             /*
481              * The paths that are locale-dependent are @count and /dayPeriods.
482              */
483             return (pathType == COUNT || pathType == DAY_PERIODS || pathType.equals(COUNT_CASE) || pathType.equals(COUNT_CASE_GENDER));
484         }
485 
486         /**
487          * Get the PathType from the given path
488          *
489          * @param path the path
490          * @return the PathType
491          *
492          * Note: it would be more elegant and cleaner, but slower, if we used XPathParts to
493          * determine the PathType. We avoid that since XPathParts.set is a performance hot spot. (NOTE: don't know if the preceding is true anymore.)
494          */
getPathTypeFromPath(String path)495         public static PathType getPathTypeFromPath(String path) {
496             /*
497              * Would changing the order of these tests ever change the return value?
498              * Assume it could if in doubt.
499              */
500             if (path.indexOf("/metazone") > 0) {
501                 return PathType.METAZONE;
502             }
503             if (path.indexOf("/days") > 0) {
504                 return PathType.DAYS;
505             }
506             if (path.indexOf("/dayPeriods") > 0) {
507                 return PathType.DAY_PERIODS;
508             }
509             if (path.indexOf("/quarters") > 0) {
510                 return PathType.QUARTERS;
511             }
512             if (path.indexOf("/months") > 0) {
513                 return PathType.MONTHS;
514             }
515             if (path.indexOf("/relative[") > 0) {
516                 /*
517                  * include "[" in "/relative[" to avoid matching "/relativeTime" or "/relativeTimePattern".
518                  */
519                 return PathType.RELATIVE;
520             }
521             if (path.indexOf("/decimalFormatLength") > 0) {
522                 return PathType.DECIMAL_FORMAT_LENGTH;
523             }
524             if (path.indexOf("/unitLength[@type=\"long\"]") > 0) {
525                 if (path.indexOf("compoundUnitPattern1") > 0) {
526                     return PathType.COUNT_CASE_GENDER;
527                 }
528                 if (path.indexOf("/unitPattern[") > 0) {
529                     return PathType.COUNT_CASE;
530                 }
531             }
532             if (path.indexOf("[@count=") > 0) {
533                 return PathType.COUNT;
534             }
535             return PathType.SINGLETON;
536         }
537 
538         /**
539          * Get the PathType from the given XPathParts
540          *
541          * @param parts the XPathParts
542          * @return the PathType
543          * @deprecated
544          */
545         @Deprecated
getPathTypeFromParts(XPathParts parts)546         private static PathType getPathTypeFromParts(XPathParts parts) {
547             if (true) {
548                 throw new UnsupportedOperationException("Code not updated. We may want to try using XPathParts in a future optimization, so leaving for now.");
549             }
550             /*
551              * Would changing the order of these tests ever change the return value?
552              * Assume it could if in doubt.
553              */
554             if (parts.containsElement("metazone")) {
555                 return PathType.METAZONE;
556             }
557             if (parts.containsElement("days")) {
558                 return PathType.DAYS;
559             }
560             if (parts.containsElement("dayPeriods")) {
561                 return PathType.DAY_PERIODS;
562             }
563             if (parts.containsElement("quarters")) {
564                 return PathType.QUARTERS;
565             }
566             if (parts.containsElement("months")) {
567                 return PathType.MONTHS;
568             }
569             if (parts.containsElement("relative")) {
570                 return PathType.RELATIVE;
571             }
572             if (parts.containsElement("decimalFormatLength")) {
573                 return PathType.DECIMAL_FORMAT_LENGTH;
574             }
575             if (parts.containsAttribute("count")) { // containsAttribute not containsElement
576                 return PathType.COUNT;
577             }
578             return PathType.SINGLETON;
579         }
580     }
581 }
582