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 }