| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.collections4.multiset; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.util.AbstractCollection; |
| import java.util.AbstractSet; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| import org.apache.commons.collections4.IteratorUtils; |
| import org.apache.commons.collections4.MultiSet; |
| import org.apache.commons.collections4.Transformer; |
| |
| /** |
| * Abstract implementation of the {@link MultiSet} interface to simplify the |
| * creation of subclass implementations. |
| * |
| * @param <E> The type held in the multiset |
| * @since 4.1 |
| */ |
| public abstract class AbstractMultiSet<E> extends AbstractCollection<E> implements MultiSet<E> { |
| |
| /** |
| * Inner class AbstractEntry. |
| * |
| * @param <E> The element type. |
| */ |
| protected abstract static class AbstractEntry<E> implements Entry<E> { |
| |
| /** |
| * Constructs a new instance. |
| */ |
| public AbstractEntry() { |
| // empty |
| } |
| |
| @Override |
| public boolean equals(final Object object) { |
| if (object instanceof Entry) { |
| final Entry<?> other = (Entry<?>) object; |
| final E element = getElement(); |
| final Object otherElement = other.getElement(); |
| |
| return this.getCount() == other.getCount() && |
| Objects.equals(element, otherElement); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| final E element = getElement(); |
| return (element == null ? 0 : element.hashCode()) ^ getCount(); |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("%s:%d", getElement(), getCount()); |
| } |
| } |
| |
| /** |
| * Inner class EntrySet. |
| * |
| * @param <E> The element type. |
| */ |
| protected static class EntrySet<E> extends AbstractSet<Entry<E>> { |
| |
| private final AbstractMultiSet<E> parent; |
| |
| /** |
| * Constructs a new view of the MultiSet. |
| * |
| * @param parent the parent MultiSet |
| */ |
| protected EntrySet(final AbstractMultiSet<E> parent) { |
| this.parent = parent; |
| } |
| |
| @Override |
| public boolean contains(final Object obj) { |
| if (!(obj instanceof Entry<?>)) { |
| return false; |
| } |
| final Entry<?> entry = (Entry<?>) obj; |
| final Object element = entry.getElement(); |
| return parent.getCount(element) == entry.getCount(); |
| } |
| |
| @Override |
| public Iterator<Entry<E>> iterator() { |
| return parent.createEntrySetIterator(); |
| } |
| |
| @Override |
| public boolean remove(final Object obj) { |
| if (!(obj instanceof Entry<?>)) { |
| return false; |
| } |
| final Entry<?> entry = (Entry<?>) obj; |
| final Object element = entry.getElement(); |
| if (parent.contains(element)) { |
| final int count = parent.getCount(element); |
| if (entry.getCount() == count) { |
| parent.remove(element, count); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public int size() { |
| return parent.uniqueElements(); |
| } |
| } |
| |
| /** |
| * Inner class iterator for the MultiSet. |
| */ |
| private static final class MultiSetIterator<E> implements Iterator<E> { |
| private final AbstractMultiSet<E> parent; |
| private final Iterator<Entry<E>> entryIterator; |
| private Entry<E> current; |
| private int itemCount; |
| private boolean canRemove; |
| |
| /** |
| * Constructs a new instance. |
| * |
| * @param parent the parent multiset |
| */ |
| MultiSetIterator(final AbstractMultiSet<E> parent) { |
| this.parent = parent; |
| this.entryIterator = parent.entrySet().iterator(); |
| this.current = null; |
| this.canRemove = false; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean hasNext() { |
| return itemCount > 0 || entryIterator.hasNext(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public E next() { |
| if (itemCount == 0) { |
| current = entryIterator.next(); |
| itemCount = current.getCount(); |
| } |
| canRemove = true; |
| itemCount--; |
| return current.getElement(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void remove() { |
| if (!canRemove) { |
| throw new IllegalStateException(); |
| } |
| final int count = current.getCount(); |
| if (count > 1) { |
| parent.remove(current.getElement()); |
| } else { |
| entryIterator.remove(); |
| } |
| canRemove = false; |
| } |
| } |
| |
| /** |
| * Inner class UniqueSet. |
| * |
| * @param <E> The element type. |
| */ |
| protected static class UniqueSet<E> extends AbstractSet<E> { |
| |
| /** The parent multiset */ |
| protected final AbstractMultiSet<E> parent; |
| |
| /** |
| * Constructs a new unique element view of the MultiSet. |
| * |
| * @param parent the parent MultiSet |
| */ |
| protected UniqueSet(final AbstractMultiSet<E> parent) { |
| this.parent = parent; |
| } |
| |
| @Override |
| public void clear() { |
| parent.clear(); |
| } |
| |
| @Override |
| public boolean contains(final Object key) { |
| return parent.contains(key); |
| } |
| |
| @Override |
| public boolean containsAll(final Collection<?> coll) { |
| return parent.containsAll(coll); |
| } |
| |
| @Override |
| public Iterator<E> iterator() { |
| return parent.createUniqueSetIterator(); |
| } |
| |
| @Override |
| public boolean remove(final Object key) { |
| return parent.remove(key, parent.getCount(key)) != 0; |
| } |
| |
| @Override |
| public int size() { |
| return parent.uniqueElements(); |
| } |
| } |
| |
| /** View of the elements */ |
| private transient Set<E> uniqueSet; |
| |
| /** View of the entries */ |
| private transient Set<Entry<E>> entrySet; |
| |
| /** |
| * Constructs a new instance subclasses. |
| */ |
| protected AbstractMultiSet() { |
| } |
| |
| @Override |
| public boolean add(final E object) { |
| add(object, 1); |
| return true; |
| } |
| |
| /** |
| * Always throws {@link UnsupportedOperationException}. |
| * |
| * @param object Ignored. |
| * @param occurrences Ignored. |
| * @throws UnsupportedOperationException Always thrown. |
| */ |
| @Override |
| public int add(final E object, final int occurrences) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Clears the multiset removing all elements from the entrySet. |
| */ |
| @Override |
| public void clear() { |
| final Iterator<Entry<E>> it = entrySet().iterator(); |
| while (it.hasNext()) { |
| it.next(); |
| it.remove(); |
| } |
| } |
| |
| /** |
| * Determines if the multiset contains the given element. |
| * |
| * @param object the object to search for |
| * @return true if the multiset contains the given element |
| */ |
| @Override |
| public boolean contains(final Object object) { |
| return getCount(object) > 0; |
| } |
| |
| /** |
| * Create a new view for the set of entries in this multiset. |
| * |
| * @return a view of the set of entries |
| */ |
| protected Set<Entry<E>> createEntrySet() { |
| return new EntrySet<>(this); |
| } |
| |
| /** |
| * Creates an entry set iterator. |
| * Subclasses can override this to return iterators with different properties. |
| * |
| * @return the entrySet iterator |
| */ |
| protected abstract Iterator<Entry<E>> createEntrySetIterator(); |
| |
| /** |
| * Create a new view for the set of unique elements in this multiset. |
| * |
| * @return a view of the set of unique elements |
| */ |
| protected Set<E> createUniqueSet() { |
| return new UniqueSet<>(this); |
| } |
| |
| /** |
| * Creates a unique set iterator. |
| * Subclasses can override this to return iterators with different properties. |
| * |
| * @return the uniqueSet iterator |
| */ |
| protected Iterator<E> createUniqueSetIterator() { |
| final Transformer<Entry<E>, E> transformer = Entry::getElement; |
| return IteratorUtils.transformedIterator(entrySet().iterator(), transformer); |
| } |
| |
| /** |
| * Reads the multiset in using a custom routine. |
| * |
| * @param in the input stream |
| * @throws IOException any of the usual I/O related exceptions |
| * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded |
| * @throws ClassCastException if the stream does not contain the correct objects |
| */ |
| protected void doReadObject(final ObjectInputStream in) |
| throws IOException, ClassNotFoundException { |
| final int entrySize = in.readInt(); |
| for (int i = 0; i < entrySize; i++) { |
| @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect |
| final E obj = (E) in.readObject(); |
| final int count = in.readInt(); |
| setCount(obj, count); |
| } |
| } |
| |
| /** |
| * Writes the multiset out using a custom routine. |
| * |
| * @param out the output stream |
| * @throws IOException any of the usual I/O related exceptions |
| */ |
| protected void doWriteObject(final ObjectOutputStream out) throws IOException { |
| out.writeInt(entrySet().size()); |
| for (final Entry<E> entry : entrySet()) { |
| out.writeObject(entry.getElement()); |
| out.writeInt(entry.getCount()); |
| } |
| } |
| |
| /** |
| * Returns an unmodifiable view of the entries of this multiset. |
| * |
| * @return the set of entries in this multiset |
| */ |
| @Override |
| public Set<Entry<E>> entrySet() { |
| if (entrySet == null) { |
| entrySet = createEntrySet(); |
| } |
| return entrySet; |
| } |
| |
| @Override |
| public boolean equals(final Object object) { |
| if (object == this) { |
| return true; |
| } |
| if (!(object instanceof MultiSet)) { |
| return false; |
| } |
| final MultiSet<?> other = (MultiSet<?>) object; |
| if (other.size() != size()) { |
| return false; |
| } |
| for (final Entry<E> entry : entrySet()) { |
| if (other.getCount(entry.getElement()) != getCount(entry.getElement())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Gets the number of occurrence of the given element in this multiset by |
| * iterating over its entrySet. |
| * |
| * @param object the object to search for |
| * @return the number of occurrences of the object, zero if not found |
| */ |
| @Override |
| public int getCount(final Object object) { |
| for (final Entry<E> entry : entrySet()) { |
| final E element = entry.getElement(); |
| if (Objects.equals(element, object)) { |
| return entry.getCount(); |
| } |
| } |
| return 0; |
| } |
| |
| @Override |
| public int hashCode() { |
| return entrySet().hashCode(); |
| } |
| |
| /** |
| * Gets an iterator over the multiset elements. Elements present in the |
| * MultiSet more than once will be returned repeatedly. |
| * |
| * @return the iterator |
| */ |
| @Override |
| public Iterator<E> iterator() { |
| return new MultiSetIterator<>(this); |
| } |
| |
| @Override |
| public boolean remove(final Object object) { |
| return remove(object, 1) != 0; |
| } |
| |
| /** |
| * Always throws {@link UnsupportedOperationException}. |
| * |
| * @param object Ignored. |
| * @param occurrences Ignored. |
| * @throws UnsupportedOperationException Always thrown. |
| */ |
| @Override |
| public int remove(final Object object, final int occurrences) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean removeAll(final Collection<?> coll) { |
| boolean result = false; |
| for (final Object obj : coll) { |
| final boolean changed = remove(obj, getCount(obj)) != 0; |
| result = result || changed; |
| } |
| return result; |
| } |
| |
| @Override |
| public int setCount(final E object, final int count) { |
| if (count < 0) { |
| throw new IllegalArgumentException("Count must not be negative."); |
| } |
| |
| final int oldCount = getCount(object); |
| if (oldCount < count) { |
| add(object, count - oldCount); |
| } else { |
| remove(object, oldCount - count); |
| } |
| return oldCount; |
| } |
| |
| /** |
| * Returns the number of elements in this multiset. |
| * |
| * @return current size of the multiset, or {@code Integer.MAX_VALUE} |
| * if the total exceeds it |
| */ |
| @Override |
| public int size() { |
| long totalSize = 0; |
| for (final Entry<E> entry : entrySet()) { |
| totalSize += entry.getCount(); |
| } |
| return (int) Math.min(totalSize, Integer.MAX_VALUE); |
| } |
| |
| /** |
| * Implement a toString() method suitable for debugging. |
| * |
| * @return a debugging toString |
| */ |
| @Override |
| public String toString() { |
| return entrySet().toString(); |
| } |
| |
| /** |
| * Returns the number of unique elements in this multiset. |
| * |
| * @return the number of unique elements |
| */ |
| protected abstract int uniqueElements(); |
| |
| /** |
| * Returns a view of the unique elements of this multiset. |
| * |
| * @return the set of unique elements in this multiset |
| */ |
| @Override |
| public Set<E> uniqueSet() { |
| if (uniqueSet == null) { |
| uniqueSet = createUniqueSet(); |
| } |
| return uniqueSet; |
| } |
| |
| } |