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