View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.collection;
18  
19  import java.util.Collection;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.Objects;
23  import java.util.function.Predicate;
24  
25  import org.apache.commons.collections4.MultiMap;
26  import org.apache.commons.collections4.Transformer;
27  import org.apache.commons.collections4.map.MultiValueMap;
28  
29  /**
30   * An IndexedCollection is a Map-like view onto a Collection. It accepts a
31   * keyTransformer to define how the keys are converted from the values.
32   * <p>
33   * Modifications made to this decorator modify the index as well as the
34   * decorated {@link Collection}. However, modifications to the underlying
35   * {@link Collection} will not update the index and it will get out of sync.
36   * </p>
37   * <p>
38   * If modification of the decorated {@link Collection} is unavoidable, then a
39   * call to {@link #reindex()} will update the index to the current contents of
40   * the {@link Collection}.
41   * </p>
42   *
43   * @param <K> the type of object in the index.
44   * @param <C> the type of object in the collection.
45   * @since 4.0
46   */
47  public class IndexedCollection<K, C> extends AbstractCollectionDecorator<C> {
48  
49      // TODO: replace with MultiValuedMap
50  
51      /** Serialization version */
52      private static final long serialVersionUID = -5512610452568370038L;
53  
54      /**
55       * Create an {@link IndexedCollection} for a non-unique index.
56       *
57       * @param <K> the index object type.
58       * @param <C> the collection type.
59       * @param coll the decorated {@link Collection}.
60       * @param keyTransformer the {@link Transformer} for generating index keys.
61       * @return the created {@link IndexedCollection}.
62       */
63      public static <K, C> IndexedCollection<K, C> nonUniqueIndexedCollection(final Collection<C> coll,
64                                                                              final Transformer<C, K> keyTransformer) {
65          return new IndexedCollection<>(coll, keyTransformer,
66                                             MultiValueMap.<K, C>multiValueMap(new HashMap<>()),
67                                             false);
68      }
69  
70      /**
71       * Create an {@link IndexedCollection} for a unique index.
72       * <p>
73       * If an element is added, which maps to an existing key, an {@link IllegalArgumentException}
74       * will be thrown.
75       *
76       * @param <K> the index object type.
77       * @param <C> the collection type.
78       * @param coll the decorated {@link Collection}.
79       * @param keyTransformer the {@link Transformer} for generating index keys.
80       * @return the created {@link IndexedCollection}.
81       */
82      public static <K, C> IndexedCollection<K, C> uniqueIndexedCollection(final Collection<C> coll,
83                                                                           final Transformer<C, K> keyTransformer) {
84          return new IndexedCollection<>(coll, keyTransformer,
85                                             MultiValueMap.<K, C>multiValueMap(new HashMap<>()),
86                                             true);
87      }
88  
89      /** The {@link Transformer} for generating index keys. */
90      private final Transformer<C, K> keyTransformer;
91  
92      /** The map of indexes to collected objects. */
93      private final MultiMap<K, C> index;
94  
95      /** The uniqueness constraint for the index. */
96      private final boolean uniqueIndex;
97  
98      /**
99       * Create a {@link IndexedCollection}.
100      *
101      * @param coll  decorated {@link Collection}
102      * @param keyTransformer  {@link Transformer} for generating index keys
103      * @param map  map to use as index
104      * @param uniqueIndex  if the index shall enforce uniqueness of index keys
105      */
106     public IndexedCollection(final Collection<C> coll, final Transformer<C, K> keyTransformer,
107                              final MultiMap<K, C> map, final boolean uniqueIndex) {
108         super(coll);
109         this.keyTransformer = keyTransformer;
110         this.index = map;
111         this.uniqueIndex = uniqueIndex;
112         reindex();
113     }
114 
115     /**
116      * {@inheritDoc}
117      *
118      * @throws IllegalArgumentException if the object maps to an existing key and the index
119      *   enforces a uniqueness constraint
120      */
121     @Override
122     public boolean add(final C object) {
123         final boolean added = super.add(object);
124         if (added) {
125             addToIndex(object);
126         }
127         return added;
128     }
129 
130     @Override
131     public boolean addAll(final Collection<? extends C> coll) {
132         boolean changed = false;
133         for (final C c: coll) {
134             changed |= add(c);
135         }
136         return changed;
137     }
138 
139     /**
140      * Provides checking for adding the index.
141      *
142      * @param object the object to index
143      * @throws IllegalArgumentException if the object maps to an existing key and the index
144      *   enforces a uniqueness constraint
145      */
146     private void addToIndex(final C object) {
147         final K key = keyTransformer.apply(object);
148         if (uniqueIndex && index.containsKey(key)) {
149             throw new IllegalArgumentException("Duplicate key in uniquely indexed collection.");
150         }
151         index.put(key, object);
152     }
153 
154     @Override
155     public void clear() {
156         super.clear();
157         index.clear();
158     }
159 
160     /**
161      * {@inheritDoc}
162      * <p>
163      * Note: uses the index for fast lookup
164      */
165     @SuppressWarnings("unchecked")
166     @Override
167     public boolean contains(final Object object) {
168         return index.containsKey(keyTransformer.apply((C) object));
169     }
170 
171     /**
172      * {@inheritDoc}
173      * <p>
174      * Note: uses the index for fast lookup
175      */
176     @Override
177     public boolean containsAll(final Collection<?> coll) {
178         for (final Object o : coll) {
179             if (!contains(o)) {
180                 return false;
181             }
182         }
183         return true;
184     }
185 
186     /**
187      * Gets the element associated with the given key.
188      * <p>
189      * In case of a non-unique index, this method will return the first
190      * value associated with the given key. To retrieve all elements associated
191      * with a key, use {@link #values(Object)}.
192      *
193      * @param key  key to look up
194      * @return element found
195      * @see #values(Object)
196      */
197     public C get(final K key) {
198         @SuppressWarnings("unchecked") // index is a MultiMap which returns a Collection
199         final Collection<C> coll = (Collection<C>) index.get(key);
200         return coll == null ? null : coll.iterator().next();
201     }
202 
203     /**
204      * Clears the index and re-indexes the entire decorated {@link Collection}.
205      */
206     public void reindex() {
207         index.clear();
208         for (final C c : decorated()) {
209             addToIndex(c);
210         }
211     }
212 
213     @SuppressWarnings("unchecked")
214     @Override
215     public boolean remove(final Object object) {
216         final boolean removed = super.remove(object);
217         if (removed) {
218             removeFromIndex((C) object);
219         }
220         return removed;
221     }
222 
223     @Override
224     public boolean removeAll(final Collection<?> coll) {
225         boolean changed = false;
226         for (final Object o : coll) {
227             changed |= remove(o);
228         }
229         return changed;
230     }
231 
232     /**
233      * Removes an object from the index.
234      *
235      * @param object the object to remove
236      */
237     private void removeFromIndex(final C object) {
238         index.remove(keyTransformer.apply(object));
239     }
240 
241     /**
242      * @since 4.4
243      */
244     @Override
245     public boolean removeIf(final Predicate<? super C> filter) {
246         if (Objects.isNull(filter)) {
247             return false;
248         }
249         boolean changed = false;
250         final Iterator<C> it = iterator();
251         while (it.hasNext()) {
252             if (filter.test(it.next())) {
253                 it.remove();
254                 changed = true;
255             }
256         }
257         if (changed) {
258             reindex();
259         }
260         return changed;
261     }
262 
263     @Override
264     public boolean retainAll(final Collection<?> coll) {
265         final boolean changed = super.retainAll(coll);
266         if (changed) {
267             reindex();
268         }
269         return changed;
270     }
271 
272     /**
273      * Gets all elements associated with the given key.
274      *
275      * @param key  key to look up
276      * @return a collection of elements found, or null if {@code contains(key) == false}
277      */
278     @SuppressWarnings("unchecked") // index is a MultiMap which returns a Collection
279     public Collection<C> values(final K key) {
280         return (Collection<C>) index.get(key);
281     }
282 
283 }