1/* GENERATED SOURCE. DO NOT MODIFY. */ 2/* 3 **************************************************************************************** 4 * Copyright (C) 2009-2016, Google, Inc.; International Business Machines Corporation * 5 * and others. All Rights Reserved. * 6 **************************************************************************************** 7 */ 8package android.icu.util; 9 10import java.util.HashMap; 11import java.util.HashSet; 12import java.util.Iterator; 13import java.util.LinkedHashMap; 14import java.util.LinkedHashSet; 15import java.util.Map; 16import java.util.Map.Entry; 17import java.util.Set; 18import java.util.regex.Matcher; 19import java.util.regex.Pattern; 20 21import android.icu.impl.ICUResourceBundle; 22import android.icu.impl.Relation; 23import android.icu.impl.Row; 24import android.icu.impl.Row.R3; 25import android.icu.impl.Utility; 26 27/** 28 * Provides a way to match the languages (locales) supported by a product to the 29 * languages (locales) acceptable to a user, and get the best match. For 30 * example: 31 * 32 * <pre> 33 * LocaleMatcher matcher = new LocaleMatcher("fr, en-GB, en"); 34 * 35 * // afterwards: 36 * matcher.getBestMatch("en-US").toLanguageTag() => "en" 37 * </pre> 38 * 39 * It takes into account when languages are close to one another, such as fil 40 * and tl, and when language regional variants are close, like en-GB and en-AU. 41 * It also handles scripts, like zh-Hant vs zh-TW. For examples, see the test 42 * file. 43 * <p>All classes implementing this interface should be immutable. Often a 44 * product will just need one static instance, built with the languages 45 * that it supports. However, it may want multiple instances with different 46 * default languages based on additional information, such as the domain. 47 * 48 * @author markdavis@google.com 49 * @hide Only a subset of ICU is exposed in Android 50 */ 51public class LocaleMatcher { 52 53 /** 54 * @deprecated This API is ICU internal only. 55 * @hide draft / provisional / internal are hidden on Android 56 */ 57 @Deprecated 58 public static final boolean DEBUG = false; 59 60 private static final ULocale UNKNOWN_LOCALE = new ULocale("und"); 61 62 /** 63 * Threshold for falling back to the default (first) language. May make this 64 * a parameter in the future. 65 */ 66 private static final double DEFAULT_THRESHOLD = 0.5; 67 68 /** 69 * The default language, in case the threshold is not met. 70 */ 71 private final ULocale defaultLanguage; 72 73 /** 74 * The default language, in case the threshold is not met. 75 */ 76 private final double threshold; 77 78 /** 79 * Create a new language matcher. The highest-weighted language is the 80 * default. That means that if no other language is matches closer than a given 81 * threshold, that default language is chosen. Typically the default is English, 82 * but it could be different based on additional information, such as the domain 83 * of the page. 84 * 85 * @param languagePriorityList weighted list 86 */ 87 public LocaleMatcher(LocalePriorityList languagePriorityList) { 88 this(languagePriorityList, defaultWritten); 89 } 90 91 /** 92 * Create a new language matcher from a String form. The highest-weighted 93 * language is the default. 94 * 95 * @param languagePriorityListString String form of LanguagePriorityList 96 */ 97 public LocaleMatcher(String languagePriorityListString) { 98 this(LocalePriorityList.add(languagePriorityListString).build()); 99 } 100 101 /** 102 * Internal testing function; may expose API later. 103 * @param languagePriorityList LocalePriorityList to match 104 * @param matcherData Internal matching data 105 * @deprecated This API is ICU internal only. 106 * @hide draft / provisional / internal are hidden on Android 107 */ 108 @Deprecated 109 public LocaleMatcher(LocalePriorityList languagePriorityList, LanguageMatcherData matcherData) { 110 this(languagePriorityList, matcherData, DEFAULT_THRESHOLD); 111 } 112 113 /** 114 * Internal testing function; may expose API later. 115 * @param languagePriorityList LocalePriorityList to match 116 * @param matcherData Internal matching data 117 * @deprecated This API is ICU internal only. 118 * @hide draft / provisional / internal are hidden on Android 119 */ 120 @Deprecated 121 public LocaleMatcher(LocalePriorityList languagePriorityList, LanguageMatcherData matcherData, double threshold) { 122 this.matcherData = matcherData == null ? defaultWritten : matcherData.freeze(); 123 for (final ULocale language : languagePriorityList) { 124 add(language, languagePriorityList.getWeight(language)); 125 } 126 processMapping(); 127 Iterator<ULocale> it = languagePriorityList.iterator(); 128 defaultLanguage = it.hasNext() ? it.next() : null; 129 this.threshold = threshold; 130 } 131 132 133 /** 134 * Returns a fraction between 0 and 1, where 1 means that the languages are a 135 * perfect match, and 0 means that they are completely different. Note that 136 * the precise values may change over time; no code should be made dependent 137 * on the values remaining constant. 138 * @param desired Desired locale 139 * @param desiredMax Maximized locale (using likely subtags) 140 * @param supported Supported locale 141 * @param supportedMax Maximized locale (using likely subtags) 142 * @return value between 0 and 1, inclusive. 143 */ 144 public double match(ULocale desired, ULocale desiredMax, ULocale supported, ULocale supportedMax) { 145 return matcherData.match(desired, desiredMax, supported, supportedMax); 146 } 147 148 149 /** 150 * Canonicalize a locale (language). Note that for now, it is canonicalizing 151 * according to CLDR conventions (he vs iw, etc), since that is what is needed 152 * for likelySubtags. 153 * @param ulocale language/locale code 154 * @return ULocale with remapped subtags. 155 */ 156 public ULocale canonicalize(ULocale ulocale) { 157 // TODO Get the data from CLDR, use Java conventions. 158 String lang = ulocale.getLanguage(); 159 String lang2 = canonicalMap.get(lang); 160 String script = ulocale.getScript(); 161 String script2 = canonicalMap.get(script); 162 String region = ulocale.getCountry(); 163 String region2 = canonicalMap.get(region); 164 if (lang2 != null || script2 != null || region2 != null) { 165 return new ULocale( 166 lang2 == null ? lang : lang2, 167 script2 == null ? script : script2, 168 region2 == null ? region : region2 169 ); 170 } 171 return ulocale; 172 } 173 174 /** 175 * Get the best match for a LanguagePriorityList 176 * 177 * @param languageList list to match 178 * @return best matching language code 179 */ 180 public ULocale getBestMatch(LocalePriorityList languageList) { 181 double bestWeight = 0; 182 ULocale bestTableMatch = null; 183 double penalty = 0; 184 OutputDouble matchWeight = new OutputDouble(); 185 for (final ULocale language : languageList) { 186 final ULocale matchLocale = getBestMatchInternal(language, matchWeight); 187 final double weight = matchWeight.value * languageList.getWeight(language) - penalty; 188 if (weight > bestWeight) { 189 bestWeight = weight; 190 bestTableMatch = matchLocale; 191 } 192 penalty += 0.07000001; 193 } 194 if (bestWeight < threshold) { 195 bestTableMatch = defaultLanguage; 196 } 197 return bestTableMatch; 198 } 199 200 /** 201 * Convenience method: Get the best match for a LanguagePriorityList 202 * 203 * @param languageList String form of language priority list 204 * @return best matching language code 205 */ 206 public ULocale getBestMatch(String languageList) { 207 return getBestMatch(LocalePriorityList.add(languageList).build()); 208 } 209 210 /** 211 * Get the best match for an individual language code. 212 * 213 * @param ulocale locale/language code to match 214 * @return best matching language code 215 */ 216 public ULocale getBestMatch(ULocale ulocale) { 217 return getBestMatchInternal(ulocale, null); 218 } 219 220 /** 221 * @deprecated This API is ICU internal only. 222 * @hide draft / provisional / internal are hidden on Android 223 */ 224 @Deprecated 225 public ULocale getBestMatch(ULocale... ulocales) { 226 return getBestMatch(LocalePriorityList.add(ulocales).build()); 227 } 228 229 /** 230 * {@inheritDoc} 231 */ 232 @Override 233 public String toString() { 234 return "{" + defaultLanguage + ", " 235 + localeToMaxLocaleAndWeight + "}"; 236 } 237 // ================= Privates ===================== 238 239 /** 240 * Get the best match for an individual language code. 241 * 242 * @param languageCode 243 * @return best matching language code and weight (as per 244 * {@link #match(ULocale, ULocale)}) 245 */ 246 private ULocale getBestMatchInternal(ULocale languageCode, OutputDouble outputWeight) { 247 languageCode = canonicalize(languageCode); 248 final ULocale maximized = addLikelySubtags(languageCode); 249 if (DEBUG) { 250 System.out.println("\ngetBestMatchInternal: " + languageCode + ";\t" + maximized); 251 } 252 double bestWeight = 0; 253 ULocale bestTableMatch = null; 254 String baseLanguage = maximized.getLanguage(); 255 Set<R3<ULocale, ULocale, Double>> searchTable = desiredLanguageToPossibleLocalesToMaxLocaleToData.get(baseLanguage); 256 if (searchTable != null) { // we preprocessed the table so as to filter by lanugage 257 if (DEBUG) System.out.println("\tSearching: " + searchTable); 258 for (final R3<ULocale, ULocale, Double> tableKeyValue : searchTable) { 259 ULocale tableKey = tableKeyValue.get0(); 260 ULocale maxLocale = tableKeyValue.get1(); 261 Double matchedWeight = tableKeyValue.get2(); 262 final double match = match(languageCode, maximized, tableKey, maxLocale); 263 if (DEBUG) { 264 System.out.println("\t" + tableKeyValue + ";\t" + match + "\n"); 265 } 266 final double weight = match * matchedWeight; 267 if (weight > bestWeight) { 268 bestWeight = weight; 269 bestTableMatch = tableKey; 270 if (weight > 0.999d) { // bail on good enough match. 271 break; 272 } 273 } 274 } 275 } 276 if (bestWeight < threshold) { 277 bestTableMatch = defaultLanguage; 278 } 279 if (outputWeight != null) { 280 outputWeight.value = bestWeight; // only return the weight when needed 281 } 282 return bestTableMatch; 283 } 284 285 /** 286 * @deprecated This API is ICU internal only. 287 * @hide draft / provisional / internal are hidden on Android 288 */ 289 @Deprecated 290 private static class OutputDouble { // TODO, move to where OutputInt is 291 double value; 292 } 293 294 private void add(ULocale language, Double weight) { 295 language = canonicalize(language); 296 R3<ULocale, ULocale, Double> row = Row.of(language, addLikelySubtags(language), weight); 297 row.freeze(); 298 localeToMaxLocaleAndWeight.add(row); 299 } 300 301 /** 302 * We preprocess the data to get just the possible matches for each desired base language. 303 */ 304 private void processMapping() { 305 for (Entry<String, Set<String>> desiredToMatchingLanguages : matcherData.matchingLanguages().keyValuesSet()) { 306 String desired = desiredToMatchingLanguages.getKey(); 307 Set<String> supported = desiredToMatchingLanguages.getValue(); 308 for (R3<ULocale, ULocale, Double> localeToMaxAndWeight : localeToMaxLocaleAndWeight) { 309 final ULocale key = localeToMaxAndWeight.get0(); 310 String lang = key.getLanguage(); 311 if (supported.contains(lang)) { 312 addFiltered(desired, localeToMaxAndWeight); 313 } 314 } 315 } 316 // now put in the values directly, since languages always map to themselves 317 for (R3<ULocale, ULocale, Double> localeToMaxAndWeight : localeToMaxLocaleAndWeight) { 318 final ULocale key = localeToMaxAndWeight.get0(); 319 String lang = key.getLanguage(); 320 addFiltered(lang, localeToMaxAndWeight); 321 } 322 } 323 324 private void addFiltered(String desired, R3<ULocale, ULocale, Double> localeToMaxAndWeight) { 325 Set<R3<ULocale, ULocale, Double>> map = desiredLanguageToPossibleLocalesToMaxLocaleToData.get(desired); 326 if (map == null) { 327 desiredLanguageToPossibleLocalesToMaxLocaleToData.put(desired, map = new LinkedHashSet<R3<ULocale, ULocale, Double>>()); 328 } 329 map.add(localeToMaxAndWeight); 330 if (DEBUG) { 331 System.out.println(desired + ", " + localeToMaxAndWeight); 332 } 333 } 334 335 Set<Row.R3<ULocale, ULocale, Double>> localeToMaxLocaleAndWeight = new LinkedHashSet<Row.R3<ULocale, ULocale, Double>>(); 336 Map<String,Set<Row.R3<ULocale, ULocale, Double>>> desiredLanguageToPossibleLocalesToMaxLocaleToData 337 = new LinkedHashMap<String,Set<Row.R3<ULocale, ULocale, Double>>>(); 338 339 // =============== Special Mapping Information ============== 340 341 /** 342 * We need to add another method to addLikelySubtags that doesn't return 343 * null, but instead substitutes Zzzz and ZZ if unknown. There are also 344 * a few cases where addLikelySubtags needs to have expanded data, to handle 345 * all deprecated codes. 346 * @param languageCode 347 * @return "fixed" addLikelySubtags 348 */ 349 private ULocale addLikelySubtags(ULocale languageCode) { 350 // max("und") = "en_Latn_US", and since matching is based on maximized tags, the undefined 351 // language would normally match English. But that would produce the counterintuitive results 352 // that getBestMatch("und", LocaleMatcher("it,en")) would be "en", and 353 // getBestMatch("en", LocaleMatcher("it,und")) would be "und". 354 // 355 // To avoid that, we change the matcher's definitions of max (AddLikelySubtagsWithDefaults) 356 // so that max("und")="und". That produces the following, more desirable results: 357 if (languageCode.equals(UNKNOWN_LOCALE)) { 358 return UNKNOWN_LOCALE; 359 } 360 final ULocale result = ULocale.addLikelySubtags(languageCode); 361 // should have method on getLikelySubtags for this 362 if (result == null || result.equals(languageCode)) { 363 final String language = languageCode.getLanguage(); 364 final String script = languageCode.getScript(); 365 final String region = languageCode.getCountry(); 366 return new ULocale((language.length()==0 ? "und" 367 : language) 368 + "_" 369 + (script.length()==0 ? "Zzzz" : script) 370 + "_" 371 + (region.length()==0 ? "ZZ" : region)); 372 } 373 return result; 374 } 375 376 private static class LocalePatternMatcher { 377 // a value of null means a wildcard; matches any. 378 private String lang; 379 private String script; 380 private String region; 381 private Level level; 382 static Pattern pattern = Pattern.compile( 383 "([a-z]{1,8}|\\*)" 384 + "(?:[_-]([A-Z][a-z]{3}|\\*))?" 385 + "(?:[_-]([A-Z]{2}|[0-9]{3}|\\*))?"); 386 387 public LocalePatternMatcher(String toMatch) { 388 Matcher matcher = pattern.matcher(toMatch); 389 if (!matcher.matches()) { 390 throw new IllegalArgumentException("Bad pattern: " + toMatch); 391 } 392 lang = matcher.group(1); 393 script = matcher.group(2); 394 region = matcher.group(3); 395 level = region != null ? Level.region : script != null ? Level.script : Level.language; 396 397 if (lang.equals("*")) { 398 lang = null; 399 } 400 if (script != null && script.equals("*")) { 401 script = null; 402 } 403 if (region != null && region.equals("*")) { 404 region = null; 405 } 406 } 407 408 boolean matches(ULocale ulocale) { 409 if (lang != null && !lang.equals(ulocale.getLanguage())) { 410 return false; 411 } 412 if (script != null && !script.equals(ulocale.getScript())) { 413 return false; 414 } 415 if (region != null && !region.equals(ulocale.getCountry())) { 416 return false; 417 } 418 return true; 419 } 420 421 public Level getLevel() { 422 return level; 423 } 424 425 public String getLanguage() { 426 return (lang == null ? "*" : lang); 427 } 428 429 public String getScript() { 430 return (script == null ? "*" : script); 431 } 432 433 public String getRegion() { 434 return (region == null ? "*" : region); 435 } 436 437 public String toString() { 438 String result = getLanguage(); 439 if (level != Level.language) { 440 result += "-" + getScript(); 441 if (level != Level.script) { 442 result += "-" + getRegion(); 443 } 444 } 445 return result; 446 } 447 448 /* (non-Javadoc) 449 * @see java.lang.Object#equals(java.lang.Object) 450 */ 451 @Override 452 public boolean equals(Object obj) { 453 LocalePatternMatcher other = (LocalePatternMatcher) obj; 454 return Utility.objectEquals(level, other.level) 455 && Utility.objectEquals(lang, other.lang) 456 && Utility.objectEquals(script, other.script) 457 && Utility.objectEquals(region, other.region); 458 } 459 460 /* (non-Javadoc) 461 * @see java.lang.Object#hashCode() 462 */ 463 @Override 464 public int hashCode() { 465 return level.ordinal() 466 ^ (lang == null ? 0 : lang.hashCode()) 467 ^ (script == null ? 0 : script.hashCode()) 468 ^ (region == null ? 0 : region.hashCode()); 469 } 470 } 471 472 enum Level { 473 language(0.99), 474 script(0.2), 475 region(0.04); 476 477 final double worst; 478 479 Level(double d) { 480 worst = d; 481 } 482 } 483 484 private static class ScoreData implements Freezable<ScoreData> { 485 @SuppressWarnings("unused") 486 private static final double maxUnequal_changeD_sameS = 0.5; 487 488 @SuppressWarnings("unused") 489 private static final double maxUnequal_changeEqual = 0.75; 490 491 LinkedHashSet<Row.R3<LocalePatternMatcher,LocalePatternMatcher,Double>> scores = new LinkedHashSet<R3<LocalePatternMatcher, LocalePatternMatcher, Double>>(); 492 final Level level; 493 494 public ScoreData(Level level) { 495 this.level = level; 496 } 497 498 void addDataToScores(String desired, String supported, R3<LocalePatternMatcher,LocalePatternMatcher,Double> data) { 499 // Map<String, Set<R3<LocalePatternMatcher,LocalePatternMatcher,Double>>> lang_result = scores.get(desired); 500 // if (lang_result == null) { 501 // scores.put(desired, lang_result = new HashMap()); 502 // } 503 // Set<R3<LocalePatternMatcher,LocalePatternMatcher,Double>> result = lang_result.get(supported); 504 // if (result == null) { 505 // lang_result.put(supported, result = new LinkedHashSet()); 506 // } 507 // result.add(data); 508 boolean added = scores.add(data); 509 if (!added) { 510 throw new ICUException("trying to add duplicate data: " + data); 511 } 512 } 513 514 double getScore(ULocale dMax, String desiredRaw, String desiredMax, 515 ULocale sMax, String supportedRaw, String supportedMax) { 516 double distance = 0; 517 if (!desiredMax.equals(supportedMax)) { 518 distance = getRawScore(dMax, sMax); 519 } else if (!desiredRaw.equals(supportedRaw)) { // maxes are equal, changes are equal 520 distance += 0.001; 521 } 522 return distance; 523 } 524 525 private double getRawScore(ULocale desiredLocale, ULocale supportedLocale) { 526 if (DEBUG) { 527 System.out.println("\t\t\t" + level + " Raw Score:\t" + desiredLocale + ";\t" + supportedLocale); 528 } 529 for (R3<LocalePatternMatcher,LocalePatternMatcher,Double> datum : scores) { // : result 530 if (datum.get0().matches(desiredLocale) 531 && datum.get1().matches(supportedLocale)) { 532 if (DEBUG) { 533 System.out.println("\t\t\t\tFOUND\t" + datum); 534 } 535 return datum.get2(); 536 } 537 } 538 if (DEBUG) { 539 System.out.println("\t\t\t\tNOTFOUND\t" + level.worst); 540 } 541 return level.worst; 542 } 543 544 public String toString() { 545 StringBuilder result = new StringBuilder().append(level); 546 for (R3<LocalePatternMatcher, LocalePatternMatcher, Double> score : scores) { 547 result.append("\n\t\t").append(score); 548 } 549 return result.toString(); 550 } 551 552 553 @SuppressWarnings("unchecked") 554 public ScoreData cloneAsThawed() { 555 try { 556 ScoreData result = (ScoreData) clone(); 557 result.scores = (LinkedHashSet<R3<LocalePatternMatcher, LocalePatternMatcher, Double>>) result.scores.clone(); 558 result.frozen = false; 559 return result; 560 } catch (CloneNotSupportedException e) { 561 throw new ICUCloneNotSupportedException(e); // will never happen 562 } 563 564 } 565 566 private volatile boolean frozen = false; 567 568 public ScoreData freeze() { 569 return this; 570 } 571 572 public boolean isFrozen() { 573 return frozen; 574 } 575 576 public Relation<String,String> getMatchingLanguages() { 577 Relation<String,String> desiredToSupported = Relation.of(new LinkedHashMap<String,Set<String>>(), HashSet.class); 578 for (R3<LocalePatternMatcher, LocalePatternMatcher, Double> item : scores) { 579 LocalePatternMatcher desired = item.get0(); 580 LocalePatternMatcher supported = item.get1(); 581 if (desired.lang != null && supported.lang != null) { // explicitly mentioned languages must have reasonable distance 582 desiredToSupported.put(desired.lang, supported.lang); 583 } 584 } 585 desiredToSupported.freeze(); 586 return desiredToSupported; 587 } 588 } 589 590 /** 591 * Only for testing and use by tools. Interface may change!! 592 * @deprecated This API is ICU internal only. 593 * @hide draft / provisional / internal are hidden on Android 594 */ 595 @Deprecated 596 public static class LanguageMatcherData implements Freezable<LanguageMatcherData> { 597 private ScoreData languageScores = new ScoreData(Level.language); 598 private ScoreData scriptScores = new ScoreData(Level.script); 599 private ScoreData regionScores = new ScoreData(Level.region); 600 private Relation<String, String> matchingLanguages; 601 private volatile boolean frozen = false; 602 603 604 /** 605 * @deprecated This API is ICU internal only. 606 * @hide draft / provisional / internal are hidden on Android 607 */ 608 @Deprecated 609 public LanguageMatcherData() { 610 } 611 612 /** 613 * @deprecated This API is ICU internal only. 614 * @hide draft / provisional / internal are hidden on Android 615 */ 616 @Deprecated 617 public Relation<String, String> matchingLanguages() { 618 return matchingLanguages; 619 } 620 621 /** 622 * @deprecated This API is ICU internal only. 623 * @hide draft / provisional / internal are hidden on Android 624 */ 625 @Deprecated 626 public String toString() { 627 return languageScores + "\n\t" + scriptScores + "\n\t" + regionScores; 628 } 629 630 /** 631 * @deprecated This API is ICU internal only. 632 * @hide draft / provisional / internal are hidden on Android 633 */ 634 @Deprecated 635 public double match(ULocale a, ULocale aMax, ULocale b, ULocale bMax) { 636 double diff = 0; 637 diff += languageScores.getScore(aMax, a.getLanguage(), aMax.getLanguage(), bMax, b.getLanguage(), bMax.getLanguage()); 638 if (diff > 0.999d) { // with no language match, we bail 639 return 0.0d; 640 } 641 diff += scriptScores.getScore(aMax, a.getScript(), aMax.getScript(), bMax, b.getScript(), bMax.getScript()); 642 diff += regionScores.getScore(aMax, a.getCountry(), aMax.getCountry(), bMax, b.getCountry(), bMax.getCountry()); 643 644 if (!a.getVariant().equals(b.getVariant())) { 645 diff += 0.01; 646 } 647 if (diff < 0.0d) { 648 diff = 0.0d; 649 } else if (diff > 1.0d) { 650 diff = 1.0d; 651 } 652 if (DEBUG) { 653 System.out.println("\t\t\tTotal Distance\t" + diff); 654 } 655 return 1.0 - diff; 656 } 657 658 659 /** 660 * Add an exceptional distance between languages, typically because regional 661 * dialects were given their own language codes. At this point the code is 662 * symmetric. We don't bother producing an equivalence class because there are 663 * so few cases; this function depends on the other permutations being 664 * added specifically. 665 * @deprecated This API is ICU internal only. 666 * @hide draft / provisional / internal are hidden on Android 667 */ 668 @SuppressWarnings("unused") 669 @Deprecated 670 private LanguageMatcherData addDistance(String desired, String supported, int percent) { 671 return addDistance(desired, supported, percent, false, null); 672 } 673 /** 674 * @deprecated This API is ICU internal only. 675 * @hide draft / provisional / internal are hidden on Android 676 */ 677 @Deprecated 678 public LanguageMatcherData addDistance(String desired, String supported, int percent, String comment) { 679 return addDistance(desired, supported, percent, false, comment); 680 } 681 /** 682 * @deprecated This API is ICU internal only. 683 * @hide draft / provisional / internal are hidden on Android 684 */ 685 @Deprecated 686 public LanguageMatcherData addDistance(String desired, String supported, int percent, boolean oneway) { 687 return addDistance(desired, supported, percent, oneway, null); 688 } 689 690 private LanguageMatcherData addDistance(String desired, String supported, int percent, boolean oneway, String comment) { 691 if (DEBUG) { 692 System.out.println("\t<languageMatch desired=\"" + desired + "\"" + 693 " supported=\"" + supported + "\"" + 694 " percent=\"" + percent + "\"" 695 + (oneway ? " oneway=\"true\"" : "") 696 + "/>" 697 + (comment == null ? "" : "\t<!-- " + comment + " -->")); 698 // // .addDistance("nn", "nb", 4, true) 699 // System.out.println(".addDistance(\"" + desired + "\"" + 700 // ", \"" + supported + "\"" + 701 // ", " + percent + "" 702 // + (oneway ? "" : ", true") 703 // + (comment == null ? "" : ", \"" + comment + "\"") 704 // + ")" 705 // ); 706 707 } 708 double score = 1-percent/100.0; // convert from percentage 709 LocalePatternMatcher desiredMatcher = new LocalePatternMatcher(desired); 710 Level desiredLen = desiredMatcher.getLevel(); 711 LocalePatternMatcher supportedMatcher = new LocalePatternMatcher(supported); 712 Level supportedLen = supportedMatcher.getLevel(); 713 if (desiredLen != supportedLen) { 714 throw new IllegalArgumentException("Lengths unequal: " + desired + ", " + supported); 715 } 716 R3<LocalePatternMatcher,LocalePatternMatcher,Double> data = Row.of(desiredMatcher, supportedMatcher, score); 717 R3<LocalePatternMatcher,LocalePatternMatcher,Double> data2 = oneway ? null : Row.of(supportedMatcher, desiredMatcher, score); 718 boolean desiredEqualsSupported = desiredMatcher.equals(supportedMatcher); 719 switch (desiredLen) { 720 case language: 721 String dlanguage = desiredMatcher.getLanguage(); 722 String slanguage = supportedMatcher.getLanguage(); 723 languageScores.addDataToScores(dlanguage, slanguage, data); 724 if (!oneway && !desiredEqualsSupported) { 725 languageScores.addDataToScores(slanguage, dlanguage, data2); 726 } 727 break; 728 case script: 729 String dscript = desiredMatcher.getScript(); 730 String sscript = supportedMatcher.getScript(); 731 scriptScores.addDataToScores(dscript, sscript, data); 732 if (!oneway && !desiredEqualsSupported) { 733 scriptScores.addDataToScores(sscript, dscript, data2); 734 } 735 break; 736 case region: 737 String dregion = desiredMatcher.getRegion(); 738 String sregion = supportedMatcher.getRegion(); 739 regionScores.addDataToScores(dregion, sregion, data); 740 if (!oneway && !desiredEqualsSupported) { 741 regionScores.addDataToScores(sregion, dregion, data2); 742 } 743 break; 744 } 745 return this; 746 } 747 748 /** 749 * {@inheritDoc} 750 * @deprecated This API is ICU internal only. 751 * @hide draft / provisional / internal are hidden on Android 752 */ 753 @Deprecated 754 public LanguageMatcherData cloneAsThawed() { 755 LanguageMatcherData result; 756 try { 757 result = (LanguageMatcherData) clone(); 758 result.languageScores = languageScores.cloneAsThawed(); 759 result.scriptScores = scriptScores.cloneAsThawed(); 760 result.regionScores = regionScores.cloneAsThawed(); 761 result.frozen = false; 762 return result; 763 } catch (CloneNotSupportedException e) { 764 throw new ICUCloneNotSupportedException(e); // will never happen 765 } 766 } 767 768 /** 769 * {@inheritDoc} 770 * @deprecated This API is ICU internal only. 771 * @hide draft / provisional / internal are hidden on Android 772 */ 773 @Deprecated 774 public LanguageMatcherData freeze() { 775 languageScores.freeze(); 776 regionScores.freeze(); 777 scriptScores.freeze(); 778 matchingLanguages = languageScores.getMatchingLanguages(); 779 frozen = true; 780 return this; 781 } 782 783 /** 784 * {@inheritDoc} 785 * @deprecated This API is ICU internal only. 786 * @hide draft / provisional / internal are hidden on Android 787 */ 788 @Deprecated 789 public boolean isFrozen() { 790 return frozen; 791 } 792 } 793 794 LanguageMatcherData matcherData; 795 796 private static final LanguageMatcherData defaultWritten; 797 798 private static HashMap<String,String> canonicalMap = new HashMap<String, String>(); 799 800 801 static { 802 canonicalMap.put("iw", "he"); 803 canonicalMap.put("mo", "ro"); 804 canonicalMap.put("tl", "fil"); 805 806 ICUResourceBundle suppData = getICUSupplementalData(); 807 ICUResourceBundle languageMatching = suppData.findTopLevel("languageMatching"); 808 ICUResourceBundle written = (ICUResourceBundle) languageMatching.get("written"); 809 defaultWritten = new LanguageMatcherData(); 810 811 for(UResourceBundleIterator iter = written.getIterator(); iter.hasNext();) { 812 ICUResourceBundle item = (ICUResourceBundle) iter.next(); 813 /* 814 "*_*_*", 815 "*_*_*", 816 "96", 817 */ 818 // <languageMatch desired="gsw" supported="de" percent="96" oneway="true" /> 819 boolean oneway = item.getSize() > 3 && "1".equals(item.getString(3)); 820 defaultWritten.addDistance(item.getString(0), item.getString(1), Integer.parseInt(item.getString(2)), oneway); 821 } 822 defaultWritten.freeze(); 823 } 824 825 /** 826 * @deprecated This API is ICU internal only. 827 * @hide draft / provisional / internal are hidden on Android 828 */ 829 @Deprecated 830 public static ICUResourceBundle getICUSupplementalData() { 831 ICUResourceBundle suppData = (ICUResourceBundle) UResourceBundle.getBundleInstance( 832 ICUResourceBundle.ICU_BASE_NAME, 833 "supplementalData", 834 ICUResourceBundle.ICU_DATA_CLASS_LOADER); 835 return suppData; 836 } 837 838 /** 839 * @deprecated This API is ICU internal only. 840 * @hide draft / provisional / internal are hidden on Android 841 */ 842 @Deprecated 843 public static double match(ULocale a, ULocale b) { 844 final LocaleMatcher matcher = new LocaleMatcher(""); 845 return matcher.match(a, matcher.addLikelySubtags(a), b, matcher.addLikelySubtags(b)); 846 } 847} 848