1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 /* 5 **************************************************************************************** 6 * Copyright (C) 2009-2016, Google, Inc.; International Business Machines Corporation 7 * and others. All Rights Reserved. 8 **************************************************************************************** 9 */ 10 package ohos.global.icu.util; 11 12 import java.util.ArrayList; 13 import java.util.Collection; 14 import java.util.HashMap; 15 import java.util.Iterator; 16 import java.util.List; 17 import java.util.Locale; 18 import java.util.Map; 19 20 import ohos.global.icu.impl.locale.LSR; 21 import ohos.global.icu.impl.locale.LocaleDistance; 22 import ohos.global.icu.impl.locale.XLikelySubtags; 23 24 /** 25 * Immutable class that picks the best match between a user's desired locales and 26 * an application's supported locales. 27 * 28 * <p>Example: 29 * <pre> 30 * LocaleMatcher matcher = LocaleMatcher.builder().setSupportedLocales("fr, en-GB, en").build(); 31 * Locale bestSupported = matcher.getBestLocale(Locale.US); // "en" 32 * </pre> 33 * 34 * <p>A matcher takes into account when languages are close to one another, 35 * such as Danish and Norwegian, 36 * and when regional variants are close, like en-GB and en-AU as opposed to en-US. 37 * 38 * <p>If there are multiple supported locales with the same (language, script, region) 39 * likely subtags, then the current implementation returns the first of those locales. 40 * It ignores variant subtags (except for pseudolocale variants) and extensions. 41 * This may change in future versions. 42 * 43 * <p>For example, the current implementation does not distinguish between 44 * de, de-DE, de-Latn, de-1901, de-u-co-phonebk. 45 * 46 * <p>If you prefer one equivalent locale over another, then provide only the preferred one, 47 * or place it earlier in the list of supported locales. 48 * 49 * <p>Otherwise, the order of supported locales may have no effect on the best-match results. 50 * The current implementation compares each desired locale with supported locales 51 * in the following order: 52 * 1. Default locale, if supported; 53 * 2. CLDR "paradigm locales" like en-GB and es-419; 54 * 3. other supported locales. 55 * This may change in future versions. 56 * 57 * <p>Often a product will just need one matcher instance, built with the languages 58 * that it supports. However, it may want multiple instances with different 59 * default languages based on additional information, such as the domain. 60 * 61 * <p>This class is not intended for public subclassing. 62 * 63 * @author markdavis@google.com 64 * @hide exposed on OHOS 65 */ 66 public final class LocaleMatcher { 67 private static final LSR UND_LSR = new LSR("und","","", LSR.EXPLICIT_LSR); 68 // In ULocale, "und" and "" make the same object. 69 private static final ULocale UND_ULOCALE = new ULocale("und"); 70 // In Locale, "und" and "" make different objects. 71 private static final Locale UND_LOCALE = new Locale("und"); 72 private static final Locale EMPTY_LOCALE = new Locale(""); 73 74 // Activates debugging output to stderr with details of GetBestMatch. 75 private static final boolean TRACE_MATCHER = false; 76 77 private static abstract class LsrIterator implements Iterator<LSR> { 78 int bestDesiredIndex = -1; 79 80 @Override remove()81 public void remove() { 82 throw new UnsupportedOperationException(); 83 } 84 rememberCurrent(int desiredIndex)85 public abstract void rememberCurrent(int desiredIndex); 86 } 87 88 /** 89 * Builder option for whether the language subtag or the script subtag is most important. 90 * 91 * @see LocaleMatcher.Builder#setFavorSubtag(LocaleMatcher.FavorSubtag) 92 * @hide exposed on OHOS 93 * @hide draft / provisional / internal are hidden on OHOS 94 */ 95 public enum FavorSubtag { 96 /** 97 * Language differences are most important, then script differences, then region differences. 98 * (This is the default behavior.) 99 * 100 * @hide draft / provisional / internal are hidden on OHOS 101 */ 102 LANGUAGE, 103 /** 104 * Makes script differences matter relatively more than language differences. 105 * 106 * @hide draft / provisional / internal are hidden on OHOS 107 */ 108 SCRIPT 109 } 110 111 /** 112 * Builder option for whether all desired locales are treated equally or 113 * earlier ones are preferred. 114 * 115 * @see LocaleMatcher.Builder#setDemotionPerDesiredLocale(LocaleMatcher.Demotion) 116 * @hide exposed on OHOS 117 * @hide draft / provisional / internal are hidden on OHOS 118 */ 119 public enum Demotion { 120 /** 121 * All desired locales are treated equally. 122 * 123 * @hide draft / provisional / internal are hidden on OHOS 124 */ 125 NONE, 126 /** 127 * Earlier desired locales are preferred. 128 * 129 * <p>From each desired locale to the next, 130 * the distance to any supported locale is increased by an additional amount 131 * which is at least as large as most region mismatches. 132 * A later desired locale has to have a better match with some supported locale 133 * due to more than merely having the same region subtag. 134 * 135 * <p>For example: <code>Supported={en, sv} desired=[en-GB, sv]</code> 136 * yields <code>Result(en-GB, en)</code> because 137 * with the demotion of sv its perfect match is no better than 138 * the region distance between the earlier desired locale en-GB and en=en-US. 139 * 140 * <p>Notes: 141 * <ul> 142 * <li>In some cases, language and/or script differences can be as small as 143 * the typical region difference. (Example: sr-Latn vs. sr-Cyrl) 144 * <li>It is possible for certain region differences to be larger than usual, 145 * and larger than the demotion. 146 * (As of CLDR 35 there is no such case, but 147 * this is possible in future versions of the data.) 148 * </ul> 149 * 150 * @hide draft / provisional / internal are hidden on OHOS 151 */ 152 REGION 153 } 154 155 /** 156 * Builder option for whether to include or ignore one-way (fallback) match data. 157 * The LocaleMatcher uses CLDR languageMatch data which includes fallback (oneway=true) entries. 158 * Sometimes it is desirable to ignore those. 159 * 160 * <p>For example, consider a web application with the UI in a given language, 161 * with a link to another, related web app. 162 * The link should include the UI language, and the target server may also use 163 * the client’s Accept-Language header data. 164 * The target server has its own list of supported languages. 165 * One may want to favor UI language consistency, that is, 166 * if there is a decent match for the original UI language, we want to use it, 167 * but not if it is merely a fallback. 168 * 169 * @see LocaleMatcher.Builder#setDirection(LocaleMatcher.Direction) 170 * @hide exposed on OHOS 171 * @hide draft / provisional / internal are hidden on OHOS 172 */ 173 public enum Direction { 174 /** 175 * Locale matching includes one-way matches such as Breton→French. (default) 176 * 177 * @hide draft / provisional / internal are hidden on OHOS 178 */ 179 WITH_ONE_WAY, 180 /** 181 * Locale matching limited to two-way matches including e.g. Danish↔Norwegian 182 * but ignoring one-way matches. 183 * 184 * @hide draft / provisional / internal are hidden on OHOS 185 */ 186 ONLY_TWO_WAY 187 } 188 189 /** 190 * Data for the best-matching pair of a desired and a supported locale. 191 * 192 * @hide exposed on OHOS 193 * @hide draft / provisional / internal are hidden on OHOS 194 */ 195 public static final class Result { 196 private final ULocale desiredULocale; 197 private final ULocale supportedULocale; 198 private final Locale desiredLocale; 199 private final Locale supportedLocale; 200 private final int desiredIndex; 201 private final int supportedIndex; 202 Result(ULocale udesired, ULocale usupported, Locale desired, Locale supported, int desIndex, int suppIndex)203 private Result(ULocale udesired, ULocale usupported, 204 Locale desired, Locale supported, 205 int desIndex, int suppIndex) { 206 desiredULocale = udesired; 207 supportedULocale = usupported; 208 desiredLocale = desired; 209 supportedLocale = supported; 210 desiredIndex = desIndex; 211 supportedIndex = suppIndex; 212 } 213 214 /** 215 * Returns the best-matching desired locale. 216 * null if the list of desired locales is empty or if none matched well enough. 217 * 218 * @return the best-matching desired locale, or null. 219 * @hide draft / provisional / internal are hidden on OHOS 220 */ getDesiredULocale()221 public ULocale getDesiredULocale() { 222 return desiredULocale == null && desiredLocale != null ? 223 ULocale.forLocale(desiredLocale) : desiredULocale; 224 } 225 /** 226 * Returns the best-matching desired locale. 227 * null if the list of desired locales is empty or if none matched well enough. 228 * 229 * @return the best-matching desired locale, or null. 230 * @hide draft / provisional / internal are hidden on OHOS 231 */ getDesiredLocale()232 public Locale getDesiredLocale() { 233 return desiredLocale == null && desiredULocale != null ? 234 desiredULocale.toLocale() : desiredLocale; 235 } 236 237 /** 238 * Returns the best-matching supported locale. 239 * If none matched well enough, this is the default locale. 240 * The default locale is null if the list of supported locales is empty and 241 * no explicit default locale is set. 242 * 243 * @return the best-matching supported locale, or null. 244 * @hide draft / provisional / internal are hidden on OHOS 245 */ getSupportedULocale()246 public ULocale getSupportedULocale() { return supportedULocale; } 247 /** 248 * Returns the best-matching supported locale. 249 * If none matched well enough, this is the default locale. 250 * The default locale is null if the list of supported locales is empty and 251 * no explicit default locale is set. 252 * 253 * @return the best-matching supported locale, or null. 254 * @hide draft / provisional / internal are hidden on OHOS 255 */ getSupportedLocale()256 public Locale getSupportedLocale() { return supportedLocale; } 257 258 /** 259 * Returns the index of the best-matching desired locale in the input Iterable order. 260 * -1 if the list of desired locales is empty or if none matched well enough. 261 * 262 * @return the index of the best-matching desired locale, or -1. 263 * @hide draft / provisional / internal are hidden on OHOS 264 */ getDesiredIndex()265 public int getDesiredIndex() { return desiredIndex; } 266 267 /** 268 * Returns the index of the best-matching supported locale in the 269 * constructor’s or builder’s input order (“set” Collection plus “added” locales). 270 * If the matcher was built from a locale list string, then the iteration order is that 271 * of a LocalePriorityList built from the same string. 272 * -1 if the list of supported locales is empty or if none matched well enough. 273 * 274 * @return the index of the best-matching supported locale, or -1. 275 * @hide draft / provisional / internal are hidden on OHOS 276 */ getSupportedIndex()277 public int getSupportedIndex() { return supportedIndex; } 278 279 /** 280 * Takes the best-matching supported locale and adds relevant fields of the 281 * best-matching desired locale, such as the -t- and -u- extensions. 282 * May replace some fields of the supported locale. 283 * The result is the locale that should be used for date and number formatting, collation, etc. 284 * Returns null if getSupportedLocale() returns null. 285 * 286 * <p>Example: desired=ar-SA-u-nu-latn, supported=ar-EG, resolved locale=ar-SA-u-nu-latn 287 * 288 * @return a locale combining the best-matching desired and supported locales. 289 * @hide draft / provisional / internal are hidden on OHOS 290 */ makeResolvedULocale()291 public ULocale makeResolvedULocale() { 292 ULocale bestDesired = getDesiredULocale(); 293 if (supportedULocale == null || bestDesired == null || 294 supportedULocale.equals(bestDesired)) { 295 return supportedULocale; 296 } 297 ULocale.Builder b = new ULocale.Builder().setLocale(supportedULocale); 298 299 // Copy the region from bestDesired, if there is one. 300 String region = bestDesired.getCountry(); 301 if (!region.isEmpty()) { 302 b.setRegion(region); 303 } 304 305 // Copy the variants from bestDesired, if there are any. 306 // Note that this will override any supportedULocale variants. 307 // For example, "sco-ulster-fonipa" + "...-fonupa" => "sco-fonupa" (replacing ulster). 308 String variants = bestDesired.getVariant(); 309 if (!variants.isEmpty()) { 310 b.setVariant(variants); 311 } 312 313 // Copy the extensions from bestDesired, if there are any. 314 // Note that this will override any supportedULocale extensions. 315 // For example, "th-u-nu-latn-ca-buddhist" + "...-u-nu-native" => "th-u-nu-native" 316 // (replacing calendar). 317 for (char extensionKey : bestDesired.getExtensionKeys()) { 318 b.setExtension(extensionKey, bestDesired.getExtension(extensionKey)); 319 } 320 return b.build(); 321 } 322 323 /** 324 * Takes the best-matching supported locale and adds relevant fields of the 325 * best-matching desired locale, such as the -t- and -u- extensions. 326 * May replace some fields of the supported locale. 327 * The result is the locale that should be used for 328 * date and number formatting, collation, etc. 329 * Returns null if getSupportedLocale() returns null. 330 * 331 * <p>Example: desired=ar-SA-u-nu-latn, supported=ar-EG, resolved locale=ar-SA-u-nu-latn 332 * 333 * @return a locale combining the best-matching desired and supported locales. 334 * @hide draft / provisional / internal are hidden on OHOS 335 */ makeResolvedLocale()336 public Locale makeResolvedLocale() { 337 ULocale resolved = makeResolvedULocale(); 338 return resolved != null ? resolved.toLocale() : null; 339 } 340 } 341 342 private final int thresholdDistance; 343 private final int demotionPerDesiredLocale; 344 private final FavorSubtag favorSubtag; 345 private final Direction direction; 346 347 // These are in input order. 348 private final ULocale[] supportedULocales; 349 private final Locale[] supportedLocales; 350 // These are in preference order: 1. Default locale 2. paradigm locales 3. others. 351 private final Map<LSR, Integer> supportedLsrToIndex; 352 // Array versions of the supportedLsrToIndex keys and values. 353 // The distance lookup loops over the supportedLSRs and returns the index of the best match. 354 private final LSR[] supportedLSRs; 355 private final int[] supportedIndexes; 356 private final int supportedLSRsLength; 357 private final ULocale defaultULocale; 358 private final Locale defaultLocale; 359 360 /** 361 * LocaleMatcher Builder. 362 * 363 * @see LocaleMatcher#builder() 364 * @hide exposed on OHOS 365 * @hide draft / provisional / internal are hidden on OHOS 366 */ 367 public static final class Builder { 368 private List<ULocale> supportedLocales; 369 private int thresholdDistance = -1; 370 private Demotion demotion; 371 private ULocale defaultLocale; 372 private FavorSubtag favor; 373 private Direction direction; 374 Builder()375 private Builder() {} 376 377 /** 378 * Parses the string like {@link LocalePriorityList} does and 379 * sets the supported locales accordingly. 380 * Clears any previously set/added supported locales first. 381 * 382 * @param locales the string of locales to set, to be parsed like LocalePriorityList does 383 * @return this Builder object 384 * @hide draft / provisional / internal are hidden on OHOS 385 */ setSupportedLocales(String locales)386 public Builder setSupportedLocales(String locales) { 387 return setSupportedULocales(LocalePriorityList.add(locales).build().getULocales()); 388 } 389 390 /** 391 * Copies the supported locales, preserving iteration order. 392 * Clears any previously set/added supported locales first. 393 * Duplicates are allowed, and are not removed. 394 * 395 * @param locales the list of locales 396 * @return this Builder object 397 * @hide draft / provisional / internal are hidden on OHOS 398 */ setSupportedULocales(Collection<ULocale> locales)399 public Builder setSupportedULocales(Collection<ULocale> locales) { 400 supportedLocales = new ArrayList<>(locales); 401 return this; 402 } 403 404 /** 405 * Copies the supported locales, preserving iteration order. 406 * Clears any previously set/added supported locales first. 407 * Duplicates are allowed, and are not removed. 408 * 409 * @param locales the list of locale 410 * @return this Builder object 411 * @hide draft / provisional / internal are hidden on OHOS 412 */ setSupportedLocales(Collection<Locale> locales)413 public Builder setSupportedLocales(Collection<Locale> locales) { 414 supportedLocales = new ArrayList<>(locales.size()); 415 for (Locale locale : locales) { 416 supportedLocales.add(ULocale.forLocale(locale)); 417 } 418 return this; 419 } 420 421 /** 422 * Adds another supported locale. 423 * Duplicates are allowed, and are not removed. 424 * 425 * @param locale another locale 426 * @return this Builder object 427 * @hide draft / provisional / internal are hidden on OHOS 428 */ addSupportedULocale(ULocale locale)429 public Builder addSupportedULocale(ULocale locale) { 430 if (supportedLocales == null) { 431 supportedLocales = new ArrayList<>(); 432 } 433 supportedLocales.add(locale); 434 return this; 435 } 436 437 /** 438 * Adds another supported locale. 439 * Duplicates are allowed, and are not removed. 440 * 441 * @param locale another locale 442 * @return this Builder object 443 * @hide draft / provisional / internal are hidden on OHOS 444 */ addSupportedLocale(Locale locale)445 public Builder addSupportedLocale(Locale locale) { 446 return addSupportedULocale(ULocale.forLocale(locale)); 447 } 448 449 /** 450 * Sets the default locale; if null, or if it is not set explicitly, 451 * then the first supported locale is used as the default locale. 452 * 453 * @param defaultLocale the default locale 454 * @return this Builder object 455 * @hide draft / provisional / internal are hidden on OHOS 456 */ setDefaultULocale(ULocale defaultLocale)457 public Builder setDefaultULocale(ULocale defaultLocale) { 458 this.defaultLocale = defaultLocale; 459 return this; 460 } 461 462 /** 463 * Sets the default locale; if null, or if it is not set explicitly, 464 * then the first supported locale is used as the default locale. 465 * 466 * @param defaultLocale the default locale 467 * @return this Builder object 468 * @hide draft / provisional / internal are hidden on OHOS 469 */ setDefaultLocale(Locale defaultLocale)470 public Builder setDefaultLocale(Locale defaultLocale) { 471 this.defaultLocale = ULocale.forLocale(defaultLocale); 472 return this; 473 } 474 475 /** 476 * If SCRIPT, then the language differences are smaller than script differences. 477 * This is used in situations (such as maps) where 478 * it is better to fall back to the same script than a similar language. 479 * 480 * @param subtag the subtag to favor 481 * @return this Builder object 482 * @hide draft / provisional / internal are hidden on OHOS 483 */ setFavorSubtag(FavorSubtag subtag)484 public Builder setFavorSubtag(FavorSubtag subtag) { 485 this.favor = subtag; 486 return this; 487 } 488 489 /** 490 * Option for whether all desired locales are treated equally or 491 * earlier ones are preferred (this is the default). 492 * 493 * @param demotion the demotion per desired locale to set. 494 * @return this Builder object 495 * @hide draft / provisional / internal are hidden on OHOS 496 */ setDemotionPerDesiredLocale(Demotion demotion)497 public Builder setDemotionPerDesiredLocale(Demotion demotion) { 498 this.demotion = demotion; 499 return this; 500 } 501 502 /** 503 * Option for whether to include or ignore one-way (fallback) match data. 504 * By default, they are included. 505 * 506 * @param direction the match direction to set. 507 * @return this Builder object 508 * @hide draft / provisional / internal are hidden on OHOS 509 */ setDirection(Direction direction)510 public Builder setDirection(Direction direction) { 511 this.direction = direction; 512 return this; 513 } 514 515 /** 516 * <i>Internal only!</i> 517 * 518 * @param thresholdDistance the thresholdDistance to set, with -1 = default 519 * @return this Builder object 520 * @deprecated This API is ICU internal only. 521 * @hide draft / provisional / internal are hidden on OHOS 522 */ 523 @Deprecated internalSetThresholdDistance(int thresholdDistance)524 public Builder internalSetThresholdDistance(int thresholdDistance) { 525 if (thresholdDistance > 100) { 526 thresholdDistance = 100; 527 } 528 this.thresholdDistance = thresholdDistance; 529 return this; 530 } 531 532 /** 533 * Builds and returns a new locale matcher. 534 * This builder can continue to be used. 535 * 536 * @return new LocaleMatcher. 537 * @hide draft / provisional / internal are hidden on OHOS 538 */ build()539 public LocaleMatcher build() { 540 return new LocaleMatcher(this); 541 } 542 543 /** 544 * {@inheritDoc} 545 * @hide draft / provisional / internal are hidden on OHOS 546 */ 547 @Override toString()548 public String toString() { 549 StringBuilder s = new StringBuilder().append("{LocaleMatcher.Builder"); 550 if (supportedLocales != null && !supportedLocales.isEmpty()) { 551 s.append(" supported={").append(supportedLocales).append('}'); 552 } 553 if (defaultLocale != null) { 554 s.append(" default=").append(defaultLocale); 555 } 556 if (favor != null) { 557 s.append(" distance=").append(favor); 558 } 559 if (thresholdDistance >= 0) { 560 s.append(String.format(" threshold=%d", thresholdDistance)); 561 } 562 if (demotion != null) { 563 s.append(" demotion=").append(demotion); 564 } 565 return s.append('}').toString(); 566 } 567 } 568 569 /** 570 * Returns a builder used in chaining parameters for building a LocaleMatcher. 571 * 572 * @return a new Builder object 573 * @hide draft / provisional / internal are hidden on OHOS 574 */ builder()575 public static Builder builder() { 576 return new Builder(); 577 } 578 579 /** 580 * Copies the supported locales, preserving iteration order, and constructs a LocaleMatcher. 581 * The first locale is used as the default locale for when there is no good match. 582 * 583 * @param supportedLocales list of locales 584 */ LocaleMatcher(LocalePriorityList supportedLocales)585 public LocaleMatcher(LocalePriorityList supportedLocales) { 586 this(builder().setSupportedULocales(supportedLocales.getULocales())); 587 } 588 589 /** 590 * Parses the string like {@link LocalePriorityList} does and 591 * constructs a LocaleMatcher for the supported locales parsed from the string. 592 * The first one (in LocalePriorityList iteration order) is used as the default locale for 593 * when there is no good match. 594 * 595 * @param supportedLocales the string of locales to set, 596 * to be parsed like LocalePriorityList does 597 */ LocaleMatcher(String supportedLocales)598 public LocaleMatcher(String supportedLocales) { 599 this(builder().setSupportedLocales(supportedLocales)); 600 } 601 LocaleMatcher(Builder builder)602 private LocaleMatcher(Builder builder) { 603 thresholdDistance = builder.thresholdDistance < 0 ? 604 LocaleDistance.INSTANCE.getDefaultScriptDistance() : builder.thresholdDistance; 605 ULocale udef = builder.defaultLocale; 606 Locale def = null; 607 LSR defLSR = null; 608 if (udef != null) { 609 def = udef.toLocale(); 610 defLSR = getMaximalLsrOrUnd(udef); 611 } 612 // Store the supported locales in input order, 613 // so that when different types are used (e.g., java.util.Locale) 614 // we can return those by parallel index. 615 int supportedLocalesLength = builder.supportedLocales != null ? 616 builder.supportedLocales.size() : 0; 617 supportedULocales = new ULocale[supportedLocalesLength]; 618 supportedLocales = new Locale[supportedLocalesLength]; 619 // Supported LRSs in input order. 620 LSR lsrs[] = new LSR[supportedLocalesLength]; 621 int i = 0; 622 if (supportedLocalesLength > 0) { 623 for (ULocale locale : builder.supportedLocales) { 624 supportedULocales[i] = locale; 625 supportedLocales[i] = locale.toLocale(); 626 lsrs[i] = getMaximalLsrOrUnd(locale); 627 ++i; 628 } 629 } 630 631 // We need an unordered map from LSR to first supported locale with that LSR, 632 // and an ordered list of (LSR, supported index) for 633 // the supported locales in the following order: 634 // 1. Default locale, if it is supported. 635 // 2. Priority locales (aka "paradigm locales") in builder order. 636 // 3. Remaining locales in builder order. 637 supportedLsrToIndex = new HashMap<>(supportedLocalesLength); 638 supportedLSRs = new LSR[supportedLocalesLength]; 639 supportedIndexes = new int[supportedLocalesLength]; 640 int suppLength = 0; 641 // Determine insertion order. 642 // Add locales immediately that are equivalent to the default. 643 byte[] order = new byte[supportedLocalesLength]; 644 int numParadigms = 0; 645 i = 0; 646 for (ULocale locale : supportedULocales) { 647 LSR lsr = lsrs[i]; 648 if (defLSR == null) { 649 assert i == 0; 650 udef = locale; 651 def = supportedLocales[0]; 652 defLSR = lsr; 653 suppLength = putIfAbsent(lsr, 0, suppLength); 654 } else if (lsr.isEquivalentTo(defLSR)) { 655 suppLength = putIfAbsent(lsr, i, suppLength); 656 } else if (LocaleDistance.INSTANCE.isParadigmLSR(lsr)) { 657 order[i] = 2; 658 ++numParadigms; 659 } else { 660 order[i] = 3; 661 } 662 ++i; 663 } 664 // Add supported paradigm locales. 665 int paradigmLimit = suppLength + numParadigms; 666 for (i = 0; i < supportedLocalesLength && suppLength < paradigmLimit; ++i) { 667 if (order[i] == 2) { 668 suppLength = putIfAbsent(lsrs[i], i, suppLength); 669 } 670 } 671 // Add remaining supported locales. 672 for (i = 0; i < supportedLocalesLength; ++i) { 673 if (order[i] == 3) { 674 suppLength = putIfAbsent(lsrs[i], i, suppLength); 675 } 676 } 677 supportedLSRsLength = suppLength; 678 // If supportedLSRsLength < supportedLocalesLength then 679 // we waste as many array slots as there are duplicate supported LSRs, 680 // but the amount of wasted space is small as long as there are few duplicates. 681 682 defaultULocale = udef; 683 defaultLocale = def; 684 demotionPerDesiredLocale = 685 builder.demotion == Demotion.NONE ? 0 : 686 LocaleDistance.INSTANCE.getDefaultDemotionPerDesiredLocale(); // null or REGION 687 favorSubtag = builder.favor; 688 direction = builder.direction; 689 if (TRACE_MATCHER) { 690 System.err.printf("new LocaleMatcher: %s\n", toString()); 691 } 692 } 693 putIfAbsent(LSR lsr, int i, int suppLength)694 private final int putIfAbsent(LSR lsr, int i, int suppLength) { 695 if (!supportedLsrToIndex.containsKey(lsr)) { 696 supportedLsrToIndex.put(lsr, i); 697 supportedLSRs[suppLength] = lsr; 698 supportedIndexes[suppLength++] = i; 699 } 700 return suppLength; 701 } 702 getMaximalLsrOrUnd(ULocale locale)703 private static final LSR getMaximalLsrOrUnd(ULocale locale) { 704 if (locale.equals(UND_ULOCALE)) { 705 return UND_LSR; 706 } else { 707 return XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(locale); 708 } 709 } 710 getMaximalLsrOrUnd(Locale locale)711 private static final LSR getMaximalLsrOrUnd(Locale locale) { 712 if (locale.equals(UND_LOCALE) || locale.equals(EMPTY_LOCALE)) { 713 return UND_LSR; 714 } else { 715 return XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(locale); 716 } 717 } 718 719 private static final class ULocaleLsrIterator extends LsrIterator { 720 private Iterator<ULocale> locales; 721 private ULocale current, remembered; 722 ULocaleLsrIterator(Iterator<ULocale> locales)723 ULocaleLsrIterator(Iterator<ULocale> locales) { 724 this.locales = locales; 725 } 726 727 @Override hasNext()728 public boolean hasNext() { 729 return locales.hasNext(); 730 } 731 732 @Override next()733 public LSR next() { 734 current = locales.next(); 735 return getMaximalLsrOrUnd(current); 736 } 737 738 @Override rememberCurrent(int desiredIndex)739 public void rememberCurrent(int desiredIndex) { 740 bestDesiredIndex = desiredIndex; 741 remembered = current; 742 } 743 } 744 745 private static final class LocaleLsrIterator extends LsrIterator { 746 private Iterator<Locale> locales; 747 private Locale current, remembered; 748 LocaleLsrIterator(Iterator<Locale> locales)749 LocaleLsrIterator(Iterator<Locale> locales) { 750 this.locales = locales; 751 } 752 753 @Override hasNext()754 public boolean hasNext() { 755 return locales.hasNext(); 756 } 757 758 @Override next()759 public LSR next() { 760 current = locales.next(); 761 return getMaximalLsrOrUnd(current); 762 } 763 764 @Override rememberCurrent(int desiredIndex)765 public void rememberCurrent(int desiredIndex) { 766 bestDesiredIndex = desiredIndex; 767 remembered = current; 768 } 769 } 770 771 /** 772 * Returns the supported locale which best matches the desired locale. 773 * 774 * @param desiredLocale Typically a user's language. 775 * @return the best-matching supported locale. 776 */ getBestMatch(ULocale desiredLocale)777 public ULocale getBestMatch(ULocale desiredLocale) { 778 LSR desiredLSR = getMaximalLsrOrUnd(desiredLocale); 779 int suppIndex = getBestSuppIndex(desiredLSR, null); 780 return suppIndex >= 0 ? supportedULocales[suppIndex] : defaultULocale; 781 } 782 783 /** 784 * Returns the supported locale which best matches one of the desired locales. 785 * 786 * @param desiredLocales Typically a user's languages, in order of preference (descending). 787 * (In ICU 4.4..63 this parameter had type LocalePriorityList.) 788 * @return the best-matching supported locale. 789 */ getBestMatch(Iterable<ULocale> desiredLocales)790 public ULocale getBestMatch(Iterable<ULocale> desiredLocales) { 791 Iterator<ULocale> desiredIter = desiredLocales.iterator(); 792 if (!desiredIter.hasNext()) { 793 return defaultULocale; 794 } 795 ULocaleLsrIterator lsrIter = new ULocaleLsrIterator(desiredIter); 796 LSR desiredLSR = lsrIter.next(); 797 int suppIndex = getBestSuppIndex(desiredLSR, lsrIter); 798 return suppIndex >= 0 ? supportedULocales[suppIndex] : defaultULocale; 799 } 800 801 /** 802 * Parses the string like {@link LocalePriorityList} does and 803 * returns the supported locale which best matches one of the desired locales. 804 * 805 * @param desiredLocaleList Typically a user's languages, 806 * as a string which is to be parsed like LocalePriorityList does. 807 * @return the best-matching supported locale. 808 */ getBestMatch(String desiredLocaleList)809 public ULocale getBestMatch(String desiredLocaleList) { 810 return getBestMatch(LocalePriorityList.add(desiredLocaleList).build()); 811 } 812 813 /** 814 * Returns the supported locale which best matches the desired locale. 815 * 816 * @param desiredLocale Typically a user's language. 817 * @return the best-matching supported locale. 818 * @hide draft / provisional / internal are hidden on OHOS 819 */ getBestLocale(Locale desiredLocale)820 public Locale getBestLocale(Locale desiredLocale) { 821 LSR desiredLSR = getMaximalLsrOrUnd(desiredLocale); 822 int suppIndex = getBestSuppIndex(desiredLSR, null); 823 return suppIndex >= 0 ? supportedLocales[suppIndex] : defaultLocale; 824 } 825 826 /** 827 * Returns the supported locale which best matches one of the desired locales. 828 * 829 * @param desiredLocales Typically a user's languages, in order of preference (descending). 830 * @return the best-matching supported locale. 831 * @hide draft / provisional / internal are hidden on OHOS 832 */ getBestLocale(Iterable<Locale> desiredLocales)833 public Locale getBestLocale(Iterable<Locale> desiredLocales) { 834 Iterator<Locale> desiredIter = desiredLocales.iterator(); 835 if (!desiredIter.hasNext()) { 836 return defaultLocale; 837 } 838 LocaleLsrIterator lsrIter = new LocaleLsrIterator(desiredIter); 839 LSR desiredLSR = lsrIter.next(); 840 int suppIndex = getBestSuppIndex(desiredLSR, lsrIter); 841 return suppIndex >= 0 ? supportedLocales[suppIndex] : defaultLocale; 842 } 843 defaultResult()844 private Result defaultResult() { 845 return new Result(null, defaultULocale, null, defaultLocale, -1, -1); 846 } 847 makeResult(ULocale desiredLocale, ULocaleLsrIterator lsrIter, int suppIndex)848 private Result makeResult(ULocale desiredLocale, ULocaleLsrIterator lsrIter, int suppIndex) { 849 if (suppIndex < 0) { 850 return defaultResult(); 851 } else if (desiredLocale != null) { 852 return new Result(desiredLocale, supportedULocales[suppIndex], 853 null, supportedLocales[suppIndex], 0, suppIndex); 854 } else { 855 return new Result(lsrIter.remembered, supportedULocales[suppIndex], 856 null, supportedLocales[suppIndex], lsrIter.bestDesiredIndex, suppIndex); 857 } 858 } 859 makeResult(Locale desiredLocale, LocaleLsrIterator lsrIter, int suppIndex)860 private Result makeResult(Locale desiredLocale, LocaleLsrIterator lsrIter, int suppIndex) { 861 if (suppIndex < 0) { 862 return defaultResult(); 863 } else if (desiredLocale != null) { 864 return new Result(null, supportedULocales[suppIndex], 865 desiredLocale, supportedLocales[suppIndex], 0, suppIndex); 866 } else { 867 return new Result(null, supportedULocales[suppIndex], 868 lsrIter.remembered, supportedLocales[suppIndex], 869 lsrIter.bestDesiredIndex, suppIndex); 870 } 871 } 872 873 /** 874 * Returns the best match between the desired locale and the supported locales. 875 * 876 * @param desiredLocale Typically a user's language. 877 * @return the best-matching pair of the desired and a supported locale. 878 * @hide draft / provisional / internal are hidden on OHOS 879 */ getBestMatchResult(ULocale desiredLocale)880 public Result getBestMatchResult(ULocale desiredLocale) { 881 LSR desiredLSR = getMaximalLsrOrUnd(desiredLocale); 882 int suppIndex = getBestSuppIndex(desiredLSR, null); 883 return makeResult(desiredLocale, null, suppIndex); 884 } 885 886 /** 887 * Returns the best match between the desired and supported locales. 888 * 889 * @param desiredLocales Typically a user's languages, in order of preference (descending). 890 * @return the best-matching pair of a desired and a supported locale. 891 * @hide draft / provisional / internal are hidden on OHOS 892 */ getBestMatchResult(Iterable<ULocale> desiredLocales)893 public Result getBestMatchResult(Iterable<ULocale> desiredLocales) { 894 Iterator<ULocale> desiredIter = desiredLocales.iterator(); 895 if (!desiredIter.hasNext()) { 896 return defaultResult(); 897 } 898 ULocaleLsrIterator lsrIter = new ULocaleLsrIterator(desiredIter); 899 LSR desiredLSR = lsrIter.next(); 900 int suppIndex = getBestSuppIndex(desiredLSR, lsrIter); 901 return makeResult(null, lsrIter, suppIndex); 902 } 903 904 /** 905 * Returns the best match between the desired locale and the supported locales. 906 * 907 * @param desiredLocale Typically a user's language. 908 * @return the best-matching pair of the desired and a supported locale. 909 * @hide draft / provisional / internal are hidden on OHOS 910 */ getBestLocaleResult(Locale desiredLocale)911 public Result getBestLocaleResult(Locale desiredLocale) { 912 LSR desiredLSR = getMaximalLsrOrUnd(desiredLocale); 913 int suppIndex = getBestSuppIndex(desiredLSR, null); 914 return makeResult(desiredLocale, null, suppIndex); 915 } 916 917 /** 918 * Returns the best match between the desired and supported locales. 919 * 920 * @param desiredLocales Typically a user's languages, in order of preference (descending). 921 * @return the best-matching pair of a desired and a supported locale. 922 * @hide draft / provisional / internal are hidden on OHOS 923 */ getBestLocaleResult(Iterable<Locale> desiredLocales)924 public Result getBestLocaleResult(Iterable<Locale> desiredLocales) { 925 Iterator<Locale> desiredIter = desiredLocales.iterator(); 926 if (!desiredIter.hasNext()) { 927 return defaultResult(); 928 } 929 LocaleLsrIterator lsrIter = new LocaleLsrIterator(desiredIter); 930 LSR desiredLSR = lsrIter.next(); 931 int suppIndex = getBestSuppIndex(desiredLSR, lsrIter); 932 return makeResult(null, lsrIter, suppIndex); 933 } 934 935 /** 936 * @param desiredLSR The first desired locale's LSR. 937 * @param remainingIter Remaining desired LSRs, null or empty if none. 938 * @return the index of the best-matching supported locale, or -1 if there is no good match. 939 */ getBestSuppIndex(LSR desiredLSR, LsrIterator remainingIter)940 private int getBestSuppIndex(LSR desiredLSR, LsrIterator remainingIter) { 941 int desiredIndex = 0; 942 int bestSupportedLsrIndex = -1; 943 StringBuilder sb = null; 944 if (TRACE_MATCHER) { 945 sb = new StringBuilder("LocaleMatcher desired:"); 946 } 947 for (int bestShiftedDistance = LocaleDistance.shiftDistance(thresholdDistance);;) { 948 if (TRACE_MATCHER) { 949 sb.append(' ').append(desiredLSR); 950 } 951 // Quick check for exact maximized LSR. 952 Integer index = supportedLsrToIndex.get(desiredLSR); 953 if (index != null) { 954 int suppIndex = index; 955 if (TRACE_MATCHER) { 956 System.err.printf("%s --> best=%s: desiredLSR=supportedLSR\n", 957 sb, supportedULocales[suppIndex]); 958 } 959 if (remainingIter != null) { remainingIter.rememberCurrent(desiredIndex); } 960 return suppIndex; 961 } 962 int bestIndexAndDistance = LocaleDistance.INSTANCE.getBestIndexAndDistance( 963 desiredLSR, supportedLSRs, supportedLSRsLength, 964 bestShiftedDistance, favorSubtag, direction); 965 if (bestIndexAndDistance >= 0) { 966 bestShiftedDistance = LocaleDistance.getShiftedDistance(bestIndexAndDistance); 967 if (remainingIter != null) { remainingIter.rememberCurrent(desiredIndex); } 968 bestSupportedLsrIndex = LocaleDistance.getIndex(bestIndexAndDistance); 969 } 970 if ((bestShiftedDistance -= LocaleDistance.shiftDistance(demotionPerDesiredLocale)) 971 <= 0) { 972 break; 973 } 974 if (remainingIter == null || !remainingIter.hasNext()) { 975 break; 976 } 977 desiredLSR = remainingIter.next(); 978 ++desiredIndex; 979 } 980 if (bestSupportedLsrIndex < 0) { 981 if (TRACE_MATCHER) { 982 System.err.printf("%s --> best=default %s: no good match\n", sb, defaultULocale); 983 } 984 return -1; 985 } 986 int suppIndex = supportedIndexes[bestSupportedLsrIndex]; 987 if (TRACE_MATCHER) { 988 System.err.printf("%s --> best=%s: best matching supported locale\n", 989 sb, supportedULocales[suppIndex]); 990 } 991 return suppIndex; 992 } 993 994 /** 995 * Returns a fraction between 0 and 1, where 1 means that the languages are a 996 * perfect match, and 0 means that they are completely different. 997 * 998 * <p>This is mostly an implementation detail, and the precise values may change over time. 999 * The implementation may use either the maximized forms or the others ones, or both. 1000 * The implementation may or may not rely on the forms to be consistent with each other. 1001 * 1002 * <p>Callers should construct and use a matcher rather than match pairs of locales directly. 1003 * 1004 * @param desired Desired locale. 1005 * @param desiredMax Maximized locale (using likely subtags). 1006 * @param supported Supported locale. 1007 * @param supportedMax Maximized locale (using likely subtags). 1008 * @return value between 0 and 1, inclusive. 1009 * @deprecated ICU 65 Build and use a matcher rather than comparing pairs of locales. 1010 */ 1011 @Deprecated match(ULocale desired, ULocale desiredMax, ULocale supported, ULocale supportedMax)1012 public double match(ULocale desired, ULocale desiredMax, ULocale supported, ULocale supportedMax) { 1013 // Returns the inverse of the distance: That is, 1-distance(desired, supported). 1014 int indexAndDistance = LocaleDistance.INSTANCE.getBestIndexAndDistance( 1015 getMaximalLsrOrUnd(desired), 1016 new LSR[] { getMaximalLsrOrUnd(supported) }, 1, 1017 LocaleDistance.shiftDistance(thresholdDistance), favorSubtag, direction); 1018 double distance = LocaleDistance.getDistanceDouble(indexAndDistance); 1019 if (TRACE_MATCHER) { 1020 System.err.printf("LocaleMatcher distance(desired=%s, supported=%s)=%g\n", 1021 String.valueOf(desired), String.valueOf(supported), distance); 1022 } 1023 return (100.0 - distance) / 100.0; 1024 } 1025 1026 /** 1027 * Partially canonicalizes a locale (language). Note that for now, it is canonicalizing 1028 * according to CLDR conventions (he vs iw, etc), since that is what is needed 1029 * for likelySubtags. 1030 * 1031 * <p>Currently, this is a much simpler canonicalization than what the ULocale class does: 1032 * The language/script/region subtags are each mapped separately, ignoring the other subtags. 1033 * If none of these change, then the input locale is returned. 1034 * Otherwise a new ULocale with only those subtags is returned, removing variants and extensions. 1035 * 1036 * @param locale language/locale code 1037 * @return ULocale with remapped subtags. 1038 */ canonicalize(ULocale locale)1039 public ULocale canonicalize(ULocale locale) { 1040 return XLikelySubtags.INSTANCE.canonicalize(locale); 1041 } 1042 1043 /** 1044 * {@inheritDoc} 1045 */ 1046 @Override toString()1047 public String toString() { 1048 StringBuilder s = new StringBuilder().append("{LocaleMatcher"); 1049 // Supported languages in the order that we try to match them. 1050 if (supportedLSRsLength > 0) { 1051 s.append(" supportedLSRs={").append(supportedLSRs[0]); 1052 for (int i = 1; i < supportedLSRsLength; ++i) { 1053 s.append(", ").append(supportedLSRs[i]); 1054 } 1055 s.append('}'); 1056 } 1057 s.append(" default=").append(defaultULocale); 1058 if (favorSubtag != null) { 1059 s.append(" favor=").append(favorSubtag); 1060 } 1061 if (direction != null) { 1062 s.append(" direction=").append(direction); 1063 } 1064 if (thresholdDistance >= 0) { 1065 s.append(String.format(" threshold=%d", thresholdDistance)); 1066 } 1067 s.append(String.format(" demotion=%d", demotionPerDesiredLocale)); 1068 return s.append('}').toString(); 1069 } 1070 } 1071