blob: 3ff472a9fc2500ecacdb692f88b050c1b5ccb1da [file] [log] [blame]
/*
* Copyright (C) 2007 The Guava Authors
*
* 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.CollectPreconditions.checkNonnegative;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.primitives.Ints;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
/**
* Basic implementation of {@code Multiset<E>} backed by an instance of {@code
* AbstractObjectCountMap<E>}.
*
* <p>For serialization to work, the subclass must specify explicit {@code readObject} and {@code
* writeObject} methods.
*
* @author Kevin Bourrillion
*/
@GwtCompatible(emulated = true)
abstract class AbstractMapBasedMultiset<E> extends AbstractMultiset<E> implements Serializable {
transient ObjectCountHashMap<E> backingMap;
transient long size;
AbstractMapBasedMultiset(int distinctElements) {
init(distinctElements);
}
abstract void init(int distinctElements);
@Override
public final int count(@NullableDecl Object element) {
return backingMap.get(element);
}
// Optional Operations - Modification Operations
/**
* {@inheritDoc}
*
* @throws IllegalArgumentException if the call would result in more than {@link
* Integer#MAX_VALUE} occurrences of {@code element} in this multiset.
*/
@CanIgnoreReturnValue
@Override
public final int add(@NullableDecl E element, int occurrences) {
if (occurrences == 0) {
return count(element);
}
checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences);
int entryIndex = backingMap.indexOf(element);
if (entryIndex == -1) {
backingMap.put(element, occurrences);
size += occurrences;
return 0;
}
int oldCount = backingMap.getValue(entryIndex);
long newCount = (long) oldCount + (long) occurrences;
checkArgument(newCount <= Integer.MAX_VALUE, "too many occurrences: %s", newCount);
backingMap.setValue(entryIndex, (int) newCount);
size += occurrences;
return oldCount;
}
@CanIgnoreReturnValue
@Override
public final int remove(@NullableDecl Object element, int occurrences) {
if (occurrences == 0) {
return count(element);
}
checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences);
int entryIndex = backingMap.indexOf(element);
if (entryIndex == -1) {
return 0;
}
int oldCount = backingMap.getValue(entryIndex);
int numberRemoved;
if (oldCount > occurrences) {
numberRemoved = occurrences;
backingMap.setValue(entryIndex, oldCount - occurrences);
} else {
numberRemoved = oldCount;
backingMap.removeEntry(entryIndex);
}
size -= numberRemoved;
return oldCount;
}
@CanIgnoreReturnValue
@Override
public final int setCount(@NullableDecl E element, int count) {
checkNonnegative(count, "count");
int oldCount = (count == 0) ? backingMap.remove(element) : backingMap.put(element, count);
size += (count - oldCount);
return oldCount;
}
@Override
public final boolean setCount(@NullableDecl E element, int oldCount, int newCount) {
checkNonnegative(oldCount, "oldCount");
checkNonnegative(newCount, "newCount");
int entryIndex = backingMap.indexOf(element);
if (entryIndex == -1) {
if (oldCount != 0) {
return false;
}
if (newCount > 0) {
backingMap.put(element, newCount);
size += newCount;
}
return true;
}
int actualOldCount = backingMap.getValue(entryIndex);
if (actualOldCount != oldCount) {
return false;
}
if (newCount == 0) {
backingMap.removeEntry(entryIndex);
size -= oldCount;
} else {
backingMap.setValue(entryIndex, newCount);
size += newCount - oldCount;
}
return true;
}
@Override
public final void clear() {
backingMap.clear();
size = 0;
}
/**
* Skeleton of per-entry iterators. We could push this down and win a few bytes, but it's complex
* enough it's not especially worth it.
*/
abstract class Itr<T> implements Iterator<T> {
int entryIndex = backingMap.firstIndex();
int toRemove = -1;
int expectedModCount = backingMap.modCount;
abstract T result(int entryIndex);
private void checkForConcurrentModification() {
if (backingMap.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
@Override
public boolean hasNext() {
checkForConcurrentModification();
return entryIndex >= 0;
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
T result = result(entryIndex);
toRemove = entryIndex;
entryIndex = backingMap.nextIndex(entryIndex);
return result;
}
@Override
public void remove() {
checkForConcurrentModification();
CollectPreconditions.checkRemove(toRemove != -1);
size -= backingMap.removeEntry(toRemove);
entryIndex = backingMap.nextIndexAfterRemove(entryIndex, toRemove);
toRemove = -1;
expectedModCount = backingMap.modCount;
}
}
@Override
final Iterator<E> elementIterator() {
return new Itr<E>() {
@Override
E result(int entryIndex) {
return backingMap.getKey(entryIndex);
}
};
}
@Override
final Iterator<Entry<E>> entryIterator() {
return new Itr<Entry<E>>() {
@Override
Entry<E> result(int entryIndex) {
return backingMap.getEntry(entryIndex);
}
};
}
/** Allocation-free implementation of {@code target.addAll(this)}. */
void addTo(Multiset<? super E> target) {
checkNotNull(target);
for (int i = backingMap.firstIndex(); i >= 0; i = backingMap.nextIndex(i)) {
target.add(backingMap.getKey(i), backingMap.getValue(i));
}
}
@Override
final int distinctElements() {
return backingMap.size();
}
@Override
public final Iterator<E> iterator() {
return Multisets.iteratorImpl(this);
}
@Override
public final int size() {
return Ints.saturatedCast(size);
}
/**
* @serialData the number of distinct elements, the first element, its count, the second element,
* its count, and so on
*/
@GwtIncompatible // java.io.ObjectOutputStream
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
Serialization.writeMultiset(this, stream);
}
@GwtIncompatible // java.io.ObjectInputStream
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
int distinctElements = Serialization.readCount(stream);
init(ObjectCountHashMap.DEFAULT_SIZE);
Serialization.populateMultiset(this, stream, distinctElements);
}
@GwtIncompatible // Not needed in emulated source.
private static final long serialVersionUID = 0;
}