606 lines
26 KiB
Java
606 lines
26 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
// © 2017 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
package android.icu.impl.locale;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.Map;
|
|
import java.util.MissingResourceException;
|
|
import java.util.Set;
|
|
import java.util.TreeMap;
|
|
|
|
import android.icu.impl.ICUData;
|
|
import android.icu.impl.ICUResourceBundle;
|
|
import android.icu.impl.UResource;
|
|
import android.icu.util.BytesTrie;
|
|
import android.icu.util.LocaleMatcher;
|
|
import android.icu.util.LocaleMatcher.FavorSubtag;
|
|
import android.icu.util.ULocale;
|
|
|
|
/**
|
|
* Offline-built data for LocaleMatcher.
|
|
* Mostly but not only the data for mapping locales to their maximized forms.
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public class LocaleDistance {
|
|
/**
|
|
* Bit flag used on the last character of a subtag in the trie.
|
|
* Must be set consistently by the builder and the lookup code.
|
|
*/
|
|
public static final int END_OF_SUBTAG = 0x80;
|
|
/** Distance value bit flag, set by the builder. */
|
|
public static final int DISTANCE_SKIP_SCRIPT = 0x80;
|
|
/** Distance value bit flag, set by trieNext(). */
|
|
private static final int DISTANCE_IS_FINAL = 0x100;
|
|
private static final int DISTANCE_IS_FINAL_OR_SKIP_SCRIPT =
|
|
DISTANCE_IS_FINAL | DISTANCE_SKIP_SCRIPT;
|
|
|
|
// The distance is shifted left to gain some fraction bits.
|
|
private static final int DISTANCE_SHIFT = 3;
|
|
private static final int DISTANCE_FRACTION_MASK = 7;
|
|
// 7 bits for 0..100
|
|
private static final int DISTANCE_INT_SHIFT = 7;
|
|
private static final int INDEX_SHIFT = DISTANCE_INT_SHIFT + DISTANCE_SHIFT;
|
|
private static final int DISTANCE_MASK = 0x3ff;
|
|
// vate static final int MAX_INDEX = 0x1fffff; // avoids sign bit
|
|
private static final int INDEX_NEG_1 = 0xfffffc00;
|
|
|
|
// Indexes into array of distances.
|
|
public static final int IX_DEF_LANG_DISTANCE = 0;
|
|
public static final int IX_DEF_SCRIPT_DISTANCE = 1;
|
|
public static final int IX_DEF_REGION_DISTANCE = 2;
|
|
public static final int IX_MIN_REGION_DISTANCE = 3;
|
|
public static final int IX_LIMIT = 4;
|
|
private static final int ABOVE_THRESHOLD = 100;
|
|
|
|
private static final boolean DEBUG_OUTPUT = LSR.DEBUG_OUTPUT;
|
|
|
|
// The trie maps each dlang+slang+dscript+sscript+dregion+sregion
|
|
// (encoded in ASCII with bit 7 set on the last character of each subtag) to a distance.
|
|
// There is also a trie value for each subsequence of whole subtags.
|
|
// One '*' is used for a (desired, supported) pair of "und", "Zzzz"/"", or "ZZ"/"".
|
|
private final BytesTrie trie;
|
|
|
|
/**
|
|
* Maps each region to zero or more single-character partitions.
|
|
*/
|
|
private final byte[] regionToPartitionsIndex;
|
|
private final String[] partitionArrays;
|
|
|
|
/**
|
|
* Used to get the paradigm region for a cluster, if there is one.
|
|
*/
|
|
private final Set<LSR> paradigmLSRs;
|
|
|
|
private final int defaultLanguageDistance;
|
|
private final int defaultScriptDistance;
|
|
private final int defaultRegionDistance;
|
|
private final int minRegionDistance;
|
|
private final int defaultDemotionPerDesiredLocale;
|
|
|
|
public static final int shiftDistance(int distance) {
|
|
return distance << DISTANCE_SHIFT;
|
|
}
|
|
|
|
public static final int getShiftedDistance(int indexAndDistance) {
|
|
return indexAndDistance & DISTANCE_MASK;
|
|
}
|
|
|
|
public static final double getDistanceDouble(int indexAndDistance) {
|
|
double shiftedDistance = getShiftedDistance(indexAndDistance);
|
|
return shiftedDistance / (1 << DISTANCE_SHIFT);
|
|
}
|
|
|
|
public static final int getDistanceFloor(int indexAndDistance) {
|
|
return (indexAndDistance & DISTANCE_MASK) >> DISTANCE_SHIFT;
|
|
}
|
|
|
|
public static final int getIndex(int indexAndDistance) {
|
|
assert indexAndDistance >= 0;
|
|
return indexAndDistance >> INDEX_SHIFT;
|
|
}
|
|
|
|
// VisibleForTesting
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public static final class Data {
|
|
public byte[] trie;
|
|
public byte[] regionToPartitionsIndex;
|
|
public String[] partitionArrays;
|
|
public Set<LSR> paradigmLSRs;
|
|
public int[] distances;
|
|
|
|
public Data(byte[] trie,
|
|
byte[] regionToPartitionsIndex, String[] partitionArrays,
|
|
Set<LSR> paradigmLSRs, int[] distances) {
|
|
this.trie = trie;
|
|
this.regionToPartitionsIndex = regionToPartitionsIndex;
|
|
this.partitionArrays = partitionArrays;
|
|
this.paradigmLSRs = paradigmLSRs;
|
|
this.distances = distances;
|
|
}
|
|
|
|
private static UResource.Value getValue(UResource.Table table,
|
|
String key, UResource.Value value) {
|
|
if (!table.findValue(key, value)) {
|
|
throw new MissingResourceException(
|
|
"langInfo.res missing data", "", "match/" + key);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// VisibleForTesting
|
|
public static Data load() throws MissingResourceException {
|
|
ICUResourceBundle langInfo = ICUResourceBundle.getBundleInstance(
|
|
ICUData.ICU_BASE_NAME, "langInfo",
|
|
ICUResourceBundle.ICU_DATA_CLASS_LOADER, ICUResourceBundle.OpenType.DIRECT);
|
|
UResource.Value value = langInfo.getValueWithFallback("match");
|
|
UResource.Table matchTable = value.getTable();
|
|
|
|
ByteBuffer buffer = getValue(matchTable, "trie", value).getBinary();
|
|
byte[] trie = new byte[buffer.remaining()];
|
|
buffer.get(trie);
|
|
|
|
buffer = getValue(matchTable, "regionToPartitions", value).getBinary();
|
|
byte[] regionToPartitions = new byte[buffer.remaining()];
|
|
buffer.get(regionToPartitions);
|
|
if (regionToPartitions.length < LSR.REGION_INDEX_LIMIT) {
|
|
throw new MissingResourceException(
|
|
"langInfo.res binary data too short", "", "match/regionToPartitions");
|
|
}
|
|
|
|
String[] partitions = getValue(matchTable, "partitions", value).getStringArray();
|
|
|
|
Set<LSR> paradigmLSRs;
|
|
if (matchTable.findValue("paradigmnum", value)) {
|
|
String[] m49 = getValue(langInfo.getValueWithFallback("likely").getTable(),
|
|
"m49", value).getStringArray();
|
|
LSR[] paradigms = LSR.decodeInts(getValue(matchTable, "paradigmnum", value).getIntVector(), m49);
|
|
// LinkedHashSet for stable order; otherwise a unit test is flaky.
|
|
paradigmLSRs = new LinkedHashSet<LSR>(Arrays.asList(paradigms));
|
|
} else {
|
|
paradigmLSRs = Collections.emptySet();
|
|
}
|
|
|
|
int[] distances = getValue(matchTable, "distances", value).getIntVector();
|
|
if (distances.length < IX_LIMIT) {
|
|
throw new MissingResourceException(
|
|
"langInfo.res intvector too short", "", "match/distances");
|
|
}
|
|
|
|
return new Data(trie, regionToPartitions, partitions, paradigmLSRs, distances);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object other) {
|
|
if (this == other) { return true; }
|
|
if (other == null || !getClass().equals(other.getClass())) { return false; }
|
|
Data od = (Data)other;
|
|
return Arrays.equals(trie, od.trie) &&
|
|
Arrays.equals(regionToPartitionsIndex, od.regionToPartitionsIndex) &&
|
|
Arrays.equals(partitionArrays, od.partitionArrays) &&
|
|
paradigmLSRs.equals(od.paradigmLSRs) &&
|
|
Arrays.equals(distances, od.distances);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() { // unused; silence ErrorProne
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// VisibleForTesting
|
|
public static final LocaleDistance INSTANCE = new LocaleDistance(Data.load());
|
|
|
|
private LocaleDistance(Data data) {
|
|
trie = new BytesTrie(data.trie, 0);
|
|
regionToPartitionsIndex = data.regionToPartitionsIndex;
|
|
partitionArrays = data.partitionArrays;
|
|
paradigmLSRs = data.paradigmLSRs;
|
|
defaultLanguageDistance = data.distances[IX_DEF_LANG_DISTANCE];
|
|
defaultScriptDistance = data.distances[IX_DEF_SCRIPT_DISTANCE];
|
|
defaultRegionDistance = data.distances[IX_DEF_REGION_DISTANCE];
|
|
minRegionDistance = data.distances[IX_MIN_REGION_DISTANCE];
|
|
|
|
// For the default demotion value, use the
|
|
// default region distance between unrelated Englishes.
|
|
// Thus, unless demotion is turned off,
|
|
// a mere region difference for one desired locale
|
|
// is as good as a perfect match for the next following desired locale.
|
|
// As of CLDR 36, we have <languageMatch desired="en_*_*" supported="en_*_*" distance="5"/>.
|
|
LSR en = new LSR("en", "Latn", "US", LSR.EXPLICIT_LSR);
|
|
LSR enGB = new LSR("en", "Latn", "GB", LSR.EXPLICIT_LSR);
|
|
int indexAndDistance = getBestIndexAndDistance(en, new LSR[] { enGB }, 1,
|
|
shiftDistance(50), FavorSubtag.LANGUAGE, LocaleMatcher.Direction.WITH_ONE_WAY);
|
|
defaultDemotionPerDesiredLocale = getDistanceFloor(indexAndDistance);
|
|
|
|
if (DEBUG_OUTPUT) {
|
|
System.out.println("*** locale distance");
|
|
System.out.println("defaultLanguageDistance=" + defaultLanguageDistance);
|
|
System.out.println("defaultScriptDistance=" + defaultScriptDistance);
|
|
System.out.println("defaultRegionDistance=" + defaultRegionDistance);
|
|
testOnlyPrintDistanceTable();
|
|
}
|
|
}
|
|
|
|
// VisibleForTesting
|
|
public int testOnlyDistance(ULocale desired, ULocale supported,
|
|
int threshold, FavorSubtag favorSubtag) {
|
|
LSR supportedLSR = LikelySubtags.INSTANCE.makeMaximizedLsrFrom(supported, false);
|
|
LSR desiredLSR = LikelySubtags.INSTANCE.makeMaximizedLsrFrom(desired, false);
|
|
int indexAndDistance = getBestIndexAndDistance(desiredLSR, new LSR[] { supportedLSR }, 1,
|
|
shiftDistance(threshold), favorSubtag, LocaleMatcher.Direction.WITH_ONE_WAY);
|
|
return getDistanceFloor(indexAndDistance);
|
|
}
|
|
|
|
/**
|
|
* Finds the supported LSR with the smallest distance from the desired one.
|
|
* Equivalent LSR subtags must be normalized into a canonical form.
|
|
*
|
|
* <p>Returns the index of the lowest-distance supported LSR in the high bits
|
|
* (negative if none has a distance below the threshold),
|
|
* and its distance (0..ABOVE_THRESHOLD) in the low bits.
|
|
*/
|
|
public int getBestIndexAndDistance(LSR desired, LSR[] supportedLSRs, int supportedLSRsLength,
|
|
int shiftedThreshold, FavorSubtag favorSubtag, LocaleMatcher.Direction direction) {
|
|
BytesTrie iter = new BytesTrie(trie);
|
|
// Look up the desired language only once for all supported LSRs.
|
|
// Its "distance" is either a match point value of 0, or a non-match negative value.
|
|
// Note: The data builder verifies that there are no <*, supported> or <desired, *> rules.
|
|
int desLangDistance = trieNext(iter, desired.language, false);
|
|
long desLangState = desLangDistance >= 0 && supportedLSRsLength > 1 ? iter.getState64() : 0;
|
|
// Index of the supported LSR with the lowest distance.
|
|
int bestIndex = -1;
|
|
// Cached lookup info from LikelySubtags.compareLikely().
|
|
int bestLikelyInfo = -1;
|
|
for (int slIndex = 0; slIndex < supportedLSRsLength; ++slIndex) {
|
|
LSR supported = supportedLSRs[slIndex];
|
|
boolean star = false;
|
|
int distance = desLangDistance;
|
|
if (distance >= 0) {
|
|
assert (distance & DISTANCE_IS_FINAL) == 0;
|
|
if (slIndex != 0) {
|
|
iter.resetToState64(desLangState);
|
|
}
|
|
distance = trieNext(iter, supported.language, true);
|
|
}
|
|
// Note: The data builder verifies that there are no rules with "any" (*) language and
|
|
// real (non *) script or region subtags.
|
|
// This means that if the lookup for either language fails we can use
|
|
// the default distances without further lookups.
|
|
int flags;
|
|
if (distance >= 0) {
|
|
flags = distance & DISTANCE_IS_FINAL_OR_SKIP_SCRIPT;
|
|
distance &= ~DISTANCE_IS_FINAL_OR_SKIP_SCRIPT;
|
|
} else { // <*, *>
|
|
if (desired.language.equals(supported.language)) {
|
|
distance = 0;
|
|
} else {
|
|
distance = defaultLanguageDistance;
|
|
}
|
|
flags = 0;
|
|
star = true;
|
|
}
|
|
assert 0 <= distance && distance <= 100;
|
|
// Round up the shifted threshold (if fraction bits are not 0)
|
|
// for comparison with un-shifted distances until we need fraction bits.
|
|
// (If we simply shifted non-zero fraction bits away, then we might ignore a language
|
|
// when it's really still a micro distance below the threshold.)
|
|
int roundedThreshold = (shiftedThreshold + DISTANCE_FRACTION_MASK) >> DISTANCE_SHIFT;
|
|
// We implement "favor subtag" by reducing the language subtag distance
|
|
// (unscientifically reducing it to a quarter of the normal value),
|
|
// so that the script distance is relatively more important.
|
|
// For example, given a default language distance of 80, we reduce it to 20,
|
|
// which is below the default threshold of 50, which is the default script distance.
|
|
if (favorSubtag == FavorSubtag.SCRIPT) {
|
|
distance >>= 2;
|
|
}
|
|
// Let distance == roundedThreshold pass until the tie-breaker logic
|
|
// at the end of the loop.
|
|
if (distance > roundedThreshold) {
|
|
continue;
|
|
}
|
|
|
|
int scriptDistance;
|
|
if (star || flags != 0) {
|
|
if (desired.script.equals(supported.script)) {
|
|
scriptDistance = 0;
|
|
} else {
|
|
scriptDistance = defaultScriptDistance;
|
|
}
|
|
} else {
|
|
scriptDistance = getDesSuppScriptDistance(iter, iter.getState64(),
|
|
desired.script, supported.script);
|
|
flags = scriptDistance & DISTANCE_IS_FINAL;
|
|
scriptDistance &= ~DISTANCE_IS_FINAL;
|
|
}
|
|
distance += scriptDistance;
|
|
if (distance > roundedThreshold) {
|
|
continue;
|
|
}
|
|
|
|
if (desired.region.equals(supported.region)) {
|
|
// regionDistance = 0
|
|
} else if (star || (flags & DISTANCE_IS_FINAL) != 0) {
|
|
distance += defaultRegionDistance;
|
|
} else {
|
|
int remainingThreshold = roundedThreshold - distance;
|
|
if (minRegionDistance > remainingThreshold) {
|
|
continue;
|
|
}
|
|
|
|
// From here on we know the regions are not equal.
|
|
// Map each region to zero or more partitions. (zero = one non-matching string)
|
|
// (Each array of single-character partition strings is encoded as one string.)
|
|
// If either side has more than one, then we find the maximum distance.
|
|
// This could be optimized by adding some more structure, but probably not worth it.
|
|
distance += getRegionPartitionsDistance(
|
|
iter, iter.getState64(),
|
|
partitionsForRegion(desired),
|
|
partitionsForRegion(supported),
|
|
remainingThreshold);
|
|
}
|
|
int shiftedDistance = shiftDistance(distance);
|
|
if (shiftedDistance == 0) {
|
|
// Distinguish between equivalent but originally unequal locales via an
|
|
// additional micro distance.
|
|
shiftedDistance |= (desired.flags ^ supported.flags);
|
|
if (shiftedDistance < shiftedThreshold) {
|
|
if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
|
|
// Is there also a match when we swap desired/supported?
|
|
isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
|
|
if (shiftedDistance == 0) {
|
|
return slIndex << INDEX_SHIFT;
|
|
}
|
|
bestIndex = slIndex;
|
|
shiftedThreshold = shiftedDistance;
|
|
bestLikelyInfo = -1;
|
|
}
|
|
}
|
|
} else {
|
|
if (shiftedDistance < shiftedThreshold) {
|
|
if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
|
|
// Is there also a match when we swap desired/supported?
|
|
isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
|
|
bestIndex = slIndex;
|
|
shiftedThreshold = shiftedDistance;
|
|
bestLikelyInfo = -1;
|
|
}
|
|
} else if (shiftedDistance == shiftedThreshold && bestIndex >= 0) {
|
|
if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
|
|
// Is there also a match when we swap desired/supported?
|
|
isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
|
|
bestLikelyInfo = LikelySubtags.INSTANCE.compareLikely(
|
|
supported, supportedLSRs[bestIndex], bestLikelyInfo);
|
|
if ((bestLikelyInfo & 1) != 0) {
|
|
// This supported locale matches as well as the previous best match,
|
|
// and neither matches perfectly,
|
|
// but this one is "more likely" (has more-default subtags).
|
|
bestIndex = slIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bestIndex >= 0 ?
|
|
(bestIndex << INDEX_SHIFT) | shiftedThreshold :
|
|
INDEX_NEG_1 | shiftDistance(ABOVE_THRESHOLD);
|
|
}
|
|
|
|
private boolean isMatch(LSR desired, LSR supported,
|
|
int shiftedThreshold, FavorSubtag favorSubtag) {
|
|
return getBestIndexAndDistance(
|
|
desired, new LSR[] { supported }, 1,
|
|
shiftedThreshold, favorSubtag, null) >= 0;
|
|
}
|
|
|
|
private static final int getDesSuppScriptDistance(BytesTrie iter, long startState,
|
|
String desired, String supported) {
|
|
// Note: The data builder verifies that there are no <*, supported> or <desired, *> rules.
|
|
int distance = trieNext(iter, desired, false);
|
|
if (distance >= 0) {
|
|
distance = trieNext(iter, supported, true);
|
|
}
|
|
if (distance < 0) {
|
|
BytesTrie.Result result = iter.resetToState64(startState).next('*'); // <*, *>
|
|
assert result.hasValue();
|
|
if (desired.equals(supported)) {
|
|
distance = 0; // same script
|
|
} else {
|
|
distance = iter.getValue();
|
|
assert distance >= 0;
|
|
}
|
|
if (result == BytesTrie.Result.FINAL_VALUE) {
|
|
distance |= DISTANCE_IS_FINAL;
|
|
}
|
|
}
|
|
return distance;
|
|
}
|
|
|
|
private static final int getRegionPartitionsDistance(BytesTrie iter, long startState,
|
|
String desiredPartitions, String supportedPartitions, int threshold) {
|
|
int desLength = desiredPartitions.length();
|
|
int suppLength = supportedPartitions.length();
|
|
if (desLength == 1 && suppLength == 1) {
|
|
// Fastpath for single desired/supported partitions.
|
|
BytesTrie.Result result = iter.next(desiredPartitions.charAt(0) | END_OF_SUBTAG);
|
|
if (result.hasNext()) {
|
|
result = iter.next(supportedPartitions.charAt(0) | END_OF_SUBTAG);
|
|
if (result.hasValue()) {
|
|
return iter.getValue();
|
|
}
|
|
}
|
|
return getFallbackRegionDistance(iter, startState);
|
|
}
|
|
|
|
int regionDistance = 0;
|
|
// Fall back to * only once, not for each pair of partition strings.
|
|
boolean star = false;
|
|
for (int di = 0;;) {
|
|
// Look up each desired-partition string only once,
|
|
// not for each (desired, supported) pair.
|
|
BytesTrie.Result result = iter.next(desiredPartitions.charAt(di++) | END_OF_SUBTAG);
|
|
if (result.hasNext()) {
|
|
long desState = suppLength > 1 ? iter.getState64() : 0;
|
|
for (int si = 0;;) {
|
|
result = iter.next(supportedPartitions.charAt(si++) | END_OF_SUBTAG);
|
|
int d;
|
|
if (result.hasValue()) {
|
|
d = iter.getValue();
|
|
} else if (star) {
|
|
d = 0;
|
|
} else {
|
|
d = getFallbackRegionDistance(iter, startState);
|
|
star = true;
|
|
}
|
|
if (d > threshold) {
|
|
return d;
|
|
} else if (regionDistance < d) {
|
|
regionDistance = d;
|
|
}
|
|
if (si < suppLength) {
|
|
iter.resetToState64(desState);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
} else if (!star) {
|
|
int d = getFallbackRegionDistance(iter, startState);
|
|
if (d > threshold) {
|
|
return d;
|
|
} else if (regionDistance < d) {
|
|
regionDistance = d;
|
|
}
|
|
star = true;
|
|
}
|
|
if (di < desLength) {
|
|
iter.resetToState64(startState);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return regionDistance;
|
|
}
|
|
|
|
private static final int getFallbackRegionDistance(BytesTrie iter, long startState) {
|
|
BytesTrie.Result result = iter.resetToState64(startState).next('*'); // <*, *>
|
|
assert result.hasValue();
|
|
int distance = iter.getValue();
|
|
assert distance >= 0;
|
|
return distance;
|
|
}
|
|
|
|
private static final int trieNext(BytesTrie iter, String s, boolean wantValue) {
|
|
if (s.isEmpty()) {
|
|
return -1; // no empty subtags in the distance data
|
|
}
|
|
for (int i = 0, end = s.length() - 1;; ++i) {
|
|
int c = s.charAt(i);
|
|
if (i < end) {
|
|
if (!iter.next(c).hasNext()) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
// last character of this subtag
|
|
BytesTrie.Result result = iter.next(c | END_OF_SUBTAG);
|
|
if (wantValue) {
|
|
if (result.hasValue()) {
|
|
int value = iter.getValue();
|
|
if (result == BytesTrie.Result.FINAL_VALUE) {
|
|
value |= DISTANCE_IS_FINAL;
|
|
}
|
|
return value;
|
|
}
|
|
} else {
|
|
if (result.hasNext()) {
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return testOnlyGetDistanceTable().toString();
|
|
}
|
|
|
|
private String partitionsForRegion(LSR lsr) {
|
|
// ill-formed region -> one non-matching string
|
|
int pIndex = regionToPartitionsIndex[lsr.regionIndex];
|
|
return partitionArrays[pIndex];
|
|
}
|
|
|
|
public boolean isParadigmLSR(LSR lsr) {
|
|
// Linear search for a very short list (length 6 as of 2019),
|
|
// because we look for equivalence not equality, and
|
|
// HashSet does not support customizing equality.
|
|
// If there are many paradigm LSRs we should revisit this.
|
|
assert paradigmLSRs.size() <= 15;
|
|
for (LSR plsr : paradigmLSRs) {
|
|
if (lsr.isEquivalentTo(plsr)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// VisibleForTesting
|
|
public int getDefaultScriptDistance() {
|
|
return defaultScriptDistance;
|
|
}
|
|
|
|
int getDefaultRegionDistance() {
|
|
return defaultRegionDistance;
|
|
}
|
|
|
|
public int getDefaultDemotionPerDesiredLocale() {
|
|
return defaultDemotionPerDesiredLocale;
|
|
}
|
|
|
|
// VisibleForTesting
|
|
public Map<String, Integer> testOnlyGetDistanceTable() {
|
|
Map<String, Integer> map = new TreeMap<>();
|
|
StringBuilder sb = new StringBuilder();
|
|
for (BytesTrie.Entry entry : trie) {
|
|
sb.setLength(0);
|
|
int length = entry.bytesLength();
|
|
for (int i = 0; i < length; ++i) {
|
|
byte b = entry.byteAt(i);
|
|
if (b == '*') {
|
|
// One * represents a (desired, supported) = (ANY, ANY) pair.
|
|
sb.append("*-*-");
|
|
} else {
|
|
if (b >= 0) {
|
|
sb.append((char) b);
|
|
} else { // end of subtag
|
|
sb.append((char) (b & 0x7f)).append('-');
|
|
}
|
|
}
|
|
}
|
|
assert sb.length() > 0 && sb.charAt(sb.length() - 1) == '-';
|
|
sb.setLength(sb.length() - 1);
|
|
map.put(sb.toString(), entry.value);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
// VisibleForTesting
|
|
public void testOnlyPrintDistanceTable() {
|
|
for (Map.Entry<String, Integer> mapping : testOnlyGetDistanceTable().entrySet()) {
|
|
String suffix = "";
|
|
int value = mapping.getValue();
|
|
if ((value & DISTANCE_SKIP_SCRIPT) != 0) {
|
|
value &= ~DISTANCE_SKIP_SCRIPT;
|
|
suffix = " skip script";
|
|
}
|
|
System.out.println(mapping.getKey() + '=' + value + suffix);
|
|
}
|
|
}
|
|
}
|