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