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