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 serialisation.
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     * Read the multiset in using a custom routine.
349     * @param in the input stream
350     * @throws IOException any of the usual I/O related exceptions
351     * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded
352     * @throws ClassCastException if the stream does not contain the correct objects
353     */
354    @Override
355    protected void doReadObject(final ObjectInputStream in)
356            throws IOException, ClassNotFoundException {
357        final int entrySize = in.readInt();
358        for (int i = 0; i < entrySize; i++) {
359            @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
360            final E obj = (E) in.readObject();
361            final int count = in.readInt();
362            map.put(obj, new MutableInteger(count));
363            size += count;
364        }
365    }
366
367    /**
368     * Write the multiset out using a custom routine.
369     * @param out the output stream
370     * @throws IOException any of the usual I/O related exceptions
371     */
372    @Override
373    protected void doWriteObject(final ObjectOutputStream out) throws IOException {
374        out.writeInt(map.size());
375        for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
376            out.writeObject(entry.getKey());
377            out.writeInt(entry.getValue().value);
378        }
379    }
380
381    @Override
382    public boolean equals(final Object object) {
383        if (object == this) {
384            return true;
385        }
386        if (!(object instanceof MultiSet)) {
387            return false;
388        }
389        final MultiSet<?> other = (MultiSet<?>) object;
390        if (other.size() != size()) {
391            return false;
392        }
393        for (final E element : map.keySet()) {
394            if (other.getCount(element) != getCount(element)) {
395                return false;
396            }
397        }
398        return true;
399    }
400
401    /**
402     * Returns the number of occurrence of the given element in this multiset by
403     * looking up its count in the underlying map.
404     *
405     * @param object the object to search for
406     * @return the number of occurrences of the object, zero if not found
407     */
408    @Override
409    public int getCount(final Object object) {
410        final MutableInteger count = map.get(object);
411        if (count != null) {
412            return count.value;
413        }
414        return 0;
415    }
416
417    /**
418     * Utility method for implementations to access the map that backs this multiset.
419     * Not intended for interactive use outside of subclasses.
420     *
421     * @return the map being used by the MultiSet
422     */
423    protected Map<E, MutableInteger> getMap() {
424        return map;
425    }
426
427    @Override
428    public int hashCode() {
429        int total = 0;
430        for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
431            final E element = entry.getKey();
432            final MutableInteger count = entry.getValue();
433            total += (element == null ? 0 : element.hashCode()) ^ count.value;
434        }
435        return total;
436    }
437
438    /**
439     * Returns true if the underlying map is empty.
440     *
441     * @return true if multiset is empty
442     */
443    @Override
444    public boolean isEmpty() {
445        return map.isEmpty();
446    }
447
448    /**
449     * Gets an iterator over the multiset elements. Elements present in the
450     * MultiSet more than once will be returned repeatedly.
451     *
452     * @return the iterator
453     */
454    @Override
455    public Iterator<E> iterator() {
456        return new MapBasedMultiSetIterator<>(this);
457    }
458
459    @Override
460    public int remove(final Object object, final int occurrences) {
461        if (occurrences < 0) {
462            throw new IllegalArgumentException("Occurrences must not be negative.");
463        }
464
465        final MutableInteger mut = map.get(object);
466        if (mut == null) {
467            return 0;
468        }
469        final int oldCount = mut.value;
470        if (occurrences > 0) {
471            modCount++;
472            if (occurrences < mut.value) {
473                mut.value -= occurrences;
474                size -= occurrences;
475            } else {
476                map.remove(object);
477                size -= mut.value;
478                mut.value = 0;
479            }
480        }
481        return oldCount;
482    }
483
484    /**
485     * Sets the map being wrapped.
486     * <p>
487     * <strong>Note:</strong> this method should only be used during deserialization
488     * </p>
489     *
490     * @param map the map to wrap
491     */
492    protected void setMap(final Map<E, MutableInteger> map) {
493        this.map = map;
494    }
495
496    /**
497     * Returns the number of elements in this multiset.
498     *
499     * @return current size of the multiset
500     */
501    @Override
502    public int size() {
503        return size;
504    }
505
506    /**
507     * Returns an array of all of this multiset's elements.
508     *
509     * @return an array of all of this multiset's elements
510     */
511    @Override
512    public Object[] toArray() {
513        final Object[] result = new Object[size()];
514        int i = 0;
515        for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
516            final E current = entry.getKey();
517            final MutableInteger count = entry.getValue();
518            for (int index = count.value; index > 0; index--) {
519                result[i++] = current;
520            }
521        }
522        return result;
523    }
524
525    /**
526     * Returns an array of all of this multiset's elements.
527     * If the input array has more elements than are in the multiset,
528     * trailing elements will be set to null.
529     *
530     * @param <T> the type of the array elements
531     * @param array the array to populate
532     * @return an array of all of this multiset's elements
533     * @throws ArrayStoreException if the runtime type of the specified array is not
534     *   a supertype of the runtime type of the elements in this list
535     * @throws NullPointerException if the specified array is null
536     */
537    @Override
538    public <T> T[] toArray(T[] array) {
539        final int size = size();
540        if (array.length < size) {
541            @SuppressWarnings("unchecked") // safe as both are of type T
542            final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), size);
543            array = unchecked;
544        }
545
546        int i = 0;
547        for (final Map.Entry<E, MutableInteger> entry : map.entrySet()) {
548            final E current = entry.getKey();
549            final MutableInteger count = entry.getValue();
550            for (int index = count.value; index > 0; index--) {
551                // unsafe, will throw ArrayStoreException if types are not compatible, see Javadoc
552                @SuppressWarnings("unchecked")
553                final T unchecked = (T) current;
554                array[i++] = unchecked;
555            }
556        }
557        while (i < array.length) {
558            array[i++] = null;
559        }
560        return array;
561    }
562
563    @Override
564    protected int uniqueElements() {
565        return map.size();
566    }
567}