2 *******************************************************************************
3 * Copyright (C) 1996-2010, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.dev.test.util;
9 import java.util.Collection;
10 import java.util.Comparator;
11 import java.util.HashMap;
12 import java.util.Iterator;
14 import java.util.SortedSet;
15 import java.util.regex.Matcher;
17 import com.ibm.icu.text.UTF16;
18 import com.ibm.icu.text.UnicodeSet;
19 import com.ibm.icu.text.UnicodeSetIterator;
22 * Utilities that ought to be on collections, but aren't
24 public final class CollectionUtilities {
27 * Join an array of items.
33 public static <T> String join(T[] array, String separator) {
34 StringBuffer result = new StringBuffer();
35 for (int i = 0; i < array.length; ++i) {
36 if (i != 0) result.append(separator);
37 result.append(array[i]);
39 return result.toString();
43 * Join a collection of items.
51 public static <T, U extends Collection<T>>String join(U collection, String separator) {
52 StringBuffer result = new StringBuffer();
54 for (Iterator it = collection.iterator(); it.hasNext();) {
55 if (first) first = false;
56 else result.append(separator);
57 result.append(it.next());
59 return result.toString();
63 * Utility like Arrays.asList()
70 public static <T> Map<T,T> asMap(T[][] source, Map<T,T> target, boolean reverse) {
75 for (int i = 0; i < source.length; ++i) {
76 target.put(source[i][from], source[i][to]);
82 * Add all items in iterator to target collection
89 public static <T, U extends Collection<T>> U addAll(Iterator<T> source, U target) {
90 while (source.hasNext()) {
91 target.add(source.next());
93 return target; // for chaining
97 * Get the size of an iterator (number of items in it).
101 public static int size(Iterator source) {
103 while (source.hasNext()) {
116 public static <T> Map<T,T> asMap(T[][] source) {
117 return asMap(source, new HashMap<T,T>(), false);
121 * Utility that ought to be on Map
123 * @param itemsToRemove
126 * @return map passed in
128 public static <K,V> Map<K,V> removeAll(Map<K,V> m, Collection<K> itemsToRemove) {
129 for (Iterator it = itemsToRemove.iterator(); it.hasNext();) {
130 Object item = it.next();
137 * Get first item in collection, or null if there is none.
143 public <T, U extends Collection<T>> T getFirst(U c) {
144 Iterator<T> it = c.iterator();
145 if (!it.hasNext()) return null;
150 * 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 public static <T, U extends Collection<T>> T getBest(U c, Comparator<T> comp, int direction) {
159 Iterator<T> it = c.iterator();
160 if (!it.hasNext()) return null;
161 T bestSoFar = it.next();
163 while (it.hasNext()) {
165 int compValue = comp.compare(item, bestSoFar);
171 while (it.hasNext()) {
173 int compValue = comp.compare(item, bestSoFar);
186 public interface ObjectMatcher<T> {
188 * Must handle null, never throw exception
192 boolean matches(T o);
199 public static class InverseMatcher<T> implements ObjectMatcher<T> {
200 ObjectMatcher<T> other;
205 public ObjectMatcher set(ObjectMatcher toInverse) {
209 public boolean matches(T value) {
210 return !other.matches(value);
215 * Remove matching items
222 public static <T, U extends Collection<T>> U removeAll(U c, ObjectMatcher<T> f) {
223 for (Iterator<T> it = c.iterator(); it.hasNext();) {
225 if (f.matches(item)) it.remove();
231 * Retain matching items
238 public static <T, U extends Collection<T>> U retainAll(U c, ObjectMatcher<T> f) {
239 for (Iterator<T> it = c.iterator(); it.hasNext();) {
241 if (!f.matches(item)) it.remove();
251 public static boolean containsSome(Collection a, Collection b) {
253 if (a.size() == 0 || b.size() == 0) return false;
254 if (a == b) return true; // must test after size test.
256 if (a instanceof SortedSet && b instanceof SortedSet) {
257 SortedSet aa = (SortedSet) a;
258 SortedSet bb = (SortedSet) b;
259 Comparator bbc = bb.comparator();
260 Comparator aac = aa.comparator();
261 if (bbc == null && aac == null) {
262 Iterator ai = aa.iterator();
263 Iterator bi = bb.iterator();
264 Comparable ao = (Comparable) ai.next(); // these are ok, since the sizes are != 0
265 Comparable bo = (Comparable) bi.next();
267 int rel = ao.compareTo(bo);
269 if (!ai.hasNext()) return false;
270 ao = (Comparable) ai.next();
271 } else if (rel > 0) {
272 if (!bi.hasNext()) return false;
273 bo = (Comparable) bi.next();
278 } else if (bbc.equals(a)) {
279 Iterator ai = aa.iterator();
280 Iterator bi = bb.iterator();
281 Object ao = ai.next(); // these are ok, since the sizes are != 0
282 Object bo = bi.next();
284 int rel = aac.compare(ao, bo);
286 if (!ai.hasNext()) return false;
288 } else if (rel > 0) {
289 if (!bi.hasNext()) return false;
297 for (Iterator it = a.iterator(); it.hasNext();) {
298 if (b.contains(it.next())) return true;
303 public static boolean containsAll(Collection a, Collection b) {
305 if (a == b) return true;
306 if (b.size() == 0) return true;
307 if (a.size() < b.size()) return false;
309 if (a instanceof SortedSet && b instanceof SortedSet) {
310 SortedSet aa = (SortedSet) a;
311 SortedSet bb = (SortedSet) b;
312 Comparator bbc = bb.comparator();
313 Comparator aac = aa.comparator();
314 if (bbc == null && aac == null) {
315 Iterator ai = aa.iterator();
316 Iterator bi = bb.iterator();
317 Comparable ao = (Comparable) ai.next(); // these are ok, since the sizes are != 0
318 Comparable bo = (Comparable) bi.next();
320 int rel = ao.compareTo(bo);
322 if (!bi.hasNext()) return true;
323 if (!ai.hasNext()) return false;
324 bo = (Comparable) bi.next();
325 ao = (Comparable) ai.next();
326 } else if (rel < 0) {
327 if (!ai.hasNext()) return false;
328 ao = (Comparable) ai.next();
333 } else if (bbc.equals(aac)) {
334 Iterator ai = aa.iterator();
335 Iterator bi = bb.iterator();
336 Object ao = ai.next(); // these are ok, since the sizes are != 0
337 Object bo = bi.next();
339 int rel = aac.compare(ao, bo);
341 if (!bi.hasNext()) return true;
342 if (!ai.hasNext()) return false;
345 } else if (rel < 0) {
346 if (!ai.hasNext()) return false;
354 return a.containsAll(b);
357 public static boolean containsNone(Collection a, Collection b) {
358 return !containsSome(a, b);
362 * Used for results of getContainmentRelation
364 public static final int
366 NOT_A_SUPERSET_B = 1,
367 NOT_A_DISJOINT_B = 2,
369 NOT_A_EQUALS_B = NOT_A_SUBSET_B | NOT_A_SUPERSET_B,
370 A_PROPER_SUBSET_OF_B = NOT_A_DISJOINT_B | NOT_A_SUPERSET_B,
371 A_PROPER_SUPERSET_B = NOT_A_SUBSET_B | NOT_A_DISJOINT_B,
372 A_PROPER_OVERLAPS_B = NOT_A_SUBSET_B | NOT_A_DISJOINT_B | NOT_A_SUPERSET_B;
375 * Assesses all the possible containment relations between collections A and B with one call.<br>
376 * Returns an int with bits set, according to a "Venn Diagram" view of A vs B.<br>
377 * NOT_A_SUPERSET_B: a - b != {}<br>
378 * NOT_A_DISJOINT_B: a * b != {} // * is intersects<br>
379 * NOT_A_SUBSET_B: b - a != {}<br>
380 * Thus the bits can be used to get the following relations:<br>
381 * for A_SUPERSET_B, use (x & CollectionUtilities.NOT_A_SUPERSET_B) == 0<br>
382 * for A_SUBSET_B, use (x & CollectionUtilities.NOT_A_SUBSET_B) == 0<br>
383 * for A_EQUALS_B, use (x & CollectionUtilities.NOT_A_EQUALS_B) == 0<br>
384 * for A_DISJOINT_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) == 0<br>
385 * for A_OVERLAPS_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) != 0<br>
387 public static int getContainmentRelation(Collection a, Collection b) {
389 return (b.size() == 0) ? ALL_EMPTY : NOT_A_SUPERSET_B;
390 } else if (b.size() == 0) {
391 return NOT_A_SUBSET_B;
394 // WARNING: one might think that the following can be short-circuited, by looking at
395 // the sizes of a and b. However, this would fail in general, where a different comparator is being
396 // used in the two collections. Unfortunately, there is no failsafe way to test for that.
397 for (Iterator it = a.iterator(); result != 6 && it.hasNext();) {
398 result |= (b.contains(it.next())) ? NOT_A_DISJOINT_B : NOT_A_SUBSET_B;
400 for (Iterator it = b.iterator(); (result & 3) != 3 && it.hasNext();) {
401 result |= (a.contains(it.next())) ? NOT_A_DISJOINT_B : NOT_A_SUPERSET_B;
406 public static String remove(String source, UnicodeSet removals) {
407 StringBuffer result = new StringBuffer();
409 for (int i = 0; i < source.length(); i += UTF16.getCharCount(cp)) {
410 cp = UTF16.charAt(source, i);
411 if (!removals.contains(cp)) UTF16.append(result, cp);
413 return result.toString();
417 * Does one string contain another, starting at a specific offset?
423 public static int matchesAt(CharSequence text, int offset, CharSequence other) {
424 int len = other.length();
427 for (; i < len; ++i, ++j) {
428 char pc = other.charAt(i);
429 char tc = text.charAt(j);
430 if (pc != tc) return -1;
436 * Returns the ending offset found by matching characters with testSet, until a position is found that doen't match
442 public int span(CharSequence string, int offset, UnicodeSet testSet) {
444 int newOffset = testSet.matchesAt(string, offset);
445 if (newOffset < 0) return offset;
450 * Returns the ending offset found by matching characters with testSet, until a position is found that does match
456 public int spanNot(CharSequence string, int offset, UnicodeSet testSet) {
458 int newOffset = testSet.matchesAt(string, offset);
459 if (newOffset >= 0) return offset;
460 ++offset; // try next character position
461 // we don't have to worry about surrogates for this.
466 * Modifies Unicode set to flatten the strings. Eg [abc{da}] => [abcd]
467 * Returns the set for chaining.
471 public static UnicodeSet flatten(UnicodeSet exemplar1) {
472 UnicodeSet result = new UnicodeSet();
473 boolean gotString = false;
474 for (UnicodeSetIterator it = new UnicodeSetIterator(exemplar1); it.nextRange();) {
475 if (it.codepoint == UnicodeSetIterator.IS_STRING) {
476 result.addAll(it.string);
479 result.add(it.codepoint, it.codepointEnd);
482 if (gotString) exemplar1.set(result);
487 * For producing filtered iterators
489 public static abstract class FilteredIterator implements Iterator {
490 private Iterator baseIterator;
491 private static final Object EMPTY = new Object();
492 private static final Object DONE = new Object();
493 private Object nextObject = EMPTY;
494 public FilteredIterator set(Iterator baseIterator) {
495 this.baseIterator = baseIterator;
498 public void remove() {
499 throw new UnsupportedOperationException("Doesn't support removal");
501 public Object next() {
502 Object result = nextObject;
506 public boolean hasNext() {
507 if (nextObject == DONE) return false;
508 if (nextObject != EMPTY) return true;
509 while (baseIterator.hasNext()) {
510 nextObject = baseIterator.next();
511 if (isIncluded(nextObject)) {
518 abstract public boolean isIncluded(Object item);
521 public static class PrefixIterator extends FilteredIterator {
522 private String prefix;
523 public PrefixIterator set(Iterator baseIterator, String prefix) {
524 super.set(baseIterator);
525 this.prefix = prefix;
528 public boolean isIncluded(Object item) {
529 return ((String)item).startsWith(prefix);
533 public static class RegexIterator extends FilteredIterator {
534 private Matcher matcher;
535 public RegexIterator set(Iterator baseIterator, Matcher matcher) {
536 super.set(baseIterator);
537 this.matcher = matcher;
540 public boolean isIncluded(Object item) {
541 return matcher.reset((String)item).matches();