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