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