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