1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 1996-2015, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.dev.util; 10 11 import java.util.Collection; 12 import java.util.Comparator; 13 import java.util.HashMap; 14 import java.util.Iterator; 15 import java.util.Map; 16 import java.util.Map.Entry; 17 import java.util.Set; 18 import java.util.SortedSet; 19 import java.util.TreeSet; 20 import java.util.regex.Matcher; 21 22 import com.ibm.icu.text.UTF16; 23 import com.ibm.icu.text.UnicodeSet; 24 import com.ibm.icu.text.UnicodeSetIterator; 25 26 /** 27 * Utilities that ought to be on collections, but aren't 28 * 29 * @internal CLDR 30 */ 31 public final class CollectionUtilities { 32 33 /** 34 * Join an array of items. 35 * @param <T> 36 * @param array 37 * @param separator 38 * @return string 39 */ join(T[] array, String separator)40 public static <T> String join(T[] array, String separator) { 41 StringBuffer result = new StringBuffer(); 42 for (int i = 0; i < array.length; ++i) { 43 if (i != 0) result.append(separator); 44 result.append(array[i]); 45 } 46 return result.toString(); 47 } 48 49 /** 50 * Join a collection of items. 51 * @param <T> 52 * @param collection 53 * @param <U> 54 * @param array 55 * @param separator 56 * @return string 57 */ join(U collection, String separator)58 public static <T, U extends Iterable<T>>String join(U collection, String separator) { 59 StringBuffer result = new StringBuffer(); 60 boolean first = true; 61 for (Iterator it = collection.iterator(); it.hasNext();) { 62 if (first) first = false; 63 else result.append(separator); 64 result.append(it.next()); 65 } 66 return result.toString(); 67 } 68 69 /** 70 * Utility like Arrays.asList() 71 * @param source 72 * @param target 73 * @param reverse 74 * @param <T> 75 * @return 76 */ asMap(T[][] source, Map<T,T> target, boolean reverse)77 public static <T> Map<T,T> asMap(T[][] source, Map<T,T> target, boolean reverse) { 78 int from = 0, to = 1; 79 if (reverse) { 80 from = 1; to = 0; 81 } 82 for (int i = 0; i < source.length; ++i) { 83 target.put(source[i][from], source[i][to]); 84 } 85 return target; 86 } 87 88 /** 89 * Add all items in iterator to target collection 90 * @param <T> 91 * @param <U> 92 * @param source 93 * @param target 94 * @return 95 */ addAll(Iterator<T> source, U target)96 public static <T, U extends Collection<T>> U addAll(Iterator<T> source, U target) { 97 while (source.hasNext()) { 98 target.add(source.next()); 99 } 100 return target; // for chaining 101 } 102 103 /** 104 * Get the size of an iterator (number of items in it). 105 * @param source 106 * @return 107 */ size(Iterator source)108 public static int size(Iterator source) { 109 int result = 0; 110 while (source.hasNext()) { 111 source.next(); 112 ++result; 113 } 114 return result; 115 } 116 117 118 /** 119 * @param <T> 120 * @param source 121 * @return 122 */ asMap(T[][] source)123 public static <T> Map<T,T> asMap(T[][] source) { 124 return asMap(source, new HashMap<T,T>(), false); 125 } 126 127 /** 128 * Utility that ought to be on Map 129 * @param m 130 * @param itemsToRemove 131 * @param <K> 132 * @param <V> 133 * @return map passed in 134 */ removeAll(Map<K,V> m, Collection<K> itemsToRemove)135 public static <K,V> Map<K,V> removeAll(Map<K,V> m, Collection<K> itemsToRemove) { 136 for (Iterator it = itemsToRemove.iterator(); it.hasNext();) { 137 Object item = it.next(); 138 m.remove(item); 139 } 140 return m; 141 } 142 143 /** 144 * Get first item in collection, or null if there is none. 145 * @param <T> 146 * @param <U> 147 * @param c 148 * @return first item 149 */ getFirst(U c)150 public <T, U extends Collection<T>> T getFirst(U c) { 151 Iterator<T> it = c.iterator(); 152 if (!it.hasNext()) return null; 153 return it.next(); 154 } 155 156 /** 157 * Get the "best" in collection. That is the least if direction is < 0, otherwise the greatest. The first is chosen if there are multiples. 158 * @param <T> 159 * @param <U> 160 * @param c 161 * @param comp 162 * @param direction 163 * @return 164 */ getBest(U c, Comparator<T> comp, int direction)165 public static <T, U extends Collection<T>> T getBest(U c, Comparator<T> comp, int direction) { 166 Iterator<T> it = c.iterator(); 167 if (!it.hasNext()) return null; 168 T bestSoFar = it.next(); 169 if (direction < 0) { 170 while (it.hasNext()) { 171 T item = it.next(); 172 int compValue = comp.compare(item, bestSoFar); 173 if (compValue < 0) { 174 bestSoFar = item; 175 } 176 } 177 } else { 178 while (it.hasNext()) { 179 T item = it.next(); 180 int compValue = comp.compare(item, bestSoFar); 181 if (compValue > 0) { 182 bestSoFar = item; 183 } 184 } 185 } 186 return bestSoFar; 187 } 188 189 /** 190 * Matches item. 191 * @param <T> 192 */ 193 public interface ObjectMatcher<T> { 194 /** 195 * Must handle null, never throw exception 196 * @param o 197 * @return 198 */ matches(T o)199 boolean matches(T o); 200 } 201 202 /** 203 * Reverse a match 204 * @param <T> 205 */ 206 public static class InverseMatcher<T> implements ObjectMatcher<T> { 207 ObjectMatcher<T> other; 208 /** 209 * @param toInverse 210 * @return 211 */ set(ObjectMatcher toInverse)212 public ObjectMatcher set(ObjectMatcher toInverse) { 213 other = toInverse; 214 return this; 215 } matches(T value)216 public boolean matches(T value) { 217 return !other.matches(value); 218 } 219 } 220 221 /** 222 * Remove matching items 223 * @param <T> 224 * @param <U> 225 * @param c 226 * @param f 227 * @return 228 */ removeAll(U c, ObjectMatcher<T> f)229 public static <T, U extends Collection<T>> U removeAll(U c, ObjectMatcher<T> f) { 230 for (Iterator<T> it = c.iterator(); it.hasNext();) { 231 T item = it.next(); 232 if (f.matches(item)) it.remove(); 233 } 234 return c; 235 } 236 237 /** 238 * Retain matching items 239 * @param <T> 240 * @param <U> 241 * @param c 242 * @param f 243 * @return 244 */ retainAll(U c, ObjectMatcher<T> f)245 public static <T, U extends Collection<T>> U retainAll(U c, ObjectMatcher<T> f) { 246 for (Iterator<T> it = c.iterator(); it.hasNext();) { 247 T item = it.next(); 248 if (!f.matches(item)) it.remove(); 249 } 250 return c; 251 } 252 253 /** 254 * @param a 255 * @param b 256 * @return 257 */ containsSome(Collection a, Collection b)258 public static boolean containsSome(Collection a, Collection b) { 259 // fast paths 260 if (a.size() == 0 || b.size() == 0) return false; 261 if (a == b) return true; // must test after size test. 262 263 if (a instanceof SortedSet && b instanceof SortedSet) { 264 SortedSet aa = (SortedSet) a; 265 SortedSet bb = (SortedSet) b; 266 Comparator bbc = bb.comparator(); 267 Comparator aac = aa.comparator(); 268 if (bbc == null && aac == null) { 269 Iterator ai = aa.iterator(); 270 Iterator bi = bb.iterator(); 271 Comparable ao = (Comparable) ai.next(); // these are ok, since the sizes are != 0 272 Comparable bo = (Comparable) bi.next(); 273 while (true) { 274 int rel = ao.compareTo(bo); 275 if (rel < 0) { 276 if (!ai.hasNext()) return false; 277 ao = (Comparable) ai.next(); 278 } else if (rel > 0) { 279 if (!bi.hasNext()) return false; 280 bo = (Comparable) bi.next(); 281 } else { 282 return true; 283 } 284 } 285 } else if (bbc.equals(a)) { 286 Iterator ai = aa.iterator(); 287 Iterator bi = bb.iterator(); 288 Object ao = ai.next(); // these are ok, since the sizes are != 0 289 Object bo = bi.next(); 290 while (true) { 291 int rel = aac.compare(ao, bo); 292 if (rel < 0) { 293 if (!ai.hasNext()) return false; 294 ao = ai.next(); 295 } else if (rel > 0) { 296 if (!bi.hasNext()) return false; 297 bo = bi.next(); 298 } else { 299 return true; 300 } 301 } 302 } 303 } 304 for (Iterator it = a.iterator(); it.hasNext();) { 305 if (b.contains(it.next())) return true; 306 } 307 return false; 308 } 309 containsAll(Collection a, Collection b)310 public static boolean containsAll(Collection a, Collection b) { 311 // fast paths 312 if (a == b) return true; 313 if (b.size() == 0) return true; 314 if (a.size() < b.size()) return false; 315 316 if (a instanceof SortedSet && b instanceof SortedSet) { 317 SortedSet aa = (SortedSet) a; 318 SortedSet bb = (SortedSet) b; 319 Comparator bbc = bb.comparator(); 320 Comparator aac = aa.comparator(); 321 if (bbc == null && aac == null) { 322 Iterator ai = aa.iterator(); 323 Iterator bi = bb.iterator(); 324 Comparable ao = (Comparable) ai.next(); // these are ok, since the sizes are != 0 325 Comparable bo = (Comparable) bi.next(); 326 while (true) { 327 int rel = ao.compareTo(bo); 328 if (rel == 0) { 329 if (!bi.hasNext()) return true; 330 if (!ai.hasNext()) return false; 331 bo = (Comparable) bi.next(); 332 ao = (Comparable) ai.next(); 333 } else if (rel < 0) { 334 if (!ai.hasNext()) return false; 335 ao = (Comparable) ai.next(); 336 } else { 337 return false; 338 } 339 } 340 } else if (bbc.equals(aac)) { 341 Iterator ai = aa.iterator(); 342 Iterator bi = bb.iterator(); 343 Object ao = ai.next(); // these are ok, since the sizes are != 0 344 Object bo = bi.next(); 345 while (true) { 346 int rel = aac.compare(ao, bo); 347 if (rel == 0) { 348 if (!bi.hasNext()) return true; 349 if (!ai.hasNext()) return false; 350 bo = bi.next(); 351 ao = ai.next(); 352 } else if (rel < 0) { 353 if (!ai.hasNext()) return false; 354 ao = ai.next(); 355 } else { 356 return false; 357 } 358 } 359 } 360 } 361 return a.containsAll(b); 362 } 363 containsNone(Collection a, Collection b)364 public static boolean containsNone(Collection a, Collection b) { 365 return !containsSome(a, b); 366 } 367 368 /** 369 * Used for results of getContainmentRelation 370 */ 371 public static final int 372 ALL_EMPTY = 0, 373 NOT_A_SUPERSET_B = 1, 374 NOT_A_DISJOINT_B = 2, 375 NOT_A_SUBSET_B = 4, 376 NOT_A_EQUALS_B = NOT_A_SUBSET_B | NOT_A_SUPERSET_B, 377 A_PROPER_SUBSET_OF_B = NOT_A_DISJOINT_B | NOT_A_SUPERSET_B, 378 A_PROPER_SUPERSET_B = NOT_A_SUBSET_B | NOT_A_DISJOINT_B, 379 A_PROPER_OVERLAPS_B = NOT_A_SUBSET_B | NOT_A_DISJOINT_B | NOT_A_SUPERSET_B; 380 381 /** 382 * Assesses all the possible containment relations between collections A and B with one call.<br> 383 * Returns an int with bits set, according to a "Venn Diagram" view of A vs B.<br> 384 * NOT_A_SUPERSET_B: a - b != {}<br> 385 * NOT_A_DISJOINT_B: a * b != {} // * is intersects<br> 386 * NOT_A_SUBSET_B: b - a != {}<br> 387 * Thus the bits can be used to get the following relations:<br> 388 * for A_SUPERSET_B, use (x & CollectionUtilities.NOT_A_SUPERSET_B) == 0<br> 389 * for A_SUBSET_B, use (x & CollectionUtilities.NOT_A_SUBSET_B) == 0<br> 390 * for A_EQUALS_B, use (x & CollectionUtilities.NOT_A_EQUALS_B) == 0<br> 391 * for A_DISJOINT_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) == 0<br> 392 * for A_OVERLAPS_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) != 0<br> 393 */ getContainmentRelation(Collection a, Collection b)394 public static int getContainmentRelation(Collection a, Collection b) { 395 if (a.size() == 0) { 396 return (b.size() == 0) ? ALL_EMPTY : NOT_A_SUPERSET_B; 397 } else if (b.size() == 0) { 398 return NOT_A_SUBSET_B; 399 } 400 int result = 0; 401 // WARNING: one might think that the following can be short-circuited, by looking at 402 // the sizes of a and b. However, this would fail in general, where a different comparator is being 403 // used in the two collections. Unfortunately, there is no failsafe way to test for that. 404 for (Iterator it = a.iterator(); result != 6 && it.hasNext();) { 405 result |= (b.contains(it.next())) ? NOT_A_DISJOINT_B : NOT_A_SUBSET_B; 406 } 407 for (Iterator it = b.iterator(); (result & 3) != 3 && it.hasNext();) { 408 result |= (a.contains(it.next())) ? NOT_A_DISJOINT_B : NOT_A_SUPERSET_B; 409 } 410 return result; 411 } 412 remove(String source, UnicodeSet removals)413 public static String remove(String source, UnicodeSet removals) { 414 StringBuffer result = new StringBuffer(); 415 int cp; 416 for (int i = 0; i < source.length(); i += UTF16.getCharCount(cp)) { 417 cp = UTF16.charAt(source, i); 418 if (!removals.contains(cp)) UTF16.append(result, cp); 419 } 420 return result.toString(); 421 } 422 423 /** 424 * Does one string contain another, starting at a specific offset? 425 * @param text 426 * @param offset 427 * @param other 428 * @return 429 */ matchesAt(CharSequence text, int offset, CharSequence other)430 public static int matchesAt(CharSequence text, int offset, CharSequence other) { 431 int len = other.length(); 432 int i = 0; 433 int j = offset; 434 for (; i < len; ++i, ++j) { 435 char pc = other.charAt(i); 436 char tc = text.charAt(j); 437 if (pc != tc) return -1; 438 } 439 return i; 440 } 441 442 /** 443 * Returns the ending offset found by matching characters with testSet, until a position is found that doen't match 444 * @param string 445 * @param offset 446 * @param testSet 447 * @return 448 */ span(CharSequence string, int offset, UnicodeSet testSet)449 public int span(CharSequence string, int offset, UnicodeSet testSet) { 450 while (true) { 451 int newOffset = testSet.matchesAt(string, offset); 452 if (newOffset < 0) return offset; 453 } 454 } 455 456 /** 457 * Returns the ending offset found by matching characters with testSet, until a position is found that does match 458 * @param string 459 * @param offset 460 * @param testSet 461 * @return 462 */ spanNot(CharSequence string, int offset, UnicodeSet testSet)463 public int spanNot(CharSequence string, int offset, UnicodeSet testSet) { 464 while (true) { 465 int newOffset = testSet.matchesAt(string, offset); 466 if (newOffset >= 0) return offset; 467 ++offset; // try next character position 468 // we don't have to worry about surrogates for this. 469 } 470 } 471 472 /** 473 * Modifies Unicode set to flatten the strings. Eg [abc{da}] => [abcd] 474 * Returns the set for chaining. 475 * @param exemplar1 476 * @return 477 */ flatten(UnicodeSet exemplar1)478 public static UnicodeSet flatten(UnicodeSet exemplar1) { 479 UnicodeSet result = new UnicodeSet(); 480 boolean gotString = false; 481 for (UnicodeSetIterator it = new UnicodeSetIterator(exemplar1); it.nextRange();) { 482 if (it.codepoint == UnicodeSetIterator.IS_STRING) { 483 result.addAll(it.string); 484 gotString = true; 485 } else { 486 result.add(it.codepoint, it.codepointEnd); 487 } 488 } 489 if (gotString) exemplar1.set(result); 490 return exemplar1; 491 } 492 493 /** 494 * For producing filtered iterators 495 */ 496 public static abstract class FilteredIterator implements Iterator { 497 private Iterator baseIterator; 498 private static final Object EMPTY = new Object(); 499 private static final Object DONE = new Object(); 500 private Object nextObject = EMPTY; set(Iterator baseIterator)501 public FilteredIterator set(Iterator baseIterator) { 502 this.baseIterator = baseIterator; 503 return this; 504 } remove()505 public void remove() { 506 throw new UnsupportedOperationException("Doesn't support removal"); 507 } next()508 public Object next() { 509 Object result = nextObject; 510 nextObject = EMPTY; 511 return result; 512 } hasNext()513 public boolean hasNext() { 514 if (nextObject == DONE) return false; 515 if (nextObject != EMPTY) return true; 516 while (baseIterator.hasNext()) { 517 nextObject = baseIterator.next(); 518 if (isIncluded(nextObject)) { 519 return true; 520 } 521 } 522 nextObject = DONE; 523 return false; 524 } isIncluded(Object item)525 abstract public boolean isIncluded(Object item); 526 } 527 528 public static class PrefixIterator extends FilteredIterator { 529 private String prefix; set(Iterator baseIterator, String prefix)530 public PrefixIterator set(Iterator baseIterator, String prefix) { 531 super.set(baseIterator); 532 this.prefix = prefix; 533 return this; 534 } isIncluded(Object item)535 public boolean isIncluded(Object item) { 536 return ((String)item).startsWith(prefix); 537 } 538 } 539 540 public static class RegexIterator extends FilteredIterator { 541 private Matcher matcher; set(Iterator baseIterator, Matcher matcher)542 public RegexIterator set(Iterator baseIterator, Matcher matcher) { 543 super.set(baseIterator); 544 this.matcher = matcher; 545 return this; 546 } isIncluded(Object item)547 public boolean isIncluded(Object item) { 548 return matcher.reset((String)item).matches(); 549 } 550 } 551 552 /** 553 * Compare, allowing nulls 554 * @param a 555 * @param b 556 * @return 557 */ equals(T a, T b)558 public static <T> boolean equals(T a, T b) { 559 return a == null 560 ? b == null 561 : b == null ? false : a.equals(b); 562 } 563 564 /** 565 * Compare, allowing nulls and putting them first 566 * @param a 567 * @param b 568 * @return 569 */ compare(T a, T b)570 public static <T extends Comparable> int compare(T a, T b) { 571 return a == null 572 ? b == null ? 0 : -1 573 : b == null ? 1 : a.compareTo(b); 574 } 575 576 /** 577 * Compare iterators 578 * @param iterator1 579 * @param iterator2 580 * @return 581 */ compare(Iterator<T> iterator1, Iterator<T> iterator2)582 public static <T extends Comparable> int compare(Iterator<T> iterator1, Iterator<T> iterator2) { 583 int diff; 584 while (true) { 585 if (!iterator1.hasNext()) { 586 return iterator2.hasNext() ? -1 : 0; 587 } else if (!iterator2.hasNext()) { 588 return 1; 589 } 590 diff = CollectionUtilities.compare(iterator1.next(), iterator2.next()); 591 if (diff != 0) { 592 return diff; 593 } 594 } 595 } 596 597 /** 598 * Compare, with shortest first, and otherwise lexicographically 599 * @param a 600 * @param b 601 * @return 602 */ compare(U o1, U o2)603 public static <T extends Comparable, U extends Collection<T>> int compare(U o1, U o2) { 604 int diff = o1.size() - o2.size(); 605 if (diff != 0) { 606 return diff; 607 } 608 Iterator<T> iterator1 = o1.iterator(); 609 Iterator<T> iterator2 = o2.iterator(); 610 return compare(iterator1, iterator2); 611 } 612 613 /** 614 * Compare, with shortest first, and otherwise lexicographically 615 * @param a 616 * @param b 617 * @return 618 */ compare(U o1, U o2)619 public static <T extends Comparable, U extends Set<T>> int compare(U o1, U o2) { 620 int diff = o1.size() - o2.size(); 621 if (diff != 0) { 622 return diff; 623 } 624 Collection<T> x1 = SortedSet.class.isInstance(o1) ? o1 : new TreeSet<T>(o1); 625 Collection<T> x2 = SortedSet.class.isInstance(o2) ? o2 : new TreeSet<T>(o2); 626 return compare(x1, x2); 627 } 628 629 public static class SetComparator<T extends Comparable> 630 implements Comparator<Set<T>> { compare(Set<T> o1, Set<T> o2)631 public int compare(Set<T> o1, Set<T> o2) { 632 return CollectionUtilities.compare(o1, o2); 633 } 634 }; 635 636 637 public static class CollectionComparator<T extends Comparable> 638 implements Comparator<Collection<T>> { compare(Collection<T> o1, Collection<T> o2)639 public int compare(Collection<T> o1, Collection<T> o2) { 640 return CollectionUtilities.compare(o1, o2); 641 } 642 }; 643 644 /** 645 * Compare, allowing nulls and putting them first 646 * @param a 647 * @param b 648 * @return 649 */ compare(T a, T b)650 public static <K extends Comparable, V extends Comparable, T extends Entry<K, V>> int compare(T a, T b) { 651 if (a == null) { 652 return b == null ? 0 : -1; 653 } else if (b == null) { 654 return 1; 655 } 656 int diff = compare(a.getKey(), b.getKey()); 657 if (diff != 0) { 658 return diff; 659 } 660 return compare(a.getValue(), b.getValue()); 661 } 662 compareEntrySets(Collection<T> o1, Collection<T> o2)663 public static <K extends Comparable, V extends Comparable, T extends Entry<K, V>> int compareEntrySets(Collection<T> o1, Collection<T> o2) { 664 int diff = o1.size() - o2.size(); 665 if (diff != 0) { 666 return diff; 667 } 668 Iterator<T> iterator1 = o1.iterator(); 669 Iterator<T> iterator2 = o2.iterator(); 670 while (true) { 671 if (!iterator1.hasNext()) { 672 return iterator2.hasNext() ? -1 : 0; 673 } else if (!iterator2.hasNext()) { 674 return 1; 675 } 676 T item1 = iterator1.next(); 677 T item2 = iterator2.next(); 678 diff = CollectionUtilities.compare(item1, item2); 679 if (diff != 0) { 680 return diff; 681 } 682 } 683 } 684 685 public static class MapComparator<K extends Comparable, V extends Comparable> implements Comparator<Map<K,V>> { compare(Map<K, V> o1, Map<K, V> o2)686 public int compare(Map<K, V> o1, Map<K, V> o2) { 687 return CollectionUtilities.compareEntrySets(o1.entrySet(), o2.entrySet()); 688 } 689 }; 690 691 public static class ComparableComparator<T extends Comparable> implements Comparator<T> { compare(T arg0, T arg1)692 public int compare(T arg0, T arg1) { 693 return CollectionUtilities.compare(arg0, arg1); 694 } 695 } 696 } 697