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.multiset;
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.ConcurrentModificationException;
24  import java.util.Iterator;
25  import java.util.Map;
26  
27  import org.apache.commons.collections4.MultiSet;
28  import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
29  
30  /**
31   * Abstract implementation of the {@link MultiSet} interface to simplify the
32   * creation of subclass implementations.
33   * <p>
34   * Subclasses specify a Map implementation to use as the internal storage. The
35   * map will be used to map multiset elements to a number; the number represents the
36   * number of occurrences of that element in the multiset.
37   * </p>
38   *
39   * @param <E> the type held in the multiset
40   * @since 4.1
41   */
42  public abstract class AbstractMapMultiSet<E> extends AbstractMultiSet<E> {
43  
44      /**
45       * Inner class EntrySetIterator.
46       */
47      protected static class EntrySetIterator<E> implements Iterator<Entry<E>> {
48  
49          /** The parent map */
50          protected final AbstractMapMultiSet<E> parent;
51  
52          protected final Iterator<Map.Entry<E, MutableInteger>> decorated;
53  
54          /** The last returned entry */
55          protected Entry<E> last;
56  
57          /** Whether remove is allowed at present */
58          protected boolean canRemove;
59  
60          /**
61           * Constructs a new instance.
62           * @param iterator  the iterator to decorate
63           * @param parent  the parent multiset
64           */
65          protected EntrySetIterator(final Iterator<Map.Entry<E, MutableInteger>> iterator,
66                                     final AbstractMapMultiSet<E> parent) {
67              this.decorated = iterator;
68              this.parent = parent;
69          }
70  
71          @Override
72          public boolean hasNext() {
73              return decorated.hasNext();
74          }
75  
76          @Override
77          public Entry<E> next() {
78              last = new MultiSetEntry<>(decorated.next());
79              canRemove = true;
80              return last;
81          }
82  
83          @Override
84          public void remove() {
85              if (!canRemove) {
86                  throw new IllegalStateException("Iterator remove() can only be called once after next()");
87              }
88              decorated.remove();
89              last = null;
90              canRemove = false;
91          }
92      }
93      /**
94       * Inner class iterator for the MultiSet.
95       */
96      private static final class MapBasedMultiSetIterator<E> implements Iterator<E> {
97          private final AbstractMapMultiSet<E> parent;
98          private final Iterator<Map.Entry<E, MutableInteger>> entryIterator;
99          private Map.Entry<E, MutableInteger> current;
100         private int itemCount;
101         private final int mods;
102         private boolean canRemove;
103 
104         /**
105          * Constructs a new instance.
106          *
107          * @param parent the parent multiset
108          */
109         MapBasedMultiSetIterator(final AbstractMapMultiSet<E> parent) {
110             this.parent = parent;
111             this.entryIterator = parent.map.entrySet().iterator();
112             this.current = null;
113             this.mods = parent.modCount;
114             this.canRemove = false;
115         }
116 
117         /** {@inheritDoc} */
118         @Override
119         public boolean hasNext() {
120             return itemCount > 0 || entryIterator.hasNext();
121         }
122 
123         /** {@inheritDoc} */
124         @Override
125         public E next() {
126             if (parent.modCount != mods) {
127                 throw new ConcurrentModificationException();
128             }
129             if (itemCount == 0) {
130                 current = entryIterator.next();
131                 itemCount = current.getValue().value;
132             }
133             canRemove = true;
134             itemCount--;
135             return current.getKey();
136         }
137 
138         /** {@inheritDoc} */
139         @Override
140         public void remove() {
141             if (parent.modCount != mods) {
142                 throw new ConcurrentModificationException();
143             }
144             if (!canRemove) {
145                 throw new IllegalStateException();
146             }
147             final MutableInteger mut = current.getValue();
148             if (mut.value > 1) {
149                 mut.value--;
150             } else {
151                 entryIterator.remove();
152             }
153             parent.size--;
154             canRemove = false;
155         }
156     }
157     /**
158      * Inner class MultiSetEntry.
159      */
160     protected static class MultiSetEntry<E> extends AbstractEntry<E> {
161 
162         protected final Map.Entry<E, MutableInteger> parentEntry;
163 
164         /**
165          * Constructs a new instance.
166          * @param parentEntry  the entry to decorate
167          */
168         protected MultiSetEntry(final Map.Entry<E, MutableInteger> parentEntry) {
169             this.parentEntry = parentEntry;
170         }
171 
172         @Override
173         public int getCount() {
174             return parentEntry.getValue().value;
175         }
176 
177         @Override
178         public E getElement() {
179             return parentEntry.getKey();
180         }
181     }
182 
183     /**
184      * Mutable integer class for storing the data.
185      */
186     protected static class MutableInteger {
187         /** The value of this mutable. */
188         protected int value;
189 
190         /**
191          * Constructs a new instance.
192          * @param value the initial value
193          */
194         MutableInteger(final int value) {
195             this.value = value;
196         }
197 
198         @Override
199         public boolean equals(final Object obj) {
200             if (!(obj instanceof MutableInteger)) {
201                 return false;
202             }
203             return ((MutableInteger) obj).value == value;
204         }
205 
206         @Override
207         public int hashCode() {
208             return value;
209         }
210     }
211 
212     /**
213      * Inner class UniqueSetIterator.
214      */
215     protected static class UniqueSetIterator<E> extends AbstractIteratorDecorator<E> {
216 
217         /** The parent multiset */
218         protected final AbstractMapMultiSet<E> parent;
219 
220         /** The last returned element */
221         protected E lastElement;
222 
223         /** Whether remove is allowed at present */
224         protected boolean canRemove;
225 
226         /**
227          * Constructs a new instance.
228          * @param iterator  the iterator to decorate
229          * @param parent  the parent multiset
230          */
231         protected UniqueSetIterator(final Iterator<E> iterator, final AbstractMapMultiSet<E> parent) {
232             super(iterator);
233             this.parent = parent;
234         }
235 
236         @Override
237         public E next() {
238             lastElement = super.next();
239             canRemove = true;
240             return lastElement;
241         }
242 
243         @Override
244         public void remove() {
245             if (!canRemove) {
246                 throw new IllegalStateException("Iterator remove() can only be called once after next()");
247             }
248             final int count = parent.getCount(lastElement);
249             super.remove();
250             parent.remove(lastElement, count);
251             lastElement = null;
252             canRemove = false;
253         }
254     }
255 
256     /** The map to use to store the data */
257     private transient Map<E, MutableInteger> map;
258 
259     /** The current total size of the multiset */
260     private transient int size;
261 
262     /** The modification count for fail fast iterators */
263     private transient int modCount;
264 
265     /**
266      * Constructor needed for subclass serialisation.
267      */
268     protected AbstractMapMultiSet() {
269     }
270 
271     /**
272      * Constructor that assigns the specified Map as the backing store. The map
273      * must be empty and non-null.
274      *
275      * @param map the map to assign
276      */
277     protected AbstractMapMultiSet(final Map<E, MutableInteger> map) {
278         this.map = map;
279     }
280 
281     @Override
282     public int add(final E object, final int occurrences) {
283         if (occurrences < 0) {
284             throw new IllegalArgumentException("Occurrences must not be negative.");
285         }
286 
287         final MutableInteger mut = map.get(object);
288         final int oldCount = mut != null ? mut.value : 0;
289 
290         if (occurrences > 0) {
291             modCount++;
292             size += occurrences;
293             if (mut == null) {
294                 map.put(object, new MutableInteger(occurrences));
295             } else {
296                 mut.value += occurrences;
297             }
298         }
299         return oldCount;
300     }
301 
302     /**
303      * Clears the multiset by clearing the underlying map.
304      */
305     @Override
306     public void clear() {
307         modCount++;
308         map.clear();
309         size = 0;
310     }
311 
312     /**
313      * Determines if the multiset contains the given element by checking if the
314      * underlying map contains the element as a key.
315      *
316      * @param object the object to search for
317      * @return true if the multiset contains the given element
318      */
319     @Override
320     public boolean contains(final Object object) {
321         return map.containsKey(object);
322     }
323 
324     @Override
325     protected Iterator<Entry<E>> createEntrySetIterator() {
326         return new EntrySetIterator<>(map.entrySet().iterator(), this);
327     }
328 
329     @Override
330     protected Iterator<E> createUniqueSetIterator() {
331         return new UniqueSetIterator<>(getMap().keySet().iterator(), this);
332     }
333 
334     /**
335      * Read the multiset in using a custom routine.
336      * @param in the input stream
337      * @throws IOException any of the usual I/O related exceptions
338      * @throws ClassNotFoundException if the stream contains an object which class can not be loaded
339      * @throws ClassCastException if the stream does not contain the correct objects
340      */
341     @Override
342     protected void doReadObject(final ObjectInputStream in)
343             throws IOException, ClassNotFoundException {
344         final int entrySize = in.readInt();
345         for (int i = 0; i < entrySize; i++) {
346             @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
347             final E obj = (E) in.readObject();
348             final int count = in.readInt();
349             map.put(obj, new MutableInteger(count));
350             size += count;
351         }
352     }
353 
354     /**
355      * Write the multiset out using a custom routine.
356      * @param out the output stream
357      * @throws IOException any of the usual I/O related exceptions
358      */
359     @Override
360     protected void doWriteObject(final ObjectOutputStream out) throws IOException {
361         out.writeInt(map.size());
362         for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
363             out.writeObject(entry.getKey());
364             out.writeInt(entry.getValue().value);
365         }
366     }
367 
368     @Override
369     public boolean equals(final Object object) {
370         if (object == this) {
371             return true;
372         }
373         if (!(object instanceof MultiSet)) {
374             return false;
375         }
376         final MultiSet<?> other = (MultiSet<?>) object;
377         if (other.size() != size()) {
378             return false;
379         }
380         for (final E element : map.keySet()) {
381             if (other.getCount(element) != getCount(element)) {
382                 return false;
383             }
384         }
385         return true;
386     }
387 
388     /**
389      * Returns the number of occurrence of the given element in this multiset by
390      * looking up its count in the underlying map.
391      *
392      * @param object the object to search for
393      * @return the number of occurrences of the object, zero if not found
394      */
395     @Override
396     public int getCount(final Object object) {
397         final MutableInteger count = map.get(object);
398         if (count != null) {
399             return count.value;
400         }
401         return 0;
402     }
403 
404     /**
405      * Utility method for implementations to access the map that backs this multiset.
406      * Not intended for interactive use outside of subclasses.
407      *
408      * @return the map being used by the MultiSet
409      */
410     protected Map<E, MutableInteger> getMap() {
411         return map;
412     }
413 
414     @Override
415     public int hashCode() {
416         int total = 0;
417         for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
418             final E element = entry.getKey();
419             final MutableInteger count = entry.getValue();
420             total += (element == null ? 0 : element.hashCode()) ^ count.value;
421         }
422         return total;
423     }
424 
425     /**
426      * Returns true if the underlying map is empty.
427      *
428      * @return true if multiset is empty
429      */
430     @Override
431     public boolean isEmpty() {
432         return map.isEmpty();
433     }
434 
435     /**
436      * Gets an iterator over the multiset elements. Elements present in the
437      * MultiSet more than once will be returned repeatedly.
438      *
439      * @return the iterator
440      */
441     @Override
442     public Iterator<E> iterator() {
443         return new MapBasedMultiSetIterator<>(this);
444     }
445 
446     @Override
447     public int remove(final Object object, final int occurrences) {
448         if (occurrences < 0) {
449             throw new IllegalArgumentException("Occurrences must not be negative.");
450         }
451 
452         final MutableInteger mut = map.get(object);
453         if (mut == null) {
454             return 0;
455         }
456         final int oldCount = mut.value;
457         if (occurrences > 0) {
458             modCount++;
459             if (occurrences < mut.value) {
460                 mut.value -= occurrences;
461                 size -= occurrences;
462             } else {
463                 map.remove(object);
464                 size -= mut.value;
465                 mut.value = 0;
466             }
467         }
468         return oldCount;
469     }
470 
471     /**
472      * Sets the map being wrapped.
473      * <p>
474      * <b>NOTE:</b> this method should only be used during deserialization
475      *
476      * @param map the map to wrap
477      */
478     protected void setMap(final Map<E, MutableInteger> map) {
479         this.map = map;
480     }
481 
482     /**
483      * Returns the number of elements in this multiset.
484      *
485      * @return current size of the multiset
486      */
487     @Override
488     public int size() {
489         return size;
490     }
491 
492     /**
493      * Returns an array of all of this multiset's elements.
494      *
495      * @return an array of all of this multiset's elements
496      */
497     @Override
498     public Object[] toArray() {
499         final Object[] result = new Object[size()];
500         int i = 0;
501         for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
502             final E current = entry.getKey();
503             final MutableInteger count = entry.getValue();
504             for (int index = count.value; index > 0; index--) {
505                 result[i++] = current;
506             }
507         }
508         return result;
509     }
510 
511     /**
512      * Returns an array of all of this multiset's elements.
513      * If the input array has more elements than are in the multiset,
514      * trailing elements will be set to null.
515      *
516      * @param <T> the type of the array elements
517      * @param array the array to populate
518      * @return an array of all of this multiset's elements
519      * @throws ArrayStoreException if the runtime type of the specified array is not
520      *   a supertype of the runtime type of the elements in this list
521      * @throws NullPointerException if the specified array is null
522      */
523     @Override
524     public <T> T[] toArray(T[] array) {
525         final int size = size();
526         if (array.length < size) {
527             @SuppressWarnings("unchecked") // safe as both are of type T
528             final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), size);
529             array = unchecked;
530         }
531 
532         int i = 0;
533         for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
534             final E current = entry.getKey();
535             final MutableInteger count = entry.getValue();
536             for (int index = count.value; index > 0; index--) {
537                 // unsafe, will throw ArrayStoreException if types are not compatible, see Javadoc
538                 @SuppressWarnings("unchecked")
539                 final T unchecked = (T) current;
540                 array[i++] = unchecked;
541             }
542         }
543         while (i < array.length) {
544             array[i++] = null;
545         }
546         return array;
547     }
548 
549     @Override
550     protected int uniqueElements() {
551         return map.size();
552     }
553 }