• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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