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.set; 018 019import java.io.Serializable; 020import java.lang.reflect.Array; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Objects; 027import java.util.Set; 028import java.util.function.Predicate; 029 030import org.apache.commons.collections4.CollectionUtils; 031import org.apache.commons.collections4.iterators.EmptyIterator; 032import org.apache.commons.collections4.iterators.IteratorChain; 033import org.apache.commons.collections4.list.UnmodifiableList; 034 035/** 036 * Decorates a set of other sets to provide a single unified view. 037 * <p> 038 * Changes made to this set will actually be made on the decorated set. 039 * Add operations require the use of a pluggable strategy. 040 * If no strategy is provided then add is unsupported. 041 * </p> 042 * <p> 043 * From version 4.0, this class does not extend 044 * {@link org.apache.commons.collections4.collection.CompositeCollection CompositeCollection} 045 * anymore due to its input restrictions (only accepts Sets). 046 * See <a href="https://issues.apache.org/jira/browse/COLLECTIONS-424">COLLECTIONS-424</a> 047 * for more details. 048 * </p> 049 * 050 * @param <E> the type of the elements in this set 051 * @since 3.0 052 */ 053public class CompositeSet<E> implements Set<E>, Serializable { 054 055 /** 056 * Defines callbacks for mutation operations. 057 * 058 * @param <E> the type of the elements in this instance. 059 */ 060 public interface SetMutator<E> extends Serializable { 061 062 /** 063 * Called when an object is to be added to the composite. 064 * 065 * @param composite the CompositeSet being changed 066 * @param sets all of the Set instances in this CompositeSet 067 * @param obj the object being added 068 * @return true if the collection is changed 069 * @throws UnsupportedOperationException if add is unsupported 070 * @throws ClassCastException if the object cannot be added due to its type 071 * @throws NullPointerException if the object cannot be added because its null 072 * @throws IllegalArgumentException if the object cannot be added 073 */ 074 boolean add(CompositeSet<E> composite, List<Set<E>> sets, E obj); 075 076 /** 077 * Called when a collection is to be added to the composite. 078 * 079 * @param composite the CompositeSet being changed 080 * @param sets all of the Set instances in this CompositeSet 081 * @param coll the collection being added 082 * @return true if the collection is changed 083 * @throws UnsupportedOperationException if add is unsupported 084 * @throws ClassCastException if the object cannot be added due to its type 085 * @throws NullPointerException if the object cannot be added because its null 086 * @throws IllegalArgumentException if the object cannot be added 087 */ 088 boolean addAll(CompositeSet<E> composite, 089 List<Set<E>> sets, 090 Collection<? extends E> coll); 091 092 /** 093 * Called when a Set is added to the CompositeSet and there is a 094 * collision between existing and added sets. 095 * <p> 096 * If {@code added} and {@code existing} still have any intersects 097 * after this method returns an IllegalArgumentException will be thrown. 098 * 099 * @param comp the CompositeSet being modified 100 * @param existing the Set already existing in the composite 101 * @param added the Set being added to the composite 102 * @param intersects the intersection of the existing and added sets 103 */ 104 void resolveCollision(CompositeSet<E> comp, 105 Set<E> existing, 106 Set<E> added, 107 Collection<E> intersects); 108 } 109 110 /** Serialization version */ 111 private static final long serialVersionUID = 5185069727540378940L; 112 113 /** SetMutator to handle changes to the collection */ 114 private SetMutator<E> mutator; 115 116 /** Sets in the composite */ 117 private final List<Set<E>> all = new ArrayList<>(); 118 119 /** 120 * Creates an empty CompositeSet. 121 */ 122 public CompositeSet() { 123 } 124 125 /** 126 * Creates a CompositeSet with just {@code set} composited. 127 * 128 * @param set the initial set in the composite 129 */ 130 public CompositeSet(final Set<E> set) { 131 addComposited(set); 132 } 133 134 /** 135 * Creates a composite set with sets as the initial set of composited Sets. 136 * 137 * @param sets the initial sets in the composite 138 */ 139 public CompositeSet(final Set<E>... sets) { 140 addComposited(sets); 141 } 142 143 /** 144 * Adds an object to the collection, throwing UnsupportedOperationException 145 * unless a SetMutator strategy is specified. 146 * 147 * @param obj the object to add 148 * @return {@code true} if the collection was modified 149 * @throws UnsupportedOperationException if SetMutator hasn't been set or add is unsupported 150 * @throws ClassCastException if the object cannot be added due to its type 151 * @throws NullPointerException if the object cannot be added because its null 152 * @throws IllegalArgumentException if the object cannot be added 153 */ 154 @Override 155 public boolean add(final E obj) { 156 if (mutator == null) { 157 throw new UnsupportedOperationException( 158 "add() is not supported on CompositeSet without a SetMutator strategy"); 159 } 160 return mutator.add(this, all, obj); 161 } 162 163 /** 164 * Adds a collection of elements to this composite, throwing 165 * UnsupportedOperationException unless a SetMutator strategy is specified. 166 * 167 * @param coll the collection to add 168 * @return true if the composite was modified 169 * @throws UnsupportedOperationException if SetMutator hasn't been set or add is unsupported 170 * @throws ClassCastException if the object cannot be added due to its type 171 * @throws NullPointerException if the object cannot be added because its null 172 * @throws IllegalArgumentException if the object cannot be added 173 */ 174 @Override 175 public boolean addAll(final Collection<? extends E> coll) { 176 if (mutator == null) { 177 throw new UnsupportedOperationException( 178 "addAll() is not supported on CompositeSet without a SetMutator strategy"); 179 } 180 return mutator.addAll(this, all, coll); 181 } 182 183 /** 184 * Adds a Set to this composite. 185 * 186 * @param set the set to add 187 * @throws IllegalArgumentException if a SetMutator is set, but fails to resolve a collision 188 * @throws UnsupportedOperationException if there is no SetMutator set 189 * @throws NullPointerException if {@code set} is null 190 * @see SetMutator 191 */ 192 public synchronized void addComposited(final Set<E> set) { 193 if (set != null) { 194 for (final Set<E> existingSet : getSets()) { 195 final Collection<E> intersects = CollectionUtils.intersection(existingSet, set); 196 if (!intersects.isEmpty()) { 197 if (mutator == null) { 198 throw new UnsupportedOperationException( 199 "Collision adding composited set with no SetMutator set"); 200 } 201 getMutator().resolveCollision(this, existingSet, set, intersects); 202 if (!CollectionUtils.intersection(existingSet, set).isEmpty()) { 203 throw new IllegalArgumentException( 204 "Attempt to add illegal entry unresolved by SetMutator.resolveCollision()"); 205 } 206 } 207 } 208 all.add(set); 209 } 210 } 211 212 /** 213 * Adds these Sets to the list of sets in this composite 214 * 215 * @param sets the Sets to be appended to the composite 216 */ 217 public void addComposited(final Set<E>... sets) { 218 if (sets != null) { 219 for (final Set<E> set : sets) { 220 addComposited(set); 221 } 222 } 223 } 224 225 /** 226 * Adds these Sets to the list of sets in this composite. 227 * 228 * @param set1 the first Set to be appended to the composite 229 * @param set2 the second Set to be appended to the composite 230 */ 231 public void addComposited(final Set<E> set1, final Set<E> set2) { 232 addComposited(set1); 233 addComposited(set2); 234 } 235 236 /** 237 * Removes all of the elements from this composite set. 238 * <p> 239 * This implementation calls {@code clear()} on each set. 240 * 241 * @throws UnsupportedOperationException if clear is unsupported 242 */ 243 @Override 244 public void clear() { 245 for (final Collection<E> coll : all) { 246 coll.clear(); 247 } 248 } 249 250 /** 251 * Checks whether this composite set contains the object. 252 * <p> 253 * This implementation calls {@code contains()} on each set. 254 * 255 * @param obj the object to search for 256 * @return true if obj is contained in any of the contained sets 257 */ 258 @Override 259 public boolean contains(final Object obj) { 260 for (final Set<E> item : all) { 261 if (item.contains(obj)) { 262 return true; 263 } 264 } 265 return false; 266 } 267 268 /** 269 * Checks whether this composite contains all the elements in the specified collection. 270 * <p> 271 * This implementation calls {@code contains()} for each element in the 272 * specified collection. 273 * 274 * @param coll the collection to check for 275 * @return true if all elements contained 276 */ 277 @Override 278 public boolean containsAll(final Collection<?> coll) { 279 if (coll == null) { 280 return false; 281 } 282 for (final Object item : coll) { 283 if (!contains(item)) { 284 return false; 285 } 286 } 287 return true; 288 } 289 290 /** 291 * {@inheritDoc} 292 * @see java.util.Set#equals 293 */ 294 @Override 295 public boolean equals(final Object obj) { 296 if (obj instanceof Set) { 297 final Set<?> set = (Set<?>) obj; 298 return set.size() == this.size() && set.containsAll(this); 299 } 300 return false; 301 } 302 303 /** 304 * Gets the set mutator to be used for this CompositeSet. 305 * @return the set mutator 306 */ 307 protected SetMutator<E> getMutator() { 308 return mutator; 309 } 310 311 /** 312 * Gets the sets being decorated. 313 * 314 * @return Unmodifiable list of all sets in this composite. 315 */ 316 public List<Set<E>> getSets() { 317 return UnmodifiableList.unmodifiableList(all); 318 } 319 320 /** 321 * {@inheritDoc} 322 * @see java.util.Set#hashCode 323 */ 324 @Override 325 public int hashCode() { 326 int code = 0; 327 for (final E e : this) { 328 code += e == null ? 0 : e.hashCode(); 329 } 330 return code; 331 } 332 333 /** 334 * Checks whether this composite set is empty. 335 * <p> 336 * This implementation calls {@code isEmpty()} on each set. 337 * 338 * @return true if all of the contained sets are empty 339 */ 340 @Override 341 public boolean isEmpty() { 342 for (final Set<E> item : all) { 343 if (!item.isEmpty()) { 344 return false; 345 } 346 } 347 return true; 348 } 349 350 /** 351 * Gets an iterator over all the sets in this composite. 352 * <p> 353 * This implementation uses an {@code IteratorChain}. 354 * 355 * @return an {@code IteratorChain} instance which supports 356 * {@code remove()}. Iteration occurs over contained collections in 357 * the order they were added, but this behavior should not be relied upon. 358 * @see IteratorChain 359 */ 360 @Override 361 public Iterator<E> iterator() { 362 if (all.isEmpty()) { 363 return EmptyIterator.<E>emptyIterator(); 364 } 365 final IteratorChain<E> chain = new IteratorChain<>(); 366 all.forEach(item -> chain.addIterator(item.iterator())); 367 return chain; 368 } 369 370 /** 371 * If a {@code CollectionMutator} is defined for this CompositeSet then this 372 * method will be called anyway. 373 * 374 * @param obj object to be removed 375 * @return true if the object is removed, false otherwise 376 */ 377 @Override 378 public boolean remove(final Object obj) { 379 for (final Set<E> set : getSets()) { 380 if (set.contains(obj)) { 381 return set.remove(obj); 382 } 383 } 384 return false; 385 } 386 387 /** 388 * Removes the elements in the specified collection from this composite set. 389 * <p> 390 * This implementation calls {@code removeAll} on each collection. 391 * 392 * @param coll the collection to remove 393 * @return true if the composite was modified 394 * @throws UnsupportedOperationException if removeAll is unsupported 395 */ 396 @Override 397 public boolean removeAll(final Collection<?> coll) { 398 if (CollectionUtils.isEmpty(coll)) { 399 return false; 400 } 401 boolean changed = false; 402 for (final Collection<E> item : all) { 403 changed |= item.removeAll(coll); 404 } 405 return changed; 406 } 407 408 /** 409 * Removes a set from those being decorated in this composite. 410 * 411 * @param set set to be removed 412 */ 413 public void removeComposited(final Set<E> set) { 414 all.remove(set); 415 } 416 417 /** 418 * @since 4.4 419 */ 420 @Override 421 public boolean removeIf(final Predicate<? super E> filter) { 422 if (Objects.isNull(filter)) { 423 return false; 424 } 425 boolean changed = false; 426 for (final Collection<E> item : all) { 427 changed |= item.removeIf(filter); 428 } 429 return changed; 430 } 431 432 /** 433 * Retains all the elements in the specified collection in this composite set, 434 * removing all others. 435 * <p> 436 * This implementation calls {@code retainAll()} on each collection. 437 * 438 * @param coll the collection to remove 439 * @return true if the composite was modified 440 * @throws UnsupportedOperationException if retainAll is unsupported 441 */ 442 @Override 443 public boolean retainAll(final Collection<?> coll) { 444 boolean changed = false; 445 for (final Collection<E> item : all) { 446 changed |= item.retainAll(coll); 447 } 448 return changed; 449 } 450 451 /** 452 * Specify a SetMutator strategy instance to handle changes. 453 * 454 * @param mutator the mutator to use 455 */ 456 public void setMutator(final SetMutator<E> mutator) { 457 this.mutator = mutator; 458 } 459 460 /** 461 * Gets the size of this composite set. 462 * <p> 463 * This implementation calls {@code size()} on each set. 464 * 465 * @return total number of elements in all contained containers 466 */ 467 @Override 468 public int size() { 469 int size = 0; 470 for (final Set<E> item : all) { 471 size += item.size(); 472 } 473 return size; 474 } 475 476 /** 477 * Returns an array containing all of the elements in this composite. 478 * 479 * @return an object array of all the elements in the collection 480 */ 481 @Override 482 public Object[] toArray() { 483 final Object[] result = new Object[size()]; 484 int i = 0; 485 for (final Iterator<E> it = iterator(); it.hasNext(); i++) { 486 result[i] = it.next(); 487 } 488 return result; 489 } 490 491 /** 492 * Returns an object array, populating the supplied array if possible. 493 * See {@code Collection} interface for full details. 494 * 495 * @param <T> the type of the elements in the collection 496 * @param array the array to use, populating if possible 497 * @return an array of all the elements in the collection 498 */ 499 @Override 500 @SuppressWarnings("unchecked") 501 public <T> T[] toArray(final T[] array) { 502 final int size = size(); 503 Object[] result = null; 504 if (array.length >= size) { 505 result = array; 506 } else { 507 result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size); 508 } 509 510 int offset = 0; 511 for (final Collection<E> item : all) { 512 for (final E e : item) { 513 result[offset++] = e; 514 } 515 } 516 if (result.length > size) { 517 result[size] = null; 518 } 519 return (T[]) result; 520 } 521 522 /** 523 * Returns a new Set containing all of the elements. 524 * 525 * @return A new HashSet containing all of the elements in this composite. 526 * The new collection is <em>not</em> backed by this composite. 527 */ 528 public Set<E> toSet() { 529 return new HashSet<>(this); 530 } 531}