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.multiset;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.ObjectOutputStream;
022import java.util.AbstractCollection;
023import java.util.AbstractSet;
024import java.util.Collection;
025import java.util.Iterator;
026import java.util.Objects;
027import java.util.Set;
028
029import org.apache.commons.collections4.IteratorUtils;
030import org.apache.commons.collections4.MultiSet;
031import org.apache.commons.collections4.Transformer;
032
033/**
034 * Abstract implementation of the {@link MultiSet} interface to simplify the
035 * creation of subclass implementations.
036 *
037 * @param <E> the type held in the multiset
038 * @since 4.1
039 */
040public abstract class AbstractMultiSet<E> extends AbstractCollection<E> implements MultiSet<E> {
041
042    /**
043     * Inner class AbstractEntry.
044     *
045     * @param <E> the element type.
046     */
047    protected abstract static class AbstractEntry<E> implements Entry<E> {
048
049        @Override
050        public boolean equals(final Object object) {
051            if (object instanceof Entry) {
052                final Entry<?> other = (Entry<?>) object;
053                final E element = getElement();
054                final Object otherElement = other.getElement();
055
056                return this.getCount() == other.getCount() &&
057                       Objects.equals(element, otherElement);
058            }
059            return false;
060        }
061
062        @Override
063        public int hashCode() {
064            final E element = getElement();
065            return (element == null ? 0 : element.hashCode()) ^ getCount();
066        }
067
068        @Override
069        public String toString() {
070            return String.format("%s:%d", getElement(), getCount());
071        }
072    }
073
074    /**
075     * Inner class EntrySet.
076     *
077     * @param <E> the element type.
078     */
079    protected static class EntrySet<E> extends AbstractSet<Entry<E>> {
080
081        private final AbstractMultiSet<E> parent;
082
083        /**
084         * Constructs a new view of the MultiSet.
085         *
086         * @param parent  the parent MultiSet
087         */
088        protected EntrySet(final AbstractMultiSet<E> parent) {
089            this.parent = parent;
090        }
091
092        @Override
093        public boolean contains(final Object obj) {
094            if (!(obj instanceof Entry<?>)) {
095                return false;
096            }
097            final Entry<?> entry = (Entry<?>) obj;
098            final Object element = entry.getElement();
099            return parent.getCount(element) == entry.getCount();
100        }
101
102        @Override
103        public Iterator<Entry<E>> iterator() {
104            return parent.createEntrySetIterator();
105        }
106
107        @Override
108        public boolean remove(final Object obj) {
109            if (!(obj instanceof Entry<?>)) {
110                return false;
111            }
112            final Entry<?> entry = (Entry<?>) obj;
113            final Object element = entry.getElement();
114            if (parent.contains(element)) {
115                final int count = parent.getCount(element);
116                if (entry.getCount() == count) {
117                    parent.remove(element, count);
118                    return true;
119                }
120            }
121            return false;
122        }
123
124        @Override
125        public int size() {
126            return parent.uniqueElements();
127        }
128    }
129
130    /**
131     * Inner class iterator for the MultiSet.
132     */
133    private static final class MultiSetIterator<E> implements Iterator<E> {
134        private final AbstractMultiSet<E> parent;
135        private final Iterator<Entry<E>> entryIterator;
136        private Entry<E> current;
137        private int itemCount;
138        private boolean canRemove;
139
140        /**
141         * Constructs a new instance.
142         *
143         * @param parent the parent multiset
144         */
145        MultiSetIterator(final AbstractMultiSet<E> parent) {
146            this.parent = parent;
147            this.entryIterator = parent.entrySet().iterator();
148            this.current = null;
149            this.canRemove = false;
150        }
151
152        /** {@inheritDoc} */
153        @Override
154        public boolean hasNext() {
155            return itemCount > 0 || entryIterator.hasNext();
156        }
157
158        /** {@inheritDoc} */
159        @Override
160        public E next() {
161            if (itemCount == 0) {
162                current = entryIterator.next();
163                itemCount = current.getCount();
164            }
165            canRemove = true;
166            itemCount--;
167            return current.getElement();
168        }
169
170        /** {@inheritDoc} */
171        @Override
172        public void remove() {
173            if (!canRemove) {
174                throw new IllegalStateException();
175            }
176            final int count = current.getCount();
177            if (count > 1) {
178                parent.remove(current.getElement());
179            } else {
180                entryIterator.remove();
181            }
182            canRemove = false;
183        }
184    }
185
186    /**
187     * Inner class UniqueSet.
188     *
189     * @param <E> the element type.
190     */
191    protected static class UniqueSet<E> extends AbstractSet<E> {
192
193        /** The parent multiset */
194        protected final AbstractMultiSet<E> parent;
195
196        /**
197         * Constructs a new unique element view of the MultiSet.
198         *
199         * @param parent  the parent MultiSet
200         */
201        protected UniqueSet(final AbstractMultiSet<E> parent) {
202            this.parent = parent;
203        }
204
205        @Override
206        public void clear() {
207            parent.clear();
208        }
209
210        @Override
211        public boolean contains(final Object key) {
212            return parent.contains(key);
213        }
214
215        @Override
216        public boolean containsAll(final Collection<?> coll) {
217            return parent.containsAll(coll);
218        }
219
220        @Override
221        public Iterator<E> iterator() {
222            return parent.createUniqueSetIterator();
223        }
224
225        @Override
226        public boolean remove(final Object key) {
227            return parent.remove(key, parent.getCount(key)) != 0;
228        }
229
230        @Override
231        public int size() {
232            return parent.uniqueElements();
233        }
234    }
235
236    /** View of the elements */
237    private transient Set<E> uniqueSet;
238
239    /** View of the entries */
240    private transient Set<Entry<E>> entrySet;
241
242    /**
243     * Constructor needed for subclass serialisation.
244     */
245    protected AbstractMultiSet() {
246    }
247
248    @Override
249    public boolean add(final E object) {
250        add(object, 1);
251        return true;
252    }
253
254    @Override
255    public int add(final E object, final int occurrences) {
256        throw new UnsupportedOperationException();
257    }
258
259    /**
260     * Clears the multiset removing all elements from the entrySet.
261     */
262    @Override
263    public void clear() {
264        final Iterator<Entry<E>> it = entrySet().iterator();
265        while (it.hasNext()) {
266            it.next();
267            it.remove();
268        }
269    }
270
271    /**
272     * Determines if the multiset contains the given element.
273     *
274     * @param object the object to search for
275     * @return true if the multiset contains the given element
276     */
277    @Override
278    public boolean contains(final Object object) {
279        return getCount(object) > 0;
280    }
281
282    /**
283     * Create a new view for the set of entries in this multiset.
284     *
285     * @return a view of the set of entries
286     */
287    protected Set<Entry<E>> createEntrySet() {
288        return new EntrySet<>(this);
289    }
290
291    /**
292     * Creates an entry set iterator.
293     * Subclasses can override this to return iterators with different properties.
294     *
295     * @return the entrySet iterator
296     */
297    protected abstract Iterator<Entry<E>> createEntrySetIterator();
298
299    /**
300     * Create a new view for the set of unique elements in this multiset.
301     *
302     * @return a view of the set of unique elements
303     */
304    protected Set<E> createUniqueSet() {
305        return new UniqueSet<>(this);
306    }
307
308    /**
309     * Creates a unique set iterator.
310     * Subclasses can override this to return iterators with different properties.
311     *
312     * @return the uniqueSet iterator
313     */
314    protected Iterator<E> createUniqueSetIterator() {
315        final Transformer<Entry<E>, E> transformer = Entry::getElement;
316        return IteratorUtils.transformedIterator(entrySet().iterator(), transformer);
317    }
318
319    /**
320     * Read the multiset in using a custom routine.
321     * @param in the input stream
322     * @throws IOException any of the usual I/O related exceptions
323     * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded
324     * @throws ClassCastException if the stream does not contain the correct objects
325     */
326    protected void doReadObject(final ObjectInputStream in)
327            throws IOException, ClassNotFoundException {
328        final int entrySize = in.readInt();
329        for (int i = 0; i < entrySize; i++) {
330            @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
331            final E obj = (E) in.readObject();
332            final int count = in.readInt();
333            setCount(obj, count);
334        }
335    }
336
337    /**
338     * Write the multiset out using a custom routine.
339     * @param out the output stream
340     * @throws IOException any of the usual I/O related exceptions
341     */
342    protected void doWriteObject(final ObjectOutputStream out) throws IOException {
343        out.writeInt(entrySet().size());
344        for (final Entry<E> entry : entrySet()) {
345            out.writeObject(entry.getElement());
346            out.writeInt(entry.getCount());
347        }
348    }
349
350    /**
351     * Returns an unmodifiable view of the entries of this multiset.
352     *
353     * @return the set of entries in this multiset
354     */
355    @Override
356    public Set<Entry<E>> entrySet() {
357        if (entrySet == null) {
358            entrySet = createEntrySet();
359        }
360        return entrySet;
361    }
362
363    @Override
364    public boolean equals(final Object object) {
365        if (object == this) {
366            return true;
367        }
368        if (!(object instanceof MultiSet)) {
369            return false;
370        }
371        final MultiSet<?> other = (MultiSet<?>) object;
372        if (other.size() != size()) {
373            return false;
374        }
375        for (final Entry<E> entry : entrySet()) {
376            if (other.getCount(entry.getElement()) != getCount(entry.getElement())) {
377                return false;
378            }
379        }
380        return true;
381    }
382
383    /**
384     * Returns the number of occurrence of the given element in this multiset by
385     * iterating over its entrySet.
386     *
387     * @param object the object to search for
388     * @return the number of occurrences of the object, zero if not found
389     */
390    @Override
391    public int getCount(final Object object) {
392        for (final Entry<E> entry : entrySet()) {
393            final E element = entry.getElement();
394            if (Objects.equals(element, object)) {
395                return entry.getCount();
396            }
397        }
398        return 0;
399    }
400
401    @Override
402    public int hashCode() {
403        return entrySet().hashCode();
404    }
405
406    /**
407     * Gets an iterator over the multiset elements. Elements present in the
408     * MultiSet more than once will be returned repeatedly.
409     *
410     * @return the iterator
411     */
412    @Override
413    public Iterator<E> iterator() {
414        return new MultiSetIterator<>(this);
415    }
416
417    @Override
418    public boolean remove(final Object object) {
419        return remove(object, 1) != 0;
420    }
421
422    @Override
423    public int remove(final Object object, final int occurrences) {
424        throw new UnsupportedOperationException();
425    }
426
427    @Override
428    public boolean removeAll(final Collection<?> coll) {
429        boolean result = false;
430        for (final Object obj : coll) {
431            final boolean changed = remove(obj, getCount(obj)) != 0;
432            result = result || changed;
433        }
434        return result;
435    }
436
437    @Override
438    public int setCount(final E object, final int count) {
439        if (count < 0) {
440            throw new IllegalArgumentException("Count must not be negative.");
441        }
442
443        final int oldCount = getCount(object);
444        if (oldCount < count) {
445            add(object, count - oldCount);
446        } else {
447            remove(object, oldCount - count);
448        }
449        return oldCount;
450    }
451
452    /**
453     * Returns the number of elements in this multiset.
454     *
455     * @return current size of the multiset
456     */
457    @Override
458    public int size() {
459        int totalSize = 0;
460        for (final Entry<E> entry : entrySet()) {
461            totalSize += entry.getCount();
462        }
463        return totalSize;
464    }
465
466    /**
467     * Implement a toString() method suitable for debugging.
468     *
469     * @return a debugging toString
470     */
471    @Override
472    public String toString() {
473        return entrySet().toString();
474    }
475
476    /**
477     * Returns the number of unique elements in this multiset.
478     *
479     * @return the number of unique elements
480     */
481    protected abstract int uniqueElements();
482
483    /**
484     * Returns a view of the unique elements of this multiset.
485     *
486     * @return the set of unique elements in this multiset
487     */
488    @Override
489    public Set<E> uniqueSet() {
490        if (uniqueSet == null) {
491            uniqueSet = createUniqueSet();
492        }
493        return uniqueSet;
494    }
495
496}