Coverage Summary for Class: AbstractMapBasedMultiset (com.google.common.collect)

Class Method, % Line, %
AbstractMapBasedMultiset 41.2% (7/17) 28.4% (19/67)
AbstractMapBasedMultiset$1 0% (0/4) 0% (0/9)
AbstractMapBasedMultiset$2 75% (3/4) 50% (5/10)
AbstractMapBasedMultiset$2$1 100% (3/3) 62.5% (5/8)
AbstractMapBasedMultiset$MapBasedMultisetIterator 0% (0/4) 0% (0/17)
Total 40.6% (13/32) 26.1% (29/111)


1 /* 2  * Copyright (C) 2007 The Guava Authors 3  * 4  * Licensed under the Apache License, Version 2.0 (the "License"); 5  * you may not use this file except in compliance with the License. 6  * You may obtain a copy of the License at 7  * 8  * http://www.apache.org/licenses/LICENSE-2.0 9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16  17 package com.google.common.collect; 18  19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.base.Preconditions.checkState; 22 import static com.google.common.collect.CollectPreconditions.checkNonnegative; 23 import static com.google.common.collect.CollectPreconditions.checkRemove; 24 import static java.util.Objects.requireNonNull; 25  26 import com.google.common.annotations.GwtCompatible; 27 import com.google.common.annotations.GwtIncompatible; 28 import com.google.common.primitives.Ints; 29 import com.google.errorprone.annotations.CanIgnoreReturnValue; 30 import java.io.InvalidObjectException; 31 import java.io.ObjectStreamException; 32 import java.io.Serializable; 33 import java.util.ConcurrentModificationException; 34 import java.util.Iterator; 35 import java.util.Map; 36 import java.util.Set; 37 import java.util.function.ObjIntConsumer; 38 import javax.annotation.CheckForNull; 39 import org.checkerframework.checker.nullness.qual.Nullable; 40  41 /** 42  * Basic implementation of {@code Multiset<E>} backed by an instance of {@code Map<E, Count>}. 43  * 44  * <p>For serialization to work, the subclass must specify explicit {@code readObject} and {@code 45  * writeObject} methods. 46  * 47  * @author Kevin Bourrillion 48  */ 49 @GwtCompatible(emulated = true) 50 @ElementTypesAreNonnullByDefault 51 abstract class AbstractMapBasedMultiset<E extends @Nullable Object> extends AbstractMultiset<E> 52  implements Serializable { 53  // TODO(lowasser): consider overhauling this back to Map<E, Integer> 54  private transient Map<E, Count> backingMap; 55  56  /* 57  * Cache the size for efficiency. Using a long lets us avoid the need for 58  * overflow checking and ensures that size() will function correctly even if 59  * the multiset had once been larger than Integer.MAX_VALUE. 60  */ 61  private transient long size; 62  63  /** Standard constructor. */ 64  protected AbstractMapBasedMultiset(Map<E, Count> backingMap) { 65  checkArgument(backingMap.isEmpty()); 66  this.backingMap = backingMap; 67  } 68  69  /** Used during deserialization only. The backing map must be empty. */ 70  void setBackingMap(Map<E, Count> backingMap) { 71  this.backingMap = backingMap; 72  } 73  74  // Required Implementations 75  76  /** 77  * {@inheritDoc} 78  * 79  * <p>Invoking {@link Multiset.Entry#getCount} on an entry in the returned set always returns the 80  * current count of that element in the multiset, as opposed to the count at the time the entry 81  * was retrieved. 82  */ 83  @Override 84  public Set<Multiset.Entry<E>> entrySet() { 85  return super.entrySet(); 86  } 87  88  @Override 89  Iterator<E> elementIterator() { 90  final Iterator<Map.Entry<E, Count>> backingEntries = backingMap.entrySet().iterator(); 91  return new Iterator<E>() { 92  @CheckForNull Map.Entry<E, Count> toRemove; 93  94  @Override 95  public boolean hasNext() { 96  return backingEntries.hasNext(); 97  } 98  99  @Override 100  @ParametricNullness 101  public E next() { 102  final Map.Entry<E, Count> mapEntry = backingEntries.next(); 103  toRemove = mapEntry; 104  return mapEntry.getKey(); 105  } 106  107  @Override 108  public void remove() { 109  checkState(toRemove != null, "no calls to next() since the last call to remove()"); 110  size -= toRemove.getValue().getAndSet(0); 111  backingEntries.remove(); 112  toRemove = null; 113  } 114  }; 115  } 116  117  @Override 118  Iterator<Entry<E>> entryIterator() { 119  final Iterator<Map.Entry<E, Count>> backingEntries = backingMap.entrySet().iterator(); 120  return new Iterator<Multiset.Entry<E>>() { 121  @CheckForNull Map.Entry<E, Count> toRemove; 122  123  @Override 124  public boolean hasNext() { 125  return backingEntries.hasNext(); 126  } 127  128  @Override 129  public Multiset.Entry<E> next() { 130  final Map.Entry<E, Count> mapEntry = backingEntries.next(); 131  toRemove = mapEntry; 132  return new Multisets.AbstractEntry<E>() { 133  @Override 134  @ParametricNullness 135  public E getElement() { 136  return mapEntry.getKey(); 137  } 138  139  @Override 140  public int getCount() { 141  Count count = mapEntry.getValue(); 142  if (count == null || count.get() == 0) { 143  Count frequency = backingMap.get(getElement()); 144  if (frequency != null) { 145  return frequency.get(); 146  } 147  } 148  return (count == null) ? 0 : count.get(); 149  } 150  }; 151  } 152  153  @Override 154  public void remove() { 155  checkState(toRemove != null, "no calls to next() since the last call to remove()"); 156  size -= toRemove.getValue().getAndSet(0); 157  backingEntries.remove(); 158  toRemove = null; 159  } 160  }; 161  } 162  163  @Override 164  public void forEachEntry(ObjIntConsumer<? super E> action) { 165  checkNotNull(action); 166  backingMap.forEach((element, count) -> action.accept(element, count.get())); 167  } 168  169  @Override 170  public void clear() { 171  for (Count frequency : backingMap.values()) { 172  frequency.set(0); 173  } 174  backingMap.clear(); 175  size = 0L; 176  } 177  178  @Override 179  int distinctElements() { 180  return backingMap.size(); 181  } 182  183  // Optimizations - Query Operations 184  185  @Override 186  public int size() { 187  return Ints.saturatedCast(size); 188  } 189  190  @Override 191  public Iterator<E> iterator() { 192  return new MapBasedMultisetIterator(); 193  } 194  195  /* 196  * Not subclassing AbstractMultiset$MultisetIterator because next() needs to 197  * retrieve the Map.Entry<E, Count> entry, which can then be used for 198  * a more efficient remove() call. 199  */ 200  private class MapBasedMultisetIterator implements Iterator<E> { 201  final Iterator<Map.Entry<E, Count>> entryIterator; 202  @CheckForNull Map.Entry<E, Count> currentEntry; 203  int occurrencesLeft; 204  boolean canRemove; 205  206  MapBasedMultisetIterator() { 207  this.entryIterator = backingMap.entrySet().iterator(); 208  } 209  210  @Override 211  public boolean hasNext() { 212  return occurrencesLeft > 0 || entryIterator.hasNext(); 213  } 214  215  @Override 216  @ParametricNullness 217  public E next() { 218  if (occurrencesLeft == 0) { 219  currentEntry = entryIterator.next(); 220  occurrencesLeft = currentEntry.getValue().get(); 221  } 222  occurrencesLeft--; 223  canRemove = true; 224  /* 225  * requireNonNull is safe because occurrencesLeft starts at 0, forcing us to initialize 226  * currentEntry above. After that, we never clear it. 227  */ 228  return requireNonNull(currentEntry).getKey(); 229  } 230  231  @Override 232  public void remove() { 233  checkRemove(canRemove); 234  /* 235  * requireNonNull is safe because canRemove is set to true only after we initialize 236  * currentEntry (which we never subsequently clear). 237  */ 238  int frequency = requireNonNull(currentEntry).getValue().get(); 239  if (frequency <= 0) { 240  throw new ConcurrentModificationException(); 241  } 242  if (currentEntry.getValue().addAndGet(-1) == 0) { 243  entryIterator.remove(); 244  } 245  size--; 246  canRemove = false; 247  } 248  } 249  250  @Override 251  public int count(@CheckForNull Object element) { 252  Count frequency = Maps.safeGet(backingMap, element); 253  return (frequency == null) ? 0 : frequency.get(); 254  } 255  256  // Optional Operations - Modification Operations 257  258  /** 259  * {@inheritDoc} 260  * 261  * @throws IllegalArgumentException if the call would result in more than {@link 262  * Integer#MAX_VALUE} occurrences of {@code element} in this multiset. 263  */ 264  @CanIgnoreReturnValue 265  @Override 266  public int add(@ParametricNullness E element, int occurrences) { 267  if (occurrences == 0) { 268  return count(element); 269  } 270  checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences); 271  Count frequency = backingMap.get(element); 272  int oldCount; 273  if (frequency == null) { 274  oldCount = 0; 275  backingMap.put(element, new Count(occurrences)); 276  } else { 277  oldCount = frequency.get(); 278  long newCount = (long) oldCount + (long) occurrences; 279  checkArgument(newCount <= Integer.MAX_VALUE, "too many occurrences: %s", newCount); 280  frequency.add(occurrences); 281  } 282  size += occurrences; 283  return oldCount; 284  } 285  286  @CanIgnoreReturnValue 287  @Override 288  public int remove(@CheckForNull Object element, int occurrences) { 289  if (occurrences == 0) { 290  return count(element); 291  } 292  checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences); 293  Count frequency = backingMap.get(element); 294  if (frequency == null) { 295  return 0; 296  } 297  298  int oldCount = frequency.get(); 299  300  int numberRemoved; 301  if (oldCount > occurrences) { 302  numberRemoved = occurrences; 303  } else { 304  numberRemoved = oldCount; 305  backingMap.remove(element); 306  } 307  308  frequency.add(-numberRemoved); 309  size -= numberRemoved; 310  return oldCount; 311  } 312  313  // Roughly a 33% performance improvement over AbstractMultiset.setCount(). 314  @CanIgnoreReturnValue 315  @Override 316  public int setCount(@ParametricNullness E element, int count) { 317  checkNonnegative(count, "count"); 318  319  Count existingCounter; 320  int oldCount; 321  if (count == 0) { 322  existingCounter = backingMap.remove(element); 323  oldCount = getAndSet(existingCounter, count); 324  } else { 325  existingCounter = backingMap.get(element); 326  oldCount = getAndSet(existingCounter, count); 327  328  if (existingCounter == null) { 329  backingMap.put(element, new Count(count)); 330  } 331  } 332  333  size += (count - oldCount); 334  return oldCount; 335  } 336  337  private static int getAndSet(@CheckForNull Count i, int count) { 338  if (i == null) { 339  return 0; 340  } 341  342  return i.getAndSet(count); 343  } 344  345  // Don't allow default serialization. 346  @GwtIncompatible // java.io.ObjectStreamException 347  private void readObjectNoData() throws ObjectStreamException { 348  throw new InvalidObjectException("Stream data required"); 349  } 350  351  @GwtIncompatible // not needed in emulated source. 352  private static final long serialVersionUID = -2250766705698539974L; 353 }