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 * https://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.io.IOException; 020import java.io.InvalidObjectException; 021import java.io.ObjectInputStream; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Objects; 029import java.util.Queue; 030import java.util.Set; 031 032import org.apache.commons.collections4.Bag; 033import org.apache.commons.collections4.MultiSet; 034import org.apache.commons.collections4.Predicate; 035import org.apache.commons.collections4.bag.HashBag; 036import org.apache.commons.collections4.bag.PredicatedBag; 037import org.apache.commons.collections4.functors.NotNullPredicate; 038import org.apache.commons.collections4.list.PredicatedList; 039import org.apache.commons.collections4.multiset.HashMultiSet; 040import org.apache.commons.collections4.multiset.PredicatedMultiSet; 041import org.apache.commons.collections4.queue.PredicatedQueue; 042import org.apache.commons.collections4.set.PredicatedSet; 043 044/** 045 * Decorates another {@link Collection} to validate that additions 046 * match a specified predicate. 047 * <p> 048 * This collection exists to provide validation for the decorated collection. 049 * It is normally created to decorate an empty collection. 050 * If an object cannot be added to the collection, an IllegalArgumentException is thrown. 051 * </p> 052 * <p> 053 * One usage would be to ensure that no null entries are added to the collection: 054 * </p> 055 * <pre> 056 * Collection coll = PredicatedCollection.predicatedCollection(new ArrayList(), NotNullPredicate.INSTANCE); 057 * </pre> 058 * <p> 059 * This class is Serializable from Commons Collections 3.1. 060 * </p> 061 * 062 * @param <E> The type of the elements in the collection. 063 * @since 3.0 064 */ 065public class PredicatedCollection<E> extends AbstractCollectionDecorator<E> { 066 067 /** 068 * Builder for creating predicated collections. 069 * <p> 070 * Create a Builder with a predicate to validate elements against, then add any elements 071 * to the builder. Elements that fail the predicate will be added to a rejected list. 072 * Finally, create or decorate a collection using the createPredicated[List,Set,Bag,Queue] methods. 073 * </p> 074 * <p> 075 * For example: 076 * </p> 077 * <pre> 078 * Predicate<String> predicate = NotNullPredicate.notNullPredicate(); 079 * PredicatedCollectionBuilder<String> builder = PredicatedCollection.builder(predicate); 080 * builder.add("item1"); 081 * builder.add(null); 082 * builder.add("item2"); 083 * List<String> predicatedList = builder.createPredicatedList(); 084 * </pre> 085 * <p> 086 * At the end of the code fragment above predicatedList is protected by the predicate supplied 087 * to the builder, and it contains item1 and item2. 088 * </p> 089 * <p> 090 * More elements can be added to the builder once a predicated collection has been created, 091 * but these elements will not be reflected in already created collections. 092 * </p> 093 * 094 * @param <E> the element type. 095 * @since 4.1 096 */ 097 public static class Builder<E> { 098 099 /** 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}