Home | History | Annotate | Download | only in util
      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