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.Queue; 026import java.util.Set; 027 028import org.apache.commons.collections4.Bag; 029import org.apache.commons.collections4.MultiSet; 030import org.apache.commons.collections4.Predicate; 031import org.apache.commons.collections4.bag.HashBag; 032import org.apache.commons.collections4.bag.PredicatedBag; 033import org.apache.commons.collections4.functors.NotNullPredicate; 034import org.apache.commons.collections4.list.PredicatedList; 035import org.apache.commons.collections4.multiset.HashMultiSet; 036import org.apache.commons.collections4.multiset.PredicatedMultiSet; 037import org.apache.commons.collections4.queue.PredicatedQueue; 038import org.apache.commons.collections4.set.PredicatedSet; 039 040/** 041 * Decorates another {@link Collection} to validate that additions 042 * match a specified predicate. 043 * <p> 044 * This collection exists to provide validation for the decorated collection. 045 * It is normally created to decorate an empty collection. 046 * If an object cannot be added to the collection, an IllegalArgumentException is thrown. 047 * <p> 048 * One usage would be to ensure that no null entries are added to the collection: 049 * <pre> 050 * Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE); 051 * </pre> 052 * <p> 053 * This class is Serializable from Commons Collections 3.1. 054 * 055 * @param <E> the type of the elements in the collection 056 * @since 3.0 057 */ 058public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> { 059 060 /** Serialization version */ 061 private static final long serialVersionUID = -5259182142076705162L; 062 063 /** The predicate to use */ 064 protected final Predicate<? super E> predicate; 065 066 /** 067 * Returns a Builder with the given predicate. 068 * 069 * @param <E> the element type 070 * @param predicate the predicate to use 071 * @return a new Builder for predicated collections 072 * @since 4.1 073 */ 074 public static <E> Builder<E> builder(final Predicate<? super E> predicate) { 075 return new Builder<>(predicate); 076 } 077 078 /** 079 * Returns a Builder with a NotNullPredicate. 080 * 081 * @param <E> the element type 082 * @return a new Builder for predicated collections that ignores null values. 083 * @since 4.1 084 */ 085 public static <E> Builder<E> notNullBuilder() { 086 return new Builder<>(NotNullPredicate.<E>notNullPredicate()); 087 } 088 089 /** 090 * Factory method to create a predicated (validating) collection. 091 * <p> 092 * If there are any elements already in the collection being decorated, they 093 * are validated. 094 * 095 * @param <T> the type of the elements in the collection 096 * @param coll the collection to decorate, must not be null 097 * @param predicate the predicate to use for validation, must not be null 098 * @return a new predicated collection 099 * @throws NullPointerException if collection or predicate is null 100 * @throws IllegalArgumentException if the collection contains invalid elements 101 * @since 4.0 102 */ 103 public static <T> PredicatedCollection<T> predicatedCollection(final Collection<T> coll, 104 final Predicate<? super T> predicate) { 105 return new PredicatedCollection<>(coll, predicate); 106 } 107 108 //----------------------------------------------------------------------- 109 /** 110 * Constructor that wraps (not copies). 111 * <p> 112 * If there are any elements already in the collection being decorated, they 113 * are validated. 114 * 115 * @param coll the collection to decorate, must not be null 116 * @param predicate the predicate to use for validation, must not be null 117 * @throws NullPointerException if collection or predicate is null 118 * @throws IllegalArgumentException if the collection contains invalid elements 119 */ 120 protected PredicatedCollection(final Collection<E> coll, final Predicate<? super E> predicate) { 121 super(coll); 122 if (predicate == null) { 123 throw new NullPointerException("Predicate must not be null."); 124 } 125 this.predicate = predicate; 126 for (final E item : coll) { 127 validate(item); 128 } 129 } 130 131 /** 132 * Validates the object being added to ensure it matches the predicate. 133 * <p> 134 * The predicate itself should not throw an exception, but return false to 135 * indicate that the object cannot be added. 136 * 137 * @param object the object being added 138 * @throws IllegalArgumentException if the add is invalid 139 */ 140 protected void validate(final E object) { 141 if (predicate.evaluate(object) == false) { 142 throw new IllegalArgumentException("Cannot add Object '" + object + "' - Predicate '" + 143 predicate + "' rejected it"); 144 } 145 } 146 147 //----------------------------------------------------------------------- 148 /** 149 * Override to validate the object being added to ensure it matches 150 * the predicate. 151 * 152 * @param object the object being added 153 * @return the result of adding to the underlying collection 154 * @throws IllegalArgumentException if the add is invalid 155 */ 156 @Override 157 public boolean add(final E object) { 158 validate(object); 159 return decorated().add(object); 160 } 161 162 /** 163 * Override to validate the objects being added to ensure they match 164 * the predicate. If any one fails, no update is made to the underlying 165 * collection. 166 * 167 * @param coll the collection being added 168 * @return the result of adding to the underlying collection 169 * @throws IllegalArgumentException if the add is invalid 170 */ 171 @Override 172 public boolean addAll(final Collection<? extends E> coll) { 173 for (final E item : coll) { 174 validate(item); 175 } 176 return decorated().addAll(coll); 177 } 178 179 /** 180 * Builder for creating predicated collections. 181 * <p> 182 * Create a Builder with a predicate to validate elements against, then add any elements 183 * to the builder. Elements that fail the predicate will be added to a rejected list. 184 * Finally create or decorate a collection using the createPredicated[List,Set,Bag,Queue] methods. 185 * <p> 186 * An example: 187 * <pre> 188 * Predicate<String> predicate = NotNullPredicate.notNullPredicate(); 189 * PredicatedCollectionBuilder<String> builder = PredicatedCollection.builder(predicate); 190 * builder.add("item1"); 191 * builder.add(null); 192 * builder.add("item2"); 193 * List<String> predicatedList = builder.createPredicatedList(); 194 * </pre> 195 * <p> 196 * At the end of the code fragment above predicatedList is protected by the predicate supplied 197 * to the builder and it contains item1 and item2. 198 * <p> 199 * More elements can be added to the builder once a predicated collection has been created, 200 * but these elements will not be reflected in already created collections. 201 * 202 * @param <E> the element type 203 * @since 4.1 204 */ 205 public static class Builder<E> { 206 207 /** The predicate to use. */ 208 private final Predicate<? super E> predicate; 209 210 /** The buffer containing valid elements. */ 211 private final List<E> accepted = new ArrayList<>(); 212 213 /** The buffer containing rejected elements. */ 214 private final List<E> rejected = new ArrayList<>(); 215 216 // ----------------------------------------------------------------------- 217 /** 218 * Constructs a PredicatedCollectionBuilder with the specified Predicate. 219 * 220 * @param predicate the predicate to use 221 * @throws NullPointerException if predicate is null 222 */ 223 public Builder(final Predicate<? super E> predicate) { 224 if (predicate == null) { 225 throw new NullPointerException("Predicate must not be null"); 226 } 227 this.predicate = predicate; 228 } 229 230 /** 231 * Adds the item to the builder. 232 * <p> 233 * If the predicate is true, it is added to the list of accepted elements, 234 * otherwise it is added to the rejected list. 235 * 236 * @param item the element to add 237 * @return the PredicatedCollectionBuilder. 238 */ 239 public Builder<E> add(final E item) { 240 if (predicate.evaluate(item)) { 241 accepted.add(item); 242 } else { 243 rejected.add(item); 244 } 245 return this; 246 } 247 248 /** 249 * Adds all elements from the given collection to the builder. 250 * <p> 251 * All elements for which the predicate evaluates to true will be added to the 252 * list of accepted elements, otherwise they are added to the rejected list. 253 * 254 * @param items the elements to add to the builder 255 * @return the PredicatedCollectionBuilder. 256 */ 257 public Builder<E> addAll(final Collection<? extends E> items) { 258 if (items != null) { 259 for (final E item : items) { 260 add(item); 261 } 262 } 263 return this; 264 } 265 266 /** 267 * Create a new predicated list filled with the accepted elements. 268 * <p> 269 * The builder is not modified by this method, so it is possible to create more collections 270 * or add more elements afterwards. Further changes will not propagate to the returned list. 271 * 272 * @return a new predicated list. 273 */ 274 public List<E> createPredicatedList() { 275 return createPredicatedList(new ArrayList<E>()); 276 } 277 278 /** 279 * Decorates the given list with validating behavior using the predicate. All accepted elements 280 * are appended to the list. If the list already contains elements, they are validated. 281 * <p> 282 * The builder is not modified by this method, so it is possible to create more collections 283 * or add more elements afterwards. Further changes will not propagate to the returned list. 284 * 285 * @param list the List to decorate, must not be null 286 * @return the decorated list. 287 * @throws NullPointerException if list is null 288 * @throws IllegalArgumentException if list contains invalid elements 289 */ 290 public List<E> createPredicatedList(final List<E> list) { 291 if (list == null) { 292 throw new NullPointerException("List must not be null."); 293 } 294 final List<E> predicatedList = PredicatedList.predicatedList(list, predicate); 295 predicatedList.addAll(accepted); 296 return predicatedList; 297 } 298 299 /** 300 * Create a new predicated set filled with the accepted elements. 301 * <p> 302 * The builder is not modified by this method, so it is possible to create more collections 303 * or add more elements afterwards. Further changes will not propagate to the returned set. 304 * 305 * @return a new predicated set. 306 */ 307 public Set<E> createPredicatedSet() { 308 return createPredicatedSet(new HashSet<E>()); 309 } 310 311 /** 312 * Decorates the given list with validating behavior using the predicate. All accepted elements 313 * are appended to the set. If the set already contains elements, they are validated. 314 * <p> 315 * The builder is not modified by this method, so it is possible to create more collections 316 * or add more elements afterwards. Further changes will not propagate to the returned set. 317 * 318 * @param set the set to decorate, must not be null 319 * @return the decorated set. 320 * @throws NullPointerException if set is null 321 * @throws IllegalArgumentException if set contains invalid elements 322 */ 323 public Set<E> createPredicatedSet(final Set<E> set) { 324 if (set == null) { 325 throw new NullPointerException("Set must not be null."); 326 } 327 final PredicatedSet<E> predicatedSet = PredicatedSet.predicatedSet(set, predicate); 328 predicatedSet.addAll(accepted); 329 return predicatedSet; 330 } 331 332 /** 333 * Create a new predicated multiset filled with the accepted elements. 334 * <p> 335 * The builder is not modified by this method, so it is possible to create more collections 336 * or add more elements afterwards. Further changes will not propagate to the returned multiset. 337 * 338 * @return a new predicated multiset. 339 */ 340 public MultiSet<E> createPredicatedMultiSet() { 341 return createPredicatedMultiSet(new HashMultiSet<E>()); 342 } 343 344 /** 345 * Decorates the given multiset with validating behavior using the predicate. All accepted elements 346 * are appended to the multiset. If the multiset already contains elements, they are validated. 347 * <p> 348 * The builder is not modified by this method, so it is possible to create more collections 349 * or add more elements afterwards. Further changes will not propagate to the returned multiset. 350 * 351 * @param multiset the multiset to decorate, must not be null 352 * @return the decorated multiset. 353 * @throws NullPointerException if multiset is null 354 * @throws IllegalArgumentException if multiset contains invalid elements 355 */ 356 public MultiSet<E> createPredicatedMultiSet(final MultiSet<E> multiset) { 357 if (multiset == null) { 358 throw new NullPointerException("MultiSet must not be null."); 359 } 360 final PredicatedMultiSet<E> predicatedMultiSet = 361 PredicatedMultiSet.predicatedMultiSet(multiset, predicate); 362 predicatedMultiSet.addAll(accepted); 363 return predicatedMultiSet; 364 } 365 366 /** 367 * Create a new predicated bag filled with the accepted elements. 368 * <p> 369 * The builder is not modified by this method, so it is possible to create more collections 370 * or add more elements afterwards. Further changes will not propagate to the returned bag. 371 * 372 * @return a new predicated bag. 373 */ 374 public Bag<E> createPredicatedBag() { 375 return createPredicatedBag(new HashBag<E>()); 376 } 377 378 /** 379 * Decorates the given bag with validating behavior using the predicate. All accepted elements 380 * are appended to the bag. If the bag already contains elements, they are validated. 381 * <p> 382 * The builder is not modified by this method, so it is possible to create more collections 383 * or add more elements afterwards. Further changes will not propagate to the returned bag. 384 * 385 * @param bag the bag to decorate, must not be null 386 * @return the decorated bag. 387 * @throws NullPointerException if bag is null 388 * @throws IllegalArgumentException if bag contains invalid elements 389 */ 390 public Bag<E> createPredicatedBag(final Bag<E> bag) { 391 if (bag == null) { 392 throw new NullPointerException("Bag must not be null."); 393 } 394 final PredicatedBag<E> predicatedBag = PredicatedBag.predicatedBag(bag, predicate); 395 predicatedBag.addAll(accepted); 396 return predicatedBag; 397 } 398 399 /** 400 * Create a new predicated queue filled with the accepted elements. 401 * <p> 402 * The builder is not modified by this method, so it is possible to create more collections 403 * or add more elements afterwards. Further changes will not propagate to the returned queue. 404 * 405 * @return a new predicated queue. 406 */ 407 public Queue<E> createPredicatedQueue() { 408 return createPredicatedQueue(new LinkedList<E>()); 409 } 410 411 /** 412 * Decorates the given queue with validating behavior using the predicate. All accepted elements 413 * are appended to the queue. If the queue already contains elements, they are validated. 414 * <p> 415 * The builder is not modified by this method, so it is possible to create more collections 416 * or add more elements afterwards. Further changes will not propagate to the returned queue. 417 * 418 * @param queue the queue to decorate, must not be null 419 * @return the decorated queue. 420 * @throws NullPointerException if queue is null 421 * @throws IllegalArgumentException if queue contains invalid elements 422 */ 423 public Queue<E> createPredicatedQueue(final Queue<E> queue) { 424 if (queue == null) { 425 throw new NullPointerException("queue must not be null"); 426 } 427 final PredicatedQueue<E> predicatedQueue = PredicatedQueue.predicatedQueue(queue, predicate); 428 predicatedQueue.addAll(accepted); 429 return predicatedQueue; 430 } 431 432 /** 433 * Returns an unmodifiable collection containing all rejected elements. 434 * 435 * @return an unmodifiable collection 436 */ 437 public Collection<E> rejectedElements() { 438 return Collections.unmodifiableCollection(rejected); 439 } 440 441 } 442 443}