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