• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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