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