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 * https://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.io.IOException;
20 import java.io.InvalidObjectException;
21 import java.io.ObjectInputStream;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashSet;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Objects;
29 import java.util.Queue;
30 import java.util.Set;
31
32 import org.apache.commons.collections4.Bag;
33 import org.apache.commons.collections4.MultiSet;
34 import org.apache.commons.collections4.Predicate;
35 import org.apache.commons.collections4.bag.HashBag;
36 import org.apache.commons.collections4.bag.PredicatedBag;
37 import org.apache.commons.collections4.functors.NotNullPredicate;
38 import org.apache.commons.collections4.list.PredicatedList;
39 import org.apache.commons.collections4.multiset.HashMultiSet;
40 import org.apache.commons.collections4.multiset.PredicatedMultiSet;
41 import org.apache.commons.collections4.queue.PredicatedQueue;
42 import org.apache.commons.collections4.set.PredicatedSet;
43
44 /**
45 * Decorates another {@link Collection} to validate that additions
46 * match a specified predicate.
47 * <p>
48 * This collection exists to provide validation for the decorated collection.
49 * It is normally created to decorate an empty collection.
50 * If an object cannot be added to the collection, an IllegalArgumentException is thrown.
51 * </p>
52 * <p>
53 * One usage would be to ensure that no null entries are added to the collection:
54 * </p>
55 * <pre>
56 * Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE);
57 * </pre>
58 * <p>
59 * This class is Serializable from Commons Collections 3.1.
60 * </p>
61 *
62 * @param <E> The type of the elements in the collection.
63 * @since 3.0
64 */
65 public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> {
66
67 /**
68 * Builder for creating predicated collections.
69 * <p>
70 * Create a Builder with a predicate to validate elements against, then add any elements
71 * to the builder. Elements that fail the predicate will be added to a rejected list.
72 * Finally, create or decorate a collection using the createPredicated[List,Set,Bag,Queue] methods.
73 * </p>
74 * <p>
75 * For example:
76 * </p>
77 * <pre>
78 * Predicate<String> predicate = NotNullPredicate.notNullPredicate();
79 * PredicatedCollectionBuilder<String> builder = PredicatedCollection.builder(predicate);
80 * builder.add("item1");
81 * builder.add(null);
82 * builder.add("item2");
83 * List<String> predicatedList = builder.createPredicatedList();
84 * </pre>
85 * <p>
86 * At the end of the code fragment above predicatedList is protected by the predicate supplied
87 * to the builder, and it contains item1 and item2.
88 * </p>
89 * <p>
90 * More elements can be added to the builder once a predicated collection has been created,
91 * but these elements will not be reflected in already created collections.
92 * </p>
93 *
94 * @param <E> the element type.
95 * @since 4.1
96 */
97 public static class Builder<E> {
98
99 /** The predicate to use. */
100 private final Predicate<? super E> predicate;
101
102 /** The buffer containing valid elements. */
103 private final List<E> accepted = new ArrayList<>();
104
105 /** The buffer containing rejected elements. */
106 private final List<E> rejected = new ArrayList<>();
107
108 /**
109 * Constructs a PredicatedCollectionBuilder with the specified Predicate.
110 *
111 * @param predicate The predicate to use.
112 * @throws NullPointerException if predicate is null.
113 */
114 public Builder(final Predicate<? super E> predicate) {
115 this.predicate = Objects.requireNonNull(predicate, "predicate");
116 }
117
118 /**
119 * Adds the item to the builder.
120 * <p>
121 * If the predicate is true, it is added to the list of accepted elements,
122 * otherwise it is added to the rejected list.
123 * </p>
124 *
125 * @param item The element to add.
126 * @return The PredicatedCollectionBuilder.
127 */
128 public Builder<E> add(final E item) {
129 if (predicate.test(item)) {
130 accepted.add(item);
131 } else {
132 rejected.add(item);
133 }
134 return this;
135 }
136
137 /**
138 * Adds all elements from the given collection to the builder.
139 * <p>
140 * All elements for which the predicate evaluates to true will be added to the
141 * list of accepted elements, otherwise they are added to the rejected list.
142 * </p>
143 *
144 * @param items The elements to add to the builder.
145 * @return The PredicatedCollectionBuilder.
146 */
147 public Builder<E> addAll(final Collection<? extends E> items) {
148 if (items != null) {
149 items.forEach(this::add);
150 }
151 return this;
152 }
153
154 /**
155 * Create a new predicated bag filled with the accepted elements.
156 * <p>
157 * The builder is not modified by this method, so it is possible to create more collections
158 * or add more elements afterwards. Further changes will not propagate to the returned bag.
159 * </p>
160 *
161 * @return A new predicated bag.
162 * @deprecated Since 4.6.0, use {@link #createPredicatedMultiSet()} instead.
163 */
164 @Deprecated
165 public Bag<E> createPredicatedBag() {
166 return createPredicatedBag(new HashBag<>());
167 }
168
169 /**
170 * Decorates the given bag with validating behavior using the predicate. All accepted elements
171 * are appended to the bag. If the bag already contains elements, they are validated.
172 * <p>
173 * The builder is not modified by this method, so it is possible to create more collections
174 * or add more elements afterwards. Further changes will not propagate to the returned bag.
175 * </p>
176 *
177 * @param bag The bag to decorate, must not be null.
178 * @return The decorated bag.
179 * @throws NullPointerException if bag is null.
180 * @throws IllegalArgumentException if bag contains invalid elements.
181 * @deprecated Since 4.6.0, use {@link #createPredicatedMultiSet(MultiSet)} instead.
182 */
183 @Deprecated
184 public Bag<E> createPredicatedBag(final Bag<E> bag) {
185 Objects.requireNonNull(bag, "bag");
186 final PredicatedBag<E> predicatedBag = PredicatedBag.predicatedBag(bag, predicate);
187 predicatedBag.addAll(accepted);
188 return predicatedBag;
189 }
190
191 /**
192 * Create a new predicated list filled with the accepted elements.
193 * <p>
194 * The builder is not modified by this method, so it is possible to create more collections
195 * or add more elements afterwards. Further changes will not propagate to the returned list.
196 * </p>
197 *
198 * @return A new predicated list.
199 */
200 public List<E> createPredicatedList() {
201 return createPredicatedList(new ArrayList<>());
202 }
203
204 /**
205 * Decorates the given list with validating behavior using the predicate. All accepted elements
206 * are appended to the list. If the list already contains elements, they are validated.
207 * <p>
208 * The builder is not modified by this method, so it is possible to create more collections
209 * or add more elements afterwards. Further changes will not propagate to the returned list.
210 * </p>
211 *
212 * @param list The List to decorate, must not be null.
213 * @return The decorated list.
214 * @throws NullPointerException if list is null.
215 * @throws IllegalArgumentException if list contains invalid elements.
216 */
217 public List<E> createPredicatedList(final List<E> list) {
218 Objects.requireNonNull(list, "list");
219 final List<E> predicatedList = PredicatedList.predicatedList(list, predicate);
220 predicatedList.addAll(accepted);
221 return predicatedList;
222 }
223
224 /**
225 * Create a new predicated multiset filled with the accepted elements.
226 * <p>
227 * The builder is not modified by this method, so it is possible to create more collections
228 * or add more elements afterwards. Further changes will not propagate to the returned multiset.
229 * </p>
230 *
231 * @return A new predicated multiset.
232 */
233 public MultiSet<E> createPredicatedMultiSet() {
234 return createPredicatedMultiSet(new HashMultiSet<>());
235 }
236
237 /**
238 * Decorates the given multiset with validating behavior using the predicate. All accepted elements
239 * are appended to the multiset. If the multiset already contains elements, they are validated.
240 * <p>
241 * The builder is not modified by this method, so it is possible to create more collections
242 * or add more elements afterwards. Further changes will not propagate to the returned multiset.
243 * </p>
244 *
245 * @param multiset The multiset to decorate, must not be null.
246 * @return The decorated multiset.
247 * @throws NullPointerException if multiset is null.
248 * @throws IllegalArgumentException if multiset contains invalid elements.
249 */
250 public MultiSet<E> createPredicatedMultiSet(final MultiSet<E> multiset) {
251 Objects.requireNonNull(multiset, "multiset");
252 final PredicatedMultiSet<E> predicatedMultiSet = PredicatedMultiSet.predicatedMultiSet(multiset, predicate);
253 predicatedMultiSet.addAll(accepted);
254 return predicatedMultiSet;
255 }
256
257 /**
258 * Create a new predicated queue filled with the accepted elements.
259 * <p>
260 * The builder is not modified by this method, so it is possible to create more collections
261 * or add more elements afterwards. Further changes will not propagate to the returned queue.
262 * </p>
263 *
264 * @return A new predicated queue.
265 */
266 public Queue<E> createPredicatedQueue() {
267 return createPredicatedQueue(new LinkedList<>());
268 }
269
270 /**
271 * Decorates the given queue with validating behavior using the predicate. All accepted elements
272 * are appended to the queue. If the queue already contains elements, they are validated.
273 * <p>
274 * The builder is not modified by this method, so it is possible to create more collections
275 * or add more elements afterwards. Further changes will not propagate to the returned queue.
276 * </p>
277 *
278 * @param queue The queue to decorate, must not be null.
279 * @return The decorated queue.
280 * @throws NullPointerException if queue is null.
281 * @throws IllegalArgumentException if queue contains invalid elements.
282 */
283 public Queue<E> createPredicatedQueue(final Queue<E> queue) {
284 Objects.requireNonNull(queue, "queue");
285 final PredicatedQueue<E> predicatedQueue = PredicatedQueue.predicatedQueue(queue, predicate);
286 predicatedQueue.addAll(accepted);
287 return predicatedQueue;
288 }
289
290 /**
291 * Create a new predicated set filled with the accepted elements.
292 * <p>
293 * The builder is not modified by this method, so it is possible to create more collections
294 * or add more elements afterwards. Further changes will not propagate to the returned set.
295 * </p>
296 *
297 * @return A new predicated set.
298 */
299 public Set<E> createPredicatedSet() {
300 return createPredicatedSet(new HashSet<>());
301 }
302
303 /**
304 * Decorates the given list with validating behavior using the predicate. All accepted elements
305 * are appended to the set. If the set already contains elements, they are validated.
306 * <p>
307 * The builder is not modified by this method, so it is possible to create more collections
308 * or add more elements afterwards. Further changes will not propagate to the returned set.
309 * </p>
310 *
311 * @param set The set to decorate, must not be null.
312 * @return The decorated set.
313 * @throws NullPointerException if set is null.
314 * @throws IllegalArgumentException if set contains invalid elements.
315 */
316 public Set<E> createPredicatedSet(final Set<E> set) {
317 Objects.requireNonNull(set, "set");
318 final PredicatedSet<E> predicatedSet = PredicatedSet.predicatedSet(set, predicate);
319 predicatedSet.addAll(accepted);
320 return predicatedSet;
321 }
322
323 /**
324 * Returns an unmodifiable collection containing all rejected elements.
325 *
326 * @return An unmodifiable collection.
327 */
328 public Collection<E> rejectedElements() {
329 return Collections.unmodifiableCollection(rejected);
330 }
331
332 }
333
334 /** Serialization version */
335 private static final long serialVersionUID = -5259182142076705162L;
336
337 /**
338 * Returns a Builder with the given predicate.
339 *
340 * @param <E> the element type.
341 * @param predicate The predicate to use.
342 * @return A new Builder for predicated collections.
343 * @since 4.1
344 */
345 public static <E> Builder<E> builder(final Predicate<? super E> predicate) {
346 return new Builder<>(predicate);
347 }
348
349 /**
350 * Returns a Builder with a NotNullPredicate.
351 *
352 * @param <E> the element type.
353 * @return A new Builder for predicated collections that ignores null values.
354 * @since 4.1
355 */
356 public static <E> Builder<E> notNullBuilder() {
357 return new Builder<>(NotNullPredicate.<E>notNullPredicate());
358 }
359
360 /**
361 * Factory method to create a predicated (validating) collection.
362 * <p>
363 * If there are any elements already in the collection being decorated, they
364 * are validated.
365 * </p>
366 *
367 * @param <T> The type of the elements in the collection.
368 * @param coll The collection to decorate, must not be null.
369 * @param predicate The predicate to use for validation, must not be null.
370 * @return A new predicated collection.
371 * @throws NullPointerException if collection or predicate is null.
372 * @throws IllegalArgumentException if the collection contains invalid elements.
373 * @since 4.0
374 */
375 public static <T> PredicatedCollection<T> predicatedCollection(final Collection<T> coll,
376 final Predicate<? super T> predicate) {
377 return new PredicatedCollection<>(coll, predicate);
378 }
379
380 /** The predicate to use */
381 protected final Predicate<? super E> predicate;
382
383 /**
384 * Constructor that wraps (not copies).
385 * <p>
386 * If there are any elements already in the collection being decorated, they
387 * are validated.
388 * </p>
389 *
390 * @param collection The collection to decorate, must not be null.
391 * @param predicate The predicate to use for validation, must not be null.
392 * @throws NullPointerException if collection or predicate is null.
393 * @throws IllegalArgumentException if the collection contains invalid elements.
394 */
395 protected PredicatedCollection(final Collection<E> collection, final Predicate<? super E> predicate) {
396 super(collection);
397 this.predicate = Objects.requireNonNull(predicate, "predicate");
398 collection.forEach(this::validate);
399 }
400
401 /**
402 * Override to validate the object being added to ensure it matches
403 * the predicate.
404 *
405 * @param object The object being added.
406 * @return The result of adding to the underlying collection.
407 * @throws IllegalArgumentException if the add is invalid.
408 */
409 @Override
410 public boolean add(final E object) {
411 validate(object);
412 return decorated().add(object);
413 }
414
415 /**
416 * Override to validate the objects being added to ensure they match
417 * the predicate. If anyone fails, no update is made to the underlying
418 * collection.
419 *
420 * @param coll The collection being added.
421 * @return The result of adding to the underlying collection.
422 * @throws IllegalArgumentException if the add is invalid.
423 */
424 @Override
425 public boolean addAll(final Collection<? extends E> coll) {
426 coll.forEach(this::validate);
427 return decorated().addAll(coll);
428 }
429
430 /**
431 * Deserializes the collection in using a custom routine.
432 *
433 * @param in The input stream.
434 * @throws IOException Thrown if an error occurs while reading from the stream.
435 * @throws ClassNotFoundException if an object read from the stream cannot be loaded.
436 */
437 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
438 in.defaultReadObject();
439 if (decorated() == null) {
440 throw new InvalidObjectException("Null collection");
441 }
442 if (predicate == null) {
443 throw new InvalidObjectException("Null predicate");
444 }
445 try {
446 decorated().forEach(this::validate);
447 } catch (final IllegalArgumentException ex) {
448 throw (InvalidObjectException) new InvalidObjectException(ex.getMessage()).initCause(ex);
449 }
450 }
451
452 /**
453 * Validates the object being added to ensure it matches the predicate.
454 * <p>
455 * The predicate itself should not throw an exception, but return false to
456 * indicate that the object cannot be added.
457 * </p>
458 *
459 * @param object The object being added.
460 * @throws IllegalArgumentException if the add is invalid.
461 */
462 protected void validate(final E object) {
463 if (!predicate.test(object)) {
464 throw new IllegalArgumentException("Cannot add Object '" + object + "' - Predicate '" + predicate + "' rejected it");
465 }
466 }
467
468 }