2 *******************************************************************************
\r
3 * Copyright (C) 1996-2009, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
7 package com.ibm.icu.dev.test.util;
\r
9 import java.util.Collection;
\r
10 import java.util.Comparator;
\r
11 import java.util.HashMap;
\r
12 import java.util.Iterator;
\r
13 import java.util.Map;
\r
14 import java.util.SortedSet;
\r
15 import java.util.regex.Matcher;
\r
17 import com.ibm.icu.text.UTF16;
\r
18 import com.ibm.icu.text.UnicodeSet;
\r
19 import com.ibm.icu.text.UnicodeSetIterator;
\r
22 * Utilities that ought to be on collections, but aren't
\r
24 public final class CollectionUtilities {
\r
26 public static String join(Object[] array, String separator) {
\r
27 StringBuffer result = new StringBuffer();
\r
28 for (int i = 0; i < array.length; ++i) {
\r
29 if (i != 0) result.append(separator);
\r
30 result.append(array[i]);
\r
32 return result.toString();
\r
35 public static String join(Collection collection, String separator) {
\r
36 StringBuffer result = new StringBuffer();
\r
37 boolean first = true;
\r
38 for (Iterator it = collection.iterator(); it.hasNext();) {
\r
39 if (first) first = false;
\r
40 else result.append(separator);
\r
41 result.append(it.next());
\r
43 return result.toString();
\r
47 * Utility like Arrays.asList()
\r
49 public static Map asMap(Object[][] source, Map target, boolean reverse) {
\r
50 int from = 0, to = 1;
\r
54 for (int i = 0; i < source.length; ++i) {
\r
55 target.put(source[i][from], source[i][to]);
\r
60 public static Collection addAll(Iterator source, Collection target) {
\r
61 while (source.hasNext()) {
\r
62 target.add(source.next());
\r
64 return target; // for chaining
\r
67 public static int size(Iterator source) {
\r
69 while (source.hasNext()) {
\r
77 public static Map asMap(Object[][] source) {
\r
78 return asMap(source, new HashMap(), false);
\r
82 * Utility that ought to be on Map
\r
84 public static Map removeAll(Map m, Collection itemsToRemove) {
\r
85 for (Iterator it = itemsToRemove.iterator(); it.hasNext();) {
\r
86 Object item = it.next();
\r
92 public Object getFirst(Collection c) {
\r
93 Iterator it = c.iterator();
\r
94 if (!it.hasNext()) return null;
\r
98 public static Object getBest(Collection c, Comparator comp, int direction) {
\r
99 Iterator it = c.iterator();
\r
100 if (!it.hasNext()) return null;
\r
101 Object bestSoFar = it.next();
\r
102 if (direction < 0) {
\r
103 while (it.hasNext()) {
\r
104 Object item = it.next();
\r
105 int compValue = comp.compare(item, bestSoFar);
\r
106 if (compValue < 0) {
\r
111 while (it.hasNext()) {
\r
112 Object item = it.next();
\r
113 int compValue = comp.compare(item, bestSoFar);
\r
114 if (compValue > 0) {
\r
122 public interface ObjectMatcher {
\r
124 * Must handle null, never throw exception
\r
126 boolean matches(Object o);
\r
129 public static class InverseMatcher implements ObjectMatcher {
\r
130 ObjectMatcher other;
\r
131 public ObjectMatcher set(ObjectMatcher toInverse) {
\r
135 public boolean matches(Object value) {
\r
136 return !other.matches(value);
\r
140 public static Collection removeAll(Collection c, ObjectMatcher f) {
\r
141 for (Iterator it = c.iterator(); it.hasNext();) {
\r
142 Object item = it.next();
\r
143 if (f.matches(item)) it.remove();
\r
148 public static Collection retainAll(Collection c, ObjectMatcher f) {
\r
149 for (Iterator it = c.iterator(); it.hasNext();) {
\r
150 Object item = it.next();
\r
151 if (!f.matches(item)) it.remove();
\r
156 public static boolean containsSome(Collection a, Collection b) {
\r
158 if (a.size() == 0 || b.size() == 0) return false;
\r
159 if (a == b) return true; // must test after size test.
\r
161 if (a instanceof SortedSet && b instanceof SortedSet) {
\r
162 SortedSet aa = (SortedSet) a;
\r
163 SortedSet bb = (SortedSet) b;
\r
164 Comparator bbc = bb.comparator();
\r
165 Comparator aac = aa.comparator();
\r
166 if (bbc == null && aac == null) {
\r
167 Iterator ai = aa.iterator();
\r
168 Iterator bi = bb.iterator();
\r
169 Comparable ao = (Comparable) ai.next(); // these are ok, since the sizes are != 0
\r
170 Comparable bo = (Comparable) bi.next();
\r
172 int rel = ao.compareTo(bo);
\r
174 if (!ai.hasNext()) return false;
\r
175 ao = (Comparable) ai.next();
\r
176 } else if (rel > 0) {
\r
177 if (!bi.hasNext()) return false;
\r
178 bo = (Comparable) bi.next();
\r
183 } else if (bbc.equals(a)) {
\r
184 Iterator ai = aa.iterator();
\r
185 Iterator bi = bb.iterator();
\r
186 Object ao = ai.next(); // these are ok, since the sizes are != 0
\r
187 Object bo = bi.next();
\r
189 int rel = aac.compare(ao, bo);
\r
191 if (!ai.hasNext()) return false;
\r
193 } else if (rel > 0) {
\r
194 if (!bi.hasNext()) return false;
\r
202 for (Iterator it = a.iterator(); it.hasNext();) {
\r
203 if (b.contains(it.next())) return true;
\r
208 public static boolean containsAll(Collection a, Collection b) {
\r
210 if (a == b) return true;
\r
211 if (b.size() == 0) return true;
\r
212 if (a.size() < b.size()) return false;
\r
214 if (a instanceof SortedSet && b instanceof SortedSet) {
\r
215 SortedSet aa = (SortedSet) a;
\r
216 SortedSet bb = (SortedSet) b;
\r
217 Comparator bbc = bb.comparator();
\r
218 Comparator aac = aa.comparator();
\r
219 if (bbc == null && aac == null) {
\r
220 Iterator ai = aa.iterator();
\r
221 Iterator bi = bb.iterator();
\r
222 Comparable ao = (Comparable) ai.next(); // these are ok, since the sizes are != 0
\r
223 Comparable bo = (Comparable) bi.next();
\r
225 int rel = ao.compareTo(bo);
\r
227 if (!bi.hasNext()) return true;
\r
228 if (!ai.hasNext()) return false;
\r
229 bo = (Comparable) bi.next();
\r
230 ao = (Comparable) ai.next();
\r
231 } else if (rel < 0) {
\r
232 if (!ai.hasNext()) return false;
\r
233 ao = (Comparable) ai.next();
\r
238 } else if (bbc.equals(aac)) {
\r
239 Iterator ai = aa.iterator();
\r
240 Iterator bi = bb.iterator();
\r
241 Object ao = ai.next(); // these are ok, since the sizes are != 0
\r
242 Object bo = bi.next();
\r
244 int rel = aac.compare(ao, bo);
\r
246 if (!bi.hasNext()) return true;
\r
247 if (!ai.hasNext()) return false;
\r
250 } else if (rel < 0) {
\r
251 if (!ai.hasNext()) return false;
\r
259 return a.containsAll(b);
\r
262 public static boolean containsNone(Collection a, Collection b) {
\r
263 return !containsSome(a, b);
\r
267 * Used for results of getContainmentRelation
\r
269 public static final int
\r
271 NOT_A_SUPERSET_B = 1,
\r
272 NOT_A_DISJOINT_B = 2,
\r
273 NOT_A_SUBSET_B = 4,
\r
274 NOT_A_EQUALS_B = NOT_A_SUBSET_B | NOT_A_SUPERSET_B,
\r
275 A_PROPER_SUBSET_OF_B = NOT_A_DISJOINT_B | NOT_A_SUPERSET_B,
\r
276 A_PROPER_SUPERSET_B = NOT_A_SUBSET_B | NOT_A_DISJOINT_B,
\r
277 A_PROPER_OVERLAPS_B = NOT_A_SUBSET_B | NOT_A_DISJOINT_B | NOT_A_SUPERSET_B;
\r
280 * Assesses all the possible containment relations between collections A and B with one call.<br>
\r
281 * Returns an int with bits set, according to a "Venn Diagram" view of A vs B.<br>
\r
282 * NOT_A_SUPERSET_B: a - b != {}<br>
\r
283 * NOT_A_DISJOINT_B: a * b != {} // * is intersects<br>
\r
284 * NOT_A_SUBSET_B: b - a != {}<br>
\r
285 * Thus the bits can be used to get the following relations:<br>
\r
286 * for A_SUPERSET_B, use (x & CollectionUtilities.NOT_A_SUPERSET_B) == 0<br>
\r
287 * for A_SUBSET_B, use (x & CollectionUtilities.NOT_A_SUBSET_B) == 0<br>
\r
288 * for A_EQUALS_B, use (x & CollectionUtilities.NOT_A_EQUALS_B) == 0<br>
\r
289 * for A_DISJOINT_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) == 0<br>
\r
290 * for A_OVERLAPS_B, use (x & CollectionUtilities.NOT_A_DISJOINT_B) != 0<br>
\r
292 public static int getContainmentRelation(Collection a, Collection b) {
\r
293 if (a.size() == 0) {
\r
294 return (b.size() == 0) ? ALL_EMPTY : NOT_A_SUPERSET_B;
\r
295 } else if (b.size() == 0) {
\r
296 return NOT_A_SUBSET_B;
\r
299 // WARNING: one might think that the following can be short-circuited, by looking at
\r
300 // the sizes of a and b. However, this would fail in general, where a different comparator is being
\r
301 // used in the two collections. Unfortunately, there is no failsafe way to test for that.
\r
302 for (Iterator it = a.iterator(); result != 6 && it.hasNext();) {
\r
303 result |= (b.contains(it.next())) ? NOT_A_DISJOINT_B : NOT_A_SUBSET_B;
\r
305 for (Iterator it = b.iterator(); (result & 3) != 3 && it.hasNext();) {
\r
306 result |= (a.contains(it.next())) ? NOT_A_DISJOINT_B : NOT_A_SUPERSET_B;
\r
311 public static String remove(String source, UnicodeSet removals) {
\r
312 StringBuffer result = new StringBuffer();
\r
314 for (int i = 0; i < source.length(); i += UTF16.getCharCount(cp)) {
\r
315 cp = UTF16.charAt(source, i);
\r
316 if (!removals.contains(cp)) UTF16.append(result, cp);
\r
318 return result.toString();
\r
322 * Does one string contain another, starting at a specific offset?
\r
328 public static int matchesAt(CharSequence text, int offset, CharSequence other) {
\r
329 int len = other.length();
\r
332 for (; i < len; ++i, ++j) {
\r
333 char pc = other.charAt(i);
\r
334 char tc = text.charAt(j);
\r
335 if (pc != tc) return -1;
\r
341 * Returns the ending offset found by matching characters with testSet, until a position is found that doen't match
\r
347 public int span(CharSequence string, int offset, UnicodeSet testSet) {
\r
349 int newOffset = testSet.matchesAt(string, offset);
\r
350 if (newOffset < 0) return offset;
\r
355 * Returns the ending offset found by matching characters with testSet, until a position is found that does match
\r
361 public int spanNot(CharSequence string, int offset, UnicodeSet testSet) {
\r
363 int newOffset = testSet.matchesAt(string, offset);
\r
364 if (newOffset >= 0) return offset;
\r
365 ++offset; // try next character position
\r
366 // we don't have to worry about surrogates for this.
\r
371 * Modifies Unicode set to flatten the strings. Eg [abc{da}] => [abcd]
\r
372 * Returns the set for chaining.
\r
376 public static UnicodeSet flatten(UnicodeSet exemplar1) {
\r
377 UnicodeSet result = new UnicodeSet();
\r
378 boolean gotString = false;
\r
379 for (UnicodeSetIterator it = new UnicodeSetIterator(exemplar1); it.nextRange();) {
\r
380 if (it.codepoint == UnicodeSetIterator.IS_STRING) {
\r
381 result.addAll(it.string);
\r
384 result.add(it.codepoint, it.codepointEnd);
\r
387 if (gotString) exemplar1.set(result);
\r
392 * For producing filtered iterators
\r
394 public static abstract class FilteredIterator implements Iterator {
\r
395 private Iterator baseIterator;
\r
396 private static final Object EMPTY = new Object();
\r
397 private static final Object DONE = new Object();
\r
398 private Object nextObject = EMPTY;
\r
399 public FilteredIterator set(Iterator baseIterator) {
\r
400 this.baseIterator = baseIterator;
\r
403 public void remove() {
\r
404 throw new UnsupportedOperationException("Doesn't support removal");
\r
406 public Object next() {
\r
407 Object result = nextObject;
\r
408 nextObject = EMPTY;
\r
411 public boolean hasNext() {
\r
412 if (nextObject == DONE) return false;
\r
413 if (nextObject != EMPTY) return true;
\r
414 while (baseIterator.hasNext()) {
\r
415 nextObject = baseIterator.next();
\r
416 if (isIncluded(nextObject)) {
\r
423 abstract public boolean isIncluded(Object item);
\r
426 public static class PrefixIterator extends FilteredIterator {
\r
427 private String prefix;
\r
428 public PrefixIterator set(Iterator baseIterator, String prefix) {
\r
429 super.set(baseIterator);
\r
430 this.prefix = prefix;
\r
433 public boolean isIncluded(Object item) {
\r
434 return ((String)item).startsWith(prefix);
\r
438 public static class RegexIterator extends FilteredIterator {
\r
439 private Matcher matcher;
\r
440 public RegexIterator set(Iterator baseIterator, Matcher matcher) {
\r
441 super.set(baseIterator);
\r
442 this.matcher = matcher;
\r
445 public boolean isIncluded(Object item) {
\r
446 return matcher.reset((String)item).matches();
\r