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