/* ******************************************************************************* * Copyright (C) 1996-2014, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.dev.util; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Matcher; import com.ibm.icu.text.UTF16; import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.text.UnicodeSetIterator; /** * Utilities that ought to be on collections, but aren't */ public final class CollectionUtilities { /** * Join an array of items. * @param * @param array * @param separator * @return string */ public static String join(T[] array, String separator) { StringBuffer result = new StringBuffer(); for (int i = 0; i < array.length; ++i) { if (i != 0) result.append(separator); result.append(array[i]); } return result.toString(); } /** * Join a collection of items. * @param * @param collection * @param * @param array * @param separator * @return string */ public static >String join(U collection, String separator) { StringBuffer result = new StringBuffer(); boolean first = true; for (Iterator it = collection.iterator(); it.hasNext();) { if (first) first = false; else result.append(separator); result.append(it.next()); } return result.toString(); } /** * Utility like Arrays.asList() * @param source * @param target * @param reverse * @param * @return */ public static Map asMap(T[][] source, Map target, boolean reverse) { int from = 0, to = 1; if (reverse) { from = 1; to = 0; } for (int i = 0; i < source.length; ++i) { target.put(source[i][from], source[i][to]); } return target; } /** * Add all items in iterator to target collection * @param * @param * @param source * @param target * @return */ public static > U addAll(Iterator source, U target) { while (source.hasNext()) { target.add(source.next()); } return target; // for chaining } /** * Get the size of an iterator (number of items in it). * @param source * @return */ public static int size(Iterator source) { int result = 0; while (source.hasNext()) { source.next(); ++result; } return result; } /** * @param * @param source * @return */ public static Map asMap(T[][] source) { return asMap(source, new HashMap(), false); } /** * Utility that ought to be on Map * @param m * @param itemsToRemove * @param * @param * @return map passed in */ public static Map removeAll(Map m, Collection itemsToRemove) { for (Iterator it = itemsToRemove.iterator(); it.hasNext();) { Object item = it.next(); m.remove(item); } return m; } /** * Get first item in collection, or null if there is none. * @param * @param * @param c * @return first item */ public > T getFirst(U c) { Iterator it = c.iterator(); if (!it.hasNext()) return null; return it.next(); } /** * Get the "best" in collection. That is the least if direction is < 0, otherwise the greatest. The first is chosen if there are multiples. * @param * @param * @param c * @param comp * @param direction * @return */ public static > T getBest(U c, Comparator comp, int direction) { Iterator it = c.iterator(); if (!it.hasNext()) return null; T bestSoFar = it.next(); if (direction < 0) { while (it.hasNext()) { T item = it.next(); int compValue = comp.compare(item, bestSoFar); if (compValue < 0) { bestSoFar = item; } } } else { while (it.hasNext()) { T item = it.next(); int compValue = comp.compare(item, bestSoFar); if (compValue > 0) { bestSoFar = item; } } } return bestSoFar; } /** * Matches item. * @param */ public interface ObjectMatcher { /** * Must handle null, never throw exception * @param o * @return */ boolean matches(T o); } /** * Reverse a match * @param */ public static class InverseMatcher implements ObjectMatcher { ObjectMatcher other; /** * @param toInverse * @return */ public ObjectMatcher set(ObjectMatcher toInverse) { other = toInverse; return this; } public boolean matches(T value) { return !other.matches(value); } } /** * Remove matching items * @param * @param * @param c * @param f * @return */ public static > U removeAll(U c, ObjectMatcher f) { for (Iterator it = c.iterator(); it.hasNext();) { T item = it.next(); if (f.matches(item)) it.remove(); } return c; } /** * Retain matching items * @param * @param * @param c * @param f * @return */ public static > U retainAll(U c, ObjectMatcher f) { for (Iterator it = c.iterator(); it.hasNext();) { T item = it.next(); if (!f.matches(item)) it.remove(); } return c; } /** * @param a * @param b * @return */ public static boolean containsSome(Collection a, Collection b) { // fast paths if (a.size() == 0 || b.size() == 0) return false; if (a == b) return true; // must test after size test. if (a instanceof SortedSet && b instanceof SortedSet) { SortedSet aa = (SortedSet) a; SortedSet bb = (SortedSet) b; Comparator bbc = bb.comparator(); Comparator aac = aa.comparator(); if (bbc == null && aac == null) { Iterator ai = aa.iterator(); Iterator bi = bb.iterator(); Comparable ao = (Comparable) ai.next(); // these are ok, since the sizes are != 0 Comparable bo = (Comparable) bi.next(); while (true) { int rel = ao.compareTo(bo); if (rel < 0) { if (!ai.hasNext()) return false; ao = (Comparable) ai.next(); } else if (rel > 0) { if (!bi.hasNext()) return false; bo = (Comparable) bi.next(); } else { return true; } } } else if (bbc.equals(a)) { Iterator ai = aa.iterator(); Iterator bi = bb.iterator(); Object ao = ai.next(); // these are ok, since the sizes are != 0 Object bo = bi.next(); while (true) { int rel = aac.compare(ao, bo); if (rel < 0) { if (!ai.hasNext()) return false; ao = ai.next(); } else if (rel > 0) { if (!bi.hasNext()) return false; bo = bi.next(); } else { return true; } } } } for (Iterator it = a.iterator(); it.hasNext();) { if (b.contains(it.next())) return true; } return false; } public static boolean containsAll(Collection a, Collection b) { // fast paths if (a == b) return true; if (b.size() == 0) return true; if (a.size() < b.size()) return false; if (a instanceof SortedSet && b instanceof SortedSet) { SortedSet aa = (SortedSet) a; SortedSet bb = (SortedSet) b; Comparator bbc = bb.comparator(); Comparator aac = aa.comparator(); if (bbc == null && aac == null) { Iterator ai = aa.iterator(); Iterator bi = bb.iterator(); Comparable ao = (Comparable) ai.next(); // these are ok, since the sizes are != 0 Comparable bo = (Comparable) bi.next(); while (true) { int rel = ao.compareTo(bo); if (rel == 0) { if (!bi.hasNext()) return true; if (!ai.hasNext()) return false; bo = (Comparable) bi.next(); ao = (Comparable) ai.next(); } else if (rel < 0) { if (!ai.hasNext()) return false; ao = (Comparable) ai.next(); } else { return false; } } } else if (bbc.equals(aac)) { Iterator ai = aa.iterator(); Iterator bi = bb.iterator(); Object ao = ai.next(); // these are ok, since the sizes are != 0 Object bo = bi.next(); while (true) { int rel = aac.compare(ao, bo); if (rel == 0) { if (!bi.hasNext()) return true; if (!ai.hasNext()) return false; bo = bi.next(); ao = ai.next(); } else if (rel < 0) { if (!ai.hasNext()) return false; ao = ai.next(); } else { return false; } } } } return a.containsAll(b); } public static boolean containsNone(Collection a, Collection b) { return !containsSome(a, b); } /** * Used for results of getContainmentRelation */ public static final int ALL_EMPTY = 0, NOT_A_SUPERSET_B = 1, NOT_A_DISJOINT_B = 2, NOT_A_SUBSET_B = 4, NOT_A_EQUALS_B = NOT_A_SUBSET_B | NOT_A_SUPERSET_B, A_PROPER_SUBSET_OF_B = NOT_A_DISJOINT_B | NOT_A_SUPERSET_B, A_PROPER_SUPERSET_B = NOT_A_SUBSET_B | NOT_A_DISJOINT_B, A_PROPER_OVERLAPS_B = NOT_A_SUBSET_B | NOT_A_DISJOINT_B | NOT_A_SUPERSET_B; /** * Assesses all the possible containment relations between collections A and B with one call.
* Returns an int with bits set, according to a "Venn Diagram" view of A vs B.
* NOT_A_SUPERSET_B: a - b != {}
* NOT_A_DISJOINT_B: a * b != {} // * is intersects
* NOT_A_SUBSET_B: b - a != {}
* Thus the bits can be used to get the following relations:
* for A_SUPERSET_B, use (x & CollectionUtilities.NOT_A_SUPERSET_B) == 0
* for A_SUBSET_B, use (x & CollectionUtilities.NOT_A_SUBSET_B) == 0
* for A_EQUALS_B, use (x & CollectionUtilities.NOT_A_EQUALS_B) == 0
* for A_DISJOINT_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) == 0
* for A_OVERLAPS_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) != 0
*/ public static int getContainmentRelation(Collection a, Collection b) { if (a.size() == 0) { return (b.size() == 0) ? ALL_EMPTY : NOT_A_SUPERSET_B; } else if (b.size() == 0) { return NOT_A_SUBSET_B; } int result = 0; // WARNING: one might think that the following can be short-circuited, by looking at // the sizes of a and b. However, this would fail in general, where a different comparator is being // used in the two collections. Unfortunately, there is no failsafe way to test for that. for (Iterator it = a.iterator(); result != 6 && it.hasNext();) { result |= (b.contains(it.next())) ? NOT_A_DISJOINT_B : NOT_A_SUBSET_B; } for (Iterator it = b.iterator(); (result & 3) != 3 && it.hasNext();) { result |= (a.contains(it.next())) ? NOT_A_DISJOINT_B : NOT_A_SUPERSET_B; } return result; } public static String remove(String source, UnicodeSet removals) { StringBuffer result = new StringBuffer(); int cp; for (int i = 0; i < source.length(); i += UTF16.getCharCount(cp)) { cp = UTF16.charAt(source, i); if (!removals.contains(cp)) UTF16.append(result, cp); } return result.toString(); } /** * Does one string contain another, starting at a specific offset? * @param text * @param offset * @param other * @return */ public static int matchesAt(CharSequence text, int offset, CharSequence other) { int len = other.length(); int i = 0; int j = offset; for (; i < len; ++i, ++j) { char pc = other.charAt(i); char tc = text.charAt(j); if (pc != tc) return -1; } return i; } /** * Returns the ending offset found by matching characters with testSet, until a position is found that doen't match * @param string * @param offset * @param testSet * @return */ public int span(CharSequence string, int offset, UnicodeSet testSet) { while (true) { int newOffset = testSet.matchesAt(string, offset); if (newOffset < 0) return offset; } } /** * Returns the ending offset found by matching characters with testSet, until a position is found that does match * @param string * @param offset * @param testSet * @return */ public int spanNot(CharSequence string, int offset, UnicodeSet testSet) { while (true) { int newOffset = testSet.matchesAt(string, offset); if (newOffset >= 0) return offset; ++offset; // try next character position // we don't have to worry about surrogates for this. } } /** * Modifies Unicode set to flatten the strings. Eg [abc{da}] => [abcd] * Returns the set for chaining. * @param exemplar1 * @return */ public static UnicodeSet flatten(UnicodeSet exemplar1) { UnicodeSet result = new UnicodeSet(); boolean gotString = false; for (UnicodeSetIterator it = new UnicodeSetIterator(exemplar1); it.nextRange();) { if (it.codepoint == UnicodeSetIterator.IS_STRING) { result.addAll(it.string); gotString = true; } else { result.add(it.codepoint, it.codepointEnd); } } if (gotString) exemplar1.set(result); return exemplar1; } /** * For producing filtered iterators */ public static abstract class FilteredIterator implements Iterator { private Iterator baseIterator; private static final Object EMPTY = new Object(); private static final Object DONE = new Object(); private Object nextObject = EMPTY; public FilteredIterator set(Iterator baseIterator) { this.baseIterator = baseIterator; return this; } public void remove() { throw new UnsupportedOperationException("Doesn't support removal"); } public Object next() { Object result = nextObject; nextObject = EMPTY; return result; } public boolean hasNext() { if (nextObject == DONE) return false; if (nextObject != EMPTY) return true; while (baseIterator.hasNext()) { nextObject = baseIterator.next(); if (isIncluded(nextObject)) { return true; } } nextObject = DONE; return false; } abstract public boolean isIncluded(Object item); } public static class PrefixIterator extends FilteredIterator { private String prefix; public PrefixIterator set(Iterator baseIterator, String prefix) { super.set(baseIterator); this.prefix = prefix; return this; } public boolean isIncluded(Object item) { return ((String)item).startsWith(prefix); } } public static class RegexIterator extends FilteredIterator { private Matcher matcher; public RegexIterator set(Iterator baseIterator, Matcher matcher) { super.set(baseIterator); this.matcher = matcher; return this; } public boolean isIncluded(Object item) { return matcher.reset((String)item).matches(); } } /** * Compare, allowing nulls * @param a * @param b * @return */ public static boolean equals(T a, T b) { return a == null ? b == null : b == null ? false : a.equals(b); } /** * Compare, allowing nulls and putting them first * @param a * @param b * @return */ public static int compare(T a, T b) { return a == null ? b == null ? 0 : -1 : b == null ? 1 : a.compareTo(b); } /** * Compare iterators * @param iterator1 * @param iterator2 * @return */ public static int compare(Iterator iterator1, Iterator iterator2) { int diff; while (true) { if (!iterator1.hasNext()) { return iterator2.hasNext() ? -1 : 0; } else if (!iterator2.hasNext()) { return 1; } diff = CollectionUtilities.compare(iterator1.next(), iterator2.next()); if (diff != 0) { return diff; } } } /** * Compare, with shortest first, and otherwise lexicographically * @param a * @param b * @return */ public static > int compare(U o1, U o2) { int diff = o1.size() - o2.size(); if (diff != 0) { return diff; } Iterator iterator1 = o1.iterator(); Iterator iterator2 = o2.iterator(); return compare(iterator1, iterator2); } /** * Compare, with shortest first, and otherwise lexicographically * @param a * @param b * @return */ public static > int compare(U o1, U o2) { int diff = o1.size() - o2.size(); if (diff != 0) { return diff; } return compare(new TreeSet(o1), new TreeSet(o2)); } public static class SetComparator implements Comparator> { public int compare(Set o1, Set o2) { return CollectionUtilities.compare(o1, o2); } }; public static class CollectionComparator implements Comparator> { public int compare(Collection o1, Collection o2) { return CollectionUtilities.compare(o1, o2); } }; /** * Compare, allowing nulls and putting them first * @param a * @param b * @return */ public static > int compare(T a, T b) { if (a == null) { return b == null ? 0 : -1; } else if (b == null) { return 1; } int diff = compare(a.getKey(), b.getKey()); if (diff != 0) { return diff; } return compare(a.getValue(), b.getValue()); } public static > int compareEntrySets(Collection o1, Collection o2) { int diff = o1.size() - o2.size(); if (diff != 0) { return diff; } Iterator iterator1 = o1.iterator(); Iterator iterator2 = o2.iterator(); while (true) { if (!iterator1.hasNext()) { return iterator2.hasNext() ? -1 : 0; } else if (!iterator2.hasNext()) { return 1; } T item1 = iterator1.next(); T item2 = iterator2.next(); diff = CollectionUtilities.compare(item1, item2); if (diff != 0) { return diff; } } } public static class MapComparator implements Comparator> { public int compare(Map o1, Map o2) { return CollectionUtilities.compareEntrySets(o1.entrySet(), o2.entrySet()); } }; public static class ComparableComparator implements Comparator { public int compare(T arg0, T arg1) { return CollectionUtilities.compare(arg0, arg1); } } }