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