1 package org.unicode.cldr.util; 2 3 import java.util.Arrays; 4 import java.util.Collection; 5 import java.util.Collections; 6 import java.util.HashSet; 7 import java.util.Iterator; 8 import java.util.List; 9 import java.util.Map; 10 import java.util.Map.Entry; 11 import java.util.Set; 12 import java.util.SortedMap; 13 import java.util.SortedSet; 14 15 import com.ibm.icu.text.Transform; 16 17 /** 18 * Convenience class for building collections and maps. Allows them to be built by chaining, making it simpler to 19 * set as parameters and fields. Also supplies some operations that are missing on the JDK maps and collections, 20 * and provides finer control for what happens with equal elements. 21 * <p> 22 * You start with Builder.with(...) and end with either .get() or .freeze(). With .freeze, the result is unmodifiable 23 * (but its objects may be). Examples: 24 * <ul> 25 * <li>Set<String> sorted = Builder.with(new TreeSet<String>()).addAll(anIterator).addAll(aCollection).addAll(item1, 26 * item2, item3).get();</li> 27 * <li>Map<String, Integer> map = Builder.with(new TreeMap<String, Integer>()).put("one",2).putAll(otherMap).freeze(); 28 * </ul> 29 * 30 * <p> 31 * The builder allows some options that the normal collections don't have, with the EqualAction. If none it specified, 32 * then it behaves like Java collections. 33 * 34 * <pre> 35 * Operations: A is current contents, B is new collection, x indicates the results 36 * A-B A&B B-A Name 37 * clear() 38 * x removeAll(B) 39 * x retainAll(B) -- option 1: keep A, option 2: substitute B 40 * x keepNew(B) 41 * x x <no operation> 42 * x x clear().addAll(B) 43 * x x xor(B) 44 * x x x addAll(B) 45 * </pre> 46 * 47 * @author markdavis 48 */ 49 public final class Builder { 50 enum EqualAction { 51 /** 52 * If you try to add an item that is already there, or change the mapping, do whatever the source collation or 53 * map does. 54 */ 55 NATIVE, 56 /** 57 * If you try to add an item that is already there, or change the mapping, take the new item. 58 */ 59 REPLACE, 60 /** 61 * If you try to add an item that is already there, or change the mapping, retain the old one. 62 */ 63 RETAIN, 64 /** 65 * If you try to add an item that is already there, or change the mapping, throw an exception. 66 */ 67 THROW 68 } 69 70 public static <E, C extends Collection<E>> CBuilder<E, C> with(C collection, EqualAction ea) { 71 return new CBuilder<E, C>(collection, ea); 72 } 73 74 public static <E, C extends Collection<E>> CBuilder<E, C> with(C collection) { 75 return new CBuilder<E, C>(collection, EqualAction.NATIVE); 76 } 77 78 public static <K, V, M extends Map<K, V>> MBuilder<K, V, M> with(M map, EqualAction ea) { 79 return new MBuilder<K, V, M>(map, ea); 80 } 81 82 public static <K, V, M extends Map<K, V>> MBuilder<K, V, M> with(M map) { 83 return new MBuilder<K, V, M>(map, EqualAction.NATIVE); 84 } 85 86 // ===== Collections ====== 87 88 public static final class CBuilder<E, U extends Collection<E>> { 89 public EqualAction getEqualAction() { 90 return equalAction; 91 } 92 93 public CBuilder<E, U> setEqualAction(EqualAction equalAction) { 94 this.equalAction = equalAction; 95 return this; 96 } 97 98 public CBuilder<E, U> clear() { 99 collection.clear(); 100 return this; 101 } 102 103 public CBuilder<E, U> add(E e) { 104 switch (equalAction) { 105 case NATIVE: 106 break; 107 case REPLACE: 108 collection.remove(e); 109 break; 110 case RETAIN: 111 if (collection.contains(e)) { 112 return this; 113 } 114 break; 115 case THROW: 116 if (collection.contains(e)) { 117 throw new IllegalArgumentException("Map already contains " + e); 118 } 119 } 120 collection.add(e); 121 return this; 122 } 123 124 public CBuilder<E, U> addAll(Iterable<? extends E> c) { 125 if (equalAction == EqualAction.REPLACE && c instanceof Collection<?>) { 126 collection.addAll((Collection<? extends E>) c); 127 } else { 128 for (E item : c) { 129 add(item); 130 } 131 } 132 return this; 133 } 134 135 @SuppressWarnings("unchecked") 136 public CBuilder<E, U> addAll(E... items) { 137 for (E item : items) { 138 collection.add(item); 139 } 140 return this; 141 } 142 143 public CBuilder<E, U> addAll(Iterator<E> items) { 144 while (items.hasNext()) { 145 collection.add(items.next()); 146 } 147 return this; 148 } 149 150 public <T> CBuilder<E, U> addAll(Transform<T, E> transform, Iterable<? extends T> c) { 151 return addAll(Transformer.iterator(transform, c)); 152 } 153 154 @SuppressWarnings("unchecked") 155 public <T> CBuilder<E, U> addAll(Transform<T, E> transform, T... items) { 156 return addAll(Transformer.iterator(transform, items)); 157 } 158 159 public <T> CBuilder<E, U> addAll(Transform<T, E> transform, Iterator<T> items) { 160 return addAll(Transformer.iterator(transform, items)); 161 } 162 163 public CBuilder<E, U> remove(E o) { 164 collection.remove(o); 165 return this; 166 } 167 168 public CBuilder<E, U> removeAll(Collection<? extends E> c) { 169 collection.removeAll(c); 170 return this; 171 } 172 173 public CBuilder<E, U> removeAll(Transform<E, Boolean> predicate) { 174 collection.removeAll(getMatchingItems(predicate, collection, new HashSet<E>())); 175 return this; 176 } 177 178 @SuppressWarnings("unchecked") 179 public CBuilder<E, U> removeAll(E... items) { 180 for (E item : items) { 181 collection.remove(item); 182 } 183 return this; 184 } 185 186 public CBuilder<E, U> removeAll(Iterator<E> items) { 187 while (items.hasNext()) { 188 collection.remove(items.next()); 189 } 190 return this; 191 } 192 193 public CBuilder<E, U> retainAll(Collection<? extends E> c) { 194 collection.retainAll(c); 195 return this; 196 } 197 198 @SuppressWarnings("unchecked") 199 public CBuilder<E, U> retainAll(E... items) { 200 collection.retainAll(Arrays.asList(items)); 201 return this; 202 } 203 204 public CBuilder<E, U> retainAll(Iterator<E> items) { 205 HashSet<E> temp = Builder.with(new HashSet<E>()).addAll(items).get(); 206 collection.retainAll(temp); 207 return this; 208 } 209 210 public CBuilder<E, U> retainAll(Transform<E, Boolean> predicate) { 211 collection.retainAll(getMatchingItems(predicate, collection, new HashSet<E>())); 212 return this; 213 } 214 215 public CBuilder<E, U> xor(Collection<? extends E> c) { 216 for (E item : c) { 217 boolean changed = collection.remove(item); 218 if (!changed) { 219 collection.add(item); 220 } 221 } 222 return this; 223 } 224 225 @SuppressWarnings("unchecked") 226 public CBuilder<E, U> xor(E... items) { 227 return xor(Arrays.asList(items)); 228 } 229 230 public CBuilder<E, U> xor(Iterator<E> items) { 231 HashSet<E> temp = Builder.with(new HashSet<E>()).addAll(items).get(); 232 return xor(temp); 233 } 234 235 public CBuilder<E, U> keepNew(Collection<? extends E> c) { 236 HashSet<E> extras = new HashSet<E>(c); 237 extras.removeAll(collection); 238 collection.clear(); 239 collection.addAll(extras); 240 return this; 241 } 242 243 @SuppressWarnings("unchecked") 244 public CBuilder<E, U> keepNew(E... items) { 245 return keepNew(Arrays.asList(items)); 246 } 247 248 public CBuilder<E, U> keepNew(Iterator<E> items) { 249 HashSet<E> temp = Builder.with(new HashSet<E>()).addAll(items).get(); 250 return keepNew(temp); 251 } 252 253 public CBuilder<E, U> filter(Transform<E, Boolean> filter) { 254 HashSet<E> temp = new HashSet<E>(); 255 for (E item : collection) { 256 if (filter.transform(item) == Boolean.FALSE) { 257 temp.add(item); 258 } 259 } 260 collection.removeAll(temp); 261 return this; 262 } 263 264 public U get() { 265 U temp = collection; 266 collection = null; 267 return temp; 268 } 269 270 @SuppressWarnings("unchecked") 271 public U freeze() { 272 U temp; 273 if (collection instanceof SortedSet) { 274 temp = (U) Collections.unmodifiableSortedSet((SortedSet<E>) collection); 275 } else if (collection instanceof Set) { 276 temp = (U) Collections.unmodifiableSet((Set<E>) collection); 277 } else if (collection instanceof List) { 278 temp = (U) Collections.unmodifiableList((List<E>) collection); 279 } else { 280 temp = (U) Collections.unmodifiableCollection(collection); 281 } 282 collection = null; 283 return temp; 284 } 285 286 public String toString() { 287 return collection.toString(); 288 } 289 290 // ===== PRIVATES ====== 291 292 private CBuilder(U set2, EqualAction ea) { 293 this.collection = set2; 294 equalAction = ea; 295 } 296 297 private U collection; 298 private EqualAction equalAction; 299 } 300 301 // ===== Maps ====== 302 303 public static final class MBuilder<K, V, M extends Map<K, V>> { 304 305 public EqualAction getEqualAction() { 306 return equalAction; 307 } 308 309 public MBuilder<K, V, M> setEqualAction(EqualAction equalAction) { 310 this.equalAction = equalAction; 311 return this; 312 } 313 314 public MBuilder<K, V, M> clear() { 315 map.clear(); 316 return this; 317 } 318 319 public MBuilder<K, V, M> put(K key, V value) { 320 switch (equalAction) { 321 case NATIVE: 322 break; 323 case REPLACE: 324 map.remove(key); 325 break; 326 case RETAIN: 327 if (map.containsKey(key)) { 328 return this; 329 } 330 break; 331 case THROW: 332 if (map.containsKey(key)) { 333 throw new IllegalArgumentException("Map already contains " + key); 334 } 335 } 336 map.put(key, value); 337 return this; 338 } 339 340 @SuppressWarnings("unchecked") 341 public MBuilder<K, V, M> on(K... keys) { 342 this.keys = Arrays.asList(keys); 343 return this; 344 } 345 346 public MBuilder<K, V, M> on(Collection<? extends K> keys) { 347 this.keys = keys; 348 return this; 349 } 350 351 public MBuilder<K, V, M> put(V value) { 352 for (K key : keys) { 353 put(key, value); 354 } 355 keys = null; 356 return this; 357 } 358 359 @SuppressWarnings("unchecked") 360 public MBuilder<K, V, M> put(V... values) { 361 int v = 0; 362 for (K key : keys) { 363 put(key, values[v++]); 364 if (v >= values.length) { 365 v = 0; 366 } 367 } 368 keys = null; 369 return this; 370 } 371 372 public MBuilder<K, V, M> put(Collection<? extends V> values) { 373 Iterator<? extends V> vi = null; 374 for (K key : keys) { 375 if (vi == null || !vi.hasNext()) { 376 vi = values.iterator(); 377 } 378 put(key, vi.next()); 379 } 380 return this; 381 } 382 383 public MBuilder<K, V, M> putAll(Map<? extends K, ? extends V> m) { 384 if (equalAction == EqualAction.NATIVE) { 385 map.putAll(m); 386 } else { 387 for (Entry<? extends K, ? extends V> keyValue : m.entrySet()) { 388 put(keyValue.getKey(), keyValue.getValue()); 389 } 390 } 391 keys = null; 392 return this; 393 } 394 395 @SuppressWarnings("unchecked") 396 public MBuilder<K, V, M> putAll(K[][] m) { 397 for (K[] pair : m) { 398 put(pair[0], (V) (pair[1])); 399 } 400 keys = null; 401 return this; 402 } 403 404 public MBuilder<K, V, M> putAllTransposed(Map<? extends V, ? extends K> m) { 405 for (Entry<? extends V, ? extends K> keyValue : m.entrySet()) { 406 put(keyValue.getValue(), keyValue.getKey()); 407 } 408 return this; 409 } 410 411 public MBuilder<K, V, M> remove(K key) { 412 map.remove(key); 413 return this; 414 } 415 416 public MBuilder<K, V, M> removeAll(Collection<? extends K> keys) { 417 map.keySet().removeAll(keys); 418 return this; 419 } 420 421 @SuppressWarnings("unchecked") 422 public MBuilder<K, V, M> removeAll(K... keys) { 423 return removeAll(Arrays.asList(keys)); 424 } 425 426 public MBuilder<K, V, M> removeAll(Transform<K, Boolean> predicate) { 427 map.keySet().removeAll(getMatchingItems(predicate, map.keySet(), new HashSet<K>())); 428 return this; 429 } 430 431 public MBuilder<K, V, M> retainAll(Transform<K, Boolean> predicate) { 432 map.keySet().retainAll(getMatchingItems(predicate, map.keySet(), new HashSet<K>())); 433 return this; 434 } 435 436 public MBuilder<K, V, M> retainAll(Collection<? extends K> keys) { 437 map.keySet().retainAll(keys); 438 return this; 439 } 440 441 @SuppressWarnings("unchecked") 442 public MBuilder<K, V, M> retainAll(K... keys) { 443 return retainAll(Arrays.asList(keys)); 444 } 445 446 public <N extends Map<K, V>> MBuilder<K, V, M> xor(N c) { 447 for (K item : c.keySet()) { 448 if (map.containsKey(item)) { 449 map.remove(item); 450 } else { 451 put(item, c.get(item)); 452 } 453 } 454 return this; 455 } 456 457 public <N extends Map<K, V>> MBuilder<K, V, M> keepNew(N c) { 458 HashSet<K> extras = new HashSet<K>(c.keySet()); 459 extras.removeAll(map.keySet()); 460 map.clear(); 461 for (K key : extras) { 462 map.put(key, c.get(key)); 463 } 464 return this; 465 } 466 467 public M get() { 468 M temp = map; 469 map = null; 470 return temp; 471 } 472 473 @SuppressWarnings("unchecked") 474 public M freeze() { 475 M temp; 476 if (map instanceof SortedMap<?, ?>) { 477 temp = (M) Collections.unmodifiableSortedMap((SortedMap<K, V>) map); 478 } else { 479 temp = (M) Collections.unmodifiableMap((Map<K, V>) map); 480 } 481 map = null; 482 return temp; 483 } 484 485 public String toString() { 486 return map.toString(); 487 } 488 489 // ===== PRIVATES ====== 490 491 private Collection<? extends K> keys; 492 private M map; 493 private EqualAction equalAction; 494 495 private MBuilder(M map, EqualAction ea) { 496 this.map = map; 497 equalAction = ea; 498 } 499 } 500 501 public static <E> Collection<E> getMatchingItems(Transform<E, Boolean> predicate, Collection<E> collection, 502 Collection<E> matchingItems) { 503 for (E item : collection) { 504 if (predicate.transform(item)) { 505 matchingItems.add(item); 506 } 507 } 508 return matchingItems; 509 } 510 } 511