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