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