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 *      https://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    /**
262     * Always throws {@link UnsupportedOperationException}.
263     *
264     * @param object Ignored.
265     * @param occurrences Ignored.
266     * @throws UnsupportedOperationException Always thrown.
267     */
268    @Override
269    public int add(final E object, final int occurrences) {
270        throw new UnsupportedOperationException();
271    }
272
273    /**
274     * Clears the multiset removing all elements from the entrySet.
275     */
276    @Override
277    public void clear() {
278        final Iterator<Entry<E>> it = entrySet().iterator();
279        while (it.hasNext()) {
280            it.next();
281            it.remove();
282        }
283    }
284
285    /**
286     * Determines if the multiset contains the given element.
287     *
288     * @param object The object to search for
289     * @return true if the multiset contains the given element
290     */
291    @Override
292    public boolean contains(final Object object) {
293        return getCount(object) > 0;
294    }
295
296    /**
297     * Create a new view for the set of entries in this multiset.
298     *
299     * @return A view of the set of entries
300     */
301    protected Set<Entry<E>> createEntrySet() {
302        return new EntrySet<>(this);
303    }
304
305    /**
306     * Creates an entry set iterator.
307     * Subclasses can override this to return iterators with different properties.
308     *
309     * @return The entrySet iterator
310     */
311    protected abstract Iterator<Entry<E>> createEntrySetIterator();
312
313    /**
314     * Create a new view for the set of unique elements in this multiset.
315     *
316     * @return A view of the set of unique elements
317     */
318    protected Set<E> createUniqueSet() {
319        return new UniqueSet<>(this);
320    }
321
322    /**
323     * Creates a unique set iterator.
324     * Subclasses can override this to return iterators with different properties.
325     *
326     * @return The uniqueSet iterator
327     */
328    protected Iterator<E> createUniqueSetIterator() {
329        final Transformer<Entry<E>, E> transformer = Entry::getElement;
330        return IteratorUtils.transformedIterator(entrySet().iterator(), transformer);
331    }
332
333    /**
334     * Reads the multiset in using a custom routine.
335     *
336     * @param in The input stream
337     * @throws IOException any of the usual I/O related exceptions
338     * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded
339     * @throws ClassCastException if the stream does not contain the correct objects
340     */
341    protected void doReadObject(final ObjectInputStream in)
342            throws IOException, ClassNotFoundException {
343        final int entrySize = in.readInt();
344        for (int i = 0; i < entrySize; i++) {
345            @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
346            final E obj = (E) in.readObject();
347            final int count = in.readInt();
348            setCount(obj, count);
349        }
350    }
351
352    /**
353     * Writes the multiset out using a custom routine.
354     *
355     * @param out The output stream
356     * @throws IOException any of the usual I/O related exceptions
357     */
358    protected void doWriteObject(final ObjectOutputStream out) throws IOException {
359        out.writeInt(entrySet().size());
360        for (final Entry<E> entry : entrySet()) {
361            out.writeObject(entry.getElement());
362            out.writeInt(entry.getCount());
363        }
364    }
365
366    /**
367     * Returns an unmodifiable view of the entries of this multiset.
368     *
369     * @return The set of entries in this multiset
370     */
371    @Override
372    public Set<Entry<E>> entrySet() {
373        if (entrySet == null) {
374            entrySet = createEntrySet();
375        }
376        return entrySet;
377    }
378
379    @Override
380    public boolean equals(final Object object) {
381        if (object == this) {
382            return true;
383        }
384        if (!(object instanceof MultiSet)) {
385            return false;
386        }
387        final MultiSet<?> other = (MultiSet<?>) object;
388        if (other.size() != size()) {
389            return false;
390        }
391        for (final Entry<E> entry : entrySet()) {
392            if (other.getCount(entry.getElement()) != getCount(entry.getElement())) {
393                return false;
394            }
395        }
396        return true;
397    }
398
399    /**
400     * Gets the number of occurrence of the given element in this multiset by
401     * iterating over its entrySet.
402     *
403     * @param object The object to search for
404     * @return The number of occurrences of the object, zero if not found
405     */
406    @Override
407    public int getCount(final Object object) {
408        for (final Entry<E> entry : entrySet()) {
409            final E element = entry.getElement();
410            if (Objects.equals(element, object)) {
411                return entry.getCount();
412            }
413        }
414        return 0;
415    }
416
417    @Override
418    public int hashCode() {
419        return entrySet().hashCode();
420    }
421
422    /**
423     * Gets an iterator over the multiset elements. Elements present in the
424     * MultiSet more than once will be returned repeatedly.
425     *
426     * @return The iterator
427     */
428    @Override
429    public Iterator<E> iterator() {
430        return new MultiSetIterator<>(this);
431    }
432
433    @Override
434    public boolean remove(final Object object) {
435        return remove(object, 1) != 0;
436    }
437
438    /**
439     * Always throws {@link UnsupportedOperationException}.
440     *
441     * @param object Ignored.
442     * @param occurrences Ignored.
443     * @throws UnsupportedOperationException Always thrown.
444     */
445    @Override
446    public int remove(final Object object, final int occurrences) {
447        throw new UnsupportedOperationException();
448    }
449
450    @Override
451    public boolean removeAll(final Collection<?> coll) {
452        boolean result = false;
453        for (final Object obj : coll) {
454            final boolean changed = remove(obj, getCount(obj)) != 0;
455            result = result || changed;
456        }
457        return result;
458    }
459
460    @Override
461    public int setCount(final E object, final int count) {
462        if (count < 0) {
463            throw new IllegalArgumentException("Count must not be negative.");
464        }
465
466        final int oldCount = getCount(object);
467        if (oldCount < count) {
468            add(object, count - oldCount);
469        } else {
470            remove(object, oldCount - count);
471        }
472        return oldCount;
473    }
474
475    /**
476     * Returns the number of elements in this multiset.
477     *
478     * @return current size of the multiset, or {@code Integer.MAX_VALUE} if the total exceeds it.
479     */
480    @Override
481    public int size() {
482        // TODO reuse IterableUtils.sumToInt(Iterable, ToIntFunction)
483        int size = 0;
484        try {
485            for (final Entry<E> entry : entrySet()) {
486                size = Math.addExact(size, entry.getCount());
487            }
488        } catch (final ArithmeticException e) {
489            size = Integer.MAX_VALUE;
490        }
491        return size;
492    }
493
494    /**
495     * Implement a toString() method suitable for debugging.
496     *
497     * @return A debugging toString
498     */
499    @Override
500    public String toString() {
501        return entrySet().toString();
502    }
503
504    /**
505     * Returns the number of unique elements in this multiset.
506     *
507     * @return The number of unique elements
508     */
509    protected abstract int uniqueElements();
510
511    /**
512     * Returns a view of the unique elements of this multiset.
513     *
514     * @return The set of unique elements in this multiset
515     */
516    @Override
517    public Set<E> uniqueSet() {
518        if (uniqueSet == null) {
519            uniqueSet = createUniqueSet();
520        }
521        return uniqueSet;
522    }
523
524}