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