blob: a0e4383ca9e7644bdf92bc436f01881e3818a0fe [file] [log] [blame]
/*
* Copyright (C) 2007 Google Inc.
*
* Licensed 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
*
* http://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 com.google.common.collect;
import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Multisets.setCountImpl;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.Nullable;
/**
* This class provides a skeletal implementation of the {@link Multiset}
* interface. A new multiset implementation can be created easily by extending
* this class and implementing the {@link Multiset#entrySet()} method, plus
* optionally overriding {@link #add(Object, int)} and
* {@link #remove(Object, int)} to enable modifications to the multiset.
*
* <p>The {@link #contains}, {@link #containsAll}, {@link #count}, and
* {@link #size} implementations all iterate across the set returned by
* {@link Multiset#entrySet()}, as do many methods acting on the set returned by
* {@link #elementSet()}. Override those methods for better performance.
*
* @author Kevin Bourrillion
*/
@GwtCompatible
abstract class AbstractMultiset<E> extends AbstractCollection<E>
implements Multiset<E> {
public abstract Set<Entry<E>> entrySet();
// Query Operations
@Override public int size() {
long sum = 0L;
for (Entry<E> entry : entrySet()) {
sum += entry.getCount();
}
return (int) Math.min(sum, Integer.MAX_VALUE);
}
@Override public boolean isEmpty() {
return entrySet().isEmpty();
}
@Override public boolean contains(@Nullable Object element) {
return elementSet().contains(element);
}
@Override public Iterator<E> iterator() {
return new MultisetIterator();
}
private class MultisetIterator implements Iterator<E> {
private final Iterator<Entry<E>> entryIterator;
private Entry<E> currentEntry;
/** Count of subsequent elements equal to current element */
private int laterCount;
/** Count of all elements equal to current element */
private int totalCount;
private boolean canRemove;
MultisetIterator() {
this.entryIterator = entrySet().iterator();
}
public boolean hasNext() {
return laterCount > 0 || entryIterator.hasNext();
}
public E next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (laterCount == 0) {
currentEntry = entryIterator.next();
totalCount = laterCount = currentEntry.getCount();
}
laterCount--;
canRemove = true;
return currentEntry.getElement();
}
public void remove() {
checkState(canRemove,
"no calls to next() since the last call to remove()");
if (totalCount == 1) {
entryIterator.remove();
} else {
AbstractMultiset.this.remove(currentEntry.getElement());
}
totalCount--;
canRemove = false;
}
}
public int count(Object element) {
for (Entry<E> entry : entrySet()) {
if (Objects.equal(entry.getElement(), element)) {
return entry.getCount();
}
}
return 0;
}
// Modification Operations
@Override public boolean add(@Nullable E element) {
add(element, 1);
return true;
}
public int add(E element, int occurrences) {
throw new UnsupportedOperationException();
}
@Override public boolean remove(Object element) {
return remove(element, 1) > 0;
}
public int remove(Object element, int occurrences) {
throw new UnsupportedOperationException();
}
public int setCount(E element, int count) {
return setCountImpl(this, element, count);
}
public boolean setCount(E element, int oldCount, int newCount) {
return setCountImpl(this, element, oldCount, newCount);
}
// Bulk Operations
@Override public boolean containsAll(Collection<?> elements) {
return elementSet().containsAll(elements);
}
@Override public boolean addAll(Collection<? extends E> elementsToAdd) {
if (elementsToAdd.isEmpty()) {
return false;
}
if (elementsToAdd instanceof Multiset) {
@SuppressWarnings("unchecked")
Multiset<? extends E> that = (Multiset<? extends E>) elementsToAdd;
for (Entry<? extends E> entry : that.entrySet()) {
add(entry.getElement(), entry.getCount());
}
} else {
super.addAll(elementsToAdd);
}
return true;
}
@Override public boolean removeAll(Collection<?> elementsToRemove) {
Collection<?> collection = (elementsToRemove instanceof Multiset)
? ((Multiset<?>) elementsToRemove).elementSet() : elementsToRemove;
return elementSet().removeAll(collection);
// TODO: implement retainAll similarly?
}
@Override public boolean retainAll(Collection<?> elementsToRetain) {
checkNotNull(elementsToRetain);
Iterator<Entry<E>> entries = entrySet().iterator();
boolean modified = false;
while (entries.hasNext()) {
Entry<E> entry = entries.next();
if (!elementsToRetain.contains(entry.getElement())) {
entries.remove();
modified = true;
}
}
return modified;
}
@Override public void clear() {
entrySet().clear();
}
// Views
private transient Set<E> elementSet;
public Set<E> elementSet() {
Set<E> result = elementSet;
if (result == null) {
elementSet = result = createElementSet();
}
return result;
}
/**
* Creates a new instance of this multiset's element set, which will be
* returned by {@link #elementSet()}.
*/
Set<E> createElementSet() {
return new ElementSet();
}
private class ElementSet extends AbstractSet<E> {
@Override public Iterator<E> iterator() {
final Iterator<Entry<E>> entryIterator = entrySet().iterator();
return new Iterator<E>() {
public boolean hasNext() {
return entryIterator.hasNext();
}
public E next() {
return entryIterator.next().getElement();
}
public void remove() {
entryIterator.remove();
}
};
}
@Override public int size() {
return entrySet().size();
}
}
// Object methods
/**
* {@inheritDoc}
*
* <p>This implementation returns {@code true} if {@code other} is a multiset
* of the same size and if, for each element, the two multisets have the same
* count.
*/
@Override public boolean equals(@Nullable Object object) {
if (object == this) {
return true;
}
if (object instanceof Multiset) {
Multiset<?> that = (Multiset<?>) object;
/*
* We can't simply check whether the entry sets are equal, since that
* approach fails when a TreeMultiset has a comparator that returns 0
* when passed unequal elements.
*/
if (this.size() != that.size()) {
return false;
}
for (Entry<?> entry : that.entrySet()) {
if (count(entry.getElement()) != entry.getCount()) {
return false;
}
}
return true;
}
return false;
}
/**
* {@inheritDoc}
*
* <p>This implementation returns the hash code of {@link
* Multiset#entrySet()}.
*/
@Override public int hashCode() {
return entrySet().hashCode();
}
/**
* {@inheritDoc}
*
* <p>This implementation returns the result of invoking {@code toString} on
* {@link Multiset#entrySet()}.
*/
@Override public String toString() {
return entrySet().toString();
}
}