blob: 488c33af75b327d6dc9b20944c42f5d55ce66081 [file] [log] [blame]
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package android.icu.impl.locale;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import android.icu.impl.ICUResourceBundle;
import android.icu.impl.Row;
import android.icu.impl.Row.R4;
import android.icu.impl.Utility;
import android.icu.impl.locale.XCldrStub.CollectionUtilities;
import android.icu.impl.locale.XCldrStub.ImmutableMap;
import android.icu.impl.locale.XCldrStub.ImmutableMultimap;
import android.icu.impl.locale.XCldrStub.ImmutableSet;
import android.icu.impl.locale.XCldrStub.LinkedHashMultimap;
import android.icu.impl.locale.XCldrStub.Multimap;
import android.icu.impl.locale.XCldrStub.Multimaps;
import android.icu.impl.locale.XCldrStub.Predicate;
import android.icu.impl.locale.XCldrStub.Splitter;
import android.icu.impl.locale.XCldrStub.TreeMultimap;
import android.icu.impl.locale.XLikelySubtags.LSR;
import android.icu.impl.locale.XLocaleDistance.RegionMapper.Builder;
import android.icu.text.LocaleDisplayNames;
import android.icu.util.LocaleMatcher;
import android.icu.util.Output;
import android.icu.util.ULocale;
import android.icu.util.UResourceBundleIterator;
/**
* @hide Only a subset of ICU is exposed in Android
*/
public class XLocaleDistance {
static final boolean PRINT_OVERRIDES = false;
public static final int ABOVE_THRESHOLD = 100;
@Deprecated
public static final String ANY = "�"; // matches any character. Uses value above any subtag.
private static String fixAny(String string) {
return "*".equals(string) ? ANY : string;
}
static final LocaleDisplayNames english = LocaleDisplayNames.getInstance(ULocale.ENGLISH);
private static List<R4<String, String, Integer, Boolean>> xGetLanguageMatcherData() {
List<R4<String, String, Integer, Boolean>> distanceList = new ArrayList<R4<String, String, Integer, Boolean>>();
ICUResourceBundle suppData = LocaleMatcher.getICUSupplementalData();
ICUResourceBundle languageMatchingNew = suppData.findTopLevel("languageMatchingNew");
ICUResourceBundle written = (ICUResourceBundle) languageMatchingNew.get("written");
for(UResourceBundleIterator iter = written.getIterator(); iter.hasNext();) {
ICUResourceBundle item = (ICUResourceBundle) iter.next();
boolean oneway = item.getSize() > 3 && "1".equals(item.getString(3));
distanceList.add(
(R4<String, String, Integer, Boolean>) // note: .freeze returning wrong type, so casting.
Row.of(
item.getString(0),
item.getString(1),
Integer.parseInt(item.getString(2)),
oneway)
.freeze());
}
return Collections.unmodifiableList(distanceList);
}
@SuppressWarnings("unused")
private static Set<String> xGetParadigmLocales() {
ICUResourceBundle suppData = LocaleMatcher.getICUSupplementalData();
ICUResourceBundle languageMatchingInfo = suppData.findTopLevel("languageMatchingInfo");
ICUResourceBundle writtenParadigmLocales = (ICUResourceBundle) languageMatchingInfo.get("written")
.get("paradigmLocales");
// paradigmLocales{ "en", "en-GB",... }
HashSet<String> paradigmLocales = new HashSet<String>(Arrays.asList(writtenParadigmLocales.getStringArray()));
return Collections.unmodifiableSet(paradigmLocales);
}
@SuppressWarnings("unused")
private static Map<String, String> xGetMatchVariables() {
ICUResourceBundle suppData = LocaleMatcher.getICUSupplementalData();
ICUResourceBundle languageMatchingInfo = suppData.findTopLevel("languageMatchingInfo");
ICUResourceBundle writtenMatchVariables = (ICUResourceBundle) languageMatchingInfo.get("written")
.get("matchVariable");
// matchVariable{ americas{"019"} cnsar{"HK+MO"} ...}
HashMap<String,String> matchVariables = new HashMap<String,String>();
for (Enumeration<String> enumer = writtenMatchVariables.getKeys(); enumer.hasMoreElements(); ) {
String key = enumer.nextElement();
matchVariables.put(key, writtenMatchVariables.getString(key));
}
return Collections.unmodifiableMap(matchVariables);
}
private static Multimap<String, String> xGetContainment() {
TreeMultimap<String,String> containment = TreeMultimap.create();
containment
.putAll("001", "019", "002", "150", "142", "009")
.putAll("011", "BF", "BJ", "CI", "CV", "GH", "GM", "GN", "GW", "LR", "ML", "MR", "NE", "NG", "SH", "SL", "SN", "TG")
.putAll("013", "BZ", "CR", "GT", "HN", "MX", "NI", "PA", "SV")
.putAll("014", "BI", "DJ", "ER", "ET", "KE", "KM", "MG", "MU", "MW", "MZ", "RE", "RW", "SC", "SO", "SS", "TZ", "UG", "YT", "ZM", "ZW")
.putAll("142", "145", "143", "030", "034", "035")
.putAll("143", "TM", "TJ", "KG", "KZ", "UZ")
.putAll("145", "AE", "AM", "AZ", "BH", "CY", "GE", "IL", "IQ", "JO", "KW", "LB", "OM", "PS", "QA", "SA", "SY", "TR", "YE", "NT", "YD")
.putAll("015", "DZ", "EG", "EH", "LY", "MA", "SD", "TN", "EA", "IC")
.putAll("150", "154", "155", "151", "039")
.putAll("151", "BG", "BY", "CZ", "HU", "MD", "PL", "RO", "RU", "SK", "UA", "SU")
.putAll("154", "GG", "IM", "JE", "AX", "DK", "EE", "FI", "FO", "GB", "IE", "IS", "LT", "LV", "NO", "SE", "SJ")
.putAll("155", "AT", "BE", "CH", "DE", "FR", "LI", "LU", "MC", "NL", "DD", "FX")
.putAll("017", "AO", "CD", "CF", "CG", "CM", "GA", "GQ", "ST", "TD", "ZR")
.putAll("018", "BW", "LS", "NA", "SZ", "ZA")
.putAll("019", "021", "013", "029", "005", "003", "419")
.putAll("002", "015", "011", "017", "014", "018")
.putAll("021", "BM", "CA", "GL", "PM", "US")
.putAll("029", "AG", "AI", "AW", "BB", "BL", "BQ", "BS", "CU", "CW", "DM", "DO", "GD", "GP", "HT", "JM", "KN", "KY", "LC", "MF", "MQ", "MS", "PR", "SX", "TC", "TT", "VC", "VG", "VI", "AN")
.putAll("003", "021", "013", "029")
.putAll("030", "CN", "HK", "JP", "KP", "KR", "MN", "MO", "TW")
.putAll("035", "BN", "ID", "KH", "LA", "MM", "MY", "PH", "SG", "TH", "TL", "VN", "BU", "TP")
.putAll("039", "AD", "AL", "BA", "ES", "GI", "GR", "HR", "IT", "ME", "MK", "MT", "RS", "PT", "SI", "SM", "VA", "XK", "CS", "YU")
.putAll("419", "013", "029", "005")
.putAll("005", "AR", "BO", "BR", "CL", "CO", "EC", "FK", "GF", "GY", "PE", "PY", "SR", "UY", "VE")
.putAll("053", "AU", "NF", "NZ")
.putAll("054", "FJ", "NC", "PG", "SB", "VU")
.putAll("057", "FM", "GU", "KI", "MH", "MP", "NR", "PW")
.putAll("061", "AS", "CK", "NU", "PF", "PN", "TK", "TO", "TV", "WF", "WS")
.putAll("034", "AF", "BD", "BT", "IN", "IR", "LK", "MV", "NP", "PK")
.putAll("009", "053", "054", "057", "061", "QO")
.putAll("QO", "AQ", "BV", "CC", "CX", "GS", "HM", "IO", "TF", "UM", "AC", "CP", "DG", "TA")
;
//Can't use following, because data from CLDR is discarded
// ICUResourceBundle suppData = LocaleMatcher.getICUSupplementalData();
// UResourceBundle territoryContainment = suppData.get("territoryContainment");
// for (int i = 0 ; i < territoryContainment.getSize(); i++) {
// UResourceBundle mapping = territoryContainment.get(i);
// String parent = mapping.getKey();
// for (int j = 0 ; j < mapping.getSize(); j++) {
// String child = mapping.getString(j);
// containment.put(parent,child);
// System.out.println(parent + " => " + child);
// }
// }
TreeMultimap<String,String> containmentResolved = TreeMultimap.create();
fill("001", containment, containmentResolved);
return ImmutableMultimap.copyOf(containmentResolved);
}
private static Set<String> fill(String region, TreeMultimap<String, String> containment, Multimap<String, String> toAddTo) {
Set<String> contained = containment.get(region);
if (contained == null) {
return Collections.emptySet();
}
toAddTo.putAll(region, contained); // do top level
// then recursively
for (String subregion : contained) {
toAddTo.putAll(region, fill(subregion, containment, toAddTo));
}
return toAddTo.get(region);
}
static final Multimap<String,String> CONTAINER_TO_CONTAINED;
static final Multimap<String,String> CONTAINER_TO_CONTAINED_FINAL;
static {
// Multimap<String, String> containerToContainedTemp = xGetContainment();
// fill(Region.getInstance("001"), containerToContainedTemp);
CONTAINER_TO_CONTAINED = xGetContainment();
Multimap<String, String> containerToFinalContainedBuilder = TreeMultimap.create();
for (Entry<String, Set<String>> entry : CONTAINER_TO_CONTAINED.asMap().entrySet()) {
String container = entry.getKey();
for (String contained : entry.getValue()) {
if (CONTAINER_TO_CONTAINED.get(contained) == null) {
containerToFinalContainedBuilder.put(container, contained);
}
}
}
CONTAINER_TO_CONTAINED_FINAL = ImmutableMultimap.copyOf(containerToFinalContainedBuilder);
}
final static private Set<String> ALL_FINAL_REGIONS = ImmutableSet.copyOf(CONTAINER_TO_CONTAINED_FINAL.get("001"));
// end of data from CLDR
private final DistanceTable languageDesired2Supported;
private final RegionMapper regionMapper;
private final int defaultLanguageDistance;
private final int defaultScriptDistance;
private final int defaultRegionDistance;
@Deprecated
public static abstract class DistanceTable {
abstract int getDistance(String desiredLang, String supportedlang, Output<DistanceTable> table, boolean starEquals);
abstract Set<String> getCloser(int threshold);
abstract String toString(boolean abbreviate);
public DistanceTable compact() {
return this;
}
// public Integer getInternalDistance(String a, String b) {
// return null;
// }
public DistanceNode getInternalNode(String any, String any2) {
return null;
}
public Map<String, Set<String>> getInternalMatches() {
return null;
}
public boolean isEmpty() {
return true;
}
}
@Deprecated
public static class DistanceNode {
final int distance;
public DistanceNode(int distance) {
this.distance = distance;
}
public DistanceTable getDistanceTable() {
return null;
}
@Override
public boolean equals(Object obj) {
return this == obj ||
(obj != null
&& obj.getClass() == this.getClass()
&& distance == ((DistanceNode) obj).distance);
}
@Override
public int hashCode() {
return distance;
}
@Override
public String toString() {
return "\ndistance: " + distance;
}
}
private interface IdMapper<K,V> {
public V toId(K source);
}
static class IdMakerFull<T> implements IdMapper<T,Integer> {
private final Map<T, Integer> objectToInt = new HashMap<T, Integer>();
private final List<T> intToObject = new ArrayList<T>();
final String name; // for debugging
IdMakerFull(String name) {
this.name = name;
}
IdMakerFull() {
this("unnamed");
}
IdMakerFull(String name, T zeroValue) {
this(name);
add(zeroValue);
}
/**
* Return an id, making one if there wasn't one already.
*/
public Integer add(T source) {
Integer result = objectToInt.get(source);
if (result == null) {
Integer newResult = intToObject.size();
objectToInt.put(source, newResult);
intToObject.add(source);
return newResult;
} else {
return result;
}
}
/**
* Return an id, or null if there is none.
*/
@Override
public Integer toId(T source) {
return objectToInt.get(source);
// return value == null ? 0 : value;
}
/**
* Return the object for the id, or null if there is none.
*/
public T fromId(int id) {
return intToObject.get(id);
}
/**
* Return interned object
*/
public T intern(T source) {
return fromId(add(source));
}
public int size() {
return intToObject.size();
}
/**
* Same as add, except if the object didn't have an id, return null;
*/
public Integer getOldAndAdd(T source) {
Integer result = objectToInt.get(source);
if (result == null) {
Integer newResult = intToObject.size();
objectToInt.put(source, newResult);
intToObject.add(source);
}
return result;
}
@Override
public String toString() {
return size() + ": " + intToObject;
}
@Override
public boolean equals(Object obj) {
return this == obj ||
(obj != null
&& obj.getClass() == this.getClass()
&& intToObject.equals(((IdMakerFull<?>) obj).intToObject));
}
@Override
public int hashCode() {
return intToObject.hashCode();
}
}
static class StringDistanceNode extends DistanceNode {
final DistanceTable distanceTable;
public StringDistanceNode(int distance, DistanceTable distanceTable) {
super(distance);
this.distanceTable = distanceTable;
}
@Override
public boolean equals(Object obj) {
StringDistanceNode other;
return this == obj ||
(obj != null
&& obj.getClass() == this.getClass()
&& distance == (other = (StringDistanceNode) obj).distance
&& Utility.equals(distanceTable, other.distanceTable)
&& super.equals(other));
}
@Override
public int hashCode() {
return distance ^ Utility.hashCode(distanceTable);
}
StringDistanceNode(int distance) {
this(distance, new StringDistanceTable());
}
public void addSubtables(String desiredSub, String supportedSub, CopyIfEmpty r) {
((StringDistanceTable) distanceTable).addSubtables(desiredSub, supportedSub, r);
}
@Override
public String toString() {
return "distance: " + distance + "\n" + distanceTable;
}
public void copyTables(StringDistanceTable value) {
if (value != null) {
((StringDistanceTable)distanceTable).copy(value);
}
}
@Override
public DistanceTable getDistanceTable() {
return distanceTable;
}
}
public XLocaleDistance(DistanceTable datadistancetable2, RegionMapper regionMapper) {
languageDesired2Supported = datadistancetable2;
this.regionMapper = regionMapper;
StringDistanceNode languageNode = (StringDistanceNode) ((StringDistanceTable) languageDesired2Supported).subtables.get(ANY).get(ANY);
defaultLanguageDistance = languageNode.distance;
StringDistanceNode scriptNode = (StringDistanceNode) ((StringDistanceTable)languageNode.distanceTable).subtables.get(ANY).get(ANY);
defaultScriptDistance = scriptNode.distance;
DistanceNode regionNode = ((StringDistanceTable)scriptNode.distanceTable).subtables.get(ANY).get(ANY);
defaultRegionDistance = regionNode.distance;
}
@SuppressWarnings("rawtypes")
private static Map newMap() { // for debugging
return new TreeMap();
}
/**
* Internal class
*/
@Deprecated
public static class StringDistanceTable extends DistanceTable {
final Map<String, Map<String, DistanceNode>> subtables;
StringDistanceTable(Map<String, Map<String, DistanceNode>> tables) {
subtables = tables;
}
@SuppressWarnings("unchecked")
StringDistanceTable() {
this(newMap());
}
@Override
public boolean isEmpty() {
return subtables.isEmpty();
}
@Override
public boolean equals(Object obj) {
return this == obj ||
(obj != null
&& obj.getClass() == this.getClass()
&& subtables.equals(((StringDistanceTable) obj).subtables));
}
@Override
public int hashCode() {
return subtables.hashCode();
}
@Override
public int getDistance(String desired, String supported, Output<DistanceTable> distanceTable, boolean starEquals) {
boolean star = false;
Map<String, DistanceNode> sub2 = subtables.get(desired);
if (sub2 == null) {
sub2 = subtables.get(ANY); // <*, supported>
star = true;
}
DistanceNode value = sub2.get(supported); // <*/desired, supported>
if (value == null) {
value = sub2.get(ANY); // <*/desired, *>
if (value == null && !star) {
sub2 = subtables.get(ANY); // <*, supported>
value = sub2.get(supported);
if (value == null) {
value = sub2.get(ANY); // <*, *>
}
}
star = true;
}
if (distanceTable != null) {
distanceTable.value = ((StringDistanceNode) value).distanceTable;
}
return starEquals && star && desired.equals(supported) ? 0 : value.distance;
}
public void copy(StringDistanceTable other) {
for (Entry<String, Map<String, DistanceNode>> e1 : other.subtables.entrySet()) {
for (Entry<String, DistanceNode> e2 : e1.getValue().entrySet()) {
DistanceNode value = e2.getValue();
@SuppressWarnings("unused")
DistanceNode subNode = addSubtable(e1.getKey(), e2.getKey(), value.distance);
}
}
}
@SuppressWarnings("unchecked")
DistanceNode addSubtable(String desired, String supported, int distance) {
Map<String, DistanceNode> sub2 = subtables.get(desired);
if (sub2 == null) {
subtables.put(desired, sub2 = newMap());
}
DistanceNode oldNode = sub2.get(supported);
if (oldNode != null) {
return oldNode;
}
final StringDistanceNode newNode = new StringDistanceNode(distance);
sub2.put(supported, newNode);
return newNode;
}
/**
* Return null if value doesn't exist
*/
private DistanceNode getNode(String desired, String supported) {
Map<String, DistanceNode> sub2 = subtables.get(desired);
if (sub2 == null) {
return null;
}
return sub2.get(supported);
}
/** add table for each subitem that matches and doesn't have a table already
*/
public void addSubtables(
String desired, String supported,
Predicate<DistanceNode> action) {
DistanceNode node = getNode(desired, supported);
if (node == null) {
// get the distance it would have
Output<DistanceTable> node2 = new Output<DistanceTable>();
int distance = getDistance(desired, supported, node2, true);
// now add it
node = addSubtable(desired, supported, distance);
if (node2.value != null) {
((StringDistanceNode)node).copyTables((StringDistanceTable)(node2.value));
}
}
action.test(node);
}
public void addSubtables(String desiredLang, String supportedLang,
String desiredScript, String supportedScript,
int percentage) {
// add to all the values that have the matching desiredLang and supportedLang
@SuppressWarnings("unused")
boolean haveKeys = false;
for (Entry<String, Map<String, DistanceNode>> e1 : subtables.entrySet()) {
String key1 = e1.getKey();
final boolean desiredIsKey = desiredLang.equals(key1);
if (desiredIsKey || desiredLang.equals(ANY)) {
for (Entry<String, DistanceNode> e2 : e1.getValue().entrySet()) {
String key2 = e2.getKey();
final boolean supportedIsKey = supportedLang.equals(key2);
haveKeys |= (desiredIsKey && supportedIsKey);
if (supportedIsKey || supportedLang.equals(ANY)) {
DistanceNode value = e2.getValue();
((StringDistanceTable)value.getDistanceTable()).addSubtable(desiredScript, supportedScript, percentage);
}
}
}
}
// now add the sequence explicitly
StringDistanceTable dt = new StringDistanceTable();
dt.addSubtable(desiredScript, supportedScript, percentage);
CopyIfEmpty r = new CopyIfEmpty(dt);
addSubtables(desiredLang, supportedLang, r);
}
public void addSubtables(String desiredLang, String supportedLang,
String desiredScript, String supportedScript,
String desiredRegion, String supportedRegion,
int percentage) {
// add to all the values that have the matching desiredLang and supportedLang
@SuppressWarnings("unused")
boolean haveKeys = false;
for (Entry<String, Map<String, DistanceNode>> e1 : subtables.entrySet()) {
String key1 = e1.getKey();
final boolean desiredIsKey = desiredLang.equals(key1);
if (desiredIsKey || desiredLang.equals(ANY)) {
for (Entry<String, DistanceNode> e2 : e1.getValue().entrySet()) {
String key2 = e2.getKey();
final boolean supportedIsKey = supportedLang.equals(key2);
haveKeys |= (desiredIsKey && supportedIsKey);
if (supportedIsKey || supportedLang.equals(ANY)) {
StringDistanceNode value = (StringDistanceNode) e2.getValue();
((StringDistanceTable)value.distanceTable).addSubtables(desiredScript, supportedScript, desiredRegion, supportedRegion, percentage);
}
}
}
}
// now add the sequence explicitly
StringDistanceTable dt = new StringDistanceTable();
dt.addSubtable(desiredRegion, supportedRegion, percentage);
AddSub r = new AddSub(desiredScript, supportedScript, dt);
addSubtables(desiredLang, supportedLang, r);
}
@Override
public String toString() {
return toString(false);
}
@Override
public String toString(boolean abbreviate) {
return toString(abbreviate, "", new IdMakerFull<Object>("interner"), new StringBuilder()).toString();
}
public StringBuilder toString(boolean abbreviate, String indent, IdMakerFull<Object> intern, StringBuilder buffer) {
String indent2 = indent.isEmpty() ? "" : "\t";
Integer id = abbreviate ? intern.getOldAndAdd(subtables) : null;
if (id != null) {
buffer.append(indent2).append('#').append(id).append('\n');
} else for (Entry<String, Map<String, DistanceNode>> e1 : subtables.entrySet()) {
final Map<String, DistanceNode> subsubtable = e1.getValue();
buffer.append(indent2).append(e1.getKey());
String indent3 = "\t";
id = abbreviate ? intern.getOldAndAdd(subsubtable) : null;
if (id != null) {
buffer.append(indent3).append('#').append(id).append('\n');
} else for (Entry<String, DistanceNode> e2 : subsubtable.entrySet()) {
DistanceNode value = e2.getValue();
buffer.append(indent3).append(e2.getKey());
id = abbreviate ? intern.getOldAndAdd(value) : null;
if (id != null) {
buffer.append('\t').append('#').append(id).append('\n');
} else {
buffer.append('\t').append(value.distance);
final DistanceTable distanceTable = value.getDistanceTable();
if (distanceTable != null) {
id = abbreviate ? intern.getOldAndAdd(distanceTable) : null;
if (id != null) {
buffer.append('\t').append('#').append(id).append('\n');
} else {
((StringDistanceTable)distanceTable).toString(abbreviate, indent+"\t\t\t", intern, buffer);
}
} else {
buffer.append('\n');
}
}
indent3 = indent+'\t';
}
indent2 = indent;
}
return buffer;
}
@Override
public StringDistanceTable compact() {
return new CompactAndImmutablizer().compact(this);
}
@Override
public Set<String> getCloser(int threshold) {
Set<String> result = new HashSet<String>();
for (Entry<String, Map<String, DistanceNode>> e1 : subtables.entrySet()) {
String desired = e1.getKey();
for (Entry<String, DistanceNode> e2 : e1.getValue().entrySet()) {
if (e2.getValue().distance < threshold) {
result.add(desired);
break;
}
}
}
return result;
}
public Integer getInternalDistance(String a, String b) {
Map<String, DistanceNode> subsub = subtables.get(a);
if (subsub == null) {
return null;
}
DistanceNode dnode = subsub.get(b);
return dnode == null ? null : dnode.distance;
}
@Override
public DistanceNode getInternalNode(String a, String b) {
Map<String, DistanceNode> subsub = subtables.get(a);
if (subsub == null) {
return null;
}
return subsub.get(b);
}
@Override
public Map<String, Set<String>> getInternalMatches() {
Map<String, Set<String>> result = new LinkedHashMap<String, Set<String>>();
for (Entry<String, Map<String, DistanceNode>> entry : subtables.entrySet()) {
result.put(entry.getKey(), new LinkedHashSet<String>(entry.getValue().keySet()));
}
return result;
}
}
static class CopyIfEmpty implements Predicate<DistanceNode> {
private final StringDistanceTable toCopy;
CopyIfEmpty(StringDistanceTable resetIfNotNull) {
this.toCopy = resetIfNotNull;
}
@Override
public boolean test(DistanceNode node) {
final StringDistanceTable subtables = (StringDistanceTable) node.getDistanceTable();
if (subtables.subtables.isEmpty()) {
subtables.copy(toCopy);
}
return true;
}
}
static class AddSub implements Predicate<DistanceNode> {
private final String desiredSub;
private final String supportedSub;
private final CopyIfEmpty r;
AddSub(String desiredSub, String supportedSub, StringDistanceTable distanceTableToCopy) {
this.r = new CopyIfEmpty(distanceTableToCopy);
this.desiredSub = desiredSub;
this.supportedSub = supportedSub;
}
@Override
public boolean test(DistanceNode node) {
if (node == null) {
throw new IllegalArgumentException("bad structure");
} else {
((StringDistanceNode)node).addSubtables(desiredSub, supportedSub, r);
}
return true;
}
}
public int distance(ULocale desired, ULocale supported, int threshold, DistanceOption distanceOption) {
LSR supportedLSR = LSR.fromMaximalized(supported);
LSR desiredLSR = LSR.fromMaximalized(desired);
return distanceRaw(desiredLSR, supportedLSR, threshold, distanceOption);
}
/**
* Returns distance, from 0 to ABOVE_THRESHOLD.
* ULocales must be in canonical, addLikelySubtags format. Returns distance
*/
public int distanceRaw(LSR desired, LSR supported, int threshold, DistanceOption distanceOption) {
return distanceRaw(desired.language, supported.language,
desired.script, supported.script,
desired.region, supported.region,
threshold, distanceOption);
}
public enum DistanceOption {NORMAL, SCRIPT_FIRST}
/**
* Returns distance, from 0 to ABOVE_THRESHOLD.
* ULocales must be in canonical, addLikelySubtags format. Returns distance
*/
public int distanceRaw(
String desiredLang, String supportedlang,
String desiredScript, String supportedScript,
String desiredRegion, String supportedRegion,
int threshold,
DistanceOption distanceOption) {
Output<DistanceTable> subtable = new Output<DistanceTable>();
int distance = languageDesired2Supported.getDistance(desiredLang, supportedlang, subtable, true);
boolean scriptFirst = distanceOption == DistanceOption.SCRIPT_FIRST;
if (scriptFirst) {
distance >>= 2;
}
if (distance < 0) {
distance = 0;
} else if (distance >= threshold) {
return ABOVE_THRESHOLD;
}
int scriptDistance = subtable.value.getDistance(desiredScript, supportedScript, subtable, true);
if (scriptFirst) {
scriptDistance >>= 1;
}
distance += scriptDistance;
if (distance >= threshold) {
return ABOVE_THRESHOLD;
}
if (desiredRegion.equals(supportedRegion)) {
return distance;
}
// From here on we know the regions are not equal
final String desiredPartition = regionMapper.toId(desiredRegion);
final String supportedPartition = regionMapper.toId(supportedRegion);
int subdistance;
// check for macros. If one is found, we take the maximum distance
// this could be optimized by adding some more structure, but probably not worth it.
Collection<String> desiredPartitions = desiredPartition.isEmpty() ? regionMapper.macroToPartitions.get(desiredRegion) : null;
Collection<String> supportedPartitions = supportedPartition.isEmpty() ? regionMapper.macroToPartitions.get(supportedRegion) : null;
if (desiredPartitions != null || supportedPartitions != null) {
subdistance = 0;
// make the code simple for now
if (desiredPartitions == null) {
desiredPartitions = Collections.singleton(desiredPartition);
}
if (supportedPartitions == null) {
supportedPartitions = Collections.singleton(supportedPartition);
}
for (String desiredPartition2 : desiredPartitions) {
for (String supportedPartition2 : supportedPartitions) {
int tempSubdistance = subtable.value.getDistance(desiredPartition2, supportedPartition2, null, false);
if (subdistance < tempSubdistance) {
subdistance = tempSubdistance;
}
}
}
} else {
subdistance = subtable.value.getDistance(desiredPartition, supportedPartition, null, false);
}
distance += subdistance;
return distance >= threshold ? ABOVE_THRESHOLD : distance;
}
private static final XLocaleDistance DEFAULT;
public static XLocaleDistance getDefault() {
return DEFAULT;
}
static {
String[][] variableOverrides = {
{"$enUS", "AS+GU+MH+MP+PR+UM+US+VI"},
{"$cnsar", "HK+MO"},
{"$americas", "019"},
{"$maghreb", "MA+DZ+TN+LY+MR+EH"},
};
String[] paradigmRegions = {
"en", "en-GB", "es", "es-419", "pt-BR", "pt-PT"
};
String[][] regionRuleOverrides = {
{"ar_*_$maghreb", "ar_*_$maghreb", "96"},
{"ar_*_$!maghreb", "ar_*_$!maghreb", "96"},
{"ar_*_*", "ar_*_*", "95"},
{"en_*_$enUS", "en_*_$enUS", "96"},
{"en_*_$!enUS", "en_*_$!enUS", "96"},
{"en_*_*", "en_*_*", "95"},
{"es_*_$americas", "es_*_$americas", "96"},
{"es_*_$!americas", "es_*_$!americas", "96"},
{"es_*_*", "es_*_*", "95"},
{"pt_*_$americas", "pt_*_$americas", "96"},
{"pt_*_$!americas", "pt_*_$!americas", "96"},
{"pt_*_*", "pt_*_*", "95"},
{"zh_Hant_$cnsar", "zh_Hant_$cnsar", "96"},
{"zh_Hant_$!cnsar", "zh_Hant_$!cnsar", "96"},
{"zh_Hant_*", "zh_Hant_*", "95"},
{"*_*_*", "*_*_*", "96"},
};
Builder rmb = new RegionMapper.Builder().addParadigms(paradigmRegions);
for (String[] variableRule : variableOverrides) {
rmb.add(variableRule[0], variableRule[1]);
}
if (PRINT_OVERRIDES) {
System.out.println("\t\t<languageMatches type=\"written\" alt=\"enhanced\">");
System.out.println("\t\t\t<paradigmLocales locales=\"" + XCldrStub.join(paradigmRegions, " ")
+ "\"/>");
for (String[] variableRule : variableOverrides) {
System.out.println("\t\t\t<matchVariable id=\"" + variableRule[0]
+ "\" value=\""
+ variableRule[1]
+ "\"/>");
}
}
final StringDistanceTable defaultDistanceTable = new StringDistanceTable();
final RegionMapper defaultRegionMapper = rmb.build();
Splitter bar = Splitter.on('_');
@SuppressWarnings({"unchecked", "rawtypes"})
List<Row.R4<List<String>, List<String>, Integer, Boolean>>[] sorted = new ArrayList[3];
sorted[0] = new ArrayList<Row.R4<List<String>, List<String>, Integer, Boolean>>();
sorted[1] = new ArrayList<Row.R4<List<String>, List<String>, Integer, Boolean>>();
sorted[2] = new ArrayList<Row.R4<List<String>, List<String>, Integer, Boolean>>();
// sort the rules so that the language-only are first, then the language-script, and finally the language-script-region.
for (R4<String, String, Integer, Boolean> info : xGetLanguageMatcherData()) {
String desiredRaw = info.get0();
String supportedRaw = info.get1();
List<String> desired = bar.splitToList(desiredRaw);
List<String> supported = bar.splitToList(supportedRaw);
Boolean oneway = info.get3();
int distance = desiredRaw.equals("*_*") ? 50 : info.get2();
int size = desired.size();
// for now, skip size == 3
if (size == 3) continue;
sorted[size-1].add(Row.of(desired, supported, distance, oneway));
}
for (List<Row.R4<List<String>, List<String>, Integer, Boolean>> item1 : sorted) {
for (Row.R4<List<String>, List<String>, Integer, Boolean> item2 : item1) {
List<String> desired = item2.get0();
List<String> supported = item2.get1();
Integer distance = item2.get2();
Boolean oneway = item2.get3();
add(defaultDistanceTable, desired, supported, distance);
if (oneway != Boolean.TRUE && !desired.equals(supported)) {
add(defaultDistanceTable, supported, desired, distance);
}
printMatchXml(desired, supported, distance, oneway);
}
}
// add new size=3
for (String[] rule : regionRuleOverrides) {
// if (PRINT_OVERRIDES) System.out.println("\t\t\t<languageMatch desired=\""
// + rule[0]
// + "\" supported=\""
// + rule[1]
// + "\" distance=\""
// + rule[2]
// + "\"/>");
// if (rule[0].equals("en_*_*") || rule[1].equals("*_*_*")) {
// int debug = 0;
// }
List<String> desiredBase = new ArrayList<String>(bar.splitToList(rule[0]));
List<String> supportedBase = new ArrayList<String>(bar.splitToList(rule[1]));
Integer distance = 100-Integer.parseInt(rule[2]);
printMatchXml(desiredBase, supportedBase, distance, false);
Collection<String> desiredRegions = defaultRegionMapper.getIdsFromVariable(desiredBase.get(2));
if (desiredRegions.isEmpty()) {
throw new IllegalArgumentException("Bad region variable: " + desiredBase.get(2));
}
Collection<String> supportedRegions = defaultRegionMapper.getIdsFromVariable(supportedBase.get(2));
if (supportedRegions.isEmpty()) {
throw new IllegalArgumentException("Bad region variable: " + supportedBase.get(2));
}
for (String desiredRegion2 : desiredRegions) {
desiredBase.set(2, desiredRegion2.toString()); // fix later
for (String supportedRegion2 : supportedRegions) {
supportedBase.set(2, supportedRegion2.toString()); // fix later
add(defaultDistanceTable, desiredBase, supportedBase, distance);
add(defaultDistanceTable, supportedBase, desiredBase, distance);
}
}
}
if (PRINT_OVERRIDES) {
System.out.println("\t\t</languageMatches>");
}
DEFAULT = new XLocaleDistance(defaultDistanceTable.compact(), defaultRegionMapper);
if (PRINT_OVERRIDES) {
System.out.println(defaultRegionMapper);
System.out.println(defaultDistanceTable);
throw new IllegalArgumentException();
}
}
private static void printMatchXml(List<String> desired, List<String> supported, Integer distance, Boolean oneway) {
if (PRINT_OVERRIDES) {
String desiredStr = CollectionUtilities.join(desired, "_");
String supportedStr = CollectionUtilities.join(supported, "_");
String desiredName = fixedName(desired);
String supportedName = fixedName(supported);
System.out.println("\t\t\t<languageMatch"
+ " desired=\"" + desiredStr
+ "\"\tsupported=\"" + supportedStr
+ "\"\tdistance=\"" + distance
+ (!oneway ? "" : "\"\toneway=\"true")
+ "\"/>\t<!-- " + desiredName + " ⇒ " + supportedName + " -->");
}
}
private static String fixedName(List<String> match) {
List<String> alt = new ArrayList<String>(match);
int size = alt.size();
assert size >= 1 && size <= 3;
StringBuilder result = new StringBuilder();
if (size >= 3) {
String region = alt.get(2);
if (region.equals("*") || region.startsWith("$")) {
result.append(region);
} else {
result.append(english.regionDisplayName(region));
}
}
if (size >= 2) {
String script = alt.get(1);
if (script.equals("*")) {
result.insert(0, script);
} else {
result.insert(0, english.scriptDisplayName(script));
}
}
if (size >= 1) {
String language = alt.get(0);
if (language.equals("*")) {
result.insert(0, language);
} else {
result.insert(0, english.languageDisplayName(language));
}
}
return CollectionUtilities.join(alt, "; ");
}
static public void add(StringDistanceTable languageDesired2Supported, List<String> desired, List<String> supported, int percentage) {
int size = desired.size();
if (size != supported.size() || size < 1 || size > 3) {
throw new IllegalArgumentException();
}
final String desiredLang = fixAny(desired.get(0));
final String supportedLang = fixAny(supported.get(0));
if (size == 1) {
languageDesired2Supported.addSubtable(desiredLang, supportedLang, percentage);
} else {
final String desiredScript = fixAny(desired.get(1));
final String supportedScript = fixAny(supported.get(1));
if (size == 2) {
languageDesired2Supported.addSubtables(desiredLang, supportedLang, desiredScript, supportedScript, percentage);
} else {
final String desiredRegion = fixAny(desired.get(2));
final String supportedRegion = fixAny(supported.get(2));
languageDesired2Supported.addSubtables(desiredLang, supportedLang, desiredScript, supportedScript, desiredRegion, supportedRegion, percentage);
}
}
}
@Override
public String toString() {
return toString(false);
}
public String toString(boolean abbreviate) {
return regionMapper + "\n" + languageDesired2Supported.toString(abbreviate);
}
// public static XLocaleDistance createDefaultInt() {
// IntDistanceTable d = new IntDistanceTable(DEFAULT_DISTANCE_TABLE);
// return new XLocaleDistance(d, DEFAULT_REGION_MAPPER);
// }
static Set<String> getContainingMacrosFor(Collection<String> input, Set<String> output) {
output.clear();
for (Entry<String, Set<String>> entry : CONTAINER_TO_CONTAINED.asMap().entrySet()) {
if (input.containsAll(entry.getValue())) { // example; if all southern Europe are contained, then add S. Europe
output.add(entry.getKey());
}
}
return output;
}
static class RegionMapper implements IdMapper<String,String> {
/**
* Used for processing rules. At the start we have a variable setting like $A1=US+CA+MX. We generate a mapping from $A1 to a set of partitions {P1, P2}
* When we hit a rule that contains a variable, we replace that rule by multiple rules for the partitions.
*/
final Multimap<String,String> variableToPartition;
/**
* Used for executing the rules. We map a region to a partition before processing.
*/
final Map<String,String> regionToPartition;
/**
* Used to support es_419 compared to es_AR, etc.
*/
final Multimap<String,String> macroToPartitions;
/**
* Used to get the paradigm region for a cluster, if there is one
*/
final Set<ULocale> paradigms;
private RegionMapper(
Multimap<String, String> variableToPartitionIn,
Map<String, String> regionToPartitionIn,
Multimap<String,String> macroToPartitionsIn,
Set<ULocale> paradigmsIn) {
variableToPartition = ImmutableMultimap.copyOf(variableToPartitionIn);
regionToPartition = ImmutableMap.copyOf(regionToPartitionIn);
macroToPartitions = ImmutableMultimap.copyOf(macroToPartitionsIn);
paradigms = ImmutableSet.copyOf(paradigmsIn);
}
@Override
public String toId(String region) {
String result = regionToPartition.get(region);
return result == null ? "" : result;
}
public Collection<String> getIdsFromVariable(String variable) {
if (variable.equals("*")) {
return Collections.singleton("*");
}
Collection<String> result = variableToPartition.get(variable);
if (result == null || result.isEmpty()) {
throw new IllegalArgumentException("Variable not defined: " + variable);
}
return result;
}
public Set<String> regions() {
return regionToPartition.keySet();
}
public Set<String> variables() {
return variableToPartition.keySet();
}
@Override
public String toString() {
TreeMultimap<String, String> partitionToVariables = Multimaps.invertFrom(variableToPartition,
TreeMultimap.<String, String>create());
TreeMultimap<String, String> partitionToRegions = TreeMultimap.create();
for (Entry<String, String> e : regionToPartition.entrySet()) {
partitionToRegions.put(e.getValue(), e.getKey());
}
StringBuilder buffer = new StringBuilder();
buffer.append("Partition ➠ Variables ➠ Regions (final)");
for (Entry<String, Set<String>> e : partitionToVariables.asMap().entrySet()) {
buffer.append('\n');
buffer.append(e.getKey() + "\t" + e.getValue() + "\t" + partitionToRegions.get(e.getKey()));
}
buffer.append("\nMacro ➠ Partitions");
for (Entry<String, Set<String>> e : macroToPartitions.asMap().entrySet()) {
buffer.append('\n');
buffer.append(e.getKey() + "\t" + e.getValue());
}
return buffer.toString();
}
static class Builder {
final private Multimap<String, String> regionToRawPartition = TreeMultimap.create();
final private RegionSet regionSet = new RegionSet();
final private Set<ULocale> paradigms = new LinkedHashSet<ULocale>();
void add(String variable, String barString) {
Set<String> tempRegions = regionSet.parseSet(barString);
for (String region : tempRegions) {
regionToRawPartition.put(region, variable);
}
// now add the inverse variable
Set<String> inverse = regionSet.inverse();
String inverseVariable = "$!" + variable.substring(1);
for (String region : inverse) {
regionToRawPartition.put(region, inverseVariable);
}
}
public Builder addParadigms(String... paradigmRegions) {
for (String paradigm : paradigmRegions) {
paradigms.add(new ULocale(paradigm));
}
return this;
}
RegionMapper build() {
final IdMakerFull<Collection<String>> id = new IdMakerFull<Collection<String>>("partition");
Multimap<String,String> variableToPartitions = TreeMultimap.create();
Map<String,String> regionToPartition = new TreeMap<String,String>();
Multimap<String,String> partitionToRegions = TreeMultimap.create();
for (Entry<String, Set<String>> e : regionToRawPartition.asMap().entrySet()) {
final String region = e.getKey();
final Collection<String> rawPartition = e.getValue();
String partition = String.valueOf((char)('α' + id.add(rawPartition)));
regionToPartition.put(region, partition);
partitionToRegions.put(partition, region);
for (String variable : rawPartition) {
variableToPartitions.put(variable, partition);
}
}
// we get a mapping of each macro to the partitions it intersects with
Multimap<String,String> macroToPartitions = TreeMultimap.create();
for (Entry<String, Set<String>> e : CONTAINER_TO_CONTAINED.asMap().entrySet()) {
String macro = e.getKey();
for (Entry<String, Set<String>> e2 : partitionToRegions.asMap().entrySet()) {
String partition = e2.getKey();
if (!Collections.disjoint(e.getValue(), e2.getValue())) {
macroToPartitions.put(macro, partition);
}
}
}
return new RegionMapper(
variableToPartitions,
regionToPartition,
macroToPartitions,
paradigms);
}
}
}
/**
* Parses a string of regions like "US+005-BR" and produces a set of resolved regions.
* All macroregions are fully resolved to sets of non-macro regions.
* <br>Syntax is simple for now:
* <pre>regionSet := region ([-+] region)*</pre>
* No precedence, so "x+y-y+z" is (((x+y)-y)+z) NOT (x+y)-(y+z)
*/
private static class RegionSet {
private enum Operation {add, remove}
// temporaries used in processing
final private Set<String> tempRegions = new TreeSet<String>();
private Operation operation = null;
private Set<String> parseSet(String barString) {
operation = Operation.add;
int last = 0;
tempRegions.clear();
int i = 0;
for (; i < barString.length(); ++i) {
char c = barString.charAt(i); // UTF16 is ok, since syntax is only ascii
switch(c) {
case '+':
add(barString, last, i);
last = i+1;
operation = Operation.add;
break;
case '-':
add(barString, last, i);
last = i+1;
operation = Operation.remove;
break;
}
}
add(barString, last, i);
return tempRegions;
}
private Set<String> inverse() {
TreeSet<String> result = new TreeSet<String>(ALL_FINAL_REGIONS);
result.removeAll(tempRegions);
return result;
}
private void add(String barString, int last, int i) {
if (i > last) {
String region = barString.substring(last,i);
changeSet(operation, region);
}
}
private void changeSet(Operation operation, String region) {
Collection<String> contained = CONTAINER_TO_CONTAINED_FINAL.get(region);
if (contained != null && !contained.isEmpty()) {
if (Operation.add == operation) {
tempRegions.addAll(contained);
} else {
tempRegions.removeAll(contained);
}
} else if (Operation.add == operation) {
tempRegions.add(region);
} else {
tempRegions.remove(region);
}
}
}
public static <K,V> Multimap<K,V> invertMap(Map<V,K> map) {
return Multimaps.invertFrom(Multimaps.forMap(map), LinkedHashMultimap.<K,V>create());
}
public Set<ULocale> getParadigms() {
return regionMapper.paradigms;
}
public int getDefaultLanguageDistance() {
return defaultLanguageDistance;
}
public int getDefaultScriptDistance() {
return defaultScriptDistance;
}
public int getDefaultRegionDistance() {
return defaultRegionDistance;
}
static class CompactAndImmutablizer extends IdMakerFull<Object> {
StringDistanceTable compact(StringDistanceTable item) {
if (toId(item) != null) {
return (StringDistanceTable) intern(item);
}
return new StringDistanceTable(compact(item.subtables, 0));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
<K,T> Map<K,T> compact(Map<K,T> item, int level) {
if (toId(item) != null) {
return (Map<K, T>) intern(item);
}
Map<K,T> copy = new LinkedHashMap<K,T>();
for (Entry<K,T> entry : item.entrySet()) {
T value = entry.getValue();
if (value instanceof Map) {
copy.put(entry.getKey(), (T)compact((Map)value, level+1));
} else {
copy.put(entry.getKey(), (T)compact((DistanceNode)value));
}
}
return ImmutableMap.copyOf(copy);
}
DistanceNode compact(DistanceNode item) {
if (toId(item) != null) {
return (DistanceNode) intern(item);
}
final DistanceTable distanceTable = item.getDistanceTable();
if (distanceTable == null || distanceTable.isEmpty()) {
return new DistanceNode(item.distance);
} else {
return new StringDistanceNode(item.distance, compact((StringDistanceTable)((StringDistanceNode)item).distanceTable));
}
}
}
@Deprecated
public StringDistanceTable internalGetDistanceTable() {
return (StringDistanceTable) languageDesired2Supported;
}
public static void main(String[] args) {
// for (Entry<String, Collection<String>> entry : containerToContained.asMap().entrySet()) {
// System.out.println(entry.getKey() + "\t⥢" + entry.getValue() + "; " + containerToFinalContained.get(entry.getKey()));
// }
// final Multimap<String,String> regionToMacros = ImmutableMultimap.copyOf(Multimaps.invertFrom(containerToContained, TreeMultimap.create()));
// for (Entry<String, Collection<String>> entry : regionToMacros.asMap().entrySet()) {
// System.out.println(entry.getKey() + "\t⥤ " + entry.getValue());
// }
if (PRINT_OVERRIDES) {
System.out.println(getDefault().toString(true));
}
DistanceTable table = getDefault().languageDesired2Supported;
DistanceTable compactedTable = table.compact();
if (!table.equals(compactedTable)) {
throw new IllegalArgumentException("Compaction isn't equal");
}
}
}