1 // 2017 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 package com.ibm.icu.impl.locale; 4 5 import java.util.ArrayList; 6 import java.util.Arrays; 7 import java.util.Collection; 8 import java.util.Collections; 9 import java.util.Enumeration; 10 import java.util.HashMap; 11 import java.util.HashSet; 12 import java.util.LinkedHashMap; 13 import java.util.LinkedHashSet; 14 import java.util.List; 15 import java.util.Map; 16 import java.util.Map.Entry; 17 import java.util.Set; 18 import java.util.TreeMap; 19 import java.util.TreeSet; 20 21 import com.ibm.icu.impl.ICUResourceBundle; 22 import com.ibm.icu.impl.Row; 23 import com.ibm.icu.impl.Row.R4; 24 import com.ibm.icu.impl.Utility; 25 import com.ibm.icu.impl.locale.XCldrStub.CollectionUtilities; 26 import com.ibm.icu.impl.locale.XCldrStub.ImmutableMap; 27 import com.ibm.icu.impl.locale.XCldrStub.ImmutableMultimap; 28 import com.ibm.icu.impl.locale.XCldrStub.ImmutableSet; 29 import com.ibm.icu.impl.locale.XCldrStub.LinkedHashMultimap; 30 import com.ibm.icu.impl.locale.XCldrStub.Multimap; 31 import com.ibm.icu.impl.locale.XCldrStub.Multimaps; 32 import com.ibm.icu.impl.locale.XCldrStub.Predicate; 33 import com.ibm.icu.impl.locale.XCldrStub.Splitter; 34 import com.ibm.icu.impl.locale.XCldrStub.TreeMultimap; 35 import com.ibm.icu.impl.locale.XLikelySubtags.LSR; 36 import com.ibm.icu.impl.locale.XLocaleDistance.RegionMapper.Builder; 37 import com.ibm.icu.text.LocaleDisplayNames; 38 import com.ibm.icu.util.LocaleMatcher; 39 import com.ibm.icu.util.Output; 40 import com.ibm.icu.util.ULocale; 41 import com.ibm.icu.util.UResourceBundleIterator; 42 43 public class XLocaleDistance { 44 45 static final boolean PRINT_OVERRIDES = false; 46 47 public static final int ABOVE_THRESHOLD = 100; 48 49 @Deprecated 50 public static final String ANY = ""; // matches any character. Uses value above any subtag. 51 52 private static String fixAny(String string) { 53 return "*".equals(string) ? ANY : string; 54 } 55 56 static final LocaleDisplayNames english = LocaleDisplayNames.getInstance(ULocale.ENGLISH); 57 58 private static List<R4<String, String, Integer, Boolean>> xGetLanguageMatcherData() { 59 List<R4<String, String, Integer, Boolean>> distanceList = new ArrayList<R4<String, String, Integer, Boolean>>(); 60 61 ICUResourceBundle suppData = LocaleMatcher.getICUSupplementalData(); 62 ICUResourceBundle languageMatchingNew = suppData.findTopLevel("languageMatchingNew"); 63 ICUResourceBundle written = (ICUResourceBundle) languageMatchingNew.get("written"); 64 65 for(UResourceBundleIterator iter = written.getIterator(); iter.hasNext();) { 66 ICUResourceBundle item = (ICUResourceBundle) iter.next(); 67 boolean oneway = item.getSize() > 3 && "1".equals(item.getString(3)); 68 distanceList.add( 69 (R4<String, String, Integer, Boolean>) // note: .freeze returning wrong type, so casting. 70 Row.of( 71 item.getString(0), 72 item.getString(1), 73 Integer.parseInt(item.getString(2)), 74 oneway) 75 .freeze()); 76 } 77 return Collections.unmodifiableList(distanceList); 78 } 79 80 @SuppressWarnings("unused") 81 private static Set<String> xGetParadigmLocales() { 82 ICUResourceBundle suppData = LocaleMatcher.getICUSupplementalData(); 83 ICUResourceBundle languageMatchingInfo = suppData.findTopLevel("languageMatchingInfo"); 84 ICUResourceBundle writtenParadigmLocales = (ICUResourceBundle) languageMatchingInfo.get("written") 85 .get("paradigmLocales"); 86 // paradigmLocales{ "en", "en-GB",... } 87 HashSet<String> paradigmLocales = new HashSet<String>(Arrays.asList(writtenParadigmLocales.getStringArray())); 88 return Collections.unmodifiableSet(paradigmLocales); 89 } 90 91 @SuppressWarnings("unused") 92 private static Map<String, String> xGetMatchVariables() { 93 ICUResourceBundle suppData = LocaleMatcher.getICUSupplementalData(); 94 ICUResourceBundle languageMatchingInfo = suppData.findTopLevel("languageMatchingInfo"); 95 ICUResourceBundle writtenMatchVariables = (ICUResourceBundle) languageMatchingInfo.get("written") 96 .get("matchVariable"); 97 // matchVariable{ americas{"019"} cnsar{"HK+MO"} ...} 98 99 HashMap<String,String> matchVariables = new HashMap<String,String>(); 100 for (Enumeration<String> enumer = writtenMatchVariables.getKeys(); enumer.hasMoreElements(); ) { 101 String key = enumer.nextElement(); 102 matchVariables.put(key, writtenMatchVariables.getString(key)); 103 } 104 return Collections.unmodifiableMap(matchVariables); 105 } 106 107 private static Multimap<String, String> xGetContainment() { 108 TreeMultimap<String,String> containment = TreeMultimap.create(); 109 containment 110 .putAll("001", "019", "002", "150", "142", "009") 111 .putAll("011", "BF", "BJ", "CI", "CV", "GH", "GM", "GN", "GW", "LR", "ML", "MR", "NE", "NG", "SH", "SL", "SN", "TG") 112 .putAll("013", "BZ", "CR", "GT", "HN", "MX", "NI", "PA", "SV") 113 .putAll("014", "BI", "DJ", "ER", "ET", "KE", "KM", "MG", "MU", "MW", "MZ", "RE", "RW", "SC", "SO", "SS", "TZ", "UG", "YT", "ZM", "ZW") 114 .putAll("142", "145", "143", "030", "034", "035") 115 .putAll("143", "TM", "TJ", "KG", "KZ", "UZ") 116 .putAll("145", "AE", "AM", "AZ", "BH", "CY", "GE", "IL", "IQ", "JO", "KW", "LB", "OM", "PS", "QA", "SA", "SY", "TR", "YE", "NT", "YD") 117 .putAll("015", "DZ", "EG", "EH", "LY", "MA", "SD", "TN", "EA", "IC") 118 .putAll("150", "154", "155", "151", "039") 119 .putAll("151", "BG", "BY", "CZ", "HU", "MD", "PL", "RO", "RU", "SK", "UA", "SU") 120 .putAll("154", "GG", "IM", "JE", "AX", "DK", "EE", "FI", "FO", "GB", "IE", "IS", "LT", "LV", "NO", "SE", "SJ") 121 .putAll("155", "AT", "BE", "CH", "DE", "FR", "LI", "LU", "MC", "NL", "DD", "FX") 122 .putAll("017", "AO", "CD", "CF", "CG", "CM", "GA", "GQ", "ST", "TD", "ZR") 123 .putAll("018", "BW", "LS", "NA", "SZ", "ZA") 124 .putAll("019", "021", "013", "029", "005", "003", "419") 125 .putAll("002", "015", "011", "017", "014", "018") 126 .putAll("021", "BM", "CA", "GL", "PM", "US") 127 .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") 128 .putAll("003", "021", "013", "029") 129 .putAll("030", "CN", "HK", "JP", "KP", "KR", "MN", "MO", "TW") 130 .putAll("035", "BN", "ID", "KH", "LA", "MM", "MY", "PH", "SG", "TH", "TL", "VN", "BU", "TP") 131 .putAll("039", "AD", "AL", "BA", "ES", "GI", "GR", "HR", "IT", "ME", "MK", "MT", "RS", "PT", "SI", "SM", "VA", "XK", "CS", "YU") 132 .putAll("419", "013", "029", "005") 133 .putAll("005", "AR", "BO", "BR", "CL", "CO", "EC", "FK", "GF", "GY", "PE", "PY", "SR", "UY", "VE") 134 .putAll("053", "AU", "NF", "NZ") 135 .putAll("054", "FJ", "NC", "PG", "SB", "VU") 136 .putAll("057", "FM", "GU", "KI", "MH", "MP", "NR", "PW") 137 .putAll("061", "AS", "CK", "NU", "PF", "PN", "TK", "TO", "TV", "WF", "WS") 138 .putAll("034", "AF", "BD", "BT", "IN", "IR", "LK", "MV", "NP", "PK") 139 .putAll("009", "053", "054", "057", "061", "QO") 140 .putAll("QO", "AQ", "BV", "CC", "CX", "GS", "HM", "IO", "TF", "UM", "AC", "CP", "DG", "TA") 141 ; 142 //Can't use following, because data from CLDR is discarded 143 // ICUResourceBundle suppData = LocaleMatcher.getICUSupplementalData(); 144 // UResourceBundle territoryContainment = suppData.get("territoryContainment"); 145 // for (int i = 0 ; i < territoryContainment.getSize(); i++) { 146 // UResourceBundle mapping = territoryContainment.get(i); 147 // String parent = mapping.getKey(); 148 // for (int j = 0 ; j < mapping.getSize(); j++) { 149 // String child = mapping.getString(j); 150 // containment.put(parent,child); 151 // System.out.println(parent + " => " + child); 152 // } 153 // } 154 TreeMultimap<String,String> containmentResolved = TreeMultimap.create(); 155 fill("001", containment, containmentResolved); 156 return ImmutableMultimap.copyOf(containmentResolved); 157 } 158 159 private static Set<String> fill(String region, TreeMultimap<String, String> containment, Multimap<String, String> toAddTo) { 160 Set<String> contained = containment.get(region); 161 if (contained == null) { 162 return Collections.emptySet(); 163 } 164 toAddTo.putAll(region, contained); // do top level 165 // then recursively 166 for (String subregion : contained) { 167 toAddTo.putAll(region, fill(subregion, containment, toAddTo)); 168 } 169 return toAddTo.get(region); 170 } 171 172 173 static final Multimap<String,String> CONTAINER_TO_CONTAINED; 174 static final Multimap<String,String> CONTAINER_TO_CONTAINED_FINAL; 175 static { 176 // Multimap<String, String> containerToContainedTemp = xGetContainment(); 177 // fill(Region.getInstance("001"), containerToContainedTemp); 178 179 CONTAINER_TO_CONTAINED = xGetContainment(); 180 Multimap<String, String> containerToFinalContainedBuilder = TreeMultimap.create(); 181 for (Entry<String, Set<String>> entry : CONTAINER_TO_CONTAINED.asMap().entrySet()) { 182 String container = entry.getKey(); 183 for (String contained : entry.getValue()) { 184 if (CONTAINER_TO_CONTAINED.get(contained) == null) { 185 containerToFinalContainedBuilder.put(container, contained); 186 } 187 } 188 } 189 CONTAINER_TO_CONTAINED_FINAL = ImmutableMultimap.copyOf(containerToFinalContainedBuilder); 190 } 191 192 final static private Set<String> ALL_FINAL_REGIONS = ImmutableSet.copyOf(CONTAINER_TO_CONTAINED_FINAL.get("001")); 193 194 // end of data from CLDR 195 196 private final DistanceTable languageDesired2Supported; 197 private final RegionMapper regionMapper; 198 private final int defaultLanguageDistance; 199 private final int defaultScriptDistance; 200 private final int defaultRegionDistance; 201 202 @Deprecated 203 public static abstract class DistanceTable { 204 abstract int getDistance(String desiredLang, String supportedlang, Output<DistanceTable> table, boolean starEquals); 205 abstract Set<String> getCloser(int threshold); 206 abstract String toString(boolean abbreviate); 207 public DistanceTable compact() { 208 return this; 209 } 210 // public Integer getInternalDistance(String a, String b) { 211 // return null; 212 // } 213 public DistanceNode getInternalNode(String any, String any2) { 214 return null; 215 } 216 public Map<String, Set<String>> getInternalMatches() { 217 return null; 218 } 219 public boolean isEmpty() { 220 return true; 221 } 222 } 223 224 @Deprecated 225 public static class DistanceNode { 226 final int distance; 227 228 public DistanceNode(int distance) { 229 this.distance = distance; 230 } 231 232 public DistanceTable getDistanceTable() { 233 return null; 234 } 235 236 @Override 237 public boolean equals(Object obj) { 238 return this == obj || 239 (obj != null 240 && obj.getClass() == this.getClass() 241 && distance == ((DistanceNode) obj).distance); 242 } 243 @Override 244 public int hashCode() { 245 return distance; 246 } 247 @Override 248 public String toString() { 249 return "\ndistance: " + distance; 250 } 251 } 252 253 private interface IdMapper<K,V> { 254 public V toId(K source); 255 } 256 257 static class IdMakerFull<T> implements IdMapper<T,Integer> { 258 private final Map<T, Integer> objectToInt = new HashMap<T, Integer>(); 259 private final List<T> intToObject = new ArrayList<T>(); 260 final String name; // for debugging 261 262 IdMakerFull(String name) { 263 this.name = name; 264 } 265 266 IdMakerFull() { 267 this("unnamed"); 268 } 269 270 IdMakerFull(String name, T zeroValue) { 271 this(name); 272 add(zeroValue); 273 } 274 275 /** 276 * Return an id, making one if there wasn't one already. 277 */ 278 public Integer add(T source) { 279 Integer result = objectToInt.get(source); 280 if (result == null) { 281 Integer newResult = intToObject.size(); 282 objectToInt.put(source, newResult); 283 intToObject.add(source); 284 return newResult; 285 } else { 286 return result; 287 } 288 } 289 290 /** 291 * Return an id, or null if there is none. 292 */ 293 @Override 294 public Integer toId(T source) { 295 return objectToInt.get(source); 296 // return value == null ? 0 : value; 297 } 298 299 /** 300 * Return the object for the id, or null if there is none. 301 */ 302 public T fromId(int id) { 303 return intToObject.get(id); 304 } 305 306 /** 307 * Return interned object 308 */ 309 public T intern(T source) { 310 return fromId(add(source)); 311 } 312 313 public int size() { 314 return intToObject.size(); 315 } 316 /** 317 * Same as add, except if the object didn't have an id, return null; 318 */ 319 public Integer getOldAndAdd(T source) { 320 Integer result = objectToInt.get(source); 321 if (result == null) { 322 Integer newResult = intToObject.size(); 323 objectToInt.put(source, newResult); 324 intToObject.add(source); 325 } 326 return result; 327 } 328 329 @Override 330 public String toString() { 331 return size() + ": " + intToObject; 332 } 333 @Override 334 public boolean equals(Object obj) { 335 return this == obj || 336 (obj != null 337 && obj.getClass() == this.getClass() 338 && intToObject.equals(((IdMakerFull<?>) obj).intToObject)); 339 } 340 @Override 341 public int hashCode() { 342 return intToObject.hashCode(); 343 } 344 } 345 346 static class StringDistanceNode extends DistanceNode { 347 final DistanceTable distanceTable; 348 349 public StringDistanceNode(int distance, DistanceTable distanceTable) { 350 super(distance); 351 this.distanceTable = distanceTable; 352 } 353 354 @Override 355 public boolean equals(Object obj) { 356 StringDistanceNode other; 357 return this == obj || 358 (obj != null 359 && obj.getClass() == this.getClass() 360 && distance == (other = (StringDistanceNode) obj).distance 361 && Utility.equals(distanceTable, other.distanceTable) 362 && super.equals(other)); 363 } 364 @Override 365 public int hashCode() { 366 return distance ^ Utility.hashCode(distanceTable); 367 } 368 369 StringDistanceNode(int distance) { 370 this(distance, new StringDistanceTable()); 371 } 372 373 public void addSubtables(String desiredSub, String supportedSub, CopyIfEmpty r) { 374 ((StringDistanceTable) distanceTable).addSubtables(desiredSub, supportedSub, r); 375 } 376 @Override 377 public String toString() { 378 return "distance: " + distance + "\n" + distanceTable; 379 } 380 381 public void copyTables(StringDistanceTable value) { 382 if (value != null) { 383 ((StringDistanceTable)distanceTable).copy(value); 384 } 385 } 386 387 @Override 388 public DistanceTable getDistanceTable() { 389 return distanceTable; 390 } 391 } 392 393 public XLocaleDistance(DistanceTable datadistancetable2, RegionMapper regionMapper) { 394 languageDesired2Supported = datadistancetable2; 395 this.regionMapper = regionMapper; 396 397 StringDistanceNode languageNode = (StringDistanceNode) ((StringDistanceTable) languageDesired2Supported).subtables.get(ANY).get(ANY); 398 defaultLanguageDistance = languageNode.distance; 399 StringDistanceNode scriptNode = (StringDistanceNode) ((StringDistanceTable)languageNode.distanceTable).subtables.get(ANY).get(ANY); 400 defaultScriptDistance = scriptNode.distance; 401 DistanceNode regionNode = ((StringDistanceTable)scriptNode.distanceTable).subtables.get(ANY).get(ANY); 402 defaultRegionDistance = regionNode.distance; 403 } 404 405 @SuppressWarnings("rawtypes") 406 private static Map newMap() { // for debugging 407 return new TreeMap(); 408 } 409 410 /** 411 * Internal class 412 */ 413 @Deprecated 414 public static class StringDistanceTable extends DistanceTable { 415 final Map<String, Map<String, DistanceNode>> subtables; 416 417 StringDistanceTable(Map<String, Map<String, DistanceNode>> tables) { 418 subtables = tables; 419 } 420 @SuppressWarnings("unchecked") 421 StringDistanceTable() { 422 this(newMap()); 423 } 424 425 @Override 426 public boolean isEmpty() { 427 return subtables.isEmpty(); 428 } 429 430 @Override 431 public boolean equals(Object obj) { 432 return this == obj || 433 (obj != null 434 && obj.getClass() == this.getClass() 435 && subtables.equals(((StringDistanceTable) obj).subtables)); 436 } 437 @Override 438 public int hashCode() { 439 return subtables.hashCode(); 440 } 441 442 @Override 443 public int getDistance(String desired, String supported, Output<DistanceTable> distanceTable, boolean starEquals) { 444 boolean star = false; 445 Map<String, DistanceNode> sub2 = subtables.get(desired); 446 if (sub2 == null) { 447 sub2 = subtables.get(ANY); // <*, supported> 448 star = true; 449 } 450 DistanceNode value = sub2.get(supported); // <*/desired, supported> 451 if (value == null) { 452 value = sub2.get(ANY); // <*/desired, *> 453 if (value == null && !star) { 454 sub2 = subtables.get(ANY); // <*, supported> 455 value = sub2.get(supported); 456 if (value == null) { 457 value = sub2.get(ANY); // <*, *> 458 } 459 } 460 star = true; 461 } 462 if (distanceTable != null) { 463 distanceTable.value = ((StringDistanceNode) value).distanceTable; 464 } 465 return starEquals && star && desired.equals(supported) ? 0 : value.distance; 466 } 467 468 public void copy(StringDistanceTable other) { 469 for (Entry<String, Map<String, DistanceNode>> e1 : other.subtables.entrySet()) { 470 for (Entry<String, DistanceNode> e2 : e1.getValue().entrySet()) { 471 DistanceNode value = e2.getValue(); 472 @SuppressWarnings("unused") 473 DistanceNode subNode = addSubtable(e1.getKey(), e2.getKey(), value.distance); 474 } 475 } 476 } 477 478 @SuppressWarnings("unchecked") 479 DistanceNode addSubtable(String desired, String supported, int distance) { 480 Map<String, DistanceNode> sub2 = subtables.get(desired); 481 if (sub2 == null) { 482 subtables.put(desired, sub2 = newMap()); 483 } 484 DistanceNode oldNode = sub2.get(supported); 485 if (oldNode != null) { 486 return oldNode; 487 } 488 489 final StringDistanceNode newNode = new StringDistanceNode(distance); 490 sub2.put(supported, newNode); 491 return newNode; 492 } 493 494 /** 495 * Return null if value doesn't exist 496 */ 497 private DistanceNode getNode(String desired, String supported) { 498 Map<String, DistanceNode> sub2 = subtables.get(desired); 499 if (sub2 == null) { 500 return null; 501 } 502 return sub2.get(supported); 503 } 504 505 506 /** add table for each subitem that matches and doesn't have a table already 507 */ 508 public void addSubtables( 509 String desired, String supported, 510 Predicate<DistanceNode> action) { 511 DistanceNode node = getNode(desired, supported); 512 if (node == null) { 513 // get the distance it would have 514 Output<DistanceTable> node2 = new Output<DistanceTable>(); 515 int distance = getDistance(desired, supported, node2, true); 516 // now add it 517 node = addSubtable(desired, supported, distance); 518 if (node2.value != null) { 519 ((StringDistanceNode)node).copyTables((StringDistanceTable)(node2.value)); 520 } 521 } 522 action.test(node); 523 } 524 525 public void addSubtables(String desiredLang, String supportedLang, 526 String desiredScript, String supportedScript, 527 int percentage) { 528 529 // add to all the values that have the matching desiredLang and supportedLang 530 @SuppressWarnings("unused") 531 boolean haveKeys = false; 532 for (Entry<String, Map<String, DistanceNode>> e1 : subtables.entrySet()) { 533 String key1 = e1.getKey(); 534 final boolean desiredIsKey = desiredLang.equals(key1); 535 if (desiredIsKey || desiredLang.equals(ANY)) { 536 for (Entry<String, DistanceNode> e2 : e1.getValue().entrySet()) { 537 String key2 = e2.getKey(); 538 final boolean supportedIsKey = supportedLang.equals(key2); 539 haveKeys |= (desiredIsKey && supportedIsKey); 540 if (supportedIsKey || supportedLang.equals(ANY)) { 541 DistanceNode value = e2.getValue(); 542 ((StringDistanceTable)value.getDistanceTable()).addSubtable(desiredScript, supportedScript, percentage); 543 } 544 } 545 } 546 } 547 // now add the sequence explicitly 548 StringDistanceTable dt = new StringDistanceTable(); 549 dt.addSubtable(desiredScript, supportedScript, percentage); 550 CopyIfEmpty r = new CopyIfEmpty(dt); 551 addSubtables(desiredLang, supportedLang, r); 552 } 553 554 public void addSubtables(String desiredLang, String supportedLang, 555 String desiredScript, String supportedScript, 556 String desiredRegion, String supportedRegion, 557 int percentage) { 558 559 // add to all the values that have the matching desiredLang and supportedLang 560 @SuppressWarnings("unused") 561 boolean haveKeys = false; 562 for (Entry<String, Map<String, DistanceNode>> e1 : subtables.entrySet()) { 563 String key1 = e1.getKey(); 564 final boolean desiredIsKey = desiredLang.equals(key1); 565 if (desiredIsKey || desiredLang.equals(ANY)) { 566 for (Entry<String, DistanceNode> e2 : e1.getValue().entrySet()) { 567 String key2 = e2.getKey(); 568 final boolean supportedIsKey = supportedLang.equals(key2); 569 haveKeys |= (desiredIsKey && supportedIsKey); 570 if (supportedIsKey || supportedLang.equals(ANY)) { 571 StringDistanceNode value = (StringDistanceNode) e2.getValue(); 572 ((StringDistanceTable)value.distanceTable).addSubtables(desiredScript, supportedScript, desiredRegion, supportedRegion, percentage); 573 } 574 } 575 } 576 } 577 // now add the sequence explicitly 578 579 StringDistanceTable dt = new StringDistanceTable(); 580 dt.addSubtable(desiredRegion, supportedRegion, percentage); 581 AddSub r = new AddSub(desiredScript, supportedScript, dt); 582 addSubtables(desiredLang, supportedLang, r); 583 } 584 585 @Override 586 public String toString() { 587 return toString(false); 588 } 589 590 @Override 591 public String toString(boolean abbreviate) { 592 return toString(abbreviate, "", new IdMakerFull<Object>("interner"), new StringBuilder()).toString(); 593 } 594 595 public StringBuilder toString(boolean abbreviate, String indent, IdMakerFull<Object> intern, StringBuilder buffer) { 596 String indent2 = indent.isEmpty() ? "" : "\t"; 597 Integer id = abbreviate ? intern.getOldAndAdd(subtables) : null; 598 if (id != null) { 599 buffer.append(indent2).append('#').append(id).append('\n'); 600 } else for (Entry<String, Map<String, DistanceNode>> e1 : subtables.entrySet()) { 601 final Map<String, DistanceNode> subsubtable = e1.getValue(); 602 buffer.append(indent2).append(e1.getKey()); 603 String indent3 = "\t"; 604 id = abbreviate ? intern.getOldAndAdd(subsubtable) : null; 605 if (id != null) { 606 buffer.append(indent3).append('#').append(id).append('\n'); 607 } else for (Entry<String, DistanceNode> e2 : subsubtable.entrySet()) { 608 DistanceNode value = e2.getValue(); 609 buffer.append(indent3).append(e2.getKey()); 610 id = abbreviate ? intern.getOldAndAdd(value) : null; 611 if (id != null) { 612 buffer.append('\t').append('#').append(id).append('\n'); 613 } else { 614 buffer.append('\t').append(value.distance); 615 final DistanceTable distanceTable = value.getDistanceTable(); 616 if (distanceTable != null) { 617 id = abbreviate ? intern.getOldAndAdd(distanceTable) : null; 618 if (id != null) { 619 buffer.append('\t').append('#').append(id).append('\n'); 620 } else { 621 ((StringDistanceTable)distanceTable).toString(abbreviate, indent+"\t\t\t", intern, buffer); 622 } 623 } else { 624 buffer.append('\n'); 625 } 626 } 627 indent3 = indent+'\t'; 628 } 629 indent2 = indent; 630 } 631 return buffer; 632 } 633 634 @Override 635 public StringDistanceTable compact() { 636 return new CompactAndImmutablizer().compact(this); 637 } 638 639 @Override 640 public Set<String> getCloser(int threshold) { 641 Set<String> result = new HashSet<String>(); 642 for (Entry<String, Map<String, DistanceNode>> e1 : subtables.entrySet()) { 643 String desired = e1.getKey(); 644 for (Entry<String, DistanceNode> e2 : e1.getValue().entrySet()) { 645 if (e2.getValue().distance < threshold) { 646 result.add(desired); 647 break; 648 } 649 } 650 } 651 return result; 652 } 653 654 public Integer getInternalDistance(String a, String b) { 655 Map<String, DistanceNode> subsub = subtables.get(a); 656 if (subsub == null) { 657 return null; 658 } 659 DistanceNode dnode = subsub.get(b); 660 return dnode == null ? null : dnode.distance; 661 } 662 663 @Override 664 public DistanceNode getInternalNode(String a, String b) { 665 Map<String, DistanceNode> subsub = subtables.get(a); 666 if (subsub == null) { 667 return null; 668 } 669 return subsub.get(b); 670 } 671 672 @Override 673 public Map<String, Set<String>> getInternalMatches() { 674 Map<String, Set<String>> result = new LinkedHashMap<String, Set<String>>(); 675 for (Entry<String, Map<String, DistanceNode>> entry : subtables.entrySet()) { 676 result.put(entry.getKey(), new LinkedHashSet<String>(entry.getValue().keySet())); 677 } 678 return result; 679 } 680 } 681 682 static class CopyIfEmpty implements Predicate<DistanceNode> { 683 private final StringDistanceTable toCopy; 684 CopyIfEmpty(StringDistanceTable resetIfNotNull) { 685 this.toCopy = resetIfNotNull; 686 } 687 @Override 688 public boolean test(DistanceNode node) { 689 final StringDistanceTable subtables = (StringDistanceTable) node.getDistanceTable(); 690 if (subtables.subtables.isEmpty()) { 691 subtables.copy(toCopy); 692 } 693 return true; 694 } 695 } 696 697 static class AddSub implements Predicate<DistanceNode> { 698 private final String desiredSub; 699 private final String supportedSub; 700 private final CopyIfEmpty r; 701 702 AddSub(String desiredSub, String supportedSub, StringDistanceTable distanceTableToCopy) { 703 this.r = new CopyIfEmpty(distanceTableToCopy); 704 this.desiredSub = desiredSub; 705 this.supportedSub = supportedSub; 706 } 707 @Override 708 public boolean test(DistanceNode node) { 709 if (node == null) { 710 throw new IllegalArgumentException("bad structure"); 711 } else { 712 ((StringDistanceNode)node).addSubtables(desiredSub, supportedSub, r); 713 } 714 return true; 715 } 716 } 717 718 public int distance(ULocale desired, ULocale supported, int threshold, DistanceOption distanceOption) { 719 LSR supportedLSR = LSR.fromMaximalized(supported); 720 LSR desiredLSR = LSR.fromMaximalized(desired); 721 return distanceRaw(desiredLSR, supportedLSR, threshold, distanceOption); 722 } 723 724 /** 725 * Returns distance, from 0 to ABOVE_THRESHOLD. 726 * ULocales must be in canonical, addLikelySubtags format. Returns distance 727 */ 728 public int distanceRaw(LSR desired, LSR supported, int threshold, DistanceOption distanceOption) { 729 return distanceRaw(desired.language, supported.language, 730 desired.script, supported.script, 731 desired.region, supported.region, 732 threshold, distanceOption); 733 } 734 735 public enum DistanceOption {NORMAL, SCRIPT_FIRST} 736 737 /** 738 * Returns distance, from 0 to ABOVE_THRESHOLD. 739 * ULocales must be in canonical, addLikelySubtags format. Returns distance 740 */ 741 public int distanceRaw( 742 String desiredLang, String supportedlang, 743 String desiredScript, String supportedScript, 744 String desiredRegion, String supportedRegion, 745 int threshold, 746 DistanceOption distanceOption) { 747 748 Output<DistanceTable> subtable = new Output<DistanceTable>(); 749 750 int distance = languageDesired2Supported.getDistance(desiredLang, supportedlang, subtable, true); 751 boolean scriptFirst = distanceOption == DistanceOption.SCRIPT_FIRST; 752 if (scriptFirst) { 753 distance >>= 2; 754 } 755 if (distance < 0) { 756 distance = 0; 757 } else if (distance >= threshold) { 758 return ABOVE_THRESHOLD; 759 } 760 761 int scriptDistance = subtable.value.getDistance(desiredScript, supportedScript, subtable, true); 762 if (scriptFirst) { 763 scriptDistance >>= 1; 764 } 765 distance += scriptDistance; 766 if (distance >= threshold) { 767 return ABOVE_THRESHOLD; 768 } 769 770 if (desiredRegion.equals(supportedRegion)) { 771 return distance; 772 } 773 774 // From here on we know the regions are not equal 775 776 final String desiredPartition = regionMapper.toId(desiredRegion); 777 final String supportedPartition = regionMapper.toId(supportedRegion); 778 int subdistance; 779 780 // check for macros. If one is found, we take the maximum distance 781 // this could be optimized by adding some more structure, but probably not worth it. 782 783 Collection<String> desiredPartitions = desiredPartition.isEmpty() ? regionMapper.macroToPartitions.get(desiredRegion) : null; 784 Collection<String> supportedPartitions = supportedPartition.isEmpty() ? regionMapper.macroToPartitions.get(supportedRegion) : null; 785 if (desiredPartitions != null || supportedPartitions != null) { 786 subdistance = 0; 787 // make the code simple for now 788 if (desiredPartitions == null) { 789 desiredPartitions = Collections.singleton(desiredPartition); 790 } 791 if (supportedPartitions == null) { 792 supportedPartitions = Collections.singleton(supportedPartition); 793 } 794 795 for (String desiredPartition2 : desiredPartitions) { 796 for (String supportedPartition2 : supportedPartitions) { 797 int tempSubdistance = subtable.value.getDistance(desiredPartition2, supportedPartition2, null, false); 798 if (subdistance < tempSubdistance) { 799 subdistance = tempSubdistance; 800 } 801 } 802 } 803 } else { 804 subdistance = subtable.value.getDistance(desiredPartition, supportedPartition, null, false); 805 } 806 distance += subdistance; 807 return distance >= threshold ? ABOVE_THRESHOLD : distance; 808 } 809 810 811 private static final XLocaleDistance DEFAULT; 812 813 public static XLocaleDistance getDefault() { 814 return DEFAULT; 815 } 816 817 static { 818 String[][] variableOverrides = { 819 {"$enUS", "AS+GU+MH+MP+PR+UM+US+VI"}, 820 821 {"$cnsar", "HK+MO"}, 822 823 {"$americas", "019"}, 824 825 {"$maghreb", "MA+DZ+TN+LY+MR+EH"}, 826 }; 827 String[] paradigmRegions = { 828 "en", "en-GB", "es", "es-419", "pt-BR", "pt-PT" 829 }; 830 String[][] regionRuleOverrides = { 831 {"ar_*_$maghreb", "ar_*_$maghreb", "96"}, 832 {"ar_*_$!maghreb", "ar_*_$!maghreb", "96"}, 833 {"ar_*_*", "ar_*_*", "95"}, 834 835 {"en_*_$enUS", "en_*_$enUS", "96"}, 836 {"en_*_$!enUS", "en_*_$!enUS", "96"}, 837 {"en_*_*", "en_*_*", "95"}, 838 839 {"es_*_$americas", "es_*_$americas", "96"}, 840 {"es_*_$!americas", "es_*_$!americas", "96"}, 841 {"es_*_*", "es_*_*", "95"}, 842 843 {"pt_*_$americas", "pt_*_$americas", "96"}, 844 {"pt_*_$!americas", "pt_*_$!americas", "96"}, 845 {"pt_*_*", "pt_*_*", "95"}, 846 847 {"zh_Hant_$cnsar", "zh_Hant_$cnsar", "96"}, 848 {"zh_Hant_$!cnsar", "zh_Hant_$!cnsar", "96"}, 849 {"zh_Hant_*", "zh_Hant_*", "95"}, 850 851 {"*_*_*", "*_*_*", "96"}, 852 }; 853 854 Builder rmb = new RegionMapper.Builder().addParadigms(paradigmRegions); 855 for (String[] variableRule : variableOverrides) { 856 rmb.add(variableRule[0], variableRule[1]); 857 } 858 if (PRINT_OVERRIDES) { 859 System.out.println("\t\t<languageMatches type=\"written\" alt=\"enhanced\">"); 860 System.out.println("\t\t\t<paradigmLocales locales=\"" + XCldrStub.join(paradigmRegions, " ") 861 + "\"/>"); 862 for (String[] variableRule : variableOverrides) { 863 System.out.println("\t\t\t<matchVariable id=\"" + variableRule[0] 864 + "\" value=\"" 865 + variableRule[1] 866 + "\"/>"); 867 } 868 } 869 870 final StringDistanceTable defaultDistanceTable = new StringDistanceTable(); 871 final RegionMapper defaultRegionMapper = rmb.build(); 872 873 Splitter bar = Splitter.on('_'); 874 875 @SuppressWarnings({"unchecked", "rawtypes"}) 876 List<Row.R4<List<String>, List<String>, Integer, Boolean>>[] sorted = new ArrayList[3]; 877 sorted[0] = new ArrayList<Row.R4<List<String>, List<String>, Integer, Boolean>>(); 878 sorted[1] = new ArrayList<Row.R4<List<String>, List<String>, Integer, Boolean>>(); 879 sorted[2] = new ArrayList<Row.R4<List<String>, List<String>, Integer, Boolean>>(); 880 881 // sort the rules so that the language-only are first, then the language-script, and finally the language-script-region. 882 for (R4<String, String, Integer, Boolean> info : xGetLanguageMatcherData()) { 883 String desiredRaw = info.get0(); 884 String supportedRaw = info.get1(); 885 List<String> desired = bar.splitToList(desiredRaw); 886 List<String> supported = bar.splitToList(supportedRaw); 887 Boolean oneway = info.get3(); 888 int distance = desiredRaw.equals("*_*") ? 50 : info.get2(); 889 int size = desired.size(); 890 891 // for now, skip size == 3 892 if (size == 3) continue; 893 894 sorted[size-1].add(Row.of(desired, supported, distance, oneway)); 895 } 896 897 for (List<Row.R4<List<String>, List<String>, Integer, Boolean>> item1 : sorted) { 898 for (Row.R4<List<String>, List<String>, Integer, Boolean> item2 : item1) { 899 List<String> desired = item2.get0(); 900 List<String> supported = item2.get1(); 901 Integer distance = item2.get2(); 902 Boolean oneway = item2.get3(); 903 add(defaultDistanceTable, desired, supported, distance); 904 if (oneway != Boolean.TRUE && !desired.equals(supported)) { 905 add(defaultDistanceTable, supported, desired, distance); 906 } 907 printMatchXml(desired, supported, distance, oneway); 908 } 909 } 910 911 // add new size=3 912 for (String[] rule : regionRuleOverrides) { 913 // if (PRINT_OVERRIDES) System.out.println("\t\t\t<languageMatch desired=\"" 914 // + rule[0] 915 // + "\" supported=\"" 916 // + rule[1] 917 // + "\" distance=\"" 918 // + rule[2] 919 // + "\"/>"); 920 // if (rule[0].equals("en_*_*") || rule[1].equals("*_*_*")) { 921 // int debug = 0; 922 // } 923 List<String> desiredBase = new ArrayList<String>(bar.splitToList(rule[0])); 924 List<String> supportedBase = new ArrayList<String>(bar.splitToList(rule[1])); 925 Integer distance = 100-Integer.parseInt(rule[2]); 926 printMatchXml(desiredBase, supportedBase, distance, false); 927 928 Collection<String> desiredRegions = defaultRegionMapper.getIdsFromVariable(desiredBase.get(2)); 929 if (desiredRegions.isEmpty()) { 930 throw new IllegalArgumentException("Bad region variable: " + desiredBase.get(2)); 931 } 932 Collection<String> supportedRegions = defaultRegionMapper.getIdsFromVariable(supportedBase.get(2)); 933 if (supportedRegions.isEmpty()) { 934 throw new IllegalArgumentException("Bad region variable: " + supportedBase.get(2)); 935 } 936 for (String desiredRegion2 : desiredRegions) { 937 desiredBase.set(2, desiredRegion2.toString()); // fix later 938 for (String supportedRegion2 : supportedRegions) { 939 supportedBase.set(2, supportedRegion2.toString()); // fix later 940 add(defaultDistanceTable, desiredBase, supportedBase, distance); 941 add(defaultDistanceTable, supportedBase, desiredBase, distance); 942 } 943 } 944 } 945 if (PRINT_OVERRIDES) { 946 System.out.println("\t\t</languageMatches>"); 947 } 948 949 DEFAULT = new XLocaleDistance(defaultDistanceTable.compact(), defaultRegionMapper); 950 951 if (PRINT_OVERRIDES) { 952 System.out.println(defaultRegionMapper); 953 System.out.println(defaultDistanceTable); 954 throw new IllegalArgumentException(); 955 } 956 } 957 958 private static void printMatchXml(List<String> desired, List<String> supported, Integer distance, Boolean oneway) { 959 if (PRINT_OVERRIDES) { 960 String desiredStr = CollectionUtilities.join(desired, "_"); 961 String supportedStr = CollectionUtilities.join(supported, "_"); 962 String desiredName = fixedName(desired); 963 String supportedName = fixedName(supported); 964 System.out.println("\t\t\t<languageMatch" 965 + " desired=\"" + desiredStr 966 + "\"\tsupported=\"" + supportedStr 967 + "\"\tdistance=\"" + distance 968 + (!oneway ? "" : "\"\toneway=\"true") 969 + "\"/>\t<!-- " + desiredName + " " + supportedName + " -->"); 970 } 971 } 972 973 private static String fixedName(List<String> match) { 974 List<String> alt = new ArrayList<String>(match); 975 int size = alt.size(); 976 assert size >= 1 && size <= 3; 977 978 StringBuilder result = new StringBuilder(); 979 980 if (size >= 3) { 981 String region = alt.get(2); 982 if (region.equals("*") || region.startsWith("$")) { 983 result.append(region); 984 } else { 985 result.append(english.regionDisplayName(region)); 986 } 987 } 988 if (size >= 2) { 989 String script = alt.get(1); 990 if (script.equals("*")) { 991 result.insert(0, script); 992 } else { 993 result.insert(0, english.scriptDisplayName(script)); 994 } 995 } 996 if (size >= 1) { 997 String language = alt.get(0); 998 if (language.equals("*")) { 999 result.insert(0, language); 1000 } else { 1001 result.insert(0, english.languageDisplayName(language)); 1002 } 1003 } 1004 return CollectionUtilities.join(alt, "; "); 1005 } 1006 1007 static public void add(StringDistanceTable languageDesired2Supported, List<String> desired, List<String> supported, int percentage) { 1008 int size = desired.size(); 1009 if (size != supported.size() || size < 1 || size > 3) { 1010 throw new IllegalArgumentException(); 1011 } 1012 final String desiredLang = fixAny(desired.get(0)); 1013 final String supportedLang = fixAny(supported.get(0)); 1014 if (size == 1) { 1015 languageDesired2Supported.addSubtable(desiredLang, supportedLang, percentage); 1016 } else { 1017 final String desiredScript = fixAny(desired.get(1)); 1018 final String supportedScript = fixAny(supported.get(1)); 1019 if (size == 2) { 1020 languageDesired2Supported.addSubtables(desiredLang, supportedLang, desiredScript, supportedScript, percentage); 1021 } else { 1022 final String desiredRegion = fixAny(desired.get(2)); 1023 final String supportedRegion = fixAny(supported.get(2)); 1024 languageDesired2Supported.addSubtables(desiredLang, supportedLang, desiredScript, supportedScript, desiredRegion, supportedRegion, percentage); 1025 } 1026 } 1027 } 1028 1029 @Override 1030 public String toString() { 1031 return toString(false); 1032 } 1033 1034 public String toString(boolean abbreviate) { 1035 return regionMapper + "\n" + languageDesired2Supported.toString(abbreviate); 1036 } 1037 1038 1039 // public static XLocaleDistance createDefaultInt() { 1040 // IntDistanceTable d = new IntDistanceTable(DEFAULT_DISTANCE_TABLE); 1041 // return new XLocaleDistance(d, DEFAULT_REGION_MAPPER); 1042 // } 1043 1044 static Set<String> getContainingMacrosFor(Collection<String> input, Set<String> output) { 1045 output.clear(); 1046 for (Entry<String, Set<String>> entry : CONTAINER_TO_CONTAINED.asMap().entrySet()) { 1047 if (input.containsAll(entry.getValue())) { // example; if all southern Europe are contained, then add S. Europe 1048 output.add(entry.getKey()); 1049 } 1050 } 1051 return output; 1052 } 1053 1054 static class RegionMapper implements IdMapper<String,String> { 1055 /** 1056 * 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} 1057 * When we hit a rule that contains a variable, we replace that rule by multiple rules for the partitions. 1058 */ 1059 final Multimap<String,String> variableToPartition; 1060 /** 1061 * Used for executing the rules. We map a region to a partition before processing. 1062 */ 1063 final Map<String,String> regionToPartition; 1064 /** 1065 * Used to support es_419 compared to es_AR, etc. 1066 */ 1067 final Multimap<String,String> macroToPartitions; 1068 /** 1069 * Used to get the paradigm region for a cluster, if there is one 1070 */ 1071 final Set<ULocale> paradigms; 1072 1073 private RegionMapper( 1074 Multimap<String, String> variableToPartitionIn, 1075 Map<String, String> regionToPartitionIn, 1076 Multimap<String,String> macroToPartitionsIn, 1077 Set<ULocale> paradigmsIn) { 1078 variableToPartition = ImmutableMultimap.copyOf(variableToPartitionIn); 1079 regionToPartition = ImmutableMap.copyOf(regionToPartitionIn); 1080 macroToPartitions = ImmutableMultimap.copyOf(macroToPartitionsIn); 1081 paradigms = ImmutableSet.copyOf(paradigmsIn); 1082 } 1083 1084 @Override 1085 public String toId(String region) { 1086 String result = regionToPartition.get(region); 1087 return result == null ? "" : result; 1088 } 1089 1090 public Collection<String> getIdsFromVariable(String variable) { 1091 if (variable.equals("*")) { 1092 return Collections.singleton("*"); 1093 } 1094 Collection<String> result = variableToPartition.get(variable); 1095 if (result == null || result.isEmpty()) { 1096 throw new IllegalArgumentException("Variable not defined: " + variable); 1097 } 1098 return result; 1099 } 1100 1101 public Set<String> regions() { 1102 return regionToPartition.keySet(); 1103 } 1104 1105 public Set<String> variables() { 1106 return variableToPartition.keySet(); 1107 } 1108 1109 @Override 1110 public String toString() { 1111 TreeMultimap<String, String> partitionToVariables = Multimaps.invertFrom(variableToPartition, 1112 TreeMultimap.<String, String>create()); 1113 TreeMultimap<String, String> partitionToRegions = TreeMultimap.create(); 1114 for (Entry<String, String> e : regionToPartition.entrySet()) { 1115 partitionToRegions.put(e.getValue(), e.getKey()); 1116 } 1117 StringBuilder buffer = new StringBuilder(); 1118 buffer.append("Partition Variables Regions (final)"); 1119 for (Entry<String, Set<String>> e : partitionToVariables.asMap().entrySet()) { 1120 buffer.append('\n'); 1121 buffer.append(e.getKey() + "\t" + e.getValue() + "\t" + partitionToRegions.get(e.getKey())); 1122 } 1123 buffer.append("\nMacro Partitions"); 1124 for (Entry<String, Set<String>> e : macroToPartitions.asMap().entrySet()) { 1125 buffer.append('\n'); 1126 buffer.append(e.getKey() + "\t" + e.getValue()); 1127 } 1128 1129 return buffer.toString(); 1130 } 1131 1132 static class Builder { 1133 final private Multimap<String, String> regionToRawPartition = TreeMultimap.create(); 1134 final private RegionSet regionSet = new RegionSet(); 1135 final private Set<ULocale> paradigms = new LinkedHashSet<ULocale>(); 1136 1137 void add(String variable, String barString) { 1138 Set<String> tempRegions = regionSet.parseSet(barString); 1139 1140 for (String region : tempRegions) { 1141 regionToRawPartition.put(region, variable); 1142 } 1143 1144 // now add the inverse variable 1145 1146 Set<String> inverse = regionSet.inverse(); 1147 String inverseVariable = "$!" + variable.substring(1); 1148 for (String region : inverse) { 1149 regionToRawPartition.put(region, inverseVariable); 1150 } 1151 } 1152 1153 public Builder addParadigms(String... paradigmRegions) { 1154 for (String paradigm : paradigmRegions) { 1155 paradigms.add(new ULocale(paradigm)); 1156 } 1157 return this; 1158 } 1159 1160 RegionMapper build() { 1161 final IdMakerFull<Collection<String>> id = new IdMakerFull<Collection<String>>("partition"); 1162 Multimap<String,String> variableToPartitions = TreeMultimap.create(); 1163 Map<String,String> regionToPartition = new TreeMap<String,String>(); 1164 Multimap<String,String> partitionToRegions = TreeMultimap.create(); 1165 1166 for (Entry<String, Set<String>> e : regionToRawPartition.asMap().entrySet()) { 1167 final String region = e.getKey(); 1168 final Collection<String> rawPartition = e.getValue(); 1169 String partition = String.valueOf((char)('' + id.add(rawPartition))); 1170 1171 regionToPartition.put(region, partition); 1172 partitionToRegions.put(partition, region); 1173 1174 for (String variable : rawPartition) { 1175 variableToPartitions.put(variable, partition); 1176 } 1177 } 1178 1179 // we get a mapping of each macro to the partitions it intersects with 1180 Multimap<String,String> macroToPartitions = TreeMultimap.create(); 1181 for (Entry<String, Set<String>> e : CONTAINER_TO_CONTAINED.asMap().entrySet()) { 1182 String macro = e.getKey(); 1183 for (Entry<String, Set<String>> e2 : partitionToRegions.asMap().entrySet()) { 1184 String partition = e2.getKey(); 1185 if (!Collections.disjoint(e.getValue(), e2.getValue())) { 1186 macroToPartitions.put(macro, partition); 1187 } 1188 } 1189 } 1190 1191 return new RegionMapper( 1192 variableToPartitions, 1193 regionToPartition, 1194 macroToPartitions, 1195 paradigms); 1196 } 1197 } 1198 } 1199 1200 /** 1201 * Parses a string of regions like "US+005-BR" and produces a set of resolved regions. 1202 * All macroregions are fully resolved to sets of non-macro regions. 1203 * <br>Syntax is simple for now: 1204 * <pre>regionSet := region ([-+] region)*</pre> 1205 * No precedence, so "x+y-y+z" is (((x+y)-y)+z) NOT (x+y)-(y+z) 1206 */ 1207 private static class RegionSet { 1208 private enum Operation {add, remove} 1209 // temporaries used in processing 1210 final private Set<String> tempRegions = new TreeSet<String>(); 1211 private Operation operation = null; 1212 1213 private Set<String> parseSet(String barString) { 1214 operation = Operation.add; 1215 int last = 0; 1216 tempRegions.clear(); 1217 int i = 0; 1218 for (; i < barString.length(); ++i) { 1219 char c = barString.charAt(i); // UTF16 is ok, since syntax is only ascii 1220 switch(c) { 1221 case '+': 1222 add(barString, last, i); 1223 last = i+1; 1224 operation = Operation.add; 1225 break; 1226 case '-': 1227 add(barString, last, i); 1228 last = i+1; 1229 operation = Operation.remove; 1230 break; 1231 } 1232 } 1233 add(barString, last, i); 1234 return tempRegions; 1235 } 1236 1237 private Set<String> inverse() { 1238 TreeSet<String> result = new TreeSet<String>(ALL_FINAL_REGIONS); 1239 result.removeAll(tempRegions); 1240 return result; 1241 } 1242 1243 private void add(String barString, int last, int i) { 1244 if (i > last) { 1245 String region = barString.substring(last,i); 1246 changeSet(operation, region); 1247 } 1248 } 1249 1250 private void changeSet(Operation operation, String region) { 1251 Collection<String> contained = CONTAINER_TO_CONTAINED_FINAL.get(region); 1252 if (contained != null && !contained.isEmpty()) { 1253 if (Operation.add == operation) { 1254 tempRegions.addAll(contained); 1255 } else { 1256 tempRegions.removeAll(contained); 1257 } 1258 } else if (Operation.add == operation) { 1259 tempRegions.add(region); 1260 } else { 1261 tempRegions.remove(region); 1262 } 1263 } 1264 } 1265 1266 public static <K,V> Multimap<K,V> invertMap(Map<V,K> map) { 1267 return Multimaps.invertFrom(Multimaps.forMap(map), LinkedHashMultimap.<K,V>create()); 1268 } 1269 1270 public Set<ULocale> getParadigms() { 1271 return regionMapper.paradigms; 1272 } 1273 1274 public int getDefaultLanguageDistance() { 1275 return defaultLanguageDistance; 1276 } 1277 1278 public int getDefaultScriptDistance() { 1279 return defaultScriptDistance; 1280 } 1281 1282 public int getDefaultRegionDistance() { 1283 return defaultRegionDistance; 1284 } 1285 1286 static class CompactAndImmutablizer extends IdMakerFull<Object> { 1287 StringDistanceTable compact(StringDistanceTable item) { 1288 if (toId(item) != null) { 1289 return (StringDistanceTable) intern(item); 1290 } 1291 return new StringDistanceTable(compact(item.subtables, 0)); 1292 } 1293 @SuppressWarnings({ "unchecked", "rawtypes" }) 1294 <K,T> Map<K,T> compact(Map<K,T> item, int level) { 1295 if (toId(item) != null) { 1296 return (Map<K, T>) intern(item); 1297 } 1298 Map<K,T> copy = new LinkedHashMap<K,T>(); 1299 for (Entry<K,T> entry : item.entrySet()) { 1300 T value = entry.getValue(); 1301 if (value instanceof Map) { 1302 copy.put(entry.getKey(), (T)compact((Map)value, level+1)); 1303 } else { 1304 copy.put(entry.getKey(), (T)compact((DistanceNode)value)); 1305 } 1306 } 1307 return ImmutableMap.copyOf(copy); 1308 } 1309 DistanceNode compact(DistanceNode item) { 1310 if (toId(item) != null) { 1311 return (DistanceNode) intern(item); 1312 } 1313 final DistanceTable distanceTable = item.getDistanceTable(); 1314 if (distanceTable == null || distanceTable.isEmpty()) { 1315 return new DistanceNode(item.distance); 1316 } else { 1317 return new StringDistanceNode(item.distance, compact((StringDistanceTable)((StringDistanceNode)item).distanceTable)); 1318 } 1319 } 1320 } 1321 1322 @Deprecated 1323 public StringDistanceTable internalGetDistanceTable() { 1324 return (StringDistanceTable) languageDesired2Supported; 1325 } 1326 1327 public static void main(String[] args) { 1328 // for (Entry<String, Collection<String>> entry : containerToContained.asMap().entrySet()) { 1329 // System.out.println(entry.getKey() + "\t" + entry.getValue() + "; " + containerToFinalContained.get(entry.getKey())); 1330 // } 1331 // final Multimap<String,String> regionToMacros = ImmutableMultimap.copyOf(Multimaps.invertFrom(containerToContained, TreeMultimap.create())); 1332 // for (Entry<String, Collection<String>> entry : regionToMacros.asMap().entrySet()) { 1333 // System.out.println(entry.getKey() + "\t " + entry.getValue()); 1334 // } 1335 if (PRINT_OVERRIDES) { 1336 System.out.println(getDefault().toString(true)); 1337 } 1338 DistanceTable table = getDefault().languageDesired2Supported; 1339 DistanceTable compactedTable = table.compact(); 1340 if (!table.equals(compactedTable)) { 1341 throw new IllegalArgumentException("Compaction isn't equal"); 1342 } 1343 } 1344 } 1345