001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.collection; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Objects; 026import java.util.Queue; 027import java.util.Set; 028 029import org.apache.commons.collections4.Bag; 030import org.apache.commons.collections4.MultiSet; 031import org.apache.commons.collections4.Predicate; 032import org.apache.commons.collections4.bag.HashBag; 033import org.apache.commons.collections4.bag.PredicatedBag; 034import org.apache.commons.collections4.functors.NotNullPredicate; 035import org.apache.commons.collections4.list.PredicatedList; 036import org.apache.commons.collections4.multiset.HashMultiSet; 037import org.apache.commons.collections4.multiset.PredicatedMultiSet; 038import org.apache.commons.collections4.queue.PredicatedQueue; 039import org.apache.commons.collections4.set.PredicatedSet; 040 041/** 042 * Decorates another {@link Collection} to validate that additions 043 * match a specified predicate. 044 * <p> 045 * This collection exists to provide validation for the decorated collection. 046 * It is normally created to decorate an empty collection. 047 * If an object cannot be added to the collection, an IllegalArgumentException is thrown. 048 * </p> 049 * <p> 050 * One usage would be to ensure that no null entries are added to the collection: 051 * </p> 052 * <pre> 053 * Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE); 054 * </pre> 055 * <p> 056 * This class is Serializable from Commons Collections 3.1. 057 * </p> 058 * 059 * @param <E> the type of the elements in the collection 060 * @since 3.0 061 */ 062public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> { 063 064 /** 065 * Builder for creating predicated collections. 066 * <p> 067 * Create a Builder with a predicate to validate elements against, then add any elements 068 * to the builder. Elements that fail the predicate will be added to a rejected list. 069 * Finally, create or decorate a collection using the createPredicated[List,Set,Bag,Queue] methods. 070 * <p> 071 * An example: 072 * <pre> 073 * Predicate<String> predicate = NotNullPredicate.notNullPredicate(); 074 * PredicatedCollectionBuilder<String> builder = PredicatedCollection.builder(predicate); 075 * builder.add("item1"); 076 * builder.add(null); 077 * builder.add("item2"); 078 * List<String> predicatedList = builder.createPredicatedList(); 079 * </pre> 080 * <p> 081 * At the end of the code fragment above predicatedList is protected by the predicate supplied 082 * to the builder, and it contains item1 and item2. 083 * <p> 084 * More elements can be added to the builder once a predicated collection has been created, 085 * but these elements will not be reflected in already created collections. 086 * 087 * @param <E> the element type 088 * @since 4.1 089 */ 090 public static class Builder<E> { 091 092 /** The predicate to use. */ 093 private final Predicate<? super E> predicate; 094 095 /** The buffer containing valid elements. */ 096 private final List<E> accepted = new ArrayList<>(); 097 098 /** The buffer containing rejected elements. */ 099 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.evaluate(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.evaluate(object)) { 423 throw new IllegalArgumentException("Cannot add Object '" + object + "' - Predicate '" + 424 predicate + "' rejected it"); 425 } 426 } 427 428}