1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.collections.collection;
  17. import java.lang.reflect.Array;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Iterator;
  22. import org.apache.commons.collections.iterators.EmptyIterator;
  23. import org.apache.commons.collections.iterators.IteratorChain;
  24. import org.apache.commons.collections.list.UnmodifiableList;
  25. /**
  26. * Decorates a collection of other collections to provide a single unified view.
  27. * <p>
  28. * Changes made to this collection will actually be made on the decorated collection.
  29. * Add and remove operations require the use of a pluggable strategy. If no
  30. * strategy is provided then add and remove are unsupported.
  31. *
  32. * @since Commons Collections 3.0
  33. * @version $Revision: 1.7 $ $Date: 2004/05/26 21:58:02 $
  34. *
  35. * @author Brian McCallister
  36. * @author Stephen Colebourne
  37. * @author Phil Steitz
  38. */
  39. public class CompositeCollection implements Collection {
  40. /** CollectionMutator to handle changes to the collection */
  41. protected CollectionMutator mutator;
  42. /** Collections in the composite */
  43. protected Collection[] all;
  44. /**
  45. * Create an empty CompositeCollection.
  46. */
  47. public CompositeCollection() {
  48. super();
  49. this.all = new Collection[0];
  50. }
  51. /**
  52. * Create a Composite Collection with only coll composited.
  53. *
  54. * @param coll a collection to decorate
  55. */
  56. public CompositeCollection(Collection coll) {
  57. this();
  58. this.addComposited(coll);
  59. }
  60. /**
  61. * Create a CompositeCollection with colls as the initial list of
  62. * composited collections.
  63. *
  64. * @param colls an array of collections to decorate
  65. */
  66. public CompositeCollection(Collection[] colls) {
  67. this();
  68. this.addComposited(colls);
  69. }
  70. //-----------------------------------------------------------------------
  71. /**
  72. * Gets the size of this composite collection.
  73. * <p>
  74. * This implementation calls <code>size()</code> on each collection.
  75. *
  76. * @return total number of elements in all contained containers
  77. */
  78. public int size() {
  79. int size = 0;
  80. for (int i = this.all.length - 1; i >= 0; i--) {
  81. size += this.all[i].size();
  82. }
  83. return size;
  84. }
  85. /**
  86. * Checks whether this composite collection is empty.
  87. * <p>
  88. * This implementation calls <code>isEmpty()</code> on each collection.
  89. *
  90. * @return true if all of the contained collections are empty
  91. */
  92. public boolean isEmpty() {
  93. for (int i = this.all.length - 1; i >= 0; i--) {
  94. if (this.all[i].isEmpty() == false) {
  95. return false;
  96. }
  97. }
  98. return true;
  99. }
  100. /**
  101. * Checks whether this composite collection contains the object.
  102. * <p>
  103. * This implementation calls <code>contains()</code> on each collection.
  104. *
  105. * @param obj the object to search for
  106. * @return true if obj is contained in any of the contained collections
  107. */
  108. public boolean contains(Object obj) {
  109. for (int i = this.all.length - 1; i >= 0; i--) {
  110. if (this.all[i].contains(obj)) {
  111. return true;
  112. }
  113. }
  114. return false;
  115. }
  116. /**
  117. * Gets an iterator over all the collections in this composite.
  118. * <p>
  119. * This implementation uses an <code>IteratorChain</code>.
  120. *
  121. * @return an <code>IteratorChain</code> instance which supports
  122. * <code>remove()</code>. Iteration occurs over contained collections in
  123. * the order they were added, but this behavior should not be relied upon.
  124. * @see IteratorChain
  125. */
  126. public Iterator iterator() {
  127. if (this.all.length == 0) {
  128. return EmptyIterator.INSTANCE;
  129. }
  130. IteratorChain chain = new IteratorChain();
  131. for (int i = 0; i < this.all.length; ++i) {
  132. chain.addIterator(this.all[i].iterator());
  133. }
  134. return chain;
  135. }
  136. /**
  137. * Returns an array containing all of the elements in this composite.
  138. *
  139. * @return an object array of all the elements in the collection
  140. */
  141. public Object[] toArray() {
  142. final Object[] result = new Object[this.size()];
  143. int i = 0;
  144. for (Iterator it = this.iterator(); it.hasNext(); i++) {
  145. result[i] = it.next();
  146. }
  147. return result;
  148. }
  149. /**
  150. * Returns an object array, populating the supplied array if possible.
  151. * See <code>Collection</code> interface for full details.
  152. *
  153. * @param array the array to use, populating if possible
  154. * @return an array of all the elements in the collection
  155. */
  156. public Object[] toArray(Object[] array) {
  157. int size = this.size();
  158. Object[] result = null;
  159. if (array.length >= size) {
  160. result = array;
  161. }
  162. else {
  163. result = (Object[]) Array.newInstance(array.getClass().getComponentType(), size);
  164. }
  165. int offset = 0;
  166. for (int i = 0; i < this.all.length; ++i) {
  167. for (Iterator it = this.all[i].iterator(); it.hasNext();) {
  168. result[offset++] = it.next();
  169. }
  170. }
  171. if (result.length > size) {
  172. result[size] = null;
  173. }
  174. return result;
  175. }
  176. /**
  177. * Adds an object to the collection, throwing UnsupportedOperationException
  178. * unless a CollectionMutator strategy is specified.
  179. *
  180. * @param obj the object to add
  181. * @return true if the collection was modified
  182. * @throws UnsupportedOperationException if CollectionMutator hasn't been set
  183. * @throws UnsupportedOperationException if add is unsupported
  184. * @throws ClassCastException if the object cannot be added due to its type
  185. * @throws NullPointerException if the object cannot be added because its null
  186. * @throws IllegalArgumentException if the object cannot be added
  187. */
  188. public boolean add(Object obj) {
  189. if (this.mutator == null) {
  190. throw new UnsupportedOperationException(
  191. "add() is not supported on CompositeCollection without a CollectionMutator strategy");
  192. }
  193. return this.mutator.add(this, this.all, obj);
  194. }
  195. /**
  196. * Removes an object from the collection, throwing UnsupportedOperationException
  197. * unless a CollectionMutator strategy is specified.
  198. *
  199. * @param obj the object being removed
  200. * @return true if the collection is changed
  201. * @throws UnsupportedOperationException if removed is unsupported
  202. * @throws ClassCastException if the object cannot be removed due to its type
  203. * @throws NullPointerException if the object cannot be removed because its null
  204. * @throws IllegalArgumentException if the object cannot be removed
  205. */
  206. public boolean remove(Object obj) {
  207. if (this.mutator == null) {
  208. throw new UnsupportedOperationException(
  209. "remove() is not supported on CompositeCollection without a CollectionMutator strategy");
  210. }
  211. return this.mutator.remove(this, this.all, obj);
  212. }
  213. /**
  214. * Checks whether this composite contains all the elements in the specified collection.
  215. * <p>
  216. * This implementation calls <code>contains()</code> for each element in the
  217. * specified collection.
  218. *
  219. * @param coll the collection to check for
  220. * @return true if all elements contained
  221. */
  222. public boolean containsAll(Collection coll) {
  223. for (Iterator it = coll.iterator(); it.hasNext();) {
  224. if (this.contains(it.next()) == false) {
  225. return false;
  226. }
  227. }
  228. return true;
  229. }
  230. /**
  231. * Adds a collection of elements to this collection, throwing
  232. * UnsupportedOperationException unless a CollectionMutator strategy is specified.
  233. *
  234. * @param coll the collection to add
  235. * @return true if the collection was modified
  236. * @throws UnsupportedOperationException if CollectionMutator hasn't been set
  237. * @throws UnsupportedOperationException if add is unsupported
  238. * @throws ClassCastException if the object cannot be added due to its type
  239. * @throws NullPointerException if the object cannot be added because its null
  240. * @throws IllegalArgumentException if the object cannot be added
  241. */
  242. public boolean addAll(Collection coll) {
  243. if (this.mutator == null) {
  244. throw new UnsupportedOperationException(
  245. "addAll() is not supported on CompositeCollection without a CollectionMutator strategy");
  246. }
  247. return this.mutator.addAll(this, this.all, coll);
  248. }
  249. /**
  250. * Removes the elements in the specified collection from this composite collection.
  251. * <p>
  252. * This implementation calls <code>removeAll</code> on each collection.
  253. *
  254. * @param coll the collection to remove
  255. * @return true if the collection was modified
  256. * @throws UnsupportedOperationException if removeAll is unsupported
  257. */
  258. public boolean removeAll(Collection coll) {
  259. if (coll.size() == 0) {
  260. return false;
  261. }
  262. boolean changed = false;
  263. for (int i = this.all.length - 1; i >= 0; i--) {
  264. changed = (this.all[i].removeAll(coll) || changed);
  265. }
  266. return changed;
  267. }
  268. /**
  269. * Retains all the elements in the specified collection in this composite collection,
  270. * removing all others.
  271. * <p>
  272. * This implementation calls <code>retainAll()</code> on each collection.
  273. *
  274. * @param coll the collection to remove
  275. * @return true if the collection was modified
  276. * @throws UnsupportedOperationException if retainAll is unsupported
  277. */
  278. public boolean retainAll(final Collection coll) {
  279. boolean changed = false;
  280. for (int i = this.all.length - 1; i >= 0; i--) {
  281. changed = (this.all[i].retainAll(coll) || changed);
  282. }
  283. return changed;
  284. }
  285. /**
  286. * Removes all of the elements from this collection .
  287. * <p>
  288. * This implementation calls <code>clear()</code> on each collection.
  289. *
  290. * @throws UnsupportedOperationException if clear is unsupported
  291. */
  292. public void clear() {
  293. for (int i = 0; i < this.all.length; ++i) {
  294. this.all[i].clear();
  295. }
  296. }
  297. //-----------------------------------------------------------------------
  298. /**
  299. * Specify a CollectionMutator strategy instance to handle changes.
  300. *
  301. * @param mutator the mutator to use
  302. */
  303. public void setMutator(CollectionMutator mutator) {
  304. this.mutator = mutator;
  305. }
  306. /**
  307. * Add these Collections to the list of collections in this composite
  308. *
  309. * @param comps Collections to be appended to the composite
  310. */
  311. public void addComposited(Collection[] comps) {
  312. ArrayList list = new ArrayList(Arrays.asList(this.all));
  313. list.addAll(Arrays.asList(comps));
  314. all = (Collection[]) list.toArray(new Collection[list.size()]);
  315. }
  316. /**
  317. * Add an additional collection to this composite.
  318. *
  319. * @param c the collection to add
  320. */
  321. public void addComposited(Collection c) {
  322. this.addComposited(new Collection[]{c});
  323. }
  324. /**
  325. * Add two additional collections to this composite.
  326. *
  327. * @param c the first collection to add
  328. * @param d the second collection to add
  329. */
  330. public void addComposited(Collection c, Collection d) {
  331. this.addComposited(new Collection[]{c, d});
  332. }
  333. /**
  334. * Removes a collection from the those being decorated in this composite.
  335. *
  336. * @param coll collection to be removed
  337. */
  338. public void removeComposited(Collection coll) {
  339. ArrayList list = new ArrayList(this.all.length);
  340. list.addAll(Arrays.asList(this.all));
  341. list.remove(coll);
  342. this.all = (Collection[]) list.toArray(new Collection[list.size()]);
  343. }
  344. /**
  345. * Returns a new collection containing all of the elements
  346. *
  347. * @return A new ArrayList containing all of the elements in this composite.
  348. * The new collection is <i>not</i> backed by this composite.
  349. */
  350. public Collection toCollection() {
  351. return new ArrayList(this);
  352. }
  353. /**
  354. * Gets the collections being decorated.
  355. *
  356. * @return Unmodifiable collection of all collections in this composite.
  357. */
  358. public Collection getCollections() {
  359. return UnmodifiableList.decorate(Arrays.asList(this.all));
  360. }
  361. //-----------------------------------------------------------------------
  362. /**
  363. * Pluggable strategy to handle changes to the composite.
  364. */
  365. public interface CollectionMutator {
  366. /**
  367. * Called when an object is to be added to the composite.
  368. *
  369. * @param composite the CompositeCollection being changed
  370. * @param collections all of the Collection instances in this CompositeCollection
  371. * @param obj the object being added
  372. * @return true if the collection is changed
  373. * @throws UnsupportedOperationException if add is unsupported
  374. * @throws ClassCastException if the object cannot be added due to its type
  375. * @throws NullPointerException if the object cannot be added because its null
  376. * @throws IllegalArgumentException if the object cannot be added
  377. */
  378. public boolean add(CompositeCollection composite, Collection[] collections, Object obj);
  379. /**
  380. * Called when a collection is to be added to the composite.
  381. *
  382. * @param composite the CompositeCollection being changed
  383. * @param collections all of the Collection instances in this CompositeCollection
  384. * @param coll the collection being added
  385. * @return true if the collection is changed
  386. * @throws UnsupportedOperationException if add is unsupported
  387. * @throws ClassCastException if the object cannot be added due to its type
  388. * @throws NullPointerException if the object cannot be added because its null
  389. * @throws IllegalArgumentException if the object cannot be added
  390. */
  391. public boolean addAll(CompositeCollection composite, Collection[] collections, Collection coll);
  392. /**
  393. * Called when an object is to be removed to the composite.
  394. *
  395. * @param composite the CompositeCollection being changed
  396. * @param collections all of the Collection instances in this CompositeCollection
  397. * @param obj the object being removed
  398. * @return true if the collection is changed
  399. * @throws UnsupportedOperationException if removed is unsupported
  400. * @throws ClassCastException if the object cannot be removed due to its type
  401. * @throws NullPointerException if the object cannot be removed because its null
  402. * @throws IllegalArgumentException if the object cannot be removed
  403. */
  404. public boolean remove(CompositeCollection composite, Collection[] collections, Object obj);
  405. }
  406. }