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.lang.reflect.Array;
23  import java.util.Collection;
24  import java.util.ConcurrentModificationException;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.Map.Entry;
28  import java.util.Objects;
29  import java.util.Set;
30  
31  import org.apache.commons.collections4.Bag;
32  import org.apache.commons.collections4.CollectionUtils;
33  import org.apache.commons.collections4.set.UnmodifiableSet;
34  
35  /**
36   * Abstract implementation of the {@link Bag} interface to simplify the creation
37   * of subclass implementations.
38   * <p>
39   * Subclasses specify a Map implementation to use as the internal storage. The
40   * map will be used to map bag elements to a number; the number represents the
41   * number of occurrences of that element in the bag.
42   * </p>
43   *
44   * @param <E> the type of elements in this bag
45   * @since 3.0 (previously DefaultMapBag v2.0)
46   */
47  public abstract class AbstractMapBag<E> implements Bag<E> {
48  
49      /**
50       * Inner class iterator for the Bag.
51       */
52      static class BagIterator<E> implements Iterator<E> {
53          private final AbstractMapBag<E> parent;
54          private final Iterator<Map.Entry<E, MutableInteger>> entryIterator;
55          private Map.Entry<E, MutableInteger> current;
56          private int itemCount;
57          private final int mods;
58          private boolean canRemove;
59  
60          /**
61           * Constructs a new instance.
62           *
63           * @param parent the parent bag
64           */
65          BagIterator(final AbstractMapBag<E> parent) {
66              this.parent = parent;
67              this.entryIterator = parent.map.entrySet().iterator();
68              this.current = null;
69              this.mods = parent.modCount;
70              this.canRemove = false;
71          }
72  
73          /** {@inheritDoc} */
74          @Override
75          public boolean hasNext() {
76              return itemCount > 0 || entryIterator.hasNext();
77          }
78  
79          /** {@inheritDoc} */
80          @Override
81          public E next() {
82              if (parent.modCount != mods) {
83                  throw new ConcurrentModificationException();
84              }
85              if (itemCount == 0) {
86                  current = entryIterator.next();
87                  itemCount = current.getValue().value;
88              }
89              canRemove = true;
90              itemCount--;
91              return current.getKey();
92          }
93  
94          /** {@inheritDoc} */
95          @Override
96          public void remove() {
97              if (parent.modCount != mods) {
98                  throw new ConcurrentModificationException();
99              }
100             if (!canRemove) {
101                 throw new IllegalStateException();
102             }
103             final MutableInteger mut = current.getValue();
104             if (mut.value > 1) {
105                 mut.value--;
106             } else {
107                 entryIterator.remove();
108             }
109             parent.size--;
110             canRemove = false;
111         }
112     }
113 
114     /**
115      * Mutable integer class for storing the data.
116      */
117     protected static class MutableInteger {
118 
119         /** The value of this mutable. */
120         protected int value;
121 
122         /**
123          * Constructs a new instance.
124          * @param value the initial value
125          */
126         MutableInteger(final int value) {
127             this.value = value;
128         }
129 
130         @Override
131         public boolean equals(final Object obj) {
132             if (!(obj instanceof MutableInteger)) {
133                 return false;
134             }
135             return ((MutableInteger) obj).value == value;
136         }
137 
138         @Override
139         public int hashCode() {
140             return value;
141         }
142     }
143 
144     /** The map to use to store the data */
145     private transient Map<E, MutableInteger> map;
146 
147     /** The current total size of the bag */
148     private int size;
149 
150     /** The modification count for fail fast iterators */
151     private transient int modCount;
152 
153     /** Unique view of the elements */
154     private transient Set<E> uniqueSet;
155 
156     /**
157      * Constructor needed for subclass serialization.
158      */
159     protected AbstractMapBag() {
160     }
161 
162     /**
163      * Constructor that assigns the specified Map as the backing store. The map
164      * must be empty and non-null.
165      *
166      * @param map the map to assign
167      */
168     protected AbstractMapBag(final Map<E, MutableInteger> map) {
169         this.map = Objects.requireNonNull(map, "map");
170     }
171 
172     /**
173      * Constructs a new instance that assigns the specified Map as the backing store. The map
174      * must be empty and non-null. The bag is filled from the iterable elements.
175      *
176      * @param map the map to assign.
177      * @param iterable The bag is filled from these iterable elements.
178      */
179     protected AbstractMapBag(final Map<E, MutableInteger> map, final Iterable<? extends E> iterable) {
180         this(map);
181         iterable.forEach(this::add);
182     }
183 
184     /**
185      * Adds a new element to the bag, incrementing its count in the underlying map.
186      *
187      * @param object the object to add
188      * @return {@code true} if the object was not already in the {@code uniqueSet}
189      */
190     @Override
191     public boolean add(final E object) {
192         return add(object, 1);
193     }
194 
195     /**
196      * Adds a new element to the bag, incrementing its count in the map.
197      *
198      * @param object the object to search for
199      * @param nCopies the number of copies to add
200      * @return {@code true} if the object was not already in the {@code uniqueSet}
201      */
202     @Override
203     public boolean add(final E object, final int nCopies) {
204         modCount++;
205         if (nCopies > 0) {
206             final MutableInteger mut = map.get(object);
207             size += nCopies;
208             if (mut == null) {
209                 map.put(object, new MutableInteger(nCopies));
210                 return true;
211             }
212             mut.value += nCopies;
213         }
214         return false;
215     }
216 
217     /**
218      * Invokes {@link #add(Object)} for each element in the given collection.
219      *
220      * @param coll the collection to add
221      * @return {@code true} if this call changed the bag
222      */
223     @Override
224     public boolean addAll(final Collection<? extends E> coll) {
225         boolean changed = false;
226         for (final E current : coll) {
227             final boolean added = add(current);
228             changed = changed || added;
229         }
230         return changed;
231     }
232 
233     /**
234      * Clears the bag by clearing the underlying map.
235      */
236     @Override
237     public void clear() {
238         modCount++;
239         map.clear();
240         size = 0;
241     }
242 
243     /**
244      * Determines if the bag contains the given element by checking if the
245      * underlying map contains the element as a key.
246      *
247      * @param object the object to search for
248      * @return true if the bag contains the given element
249      */
250     @Override
251     public boolean contains(final Object object) {
252         return map.containsKey(object);
253     }
254 
255     /**
256      * Returns {@code true} if the bag contains all elements in the given
257      * collection, respecting cardinality.
258      *
259      * @param other the bag to check against
260      * @return {@code true} if the Bag contains all the collection
261      */
262     boolean containsAll(final Bag<?> other) {
263         for (final Object current : other.uniqueSet()) {
264             if (getCount(current) < other.getCount(current)) {
265                 return false;
266             }
267         }
268         return true;
269     }
270 
271     /**
272      * Determines if the bag contains the given elements.
273      *
274      * @param coll the collection to check against
275      * @return {@code true} if the Bag contains all the collection
276      */
277     @Override
278     public boolean containsAll(final Collection<?> coll) {
279         if (coll instanceof Bag) {
280             return containsAll((Bag<?>) coll);
281         }
282         return containsAll(new HashBag<>(coll));
283     }
284 
285     /**
286      * Reads the map in using a custom routine.
287      *
288      * @param map the map to use
289      * @param in the input stream
290      * @throws IOException any of the usual I/O related exceptions
291      * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded
292      * @throws ClassCastException if the stream does not contain the correct objects
293      */
294     protected void doReadObject(final Map<E, MutableInteger> map, final ObjectInputStream in)
295             throws IOException, ClassNotFoundException {
296         this.map = map;
297         final int entrySize = in.readInt();
298         for (int i = 0; i < entrySize; i++) {
299             @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
300             final E obj = (E) in.readObject();
301             final int count = in.readInt();
302             map.put(obj, new MutableInteger(count));
303             size += count;
304         }
305     }
306 
307     /**
308      * Writes the map out using a custom routine.
309      *
310      * @param out the output stream
311      * @throws IOException any of the usual I/O related exceptions
312      */
313     protected void doWriteObject(final ObjectOutputStream out) throws IOException {
314         out.writeInt(map.size());
315         for (final Entry<E, MutableInteger> entry : map.entrySet()) {
316             out.writeObject(entry.getKey());
317             out.writeInt(entry.getValue().value);
318         }
319     }
320 
321     /**
322      * Compares this Bag to another. This Bag equals another Bag if it contains
323      * the same number of occurrences of the same elements.
324      *
325      * @param object the Bag to compare to
326      * @return true if equal
327      */
328     @Override
329     public boolean equals(final Object object) {
330         if (object == this) {
331             return true;
332         }
333         if (!(object instanceof Bag)) {
334             return false;
335         }
336         final Bag<?> other = (Bag<?>) object;
337         if (other.size() != size()) {
338             return false;
339         }
340         for (final E element : map.keySet()) {
341             if (other.getCount(element) != getCount(element)) {
342                 return false;
343             }
344         }
345         return true;
346     }
347 
348     /**
349      * Gets the number of occurrence of the given element in this bag by
350      * looking up its count in the underlying map.
351      *
352      * @param object the object to search for
353      * @return the number of occurrences of the object, zero if not found
354      */
355     @Override
356     public int getCount(final Object object) {
357         final MutableInteger count = map.get(object);
358         if (count != null) {
359             return count.value;
360         }
361         return 0;
362     }
363 
364     /**
365      * Utility method for implementations to access the map that backs this bag.
366      * Not intended for interactive use outside of subclasses.
367      *
368      * @return the map being used by the Bag
369      */
370     protected Map<E, MutableInteger> getMap() {
371         return map;
372     }
373 
374     /**
375      * Gets a hash code for the Bag compatible with the definition of equals.
376      * The hash code is defined as the sum total of a hash code for each
377      * element. The per element hash code is defined as
378      * {@code (e==null ? 0 : e.hashCode()) ^ noOccurrences)}. This hash code
379      * is compatible with the Set interface.
380      *
381      * @return the hash code of the Bag
382      */
383     @Override
384     public int hashCode() {
385         int total = 0;
386         for (final Entry<E, MutableInteger> entry : map.entrySet()) {
387             final E element = entry.getKey();
388             final MutableInteger count = entry.getValue();
389             total += (element == null ? 0 : element.hashCode()) ^ count.value;
390         }
391         return total;
392     }
393 
394     /**
395      * Returns true if the underlying map is empty.
396      *
397      * @return true if bag is empty
398      */
399     @Override
400     public boolean isEmpty() {
401         return map.isEmpty();
402     }
403 
404     /**
405      * Gets an iterator over the bag elements. Elements present in the Bag more
406      * than once will be returned repeatedly.
407      *
408      * @return the iterator
409      */
410     @Override
411     public Iterator<E> iterator() {
412         return new BagIterator<>(this);
413     }
414 
415     /**
416      * Removes all copies of the specified object from the bag.
417      *
418      * @param object the object to remove
419      * @return true if the bag changed
420      */
421     @Override
422     public boolean remove(final Object object) {
423         final MutableInteger mut = map.get(object);
424         if (mut == null) {
425             return false;
426         }
427         modCount++;
428         map.remove(object);
429         size -= mut.value;
430         return true;
431     }
432 
433     /**
434      * Removes a specified number of copies of an object from the bag.
435      *
436      * @param object the object to remove
437      * @param nCopies the number of copies to remove
438      * @return true if the bag changed
439      */
440     @Override
441     public boolean remove(final Object object, final int nCopies) {
442         final MutableInteger mut = map.get(object);
443         if (mut == null) {
444             return false;
445         }
446         if (nCopies <= 0) {
447             return false;
448         }
449         modCount++;
450         if (nCopies < mut.value) {
451             mut.value -= nCopies;
452             size -= nCopies;
453         } else {
454             map.remove(object);
455             size -= mut.value;
456         }
457         return true;
458     }
459 
460     /**
461      * Removes objects from the bag according to their count in the specified
462      * collection.
463      *
464      * @param coll the collection to use
465      * @return true if the bag changed
466      */
467     @Override
468     public boolean removeAll(final Collection<?> coll) {
469         boolean result = false;
470         if (coll != null) {
471             for (final Object current : coll) {
472                 final boolean changed = remove(current, 1);
473                 result = result || changed;
474             }
475         }
476         return result;
477     }
478 
479     /**
480      * Remove any members of the bag that are not in the given bag, respecting
481      * cardinality.
482      * @see #retainAll(Collection)
483      * @param other the bag to retain
484      * @return {@code true} if this call changed the collection
485      */
486     boolean retainAll(final Bag<?> other) {
487         boolean result = false;
488         final Bag<E> excess = new HashBag<>();
489         for (final E current : uniqueSet()) {
490             final int myCount = getCount(current);
491             final int otherCount = other.getCount(current);
492             if (1 <= otherCount && otherCount <= myCount) {
493                 excess.add(current, myCount - otherCount);
494             } else {
495                 excess.add(current, myCount);
496             }
497         }
498         if (!excess.isEmpty()) {
499             result = removeAll(excess);
500         }
501         return result;
502     }
503 
504     /**
505      * Remove any members of the bag that are not in the given bag, respecting
506      * cardinality.
507      *
508      * @param coll the collection to retain
509      * @return true if this call changed the collection
510      */
511     @Override
512     public boolean retainAll(final Collection<?> coll) {
513         if (coll instanceof Bag) {
514             return retainAll((Bag<?>) coll);
515         }
516         return retainAll(new HashBag<>(coll));
517     }
518 
519     /**
520      * Returns the number of elements in this bag.
521      *
522      * @return current size of the bag
523      */
524     @Override
525     public int size() {
526         return size;
527     }
528 
529     /**
530      * Returns an array of all of this bag's elements.
531      *
532      * @return an array of all of this bag's elements
533      */
534     @Override
535     public Object[] toArray() {
536         final Object[] result = new Object[size()];
537         int i = 0;
538         for (final E current : map.keySet()) {
539             for (int index = getCount(current); index > 0; index--) {
540                 result[i++] = current;
541             }
542         }
543         return result;
544     }
545 
546     /**
547      * Returns an array of all of this bag's elements.
548      * If the input array has more elements than are in the bag,
549      * trailing elements will be set to null.
550      *
551      * @param <T> the type of the array elements
552      * @param array the array to populate
553      * @return an array of all of this bag's elements
554      * @throws ArrayStoreException if the runtime type of the specified array is not
555      *   a supertype of the runtime type of the elements in this list
556      * @throws NullPointerException if the specified array is null
557      */
558     @Override
559     public <T> T[] toArray(T[] array) {
560         final int size = size();
561         if (array.length < size) {
562             @SuppressWarnings("unchecked") // safe as both are of type T
563             final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), size);
564             array = unchecked;
565         }
566 
567         int i = 0;
568         for (final E current : map.keySet()) {
569             for (int index = getCount(current); index > 0; index--) {
570                 // unsafe, will throw ArrayStoreException if types are not compatible, see Javadoc
571                 @SuppressWarnings("unchecked")
572                 final T unchecked = (T) current;
573                 array[i++] = unchecked;
574             }
575         }
576         while (i < array.length) {
577             array[i++] = null;
578         }
579         return array;
580     }
581 
582     /**
583      * Implement a toString() method suitable for debugging.
584      *
585      * @return a debugging toString
586      */
587     @Override
588     public String toString() {
589         if (isEmpty()) {
590             return "[]";
591         }
592         final StringBuilder buf = new StringBuilder();
593         buf.append(CollectionUtils.DEFAULT_TOSTRING_PREFIX);
594         final Iterator<E> it = uniqueSet().iterator();
595         while (it.hasNext()) {
596             final Object current = it.next();
597             final int count = getCount(current);
598             buf.append(count);
599             buf.append(CollectionUtils.COLON);
600             buf.append(current);
601             if (it.hasNext()) {
602                 buf.append(CollectionUtils.COMMA);
603             }
604         }
605         buf.append(CollectionUtils.DEFAULT_TOSTRING_SUFFIX);
606         return buf.toString();
607     }
608 
609     /**
610      * Returns an unmodifiable view of the underlying map's key set.
611      *
612      * @return the set of unique elements in this bag
613      */
614     @Override
615     public Set<E> uniqueSet() {
616         if (uniqueSet == null) {
617             uniqueSet = UnmodifiableSet.<E>unmodifiableSet(map.keySet());
618         }
619         return uniqueSet;
620     }
621 
622 }