blob: 918eddf01aaa445518d3cb5aa43552658bd472df [file] [log] [blame]
/*
*******************************************************************************
* 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 <T>
* @param array
* @param separator
* @return string
*/
public static <T> 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 <T>
* @param collection
* @param <U>
* @param array
* @param separator
* @return string
*/
public static <T, U extends Iterable<T>>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 <T>
* @return
*/
public static <T> Map<T,T> asMap(T[][] source, Map<T,T> 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 <T>
* @param <U>
* @param source
* @param target
* @return
*/
public static <T, U extends Collection<T>> U addAll(Iterator<T> 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 <T>
* @param source
* @return
*/
public static <T> Map<T,T> asMap(T[][] source) {
return asMap(source, new HashMap<T,T>(), false);
}
/**
* Utility that ought to be on Map
* @param m
* @param itemsToRemove
* @param <K>
* @param <V>
* @return map passed in
*/
public static <K,V> Map<K,V> removeAll(Map<K,V> m, Collection<K> 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 <T>
* @param <U>
* @param c
* @return first item
*/
public <T, U extends Collection<T>> T getFirst(U c) {
Iterator<T> 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 <T>
* @param <U>
* @param c
* @param comp
* @param direction
* @return
*/
public static <T, U extends Collection<T>> T getBest(U c, Comparator<T> comp, int direction) {
Iterator<T> 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 <T>
*/
public interface ObjectMatcher<T> {
/**
* Must handle null, never throw exception
* @param o
* @return
*/
boolean matches(T o);
}
/**
* Reverse a match
* @param <T>
*/
public static class InverseMatcher<T> implements ObjectMatcher<T> {
ObjectMatcher<T> 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 <T>
* @param <U>
* @param c
* @param f
* @return
*/
public static <T, U extends Collection<T>> U removeAll(U c, ObjectMatcher<T> f) {
for (Iterator<T> it = c.iterator(); it.hasNext();) {
T item = it.next();
if (f.matches(item)) it.remove();
}
return c;
}
/**
* Retain matching items
* @param <T>
* @param <U>
* @param c
* @param f
* @return
*/
public static <T, U extends Collection<T>> U retainAll(U c, ObjectMatcher<T> f) {
for (Iterator<T> 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.<br>
* Returns an int with bits set, according to a "Venn Diagram" view of A vs B.<br>
* NOT_A_SUPERSET_B: a - b != {}<br>
* NOT_A_DISJOINT_B: a * b != {} // * is intersects<br>
* NOT_A_SUBSET_B: b - a != {}<br>
* Thus the bits can be used to get the following relations:<br>
* for A_SUPERSET_B, use (x & CollectionUtilities.NOT_A_SUPERSET_B) == 0<br>
* for A_SUBSET_B, use (x & CollectionUtilities.NOT_A_SUBSET_B) == 0<br>
* for A_EQUALS_B, use (x & CollectionUtilities.NOT_A_EQUALS_B) == 0<br>
* for A_DISJOINT_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) == 0<br>
* for A_OVERLAPS_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) != 0<br>
*/
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 <T> 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 <T extends Comparable> 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 <T extends Comparable> int compare(Iterator<T> iterator1, Iterator<T> 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 <T extends Comparable, U extends Collection<T>> int compare(U o1, U o2) {
int diff = o1.size() - o2.size();
if (diff != 0) {
return diff;
}
Iterator<T> iterator1 = o1.iterator();
Iterator<T> iterator2 = o2.iterator();
return compare(iterator1, iterator2);
}
/**
* Compare, with shortest first, and otherwise lexicographically
* @param a
* @param b
* @return
*/
public static <T extends Comparable, U extends Set<T>> 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<T extends Comparable>
implements Comparator<Set<T>> {
public int compare(Set<T> o1, Set<T> o2) {
return CollectionUtilities.compare(o1, o2);
}
};
public static class CollectionComparator<T extends Comparable>
implements Comparator<Collection<T>> {
public int compare(Collection<T> o1, Collection<T> o2) {
return CollectionUtilities.compare(o1, o2);
}
};
/**
* Compare, allowing nulls and putting them first
* @param a
* @param b
* @return
*/
public static <K extends Comparable, V extends Comparable, T extends Entry<K, V>> 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 <K extends Comparable, V extends Comparable, T extends Entry<K, V>> int compareEntrySets(Collection<T> o1, Collection<T> o2) {
int diff = o1.size() - o2.size();
if (diff != 0) {
return diff;
}
Iterator<T> iterator1 = o1.iterator();
Iterator<T> 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<K extends Comparable, V extends Comparable> implements Comparator<Map<K,V>> {
public int compare(Map<K, V> o1, Map<K, V> o2) {
return CollectionUtilities.compareEntrySets(o1.entrySet(), o2.entrySet());
}
};
public static class ComparableComparator<T extends Comparable> implements Comparator<T> {
public int compare(T arg0, T arg1) {
return CollectionUtilities.compare(arg0, arg1);
}
}
}