View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.bag;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.util.Collection;
23  import java.util.Iterator;
24  
25  import org.apache.commons.collections4.Bag;
26  
27  /**
28   * Decorates another {@link Bag} to comply with the Collection contract.
29   * <p>
30   * By decorating an existing {@link Bag} instance with a {@link CollectionBag},
31   * it can be safely passed on to methods that require Collection types that
32   * are fully compliant with the Collection contract.
33   * </p>
34   * <p>
35   * The method Javadoc highlights the differences compared to the original Bag interface.
36   * </p>
37   *
38   * @see Bag
39   * @param <E> the type of elements in this bag
40   * @since 4.0
41   */
42  public final class CollectionBag<E> extends AbstractBagDecorator<E> {
43  
44      /** Serialization version */
45      private static final long serialVersionUID = -2560033712679053143L;
46  
47      /**
48       * Factory method to create a bag that complies to the Collection contract.
49       *
50       * @param <E> the type of the elements in the bag
51       * @param bag  the bag to decorate, must not be null
52       * @return a Bag that complies to the Collection contract
53       * @throws NullPointerException if bag is null
54       */
55      public static <E> Bag<E> collectionBag(final Bag<E> bag) {
56          return new CollectionBag<>(bag);
57      }
58  
59      /**
60       * Constructor that wraps (not copies).
61       *
62       * @param bag  the bag to decorate, must not be null
63       * @throws NullPointerException if bag is null
64       */
65      public CollectionBag(final Bag<E> bag) {
66          super(bag);
67      }
68  
69      /**
70       * <em>(Change)</em> Adds one copy of the specified object to the Bag.
71       * <p>
72       * Since this method always increases the size of the bag, it will always return {@code true}.
73       * </p>
74       *
75       * @param object the object to add
76       * @return {@code true}, always
77       * @throws ClassCastException if the class of the specified element prevents it from being added to this collection
78       */
79      @Override
80      public boolean add(final E object) {
81          return add(object, 1);
82      }
83  
84      /**
85       * <em>(Change)</em>
86       * Adds {@code count} copies of the specified object to the Bag.
87       * <p>
88       * Since this method always increases the size of the bag, it
89       * will always return {@code true}.
90       *
91       * @param object  the object to add
92       * @param count  the number of copies to add
93       * @return {@code true}, always
94       * @throws ClassCastException if the class of the specified element prevents it from being added to this collection
95       */
96      @Override
97      public boolean add(final E object, final int count) {
98          decorated().add(object, count);
99          return true;
100     }
101 
102     @Override
103     public boolean addAll(final Collection<? extends E> coll) {
104         boolean changed = false;
105         for (final E current : coll) {
106             final boolean added = add(current, 1);
107             changed = changed || added;
108         }
109         return changed;
110     }
111 
112     /**
113      * <em>(Change)</em>
114      * Returns {@code true} if the bag contains all elements in
115      * the given collection, <strong>not</strong> respecting cardinality. That is,
116      * if the given collection {@code coll} contains at least one of
117      * every object contained in this object.
118      *
119      * @param coll  the collection to check against
120      * @return {@code true} if the Bag contains at least one of every object in the collection
121      */
122     @Override
123     public boolean containsAll(final Collection<?> coll) {
124         return coll.stream().allMatch(this::contains);
125     }
126 
127     /**
128      * Read the collection in using a custom routine.
129      *
130      * @param in  the input stream
131      * @throws IOException if an error occurs while reading from the stream
132      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
133      * @throws ClassCastException if deserialized object has wrong type
134      */
135     @SuppressWarnings("unchecked") // will throw CCE, see Javadoc
136     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
137         in.defaultReadObject();
138         setCollection((Collection<E>) in.readObject());
139     }
140 
141     /**
142      * <em>(Change)</em>
143      * Removes the first occurrence of the given object from the bag.
144      * <p>
145      * This will also remove the object from the {@link #uniqueSet()} if the
146      * bag contains no occurrence anymore of the object after this operation.
147      * </p>
148      *
149      * @param object  the object to remove
150      * @return {@code true} if this call changed the collection
151      */
152     @Override
153     public boolean remove(final Object object) {
154         return remove(object, 1);
155     }
156 
157     /**
158      * <em>(Change)</em>
159      * Remove all elements represented in the given collection,
160      * <strong>not</strong> respecting cardinality. That is, remove <em>all</em>
161      * occurrences of every object contained in the given collection.
162      *
163      * @param coll  the collection to remove
164      * @return {@code true} if this call changed the collection
165      */
166     @Override
167     public boolean removeAll(final Collection<?> coll) {
168         if (coll != null) {
169             boolean result = false;
170             for (final Object obj : coll) {
171                 final boolean changed = remove(obj, getCount(obj));
172                 result = result || changed;
173             }
174             return result;
175         }
176         // let the decorated bag handle the case of null argument
177         return decorated().removeAll(null);
178     }
179 
180     /**
181      * <em>(Change)</em>
182      * Remove any members of the bag that are not in the given collection,
183      * <em>not</em> respecting cardinality. That is, any object in the given
184      * collection {@code coll} will be retained in the bag with the same
185      * number of copies prior to this operation. All other objects will be
186      * completely removed from this bag.
187      * <p>
188      * This implementation iterates over the elements of this bag, checking
189      * each element in turn to see if it's contained in {@code coll}.
190      * If it's not contained, it's removed from this bag. As a consequence,
191      * it is advised to use a collection type for {@code coll} that provides
192      * a fast (e.g. O(1)) implementation of {@link Collection#contains(Object)}.
193      * </p>
194      *
195      * @param coll  the collection to retain
196      * @return {@code true} if this call changed the collection
197      */
198     @Override
199     public boolean retainAll(final Collection<?> coll) {
200         if (coll != null) {
201             boolean modified = false;
202             final Iterator<E> e = iterator();
203             while (e.hasNext()) {
204                 if (!coll.contains(e.next())) {
205                     e.remove();
206                     modified = true;
207                 }
208             }
209             return modified;
210         }
211         // let the decorated bag handle the case of null argument
212         return decorated().retainAll(null);
213     }
214 
215     /**
216      * Writes the collection out using a custom routine.
217      *
218      * @param out  the output stream
219      * @throws IOException if an error occurs while writing to the stream
220      */
221     private void writeObject(final ObjectOutputStream out) throws IOException {
222         out.defaultWriteObject();
223         out.writeObject(decorated());
224     }
225 
226 }