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