summaryrefslogtreecommitdiffstats
path: root/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java
diff options
context:
space:
mode:
authorYohann Roussel <yroussel@google.com>2014-03-19 16:25:37 +0100
committerYohann Roussel <yroussel@google.com>2014-03-20 15:13:33 +0100
commit4eceb95409e844fdc33c9c706e1dc307bfd40303 (patch)
treeee9f4f3fc79f757c79081c336bce4f1782c6ccd8 /guava/src/com/google/common/collect/AbstractMapBasedMultiset.java
parent3d2402901b1a6462e2cf47a6fd09711f327961c3 (diff)
downloadtoolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.zip
toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.gz
toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.bz2
Initial Jack import.
Change-Id: I953cf0a520195a7187d791b2885848ad0d5a9b43
Diffstat (limited to 'guava/src/com/google/common/collect/AbstractMapBasedMultiset.java')
-rw-r--r--guava/src/com/google/common/collect/AbstractMapBasedMultiset.java325
1 files changed, 325 insertions, 0 deletions
diff --git a/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java b/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java
new file mode 100644
index 0000000..83d1953
--- /dev/null
+++ b/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java
@@ -0,0 +1,325 @@
+/*
+ * 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.base.Preconditions.checkState;
+import static com.google.common.collect.Multisets.checkNonnegative;
+
+import com.google.common.annotations.GwtCompatible;
+import com.google.common.annotations.GwtIncompatible;
+import com.google.common.primitives.Ints;
+
+import java.io.InvalidObjectException;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * Basic implementation of {@code Multiset<E>} backed by an instance of {@code
+ * Map<E, Count>}.
+ *
+ * <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 {
+
+ private transient Map<E, Count> backingMap;
+
+ /*
+ * Cache the size for efficiency. Using a long lets us avoid the need for
+ * overflow checking and ensures that size() will function correctly even if
+ * the multiset had once been larger than Integer.MAX_VALUE.
+ */
+ private transient long size;
+
+ /** Standard constructor. */
+ protected AbstractMapBasedMultiset(Map<E, Count> backingMap) {
+ this.backingMap = checkNotNull(backingMap);
+ this.size = super.size();
+ }
+
+ Map<E, Count> backingMap() {
+ return backingMap;
+ }
+
+ /** Used during deserialization only. The backing map must be empty. */
+ void setBackingMap(Map<E, Count> backingMap) {
+ this.backingMap = backingMap;
+ }
+
+ // Required Implementations
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Invoking {@link Multiset.Entry#getCount} on an entry in the returned
+ * set always returns the current count of that element in the multiset, as
+ * opposed to the count at the time the entry was retrieved.
+ */
+ @Override
+ public Set<Multiset.Entry<E>> entrySet() {
+ return super.entrySet();
+ }
+
+ @Override
+ Iterator<Entry<E>> entryIterator() {
+ final Iterator<Map.Entry<E, Count>> backingEntries =
+ backingMap.entrySet().iterator();
+ return new Iterator<Multiset.Entry<E>>() {
+ Map.Entry<E, Count> toRemove;
+
+ @Override
+ public boolean hasNext() {
+ return backingEntries.hasNext();
+ }
+
+ @Override
+ public Multiset.Entry<E> next() {
+ final Map.Entry<E, Count> mapEntry = backingEntries.next();
+ toRemove = mapEntry;
+ return new Multisets.AbstractEntry<E>() {
+ @Override
+ public E getElement() {
+ return mapEntry.getKey();
+ }
+ @Override
+ public int getCount() {
+ int count = mapEntry.getValue().get();
+ if (count == 0) {
+ Count frequency = backingMap.get(getElement());
+ if (frequency != null) {
+ count = frequency.get();
+ }
+ }
+ return count;
+ }
+ };
+ }
+
+ @Override
+ public void remove() {
+ Iterators.checkRemove(toRemove != null);
+ size -= toRemove.getValue().getAndSet(0);
+ backingEntries.remove();
+ toRemove = null;
+ }
+ };
+ }
+
+ @Override
+ public void clear() {
+ for (Count frequency : backingMap.values()) {
+ frequency.set(0);
+ }
+ backingMap.clear();
+ size = 0L;
+ }
+
+ @Override
+ int distinctElements() {
+ return backingMap.size();
+ }
+
+ // Optimizations - Query Operations
+
+ @Override public int size() {
+ return Ints.saturatedCast(size);
+ }
+
+ @Override public Iterator<E> iterator() {
+ return new MapBasedMultisetIterator();
+ }
+
+ /*
+ * Not subclassing AbstractMultiset$MultisetIterator because next() needs to
+ * retrieve the Map.Entry<E, AtomicInteger> entry, which can then be used for
+ * a more efficient remove() call.
+ */
+ private class MapBasedMultisetIterator implements Iterator<E> {
+ final Iterator<Map.Entry<E, Count>> entryIterator;
+ Map.Entry<E, Count> currentEntry;
+ int occurrencesLeft;
+ boolean canRemove;
+
+ MapBasedMultisetIterator() {
+ this.entryIterator = backingMap.entrySet().iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return occurrencesLeft > 0 || entryIterator.hasNext();
+ }
+
+ @Override
+ public E next() {
+ if (occurrencesLeft == 0) {
+ currentEntry = entryIterator.next();
+ occurrencesLeft = currentEntry.getValue().get();
+ }
+ occurrencesLeft--;
+ canRemove = true;
+ return currentEntry.getKey();
+ }
+
+ @Override
+ public void remove() {
+ checkState(canRemove,
+ "no calls to next() since the last call to remove()");
+ int frequency = currentEntry.getValue().get();
+ if (frequency <= 0) {
+ throw new ConcurrentModificationException();
+ }
+ if (currentEntry.getValue().addAndGet(-1) == 0) {
+ entryIterator.remove();
+ }
+ size--;
+ canRemove = false;
+ }
+ }
+
+ @Override public int count(@Nullable Object element) {
+ try {
+ Count frequency = backingMap.get(element);
+ return (frequency == null) ? 0 : frequency.get();
+ } catch (NullPointerException e) {
+ return 0;
+ } catch (ClassCastException e) {
+ return 0;
+ }
+ }
+
+ // 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.
+ */
+ @Override public int add(@Nullable E element, int occurrences) {
+ if (occurrences == 0) {
+ return count(element);
+ }
+ checkArgument(
+ occurrences > 0, "occurrences cannot be negative: %s", occurrences);
+ Count frequency = backingMap.get(element);
+ int oldCount;
+ if (frequency == null) {
+ oldCount = 0;
+ backingMap.put(element, new Count(occurrences));
+ } else {
+ oldCount = frequency.get();
+ long newCount = (long) oldCount + (long) occurrences;
+ checkArgument(newCount <= Integer.MAX_VALUE,
+ "too many occurrences: %s", newCount);
+ frequency.getAndAdd(occurrences);
+ }
+ size += occurrences;
+ return oldCount;
+ }
+
+ @Override public int remove(@Nullable Object element, int occurrences) {
+ if (occurrences == 0) {
+ return count(element);
+ }
+ checkArgument(
+ occurrences > 0, "occurrences cannot be negative: %s", occurrences);
+ Count frequency = backingMap.get(element);
+ if (frequency == null) {
+ return 0;
+ }
+
+ int oldCount = frequency.get();
+
+ int numberRemoved;
+ if (oldCount > occurrences) {
+ numberRemoved = occurrences;
+ } else {
+ numberRemoved = oldCount;
+ backingMap.remove(element);
+ }
+
+ frequency.addAndGet(-numberRemoved);
+ size -= numberRemoved;
+ return oldCount;
+ }
+
+ // Roughly a 33% performance improvement over AbstractMultiset.setCount().
+ @Override public int setCount(@Nullable E element, int count) {
+ checkNonnegative(count, "count");
+
+ Count existingCounter;
+ int oldCount;
+ if (count == 0) {
+ existingCounter = backingMap.remove(element);
+ oldCount = getAndSet(existingCounter, count);
+ } else {
+ existingCounter = backingMap.get(element);
+ oldCount = getAndSet(existingCounter, count);
+
+ if (existingCounter == null) {
+ backingMap.put(element, new Count(count));
+ }
+ }
+
+ size += (count - oldCount);
+ return oldCount;
+ }
+
+ private static int getAndSet(Count i, int count) {
+ if (i == null) {
+ return 0;
+ }
+
+ return i.getAndSet(count);
+ }
+
+ // Views
+
+ @Override Set<E> createElementSet() {
+ return new MapBasedElementSet();
+ }
+
+ class MapBasedElementSet extends Multisets.ElementSet<E> {
+ @Override
+ Multiset<E> multiset() {
+ return AbstractMapBasedMultiset.this;
+ }
+ }
+
+ // Don't allow default serialization.
+ @GwtIncompatible("java.io.ObjectStreamException")
+ @SuppressWarnings("unused") // actually used during deserialization
+ private void readObjectNoData() throws ObjectStreamException {
+ throw new InvalidObjectException("Stream data required");
+ }
+
+ @GwtIncompatible("not needed in emulated source.")
+ private static final long serialVersionUID = -2250766705698539974L;
+}