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.multiset;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.ObjectOutputStream;
022import java.lang.reflect.Array;
023import java.util.ConcurrentModificationException;
024import java.util.Iterator;
025import java.util.Map;
026
027import org.apache.commons.collections4.MultiSet;
028import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
029
030/**
031 * Abstract implementation of the {@link MultiSet} interface to simplify the
032 * creation of subclass implementations.
033 * <p>
034 * Subclasses specify a Map implementation to use as the internal storage. The
035 * map will be used to map multiset elements to a number; the number represents the
036 * number of occurrences of that element in the multiset.
037 * </p>
038 *
039 * @param <E> the type held in the multiset.
040 * @since 4.1
041 */
042public abstract class AbstractMapMultiSet<E> extends AbstractMultiSet<E> {
043
044    /**
045     * Inner class EntrySetIterator.
046     *
047     * @param <E> the element type.
048     */
049    protected static class EntrySetIterator<E> implements Iterator<Entry<E>> {
050
051        /** The parent map */
052        protected final AbstractMapMultiSet<E> parent;
053
054        /**
055         * The source Iterator.
056         */
057        protected final Iterator<Map.Entry<E, MutableInteger>> decorated;
058
059        /** The last returned entry */
060        protected Entry<E> last;
061
062        /** Whether remove is allowed at present */
063        protected boolean canRemove;
064
065        /**
066         * Constructs a new instance.
067         * @param decorated  the iterator to decorate
068         * @param parent  the parent multiset
069         */
070        protected EntrySetIterator(final Iterator<Map.Entry<E, MutableInteger>> decorated,
071                                   final AbstractMapMultiSet<E> parent) {
072            this.decorated = decorated;
073            this.parent = parent;
074        }
075
076        @Override
077        public boolean hasNext() {
078            return decorated.hasNext();
079        }
080
081        @Override
082        public Entry<E> next() {
083            last = new MultiSetEntry<>(decorated.next());
084            canRemove = true;
085            return last;
086        }
087
088        @Override
089        public void remove() {
090            if (!canRemove) {
091                throw new IllegalStateException("Iterator remove() can only be called once after next()");
092            }
093            decorated.remove();
094            last = null;
095            canRemove = false;
096        }
097    }
098    /**
099     * Inner class iterator for the MultiSet.
100     */
101    private static final class MapBasedMultiSetIterator<E> implements Iterator<E> {
102        private final AbstractMapMultiSet<E> parent;
103        private final Iterator<Map.Entry<E, MutableInteger>> entryIterator;
104        private Map.Entry<E, MutableInteger> current;
105        private int itemCount;
106        private final int mods;
107        private boolean canRemove;
108
109        /**
110         * Constructs a new instance.
111         *
112         * @param parent the parent multiset
113         */
114        MapBasedMultiSetIterator(final AbstractMapMultiSet<E> parent) {
115            this.parent = parent;
116            this.entryIterator = parent.map.entrySet().iterator();
117            this.current = null;
118            this.mods = parent.modCount;
119            this.canRemove = false;
120        }
121
122        /** {@inheritDoc} */
123        @Override
124        public boolean hasNext() {
125            return itemCount > 0 || entryIterator.hasNext();
126        }
127
128        /** {@inheritDoc} */
129        @Override
130        public E next() {
131            if (parent.modCount != mods) {
132                throw new ConcurrentModificationException();
133            }
134            if (itemCount == 0) {
135                current = entryIterator.next();
136                itemCount = current.getValue().value;
137            }
138            canRemove = true;
139            itemCount--;
140            return current.getKey();
141        }
142
143        /** {@inheritDoc} */
144        @Override
145        public void remove() {
146            if (parent.modCount != mods) {
147                throw new ConcurrentModificationException();
148            }
149            if (!canRemove) {
150                throw new IllegalStateException();
151            }
152            final MutableInteger mut = current.getValue();
153            if (mut.value > 1) {
154                mut.value--;
155            } else {
156                entryIterator.remove();
157            }
158            parent.size--;
159            canRemove = false;
160        }
161    }
162
163    /**
164     * Inner class MultiSetEntry.
165     *
166     * @param <E> the key type.
167     */
168    protected static class MultiSetEntry<E> extends AbstractEntry<E> {
169
170        /**
171         * The parent entry.
172         */
173        protected final Map.Entry<E, MutableInteger> parentEntry;
174
175        /**
176         * Constructs a new instance.
177         * @param parentEntry  the entry to decorate
178         */
179        protected MultiSetEntry(final Map.Entry<E, MutableInteger> parentEntry) {
180            this.parentEntry = parentEntry;
181        }
182
183        @Override
184        public int getCount() {
185            return parentEntry.getValue().value;
186        }
187
188        @Override
189        public E getElement() {
190            return parentEntry.getKey();
191        }
192    }
193
194    /**
195     * Mutable integer class for storing the data.
196     */
197    protected static class MutableInteger {
198        /** The value of this mutable. */
199        protected int value;
200
201        /**
202         * Constructs a new instance.
203         * @param value the initial value
204         */
205        MutableInteger(final int value) {
206            this.value = value;
207        }
208
209        @Override
210        public boolean equals(final Object obj) {
211            if (!(obj instanceof MutableInteger)) {
212                return false;
213            }
214            return ((MutableInteger) obj).value == value;
215        }
216
217        @Override
218        public int hashCode() {
219            return value;
220        }
221    }
222
223    /**
224     * Inner class UniqueSetIterator.
225     *
226     * @param <E> the element type.
227     */
228    protected static class UniqueSetIterator<E> extends AbstractIteratorDecorator<E> {
229
230        /** The parent multiset */
231        protected final AbstractMapMultiSet<E> parent;
232
233        /** The last returned element */
234        protected E lastElement;
235
236        /** Whether remove is allowed at present */
237        protected boolean canRemove;
238
239        /**
240         * Constructs a new instance.
241         * @param iterator  the iterator to decorate
242         * @param parent  the parent multiset
243         */
244        protected UniqueSetIterator(final Iterator<E> iterator, final AbstractMapMultiSet<E> parent) {
245            super(iterator);
246            this.parent = parent;
247        }
248
249        @Override
250        public E next() {
251            lastElement = super.next();
252            canRemove = true;
253            return lastElement;
254        }
255
256        @Override
257        public void remove() {
258            if (!canRemove) {
259                throw new IllegalStateException("Iterator remove() can only be called once after next()");
260            }
261            final int count = parent.getCount(lastElement);
262            super.remove();
263            parent.remove(lastElement, count);
264            lastElement = null;
265            canRemove = false;
266        }
267    }
268
269    /** The map to use to store the data */
270    private transient Map<E, MutableInteger> map;
271
272    /** The current total size of the multiset */
273    private transient int size;
274
275    /** The modification count for fail fast iterators */
276    private transient int modCount;
277
278    /**
279     * Constructor needed for subclass serialization.
280     */
281    protected AbstractMapMultiSet() {
282    }
283
284    /**
285     * Constructor that assigns the specified Map as the backing store. The map
286     * must be empty and non-null.
287     *
288     * @param map the map to assign
289     */
290    protected AbstractMapMultiSet(final Map<E, MutableInteger> map) {
291        this.map = map;
292    }
293
294    @Override
295    public int add(final E object, final int occurrences) {
296        if (occurrences < 0) {
297            throw new IllegalArgumentException("Occurrences must not be negative.");
298        }
299
300        final MutableInteger mut = map.get(object);
301        final int oldCount = mut != null ? mut.value : 0;
302
303        if (occurrences > 0) {
304            modCount++;
305            size += occurrences;
306            if (mut == null) {
307                map.put(object, new MutableInteger(occurrences));
308            } else {
309                mut.value += occurrences;
310            }
311        }
312        return oldCount;
313    }
314
315    /**
316     * Clears the multiset by clearing the underlying map.
317     */
318    @Override
319    public void clear() {
320        modCount++;
321        map.clear();
322        size = 0;
323    }
324
325    /**
326     * Determines if the multiset contains the given element by checking if the
327     * underlying map contains the element as a key.
328     *
329     * @param object the object to search for
330     * @return true if the multiset contains the given element
331     */
332    @Override
333    public boolean contains(final Object object) {
334        return map.containsKey(object);
335    }
336
337    @Override
338    protected Iterator<Entry<E>> createEntrySetIterator() {
339        return new EntrySetIterator<>(map.entrySet().iterator(), this);
340    }
341
342    @Override
343    protected Iterator<E> createUniqueSetIterator() {
344        return new UniqueSetIterator<>(getMap().keySet().iterator(), this);
345    }
346
347    /**
348     * Reads the multiset in using a custom routine.
349     *
350     * @param in the input stream
351     * @throws IOException any of the usual I/O related exceptions
352     * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded
353     * @throws ClassCastException if the stream does not contain the correct objects
354     */
355    @Override
356    protected void doReadObject(final ObjectInputStream in)
357            throws IOException, ClassNotFoundException {
358        final int entrySize = in.readInt();
359        for (int i = 0; i < entrySize; i++) {
360            @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
361            final E obj = (E) in.readObject();
362            final int count = in.readInt();
363            map.put(obj, new MutableInteger(count));
364            size += count;
365        }
366    }
367
368    /**
369     * Writes the multiset out using a custom routine.
370     *
371     * @param out the output stream
372     * @throws IOException any of the usual I/O related exceptions
373     */
374    @Override
375    protected void doWriteObject(final ObjectOutputStream out) throws IOException {
376        out.writeInt(map.size());
377        for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
378            out.writeObject(entry.getKey());
379            out.writeInt(entry.getValue().value);
380        }
381    }
382
383    @Override
384    public boolean equals(final Object object) {
385        if (object == this) {
386            return true;
387        }
388        if (!(object instanceof MultiSet)) {
389            return false;
390        }
391        final MultiSet<?> other = (MultiSet<?>) object;
392        if (other.size() != size()) {
393            return false;
394        }
395        for (final E element : map.keySet()) {
396            if (other.getCount(element) != getCount(element)) {
397                return false;
398            }
399        }
400        return true;
401    }
402
403    /**
404     * Gets the number of occurrence of the given element in this multiset by
405     * looking up its count in the underlying map.
406     *
407     * @param object the object to search for
408     * @return the number of occurrences of the object, zero if not found
409     */
410    @Override
411    public int getCount(final Object object) {
412        final MutableInteger count = map.get(object);
413        if (count != null) {
414            return count.value;
415        }
416        return 0;
417    }
418
419    /**
420     * Gets the map that backs this multiset.
421     * Not intended for interactive use outside of subclasses.
422     *
423     * @return the map being used by the MultiSet
424     */
425    protected Map<E, MutableInteger> getMap() {
426        return map;
427    }
428
429    @Override
430    public int hashCode() {
431        int total = 0;
432        for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
433            final E element = entry.getKey();
434            final MutableInteger count = entry.getValue();
435            total += (element == null ? 0 : element.hashCode()) ^ count.value;
436        }
437        return total;
438    }
439
440    /**
441     * Returns true if the underlying map is empty.
442     *
443     * @return true if multiset is empty
444     */
445    @Override
446    public boolean isEmpty() {
447        return map.isEmpty();
448    }
449
450    /**
451     * Gets an iterator over the multiset elements. Elements present in the
452     * MultiSet more than once will be returned repeatedly.
453     *
454     * @return the iterator
455     */
456    @Override
457    public Iterator<E> iterator() {
458        return new MapBasedMultiSetIterator<>(this);
459    }
460
461    @Override
462    public int remove(final Object object, final int occurrences) {
463        if (occurrences < 0) {
464            throw new IllegalArgumentException("Occurrences must not be negative.");
465        }
466
467        final MutableInteger mut = map.get(object);
468        if (mut == null) {
469            return 0;
470        }
471        final int oldCount = mut.value;
472        if (occurrences > 0) {
473            modCount++;
474            if (occurrences < mut.value) {
475                mut.value -= occurrences;
476                size -= occurrences;
477            } else {
478                map.remove(object);
479                size -= mut.value;
480                mut.value = 0;
481            }
482        }
483        return oldCount;
484    }
485
486    /**
487     * Sets the map being wrapped.
488     * <p>
489     * <strong>Note:</strong> this method should only be used during deserialization
490     * </p>
491     *
492     * @param map the map to wrap
493     */
494    protected void setMap(final Map<E, MutableInteger> map) {
495        this.map = map;
496    }
497
498    /**
499     * Returns the number of elements in this multiset.
500     *
501     * @return current size of the multiset
502     */
503    @Override
504    public int size() {
505        return size;
506    }
507
508    /**
509     * Returns an array of all of this multiset's elements.
510     *
511     * @return an array of all of this multiset's elements
512     */
513    @Override
514    public Object[] toArray() {
515        final Object[] result = new Object[size()];
516        int i = 0;
517        for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
518            final E current = entry.getKey();
519            final MutableInteger count = entry.getValue();
520            for (int index = count.value; index > 0; index--) {
521                result[i++] = current;
522            }
523        }
524        return result;
525    }
526
527    /**
528     * Returns an array of all of this multiset's elements.
529     * If the input array has more elements than are in the multiset,
530     * trailing elements will be set to null.
531     *
532     * @param <T> the type of the array elements
533     * @param array the array to populate
534     * @return an array of all of this multiset's elements
535     * @throws ArrayStoreException if the runtime type of the specified array is not
536     *   a supertype of the runtime type of the elements in this list
537     * @throws NullPointerException if the specified array is null
538     */
539    @Override
540    public <T> T[] toArray(T[] array) {
541        final int size = size();
542        if (array.length < size) {
543            @SuppressWarnings("unchecked") // safe as both are of type T
544            final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), size);
545            array = unchecked;
546        }
547
548        int i = 0;
549        for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
550            final E current = entry.getKey();
551            final MutableInteger count = entry.getValue();
552            for (int index = count.value; index > 0; index--) {
553                // unsafe, will throw ArrayStoreException if types are not compatible, see Javadoc
554                @SuppressWarnings("unchecked")
555                final T unchecked = (T) current;
556                array[i++] = unchecked;
557            }
558        }
559        while (i < array.length) {
560            array[i++] = null;
561        }
562        return array;
563    }
564
565    @Override
566    protected int uniqueElements() {
567        return map.size();
568    }
569}