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