![JAR search and dependency download from the Maven repository](/logo.png)
com.ibm.icu.impl.locale.XLocaleDistance Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.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.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.Row;
import com.ibm.icu.impl.Row.R4;
import com.ibm.icu.impl.locale.XCldrStub.CollectionUtilities;
import com.ibm.icu.impl.locale.XCldrStub.ImmutableMap;
import com.ibm.icu.impl.locale.XCldrStub.ImmutableMultimap;
import com.ibm.icu.impl.locale.XCldrStub.ImmutableSet;
import com.ibm.icu.impl.locale.XCldrStub.LinkedHashMultimap;
import com.ibm.icu.impl.locale.XCldrStub.Multimap;
import com.ibm.icu.impl.locale.XCldrStub.Multimaps;
import com.ibm.icu.impl.locale.XCldrStub.Predicate;
import com.ibm.icu.impl.locale.XCldrStub.Splitter;
import com.ibm.icu.impl.locale.XCldrStub.TreeMultimap;
import com.ibm.icu.impl.locale.XLikelySubtags.LSR;
import com.ibm.icu.impl.locale.XLocaleDistance.RegionMapper.Builder;
import com.ibm.icu.text.LocaleDisplayNames;
import com.ibm.icu.util.LocaleMatcher;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundleIterator;
public class XLocaleDistance {
static final boolean PRINT_OVERRIDES = false;
public static final int ABOVE_THRESHOLD = 100;
// Activates debugging output to stderr with details of GetBestMatch.
// Be sure to set this to false before checking this in for production!
private static final boolean TRACE_DISTANCE = false;
@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> xGetLanguageMatcherData() {
List> distanceList = new ArrayList<>();
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) // 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 xGetParadigmLocales() {
ICUResourceBundle suppData = LocaleMatcher.getICUSupplementalData();
ICUResourceBundle languageMatchingInfo = suppData.findTopLevel("languageMatchingInfo");
ICUResourceBundle writtenParadigmLocales = (ICUResourceBundle) languageMatchingInfo.get("written")
.get("paradigmLocales");
// paradigmLocales{ "en", "en-GB",... }
HashSet paradigmLocales = new HashSet<>(Arrays.asList(writtenParadigmLocales.getStringArray()));
return Collections.unmodifiableSet(paradigmLocales);
}
@SuppressWarnings("unused")
private static Map 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 matchVariables = new HashMap<>();
for (Enumeration enumer = writtenMatchVariables.getKeys(); enumer.hasMoreElements(); ) {
String key = enumer.nextElement();
matchVariables.put(key, writtenMatchVariables.getString(key));
}
return Collections.unmodifiableMap(matchVariables);
}
private static Multimap xGetContainment() {
TreeMultimap 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 containmentResolved = TreeMultimap.create();
fill("001", containment, containmentResolved);
return ImmutableMultimap.copyOf(containmentResolved);
}
private static Set fill(String region, TreeMultimap containment, Multimap toAddTo) {
Set 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 CONTAINER_TO_CONTAINED;
static final Multimap CONTAINER_TO_CONTAINED_FINAL;
static {
// Multimap containerToContainedTemp = xGetContainment();
// fill(Region.getInstance("001"), containerToContainedTemp);
CONTAINER_TO_CONTAINED = xGetContainment();
Multimap containerToFinalContainedBuilder = TreeMultimap.create();
for (Entry> 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 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 table, boolean starEquals);
abstract Set 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> 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 {
public V toId(K source);
}
static class IdMakerFull implements IdMapper {
private final Map objectToInt = new HashMap<>();
private final List intToObject = new ArrayList<>();
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
&& Objects.equals(distanceTable, other.distanceTable)
&& super.equals(other));
}
@Override
public int hashCode() {
return distance ^ Objects.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> subtables;
StringDistanceTable(Map> 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, boolean starEquals) {
if (TRACE_DISTANCE) {
System.err.printf(" Entering getDistance: desired=%s supported=%s starEquals=%s\n",
desired, supported, Boolean.toString(starEquals));
}
boolean star = false;
Map 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;
}
int result = starEquals && star && desired.equals(supported) ? 0 : value.distance;
if (TRACE_DISTANCE) {
System.err.printf(" Returning from getDistance: %d\n", result);
}
return result;
}
public void copy(StringDistanceTable other) {
for (Entry> e1 : other.subtables.entrySet()) {
for (Entry 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 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 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 action) {
DistanceNode node = getNode(desired, supported);
if (node == null) {
// get the distance it would have
Output node2 = new Output<>();
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> e1 : subtables.entrySet()) {
String key1 = e1.getKey();
final boolean desiredIsKey = desiredLang.equals(key1);
if (desiredIsKey || desiredLang.equals(ANY)) {
for (Entry 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> e1 : subtables.entrySet()) {
String key1 = e1.getKey();
final boolean desiredIsKey = desiredLang.equals(key1);
if (desiredIsKey || desiredLang.equals(ANY)) {
for (Entry 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<>("interner"), new StringBuilder()).toString();
}
public StringBuilder toString(boolean abbreviate, String indent, IdMakerFull
© 2015 - 2025 Weber Informatics LLC | Privacy Policy