PredicatedCollection.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.MultiSet;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.bag.PredicatedBag;
import org.apache.commons.collections4.functors.NotNullPredicate;
import org.apache.commons.collections4.list.PredicatedList;
import org.apache.commons.collections4.multiset.HashMultiSet;
import org.apache.commons.collections4.multiset.PredicatedMultiSet;
import org.apache.commons.collections4.queue.PredicatedQueue;
import org.apache.commons.collections4.set.PredicatedSet;
/**
* Decorates another {@link Collection} to validate that additions
* match a specified predicate.
* <p>
* This collection exists to provide validation for the decorated collection.
* It is normally created to decorate an empty collection.
* If an object cannot be added to the collection, an IllegalArgumentException is thrown.
* </p>
* <p>
* One usage would be to ensure that no null entries are added to the collection:
* </p>
* <pre>
* Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE);
* </pre>
* <p>
* This class is Serializable from Commons Collections 3.1.
* </p>
*
* @param <E> the type of the elements in the collection
* @since 3.0
*/
public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> {
/**
* Builder for creating predicated collections.
* <p>
* Create a Builder with a predicate to validate elements against, then add any elements
* to the builder. Elements that fail the predicate will be added to a rejected list.
* Finally, create or decorate a collection using the createPredicated[List,Set,Bag,Queue] methods.
* <p>
* An example:
* <pre>
* Predicate<String> predicate = NotNullPredicate.notNullPredicate();
* PredicatedCollectionBuilder<String> builder = PredicatedCollection.builder(predicate);
* builder.add("item1");
* builder.add(null);
* builder.add("item2");
* List<String> predicatedList = builder.createPredicatedList();
* </pre>
* <p>
* At the end of the code fragment above predicatedList is protected by the predicate supplied
* to the builder, and it contains item1 and item2.
* <p>
* More elements can be added to the builder once a predicated collection has been created,
* but these elements will not be reflected in already created collections.
*
* @param <E> the element type
* @since 4.1
*/
public static class Builder<E> {
/** The predicate to use. */
private final Predicate<? super E> predicate;
/** The buffer containing valid elements. */
private final List<E> accepted = new ArrayList<>();
/** The buffer containing rejected elements. */
private final List<E> rejected = new ArrayList<>();
/**
* Constructs a PredicatedCollectionBuilder with the specified Predicate.
*
* @param predicate the predicate to use
* @throws NullPointerException if predicate is null
*/
public Builder(final Predicate<? super E> predicate) {
this.predicate = Objects.requireNonNull(predicate, "predicate");
}
/**
* Adds the item to the builder.
* <p>
* If the predicate is true, it is added to the list of accepted elements,
* otherwise it is added to the rejected list.
*
* @param item the element to add
* @return the PredicatedCollectionBuilder.
*/
public Builder<E> add(final E item) {
if (predicate.test(item)) {
accepted.add(item);
} else {
rejected.add(item);
}
return this;
}
/**
* Adds all elements from the given collection to the builder.
* <p>
* All elements for which the predicate evaluates to true will be added to the
* list of accepted elements, otherwise they are added to the rejected list.
*
* @param items the elements to add to the builder
* @return the PredicatedCollectionBuilder.
*/
public Builder<E> addAll(final Collection<? extends E> items) {
if (items != null) {
for (final E item : items) {
add(item);
}
}
return this;
}
/**
* Create a new predicated bag filled with the accepted elements.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned bag.
*
* @return a new predicated bag.
*/
public Bag<E> createPredicatedBag() {
return createPredicatedBag(new HashBag<>());
}
/**
* Decorates the given bag with validating behavior using the predicate. All accepted elements
* are appended to the bag. If the bag already contains elements, they are validated.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned bag.
*
* @param bag the bag to decorate, must not be null
* @return the decorated bag.
* @throws NullPointerException if bag is null
* @throws IllegalArgumentException if bag contains invalid elements
*/
public Bag<E> createPredicatedBag(final Bag<E> bag) {
Objects.requireNonNull(bag, "bag");
final PredicatedBag<E> predicatedBag = PredicatedBag.predicatedBag(bag, predicate);
predicatedBag.addAll(accepted);
return predicatedBag;
}
/**
* Create a new predicated list filled with the accepted elements.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned list.
*
* @return a new predicated list.
*/
public List<E> createPredicatedList() {
return createPredicatedList(new ArrayList<>());
}
/**
* Decorates the given list with validating behavior using the predicate. All accepted elements
* are appended to the list. If the list already contains elements, they are validated.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned list.
*
* @param list the List to decorate, must not be null
* @return the decorated list.
* @throws NullPointerException if list is null
* @throws IllegalArgumentException if list contains invalid elements
*/
public List<E> createPredicatedList(final List<E> list) {
Objects.requireNonNull(list, "list");
final List<E> predicatedList = PredicatedList.predicatedList(list, predicate);
predicatedList.addAll(accepted);
return predicatedList;
}
/**
* Create a new predicated multiset filled with the accepted elements.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned multiset.
*
* @return a new predicated multiset.
*/
public MultiSet<E> createPredicatedMultiSet() {
return createPredicatedMultiSet(new HashMultiSet<>());
}
/**
* Decorates the given multiset with validating behavior using the predicate. All accepted elements
* are appended to the multiset. If the multiset already contains elements, they are validated.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned multiset.
*
* @param multiset the multiset to decorate, must not be null
* @return the decorated multiset.
* @throws NullPointerException if multiset is null
* @throws IllegalArgumentException if multiset contains invalid elements
*/
public MultiSet<E> createPredicatedMultiSet(final MultiSet<E> multiset) {
Objects.requireNonNull(multiset, "multiset");
final PredicatedMultiSet<E> predicatedMultiSet =
PredicatedMultiSet.predicatedMultiSet(multiset, predicate);
predicatedMultiSet.addAll(accepted);
return predicatedMultiSet;
}
/**
* Create a new predicated queue filled with the accepted elements.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned queue.
*
* @return a new predicated queue.
*/
public Queue<E> createPredicatedQueue() {
return createPredicatedQueue(new LinkedList<>());
}
/**
* Decorates the given queue with validating behavior using the predicate. All accepted elements
* are appended to the queue. If the queue already contains elements, they are validated.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned queue.
*
* @param queue the queue to decorate, must not be null
* @return the decorated queue.
* @throws NullPointerException if queue is null
* @throws IllegalArgumentException if queue contains invalid elements
*/
public Queue<E> createPredicatedQueue(final Queue<E> queue) {
Objects.requireNonNull(queue, "queue");
final PredicatedQueue<E> predicatedQueue = PredicatedQueue.predicatedQueue(queue, predicate);
predicatedQueue.addAll(accepted);
return predicatedQueue;
}
/**
* Create a new predicated set filled with the accepted elements.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned set.
*
* @return a new predicated set.
*/
public Set<E> createPredicatedSet() {
return createPredicatedSet(new HashSet<>());
}
/**
* Decorates the given list with validating behavior using the predicate. All accepted elements
* are appended to the set. If the set already contains elements, they are validated.
* <p>
* The builder is not modified by this method, so it is possible to create more collections
* or add more elements afterwards. Further changes will not propagate to the returned set.
*
* @param set the set to decorate, must not be null
* @return the decorated set.
* @throws NullPointerException if set is null
* @throws IllegalArgumentException if set contains invalid elements
*/
public Set<E> createPredicatedSet(final Set<E> set) {
Objects.requireNonNull(set, "set");
final PredicatedSet<E> predicatedSet = PredicatedSet.predicatedSet(set, predicate);
predicatedSet.addAll(accepted);
return predicatedSet;
}
/**
* Returns an unmodifiable collection containing all rejected elements.
*
* @return an unmodifiable collection
*/
public Collection<E> rejectedElements() {
return Collections.unmodifiableCollection(rejected);
}
}
/** Serialization version */
private static final long serialVersionUID = -5259182142076705162L;
/**
* Returns a Builder with the given predicate.
*
* @param <E> the element type
* @param predicate the predicate to use
* @return a new Builder for predicated collections
* @since 4.1
*/
public static <E> Builder<E> builder(final Predicate<? super E> predicate) {
return new Builder<>(predicate);
}
/**
* Returns a Builder with a NotNullPredicate.
*
* @param <E> the element type
* @return a new Builder for predicated collections that ignores null values.
* @since 4.1
*/
public static <E> Builder<E> notNullBuilder() {
return new Builder<>(NotNullPredicate.<E>notNullPredicate());
}
/**
* Factory method to create a predicated (validating) collection.
* <p>
* If there are any elements already in the collection being decorated, they
* are validated.
*
* @param <T> the type of the elements in the collection
* @param coll the collection to decorate, must not be null
* @param predicate the predicate to use for validation, must not be null
* @return a new predicated collection
* @throws NullPointerException if collection or predicate is null
* @throws IllegalArgumentException if the collection contains invalid elements
* @since 4.0
*/
public static <T> PredicatedCollection<T> predicatedCollection(final Collection<T> coll,
final Predicate<? super T> predicate) {
return new PredicatedCollection<>(coll, predicate);
}
/** The predicate to use */
protected final Predicate<? super E> predicate;
/**
* Constructor that wraps (not copies).
* <p>
* If there are any elements already in the collection being decorated, they
* are validated.
*
* @param collection the collection to decorate, must not be null
* @param predicate the predicate to use for validation, must not be null
* @throws NullPointerException if collection or predicate is null
* @throws IllegalArgumentException if the collection contains invalid elements
*/
protected PredicatedCollection(final Collection<E> collection, final Predicate<? super E> predicate) {
super(collection);
this.predicate = Objects.requireNonNull(predicate, "predicate");
for (final E item : collection) {
validate(item);
}
}
/**
* Override to validate the object being added to ensure it matches
* the predicate.
*
* @param object the object being added
* @return the result of adding to the underlying collection
* @throws IllegalArgumentException if the add is invalid
*/
@Override
public boolean add(final E object) {
validate(object);
return decorated().add(object);
}
/**
* Override to validate the objects being added to ensure they match
* the predicate. If anyone fails, no update is made to the underlying
* collection.
*
* @param coll the collection being added
* @return the result of adding to the underlying collection
* @throws IllegalArgumentException if the add is invalid
*/
@Override
public boolean addAll(final Collection<? extends E> coll) {
for (final E item : coll) {
validate(item);
}
return decorated().addAll(coll);
}
/**
* Validates the object being added to ensure it matches the predicate.
* <p>
* The predicate itself should not throw an exception, but return false to
* indicate that the object cannot be added.
*
* @param object the object being added
* @throws IllegalArgumentException if the add is invalid
*/
protected void validate(final E object) {
if (!predicate.test(object)) {
throw new IllegalArgumentException("Cannot add Object '" + object + "' - Predicate '" +
predicate + "' rejected it");
}
}
}