• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2017 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 package ohos.global.icu.impl.locale;
5 
6 import java.nio.ByteBuffer;
7 import java.util.Arrays;
8 import java.util.Collections;
9 import java.util.LinkedHashSet;
10 import java.util.Map;
11 import java.util.MissingResourceException;
12 import java.util.Set;
13 import java.util.TreeMap;
14 
15 import ohos.global.icu.impl.ICUData;
16 import ohos.global.icu.impl.ICUResourceBundle;
17 import ohos.global.icu.impl.UResource;
18 import ohos.global.icu.util.BytesTrie;
19 import ohos.global.icu.util.LocaleMatcher;
20 import ohos.global.icu.util.LocaleMatcher.FavorSubtag;
21 import ohos.global.icu.util.ULocale;
22 
23 /**
24  * Offline-built data for LocaleMatcher.
25  * Mostly but not only the data for mapping locales to their maximized forms.
26  * @hide exposed on OHOS
27  */
28 public class LocaleDistance {
29     /**
30      * Bit flag used on the last character of a subtag in the trie.
31      * Must be set consistently by the builder and the lookup code.
32      */
33     public static final int END_OF_SUBTAG = 0x80;
34     /** Distance value bit flag, set by the builder. */
35     public static final int DISTANCE_SKIP_SCRIPT = 0x80;
36     /** Distance value bit flag, set by trieNext(). */
37     private static final int DISTANCE_IS_FINAL = 0x100;
38     private static final int DISTANCE_IS_FINAL_OR_SKIP_SCRIPT =
39             DISTANCE_IS_FINAL | DISTANCE_SKIP_SCRIPT;
40 
41     // The distance is shifted left to gain some fraction bits.
42     private static final int DISTANCE_SHIFT = 3;
43     private static final int DISTANCE_FRACTION_MASK = 7;
44     // 7 bits for 0..100
45     private static final int DISTANCE_INT_SHIFT = 7;
46     private static final int INDEX_SHIFT = DISTANCE_INT_SHIFT + DISTANCE_SHIFT;
47     private static final int DISTANCE_MASK = 0x3ff;
48     // vate static final int MAX_INDEX = 0x1fffff;  // avoids sign bit
49     private static final int INDEX_NEG_1 = 0xfffffc00;
50 
51     // Indexes into array of distances.
52     public static final int IX_DEF_LANG_DISTANCE = 0;
53     public static final int IX_DEF_SCRIPT_DISTANCE = 1;
54     public static final int IX_DEF_REGION_DISTANCE = 2;
55     public static final int IX_MIN_REGION_DISTANCE = 3;
56     public static final int IX_LIMIT = 4;
57     private static final int ABOVE_THRESHOLD = 100;
58 
59     private static final boolean DEBUG_OUTPUT = LSR.DEBUG_OUTPUT;
60 
61     // The trie maps each dlang+slang+dscript+sscript+dregion+sregion
62     // (encoded in ASCII with bit 7 set on the last character of each subtag) to a distance.
63     // There is also a trie value for each subsequence of whole subtags.
64     // One '*' is used for a (desired, supported) pair of "und", "Zzzz"/"", or "ZZ"/"".
65     private final BytesTrie trie;
66 
67     /**
68      * Maps each region to zero or more single-character partitions.
69      */
70     private final byte[] regionToPartitionsIndex;
71     private final String[] partitionArrays;
72 
73     /**
74      * Used to get the paradigm region for a cluster, if there is one.
75      */
76     private final Set<LSR> paradigmLSRs;
77 
78     private final int defaultLanguageDistance;
79     private final int defaultScriptDistance;
80     private final int defaultRegionDistance;
81     private final int minRegionDistance;
82     private final int defaultDemotionPerDesiredLocale;
83 
shiftDistance(int distance)84     public static final int shiftDistance(int distance) {
85         return distance << DISTANCE_SHIFT;
86     }
87 
getShiftedDistance(int indexAndDistance)88     public static final int getShiftedDistance(int indexAndDistance) {
89         return indexAndDistance & DISTANCE_MASK;
90     }
91 
getDistanceDouble(int indexAndDistance)92     public static final double getDistanceDouble(int indexAndDistance) {
93         double shiftedDistance = getShiftedDistance(indexAndDistance);
94         return shiftedDistance / (1 << DISTANCE_SHIFT);
95     }
96 
getDistanceFloor(int indexAndDistance)97     private static final int getDistanceFloor(int indexAndDistance) {
98         return (indexAndDistance & DISTANCE_MASK) >> DISTANCE_SHIFT;
99     }
100 
getIndex(int indexAndDistance)101     public static final int getIndex(int indexAndDistance) {
102         assert indexAndDistance >= 0;
103         return indexAndDistance >> INDEX_SHIFT;
104     }
105 
106     // VisibleForTesting
107     /**
108      * @hide exposed on OHOS
109      */
110     public static final class Data {
111         public byte[] trie;
112         public byte[] regionToPartitionsIndex;
113         public String[] partitionArrays;
114         public Set<LSR> paradigmLSRs;
115         public int[] distances;
116 
Data(byte[] trie, byte[] regionToPartitionsIndex, String[] partitionArrays, Set<LSR> paradigmLSRs, int[] distances)117         public Data(byte[] trie,
118                 byte[] regionToPartitionsIndex, String[] partitionArrays,
119                 Set<LSR> paradigmLSRs, int[] distances) {
120             this.trie = trie;
121             this.regionToPartitionsIndex = regionToPartitionsIndex;
122             this.partitionArrays = partitionArrays;
123             this.paradigmLSRs = paradigmLSRs;
124             this.distances = distances;
125         }
126 
getValue(UResource.Table table, String key, UResource.Value value)127         private static UResource.Value getValue(UResource.Table table,
128                 String key, UResource.Value value) {
129             if (!table.findValue(key, value)) {
130                 throw new MissingResourceException(
131                         "langInfo.res missing data", "", "match/" + key);
132             }
133             return value;
134         }
135 
136         // VisibleForTesting
load()137         public static Data load() throws MissingResourceException {
138             ICUResourceBundle langInfo = ICUResourceBundle.getBundleInstance(
139                     ICUData.ICU_BASE_NAME, "langInfo",
140                     ICUResourceBundle.ICU_DATA_CLASS_LOADER, ICUResourceBundle.OpenType.DIRECT);
141             UResource.Value value = langInfo.getValueWithFallback("match");
142             UResource.Table matchTable = value.getTable();
143 
144             ByteBuffer buffer = getValue(matchTable, "trie", value).getBinary();
145             byte[] trie = new byte[buffer.remaining()];
146             buffer.get(trie);
147 
148             buffer = getValue(matchTable, "regionToPartitions", value).getBinary();
149             byte[] regionToPartitions = new byte[buffer.remaining()];
150             buffer.get(regionToPartitions);
151             if (regionToPartitions.length < LSR.REGION_INDEX_LIMIT) {
152                 throw new MissingResourceException(
153                         "langInfo.res binary data too short", "", "match/regionToPartitions");
154             }
155 
156             String[] partitions = getValue(matchTable, "partitions", value).getStringArray();
157 
158             Set<LSR> paradigmLSRs;
159             if (matchTable.findValue("paradigms", value)) {
160                 String[] paradigms = value.getStringArray();
161                 // LinkedHashSet for stable order; otherwise a unit test is flaky.
162                 paradigmLSRs = new LinkedHashSet<>(paradigms.length / 3);
163                 for (int i = 0; i < paradigms.length; i += 3) {
164                     paradigmLSRs.add(new LSR(paradigms[i], paradigms[i + 1], paradigms[i + 2],
165                             LSR.DONT_CARE_FLAGS));
166                 }
167             } else {
168                 paradigmLSRs = Collections.emptySet();
169             }
170 
171             int[] distances = getValue(matchTable, "distances", value).getIntVector();
172             if (distances.length < IX_LIMIT) {
173                 throw new MissingResourceException(
174                         "langInfo.res intvector too short", "", "match/distances");
175             }
176 
177             return new Data(trie, regionToPartitions, partitions, paradigmLSRs, distances);
178         }
179 
180         @Override
equals(Object other)181         public boolean equals(Object other) {
182             if (this == other) { return true; }
183             if (other == null || !getClass().equals(other.getClass())) { return false; }
184             Data od = (Data)other;
185             return Arrays.equals(trie, od.trie) &&
186                     Arrays.equals(regionToPartitionsIndex, od.regionToPartitionsIndex) &&
187                     Arrays.equals(partitionArrays, od.partitionArrays) &&
188                     paradigmLSRs.equals(od.paradigmLSRs) &&
189                     Arrays.equals(distances, od.distances);
190         }
191 
192         @Override
hashCode()193         public int hashCode() {  // unused; silence ErrorProne
194             return 1;
195         }
196     }
197 
198     // VisibleForTesting
199     public static final LocaleDistance INSTANCE = new LocaleDistance(Data.load());
200 
LocaleDistance(Data data)201     private LocaleDistance(Data data) {
202         trie = new BytesTrie(data.trie, 0);
203         regionToPartitionsIndex = data.regionToPartitionsIndex;
204         partitionArrays = data.partitionArrays;
205         paradigmLSRs = data.paradigmLSRs;
206         defaultLanguageDistance = data.distances[IX_DEF_LANG_DISTANCE];
207         defaultScriptDistance = data.distances[IX_DEF_SCRIPT_DISTANCE];
208         defaultRegionDistance = data.distances[IX_DEF_REGION_DISTANCE];
209         minRegionDistance = data.distances[IX_MIN_REGION_DISTANCE];
210 
211         // For the default demotion value, use the
212         // default region distance between unrelated Englishes.
213         // Thus, unless demotion is turned off,
214         // a mere region difference for one desired locale
215         // is as good as a perfect match for the next following desired locale.
216         // As of CLDR 36, we have <languageMatch desired="en_*_*" supported="en_*_*" distance="5"/>.
217         LSR en = new LSR("en", "Latn", "US", LSR.EXPLICIT_LSR);
218         LSR enGB = new LSR("en", "Latn", "GB", LSR.EXPLICIT_LSR);
219         int indexAndDistance = getBestIndexAndDistance(en, new LSR[] { enGB }, 1,
220                 shiftDistance(50), FavorSubtag.LANGUAGE, LocaleMatcher.Direction.WITH_ONE_WAY);
221         defaultDemotionPerDesiredLocale  = getDistanceFloor(indexAndDistance);
222 
223         if (DEBUG_OUTPUT) {
224             System.out.println("*** locale distance");
225             System.out.println("defaultLanguageDistance=" + defaultLanguageDistance);
226             System.out.println("defaultScriptDistance=" + defaultScriptDistance);
227             System.out.println("defaultRegionDistance=" + defaultRegionDistance);
228             testOnlyPrintDistanceTable();
229         }
230     }
231 
232     // VisibleForTesting
testOnlyDistance(ULocale desired, ULocale supported, int threshold, FavorSubtag favorSubtag)233     public int testOnlyDistance(ULocale desired, ULocale supported,
234             int threshold, FavorSubtag favorSubtag) {
235         LSR supportedLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(supported);
236         LSR desiredLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(desired);
237         int indexAndDistance = getBestIndexAndDistance(desiredLSR, new LSR[] { supportedLSR }, 1,
238                 shiftDistance(threshold), favorSubtag, LocaleMatcher.Direction.WITH_ONE_WAY);
239         return getDistanceFloor(indexAndDistance);
240     }
241 
242     /**
243      * Finds the supported LSR with the smallest distance from the desired one.
244      * Equivalent LSR subtags must be normalized into a canonical form.
245      *
246      * <p>Returns the index of the lowest-distance supported LSR in the high bits
247      * (negative if none has a distance below the threshold),
248      * and its distance (0..ABOVE_THRESHOLD) in the low bits.
249      */
getBestIndexAndDistance(LSR desired, LSR[] supportedLSRs, int supportedLSRsLength, int shiftedThreshold, FavorSubtag favorSubtag, LocaleMatcher.Direction direction)250     public int getBestIndexAndDistance(LSR desired, LSR[] supportedLSRs, int supportedLSRsLength,
251             int shiftedThreshold, FavorSubtag favorSubtag, LocaleMatcher.Direction direction) {
252         BytesTrie iter = new BytesTrie(trie);
253         // Look up the desired language only once for all supported LSRs.
254         // Its "distance" is either a match point value of 0, or a non-match negative value.
255         // Note: The data builder verifies that there are no <*, supported> or <desired, *> rules.
256         int desLangDistance = trieNext(iter, desired.language, false);
257         long desLangState = desLangDistance >= 0 && supportedLSRsLength > 1 ? iter.getState64() : 0;
258         // Index of the supported LSR with the lowest distance.
259         int bestIndex = -1;
260         // Cached lookup info from XLikelySubtags.compareLikely().
261         int bestLikelyInfo = -1;
262         for (int slIndex = 0; slIndex < supportedLSRsLength; ++slIndex) {
263             LSR supported = supportedLSRs[slIndex];
264             boolean star = false;
265             int distance = desLangDistance;
266             if (distance >= 0) {
267                 assert (distance & DISTANCE_IS_FINAL) == 0;
268                 if (slIndex != 0) {
269                     iter.resetToState64(desLangState);
270                 }
271                 distance = trieNext(iter, supported.language, true);
272             }
273             // Note: The data builder verifies that there are no rules with "any" (*) language and
274             // real (non *) script or region subtags.
275             // This means that if the lookup for either language fails we can use
276             // the default distances without further lookups.
277             int flags;
278             if (distance >= 0) {
279                 flags = distance & DISTANCE_IS_FINAL_OR_SKIP_SCRIPT;
280                 distance &= ~DISTANCE_IS_FINAL_OR_SKIP_SCRIPT;
281             } else {  // <*, *>
282                 if (desired.language.equals(supported.language)) {
283                     distance = 0;
284                 } else {
285                     distance = defaultLanguageDistance;
286                 }
287                 flags = 0;
288                 star = true;
289             }
290             assert 0 <= distance && distance <= 100;
291             // Round up the shifted threshold (if fraction bits are not 0)
292             // for comparison with un-shifted distances until we need fraction bits.
293             // (If we simply shifted non-zero fraction bits away, then we might ignore a language
294             // when it's really still a micro distance below the threshold.)
295             int roundedThreshold = (shiftedThreshold + DISTANCE_FRACTION_MASK) >> DISTANCE_SHIFT;
296             // We implement "favor subtag" by reducing the language subtag distance
297             // (unscientifically reducing it to a quarter of the normal value),
298             // so that the script distance is relatively more important.
299             // For example, given a default language distance of 80, we reduce it to 20,
300             // which is below the default threshold of 50, which is the default script distance.
301             if (favorSubtag == FavorSubtag.SCRIPT) {
302                 distance >>= 2;
303             }
304             // Let distance == roundedThreshold pass until the tie-breaker logic
305             // at the end of the loop.
306             if (distance > roundedThreshold) {
307                 continue;
308             }
309 
310             int scriptDistance;
311             if (star || flags != 0) {
312                 if (desired.script.equals(supported.script)) {
313                     scriptDistance = 0;
314                 } else {
315                     scriptDistance = defaultScriptDistance;
316                 }
317             } else {
318                 scriptDistance = getDesSuppScriptDistance(iter, iter.getState64(),
319                         desired.script, supported.script);
320                 flags = scriptDistance & DISTANCE_IS_FINAL;
321                 scriptDistance &= ~DISTANCE_IS_FINAL;
322             }
323             distance += scriptDistance;
324             if (distance > roundedThreshold) {
325                 continue;
326             }
327 
328             if (desired.region.equals(supported.region)) {
329                 // regionDistance = 0
330             } else if (star || (flags & DISTANCE_IS_FINAL) != 0) {
331                 distance += defaultRegionDistance;
332             } else {
333                 int remainingThreshold = roundedThreshold - distance;
334                 if (minRegionDistance > remainingThreshold) {
335                     continue;
336                 }
337 
338                 // From here on we know the regions are not equal.
339                 // Map each region to zero or more partitions. (zero = one non-matching string)
340                 // (Each array of single-character partition strings is encoded as one string.)
341                 // If either side has more than one, then we find the maximum distance.
342                 // This could be optimized by adding some more structure, but probably not worth it.
343                 distance += getRegionPartitionsDistance(
344                         iter, iter.getState64(),
345                         partitionsForRegion(desired),
346                         partitionsForRegion(supported),
347                         remainingThreshold);
348             }
349             int shiftedDistance = shiftDistance(distance);
350             if (shiftedDistance == 0) {
351                 // Distinguish between equivalent but originally unequal locales via an
352                 // additional micro distance.
353                 shiftedDistance |= (desired.flags ^ supported.flags);
354                 if (shiftedDistance < shiftedThreshold) {
355                     if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
356                             // Is there also a match when we swap desired/supported?
357                             isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
358                         if (shiftedDistance == 0) {
359                             return slIndex << INDEX_SHIFT;
360                         }
361                         bestIndex = slIndex;
362                         shiftedThreshold = shiftedDistance;
363                         bestLikelyInfo = -1;
364                     }
365                 }
366             } else {
367                 if (shiftedDistance < shiftedThreshold) {
368                     if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
369                             // Is there also a match when we swap desired/supported?
370                             isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
371                         bestIndex = slIndex;
372                         shiftedThreshold = shiftedDistance;
373                         bestLikelyInfo = -1;
374                     }
375                 } else if (shiftedDistance == shiftedThreshold && bestIndex >= 0) {
376                     if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
377                             // Is there also a match when we swap desired/supported?
378                             isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
379                         bestLikelyInfo = XLikelySubtags.INSTANCE.compareLikely(
380                                 supported, supportedLSRs[bestIndex], bestLikelyInfo);
381                         if ((bestLikelyInfo & 1) != 0) {
382                             // This supported locale matches as well as the previous best match,
383                             // and neither matches perfectly,
384                             // but this one is "more likely" (has more-default subtags).
385                             bestIndex = slIndex;
386                         }
387                     }
388                 }
389             }
390         }
391         return bestIndex >= 0 ?
392                 (bestIndex << INDEX_SHIFT) | shiftedThreshold :
393                 INDEX_NEG_1 | shiftDistance(ABOVE_THRESHOLD);
394     }
395 
isMatch(LSR desired, LSR supported, int shiftedThreshold, FavorSubtag favorSubtag)396     private boolean isMatch(LSR desired, LSR supported,
397             int shiftedThreshold, FavorSubtag favorSubtag) {
398         return getBestIndexAndDistance(
399                 desired, new LSR[] { supported }, 1,
400                 shiftedThreshold, favorSubtag, null) >= 0;
401     }
402 
getDesSuppScriptDistance(BytesTrie iter, long startState, String desired, String supported)403     private static final int getDesSuppScriptDistance(BytesTrie iter, long startState,
404             String desired, String supported) {
405         // Note: The data builder verifies that there are no <*, supported> or <desired, *> rules.
406         int distance = trieNext(iter, desired, false);
407         if (distance >= 0) {
408             distance = trieNext(iter, supported, true);
409         }
410         if (distance < 0) {
411             BytesTrie.Result result = iter.resetToState64(startState).next('*');  // <*, *>
412             assert result.hasValue();
413             if (desired.equals(supported)) {
414                 distance = 0;  // same script
415             } else {
416                 distance = iter.getValue();
417                 assert distance >= 0;
418             }
419             if (result == BytesTrie.Result.FINAL_VALUE) {
420                 distance |= DISTANCE_IS_FINAL;
421             }
422         }
423         return distance;
424     }
425 
getRegionPartitionsDistance(BytesTrie iter, long startState, String desiredPartitions, String supportedPartitions, int threshold)426     private static final int getRegionPartitionsDistance(BytesTrie iter, long startState,
427             String desiredPartitions, String supportedPartitions, int threshold) {
428         int desLength = desiredPartitions.length();
429         int suppLength = supportedPartitions.length();
430         if (desLength == 1 && suppLength == 1) {
431             // Fastpath for single desired/supported partitions.
432             BytesTrie.Result result = iter.next(desiredPartitions.charAt(0) | END_OF_SUBTAG);
433             if (result.hasNext()) {
434                 result = iter.next(supportedPartitions.charAt(0) | END_OF_SUBTAG);
435                 if (result.hasValue()) {
436                     return iter.getValue();
437                 }
438             }
439             return getFallbackRegionDistance(iter, startState);
440         }
441 
442         int regionDistance = 0;
443         // Fall back to * only once, not for each pair of partition strings.
444         boolean star = false;
445         for (int di = 0;;) {
446             // Look up each desired-partition string only once,
447             // not for each (desired, supported) pair.
448             BytesTrie.Result result = iter.next(desiredPartitions.charAt(di++) | END_OF_SUBTAG);
449             if (result.hasNext()) {
450                 long desState = suppLength > 1 ? iter.getState64() : 0;
451                 for (int si = 0;;) {
452                     result = iter.next(supportedPartitions.charAt(si++) | END_OF_SUBTAG);
453                     int d;
454                     if (result.hasValue()) {
455                         d = iter.getValue();
456                     } else if (star) {
457                         d = 0;
458                     } else {
459                         d = getFallbackRegionDistance(iter, startState);
460                         star = true;
461                     }
462                     if (d > threshold) {
463                         return d;
464                     } else if (regionDistance < d) {
465                         regionDistance = d;
466                     }
467                     if (si < suppLength) {
468                         iter.resetToState64(desState);
469                     } else {
470                         break;
471                     }
472                 }
473             } else if (!star) {
474                 int d = getFallbackRegionDistance(iter, startState);
475                 if (d > threshold) {
476                     return d;
477                 } else if (regionDistance < d) {
478                     regionDistance = d;
479                 }
480                 star = true;
481             }
482             if (di < desLength) {
483                 iter.resetToState64(startState);
484             } else {
485                 break;
486             }
487         }
488         return regionDistance;
489     }
490 
getFallbackRegionDistance(BytesTrie iter, long startState)491     private static final int getFallbackRegionDistance(BytesTrie iter, long startState) {
492         BytesTrie.Result result = iter.resetToState64(startState).next('*');  // <*, *>
493         assert result.hasValue();
494         int distance = iter.getValue();
495         assert distance >= 0;
496         return distance;
497     }
498 
trieNext(BytesTrie iter, String s, boolean wantValue)499     private static final int trieNext(BytesTrie iter, String s, boolean wantValue) {
500         if (s.isEmpty()) {
501             return -1;  // no empty subtags in the distance data
502         }
503         for (int i = 0, end = s.length() - 1;; ++i) {
504             int c = s.charAt(i);
505             if (i < end) {
506                 if (!iter.next(c).hasNext()) {
507                     return -1;
508                 }
509             } else {
510                 // last character of this subtag
511                 BytesTrie.Result result = iter.next(c | END_OF_SUBTAG);
512                 if (wantValue) {
513                     if (result.hasValue()) {
514                         int value = iter.getValue();
515                         if (result == BytesTrie.Result.FINAL_VALUE) {
516                             value |= DISTANCE_IS_FINAL;
517                         }
518                         return value;
519                     }
520                 } else {
521                     if (result.hasNext()) {
522                         return 0;
523                     }
524                 }
525                 return -1;
526             }
527         }
528     }
529 
530     @Override
toString()531     public String toString() {
532         return testOnlyGetDistanceTable().toString();
533     }
534 
partitionsForRegion(LSR lsr)535     private String partitionsForRegion(LSR lsr) {
536         // ill-formed region -> one non-matching string
537         int pIndex = regionToPartitionsIndex[lsr.regionIndex];
538         return partitionArrays[pIndex];
539     }
540 
isParadigmLSR(LSR lsr)541     public boolean isParadigmLSR(LSR lsr) {
542         // Linear search for a very short list (length 6 as of 2019),
543         // because we look for equivalence not equality, and
544         // HashSet does not support customizing equality.
545         // If there are many paradigm LSRs we should revisit this.
546         assert paradigmLSRs.size() <= 15;
547         for (LSR plsr : paradigmLSRs) {
548             if (lsr.isEquivalentTo(plsr)) {
549                 return true;
550             }
551         }
552         return false;
553     }
554 
555     // VisibleForTesting
getDefaultScriptDistance()556     public int getDefaultScriptDistance() {
557         return defaultScriptDistance;
558     }
559 
getDefaultRegionDistance()560     int getDefaultRegionDistance() {
561         return defaultRegionDistance;
562     }
563 
getDefaultDemotionPerDesiredLocale()564     public int getDefaultDemotionPerDesiredLocale() {
565         return defaultDemotionPerDesiredLocale;
566     }
567 
568     // VisibleForTesting
testOnlyGetDistanceTable()569     public Map<String, Integer> testOnlyGetDistanceTable() {
570         Map<String, Integer> map = new TreeMap<>();
571         StringBuilder sb = new StringBuilder();
572         for (BytesTrie.Entry entry : trie) {
573             sb.setLength(0);
574             int length = entry.bytesLength();
575             for (int i = 0; i < length; ++i) {
576                 byte b = entry.byteAt(i);
577                 if (b == '*') {
578                     // One * represents a (desired, supported) = (ANY, ANY) pair.
579                     sb.append("*-*-");
580                 } else {
581                     if (b >= 0) {
582                         sb.append((char) b);
583                     } else {  // end of subtag
584                         sb.append((char) (b & 0x7f)).append('-');
585                     }
586                 }
587             }
588             assert sb.length() > 0 && sb.charAt(sb.length() - 1) == '-';
589             sb.setLength(sb.length() - 1);
590             map.put(sb.toString(), entry.value);
591         }
592         return map;
593     }
594 
595     // VisibleForTesting
testOnlyPrintDistanceTable()596     public void testOnlyPrintDistanceTable() {
597         for (Map.Entry<String, Integer> mapping : testOnlyGetDistanceTable().entrySet()) {
598             String suffix = "";
599             int value = mapping.getValue();
600             if ((value & DISTANCE_SKIP_SCRIPT) != 0) {
601                 value &= ~DISTANCE_SKIP_SCRIPT;
602                 suffix = " skip script";
603             }
604             System.out.println(mapping.getKey() + '=' + value + suffix);
605         }
606     }
607 }
608