001/*
002 * Copyright (C) 2007 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.collect;
018
019import static com.google.common.base.Preconditions.checkArgument;
020import static com.google.common.base.Preconditions.checkNotNull;
021import static com.google.common.base.Preconditions.checkState;
022import static com.google.common.collect.CollectPreconditions.checkNonnegative;
023import static com.google.common.collect.CollectPreconditions.checkPositive;
024import static com.google.common.collect.Collections2.safeContains;
025import static com.google.common.collect.Collections2.safeRemove;
026import static com.google.common.collect.Lists.newArrayListWithExpectedSize;
027import static com.google.common.collect.Maps.safeGet;
028import static java.lang.Math.max;
029import static java.util.Objects.requireNonNull;
030
031import com.google.common.annotations.GwtIncompatible;
032import com.google.common.annotations.J2ktIncompatible;
033import com.google.common.primitives.Ints;
034import com.google.errorprone.annotations.CanIgnoreReturnValue;
035import java.io.IOException;
036import java.io.ObjectInputStream;
037import java.io.ObjectOutputStream;
038import java.io.Serializable;
039import java.util.Collection;
040import java.util.Iterator;
041import java.util.List;
042import java.util.Map;
043import java.util.Set;
044import java.util.concurrent.ConcurrentHashMap;
045import java.util.concurrent.ConcurrentMap;
046import java.util.concurrent.atomic.AtomicInteger;
047import org.jspecify.annotations.Nullable;
048
049/**
050 * A multiset that supports concurrent modifications and that provides atomic versions of most
051 * {@code Multiset} operations (exceptions where noted). Null elements are not supported.
052 *
053 * <p>See the Guava User Guide article on <a href=
054 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}</a>.
055 *
056 * @author Cliff L. Biffle
057 * @author mike nonemacher
058 * @since 2.0
059 */
060@J2ktIncompatible
061@GwtIncompatible
062public final class ConcurrentHashMultiset<E> extends AbstractMultiset<E> implements Serializable {
063
064  /*
065   * The ConcurrentHashMultiset's atomic operations are implemented primarily in terms of
066   * AtomicInteger's atomic operations, with some help from ConcurrentMap's atomic operations on
067   * creation and removal (including automatic removal of zeroes). If the modification of an
068   * AtomicInteger results in zero, we compareAndSet the value to zero; if that succeeds, we remove
069   * the entry from the Map. If another operation sees a zero in the map, it knows that the entry is
070   * about to be removed, so this operation may remove it (often by replacing it with a new
071   * AtomicInteger).
072   */
073
074  /** The number of occurrences of each element. */
075  private final transient ConcurrentMap<E, AtomicInteger> countMap;
076
077  /**
078   * An instance created in {@link #readObject} to be returned from {@link #readResolve}. This field
079   * is used only by those methods, and it is never set in a "normal" instance.
080   *
081   * <p>This class needs to write deserialized data into fields that are {@code final transient}.
082   * Such writes will become impossible to perform in {@link #readObject} after JEP 500. Instead, we
083   * must create a new instance with the desired field values, stash it in this field, and then
084   * instruct Java serialization to use it instead of the originally created object.
085   *
086   * <p>We have chosen this approach over at least two alternatives:
087   *
088   * <ul>
089   *   <li>We could change the serialization of this class incompatibly. We have reserved the right
090   *       to make such changes to our serialized forms, and we have made them before, usually
091   *       without trouble. In this case, my guess is that our chosen approach is even less likely
092   *       to lead to trouble than an incompatible change would be.
093   *   <li>We could make {@link #countMap} no longer be {@code final}. Then we could write to it
094   *       directly during deserialization. However, we would lose Java's guarantees for {@code
095   *       final} fields, including that their values are guaranteed to be visible even when an
096   *       instance is unsafely published.
097   * </ul>
098   */
099  private transient @Nullable ConcurrentHashMultiset<E> deserializationReplacement;
100
101  /**
102   * Creates a new, empty {@code ConcurrentHashMultiset} using the default initial capacity, load
103   * factor, and concurrency settings.
104   */
105  public static <E> ConcurrentHashMultiset<E> create() {
106    return create(new ConcurrentHashMap<>());
107  }
108
109  /**
110   * Creates a new {@code ConcurrentHashMultiset} containing the specified elements, using the
111   * default initial capacity, load factor, and concurrency settings.
112   *
113   * <p>This implementation is highly efficient when {@code elements} is itself a {@link Multiset}.
114   *
115   * @param elements the elements that the multiset should contain
116   */
117  public static <E> ConcurrentHashMultiset<E> create(Iterable<? extends E> elements) {
118    ConcurrentHashMultiset<E> multiset = create();
119    Iterables.addAll(multiset, elements);
120    return multiset;
121  }
122
123  /**
124   * Creates a new, empty {@code ConcurrentHashMultiset} using {@code countMap} as the internal
125   * backing map.
126   *
127   * <p>This instance will assume ownership of {@code countMap}, and other code should not maintain
128   * references to the map or modify it in any way.
129   *
130   * <p>The returned multiset is serializable if the input map is.
131   *
132   * @param countMap backing map for storing the elements in the multiset and their counts. It must
133   *     be empty.
134   * @throws IllegalArgumentException if {@code countMap} is not empty
135   * @since 20.0
136   */
137  public static <E> ConcurrentHashMultiset<E> create(ConcurrentMap<E, AtomicInteger> countMap) {
138    checkArgument(countMap.isEmpty(), "the backing map (%s) must be empty", countMap);
139    return new ConcurrentHashMultiset<>(countMap);
140  }
141
142  private ConcurrentHashMultiset(ConcurrentMap<E, AtomicInteger> countMap) {
143    this.countMap = countMap;
144  }
145
146  // Query Operations
147
148  /**
149   * Returns the number of occurrences of {@code element} in this multiset.
150   *
151   * @param element the element to look for
152   * @return the nonnegative number of occurrences of the element
153   */
154  @Override
155  public int count(@Nullable Object element) {
156    AtomicInteger existingCounter = safeGet(countMap, element);
157    return (existingCounter == null) ? 0 : existingCounter.get();
158  }
159
160  /**
161   * {@inheritDoc}
162   *
163   * <p>If the data in the multiset is modified by any other threads during this method, it is
164   * undefined which (if any) of these modifications will be reflected in the result.
165   */
166  @Override
167  public int size() {
168    long sum = 0L;
169    for (AtomicInteger value : countMap.values()) {
170      sum += value.get();
171    }
172    return Ints.saturatedCast(sum);
173  }
174
175  /*
176   * We override the toArray methods for two reasons:
177   *
178   * 1. Both superclass toArray methods assume that size() gives a correct answer, while our size()
179   * might not (and the answer might change while we're building the array).
180   *
181   * TODO(cpovirk): Is this an issue anywhere anymore? It looks to have been fixed for Java 8
182   * (https://bugs.openjdk.org/browse/JDK-7121314) and before Lollipop
183   * (https://r.android.com/47508). We *would* need to worry for J2KT, whose own concurrency support
184   * is evolving (b/381065164, b/458160722), but this class is @J2ktIncompatible.
185   *
186   * 2. The superclass toArray() method declares the more general return type `@Nullable Object[]`,
187   * but we know that our values will never be `null`.
188   */
189
190  @Override
191  public Object[] toArray() {
192    return snapshotElementsToList().toArray();
193  }
194
195  @Override
196  @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations
197  public <T extends @Nullable Object> T[] toArray(T[] array) {
198    return snapshotElementsToList().toArray(array);
199  }
200
201  /*
202   * We'd love to use 'new ArrayList(this)' or 'list.addAll(this)', but
203   * either of these would recurse back to us again!
204   */
205  private List<E> snapshotElementsToList() {
206    List<E> list = newArrayListWithExpectedSize(size());
207    for (Entry<E> entry : entrySet()) {
208      E element = entry.getElement();
209      for (int i = entry.getCount(); i > 0; i--) {
210        list.add(element);
211      }
212    }
213    return list;
214  }
215
216  // Modification Operations
217
218  /**
219   * Adds a number of occurrences of the specified element to this multiset.
220   *
221   * @param element the element to add
222   * @param occurrences the number of occurrences to add
223   * @return the previous count of the element before the operation; possibly zero
224   * @throws IllegalArgumentException if {@code occurrences} is negative, or if the resulting amount
225   *     would exceed {@link Integer#MAX_VALUE}
226   */
227  @CanIgnoreReturnValue
228  @Override
229  public int add(E element, int occurrences) {
230    checkNotNull(element);
231    if (occurrences == 0) {
232      return count(element);
233    }
234    checkPositive(occurrences, "occurrences");
235
236    while (true) {
237      AtomicInteger existingCounter = safeGet(countMap, element);
238      if (existingCounter == null) {
239        existingCounter = countMap.putIfAbsent(element, new AtomicInteger(occurrences));
240        if (existingCounter == null) {
241          return 0;
242        }
243        // existingCounter != null: fall through to operate against the existing AtomicInteger
244      }
245
246      while (true) {
247        int oldValue = existingCounter.get();
248        if (oldValue != 0) {
249          try {
250            int newValue = Math.addExact(oldValue, occurrences);
251            if (existingCounter.compareAndSet(oldValue, newValue)) {
252              // newValue can't == 0, so no need to check & remove
253              return oldValue;
254            }
255          } catch (ArithmeticException overflow) {
256            throw new IllegalArgumentException(
257                "Overflow adding " + occurrences + " occurrences to a count of " + oldValue);
258          }
259        } else {
260          // In the case of a concurrent remove, we might observe a zero value, which means another
261          // thread is about to remove (element, existingCounter) from the map. Rather than wait,
262          // we can just do that work here.
263          AtomicInteger newCounter = new AtomicInteger(occurrences);
264          if ((countMap.putIfAbsent(element, newCounter) == null)
265              || countMap.replace(element, existingCounter, newCounter)) {
266            return 0;
267          }
268          break;
269        }
270      }
271
272      // If we're still here, there was a race, so just try again.
273    }
274  }
275
276  /**
277   * Removes a number of occurrences of the specified element from this multiset. If the multiset
278   * contains fewer than this number of occurrences to begin with, all occurrences will be removed.
279   *
280   * @param element the element whose occurrences should be removed
281   * @param occurrences the number of occurrences of the element to remove
282   * @return the count of the element before the operation; possibly zero
283   * @throws IllegalArgumentException if {@code occurrences} is negative
284   */
285  /*
286   * TODO(cpovirk): remove and removeExactly currently accept null inputs only
287   * if occurrences == 0. This satisfies both NullPointerTester and
288   * CollectionRemoveTester.testRemove_nullAllowed, but it's not clear that it's
289   * a good policy, especially because, in order for the test to pass, the
290   * parameter must be misleadingly annotated as @Nullable. I suspect that
291   * we'll want to remove @Nullable, add an eager checkNotNull, and loosen up
292   * testRemove_nullAllowed.
293   */
294  @CanIgnoreReturnValue
295  @Override
296  public int remove(@Nullable Object element, int occurrences) {
297    if (occurrences == 0) {
298      return count(element);
299    }
300    checkPositive(occurrences, "occurrences");
301
302    AtomicInteger existingCounter = safeGet(countMap, element);
303    if (existingCounter == null) {
304      return 0;
305    }
306    while (true) {
307      int oldValue = existingCounter.get();
308      if (oldValue != 0) {
309        int newValue = max(0, oldValue - occurrences);
310        if (existingCounter.compareAndSet(oldValue, newValue)) {
311          if (newValue == 0) {
312            // Just CASed to 0; remove the entry to clean up the map. If the removal fails,
313            // another thread has already replaced it with a new counter, which is fine.
314            countMap.remove(element, existingCounter);
315          }
316          return oldValue;
317        }
318      } else {
319        return 0;
320      }
321    }
322  }
323
324  /**
325   * Removes exactly the specified number of occurrences of {@code element}, or makes no change if
326   * this is not possible.
327   *
328   * <p>This method, in contrast to {@link #remove(Object, int)}, has no effect when the element
329   * count is smaller than {@code occurrences}.
330   *
331   * @param element the element to remove
332   * @param occurrences the number of occurrences of {@code element} to remove
333   * @return {@code true} if the removal was possible (including if {@code occurrences} is zero)
334   * @throws IllegalArgumentException if {@code occurrences} is negative
335   */
336  @CanIgnoreReturnValue
337  public boolean removeExactly(@Nullable Object element, int occurrences) {
338    if (occurrences == 0) {
339      return true;
340    }
341    checkPositive(occurrences, "occurrences");
342
343    AtomicInteger existingCounter = safeGet(countMap, element);
344    if (existingCounter == null) {
345      return false;
346    }
347    while (true) {
348      int oldValue = existingCounter.get();
349      if (oldValue < occurrences) {
350        return false;
351      }
352      int newValue = oldValue - occurrences;
353      if (existingCounter.compareAndSet(oldValue, newValue)) {
354        if (newValue == 0) {
355          // Just CASed to 0; remove the entry to clean up the map. If the removal fails,
356          // another thread has already replaced it with a new counter, which is fine.
357          countMap.remove(element, existingCounter);
358        }
359        return true;
360      }
361    }
362  }
363
364  /**
365   * Adds or removes occurrences of {@code element} such that the {@link #count} of the element
366   * becomes {@code count}.
367   *
368   * @return the count of {@code element} in the multiset before this call
369   * @throws IllegalArgumentException if {@code count} is negative
370   */
371  @CanIgnoreReturnValue
372  @Override
373  public int setCount(E element, int count) {
374    checkNotNull(element);
375    checkNonnegative(count, "count");
376    while (true) {
377      AtomicInteger existingCounter = safeGet(countMap, element);
378      if (existingCounter == null) {
379        if (count == 0) {
380          return 0;
381        } else {
382          existingCounter = countMap.putIfAbsent(element, new AtomicInteger(count));
383          if (existingCounter == null) {
384            return 0;
385          }
386          // existingCounter != null: fall through
387        }
388      }
389
390      while (true) {
391        int oldValue = existingCounter.get();
392        if (oldValue == 0) {
393          if (count == 0) {
394            return 0;
395          } else {
396            AtomicInteger newCounter = new AtomicInteger(count);
397            if ((countMap.putIfAbsent(element, newCounter) == null)
398                || countMap.replace(element, existingCounter, newCounter)) {
399              return 0;
400            }
401          }
402          break;
403        } else {
404          if (existingCounter.compareAndSet(oldValue, count)) {
405            if (count == 0) {
406              // Just CASed to 0; remove the entry to clean up the map. If the removal fails,
407              // another thread has already replaced it with a new counter, which is fine.
408              countMap.remove(element, existingCounter);
409            }
410            return oldValue;
411          }
412        }
413      }
414    }
415  }
416
417  /**
418   * Sets the number of occurrences of {@code element} to {@code newCount}, but only if the count is
419   * currently {@code expectedOldCount}. If {@code element} does not appear in the multiset exactly
420   * {@code expectedOldCount} times, no changes will be made.
421   *
422   * @return {@code true} if the change was successful. This usually indicates that the multiset has
423   *     been modified, but not always: in the case that {@code expectedOldCount == newCount}, the
424   *     method will return {@code true} if the condition was met.
425   * @throws IllegalArgumentException if {@code expectedOldCount} or {@code newCount} is negative
426   */
427  @CanIgnoreReturnValue
428  @Override
429  public boolean setCount(E element, int expectedOldCount, int newCount) {
430    checkNotNull(element);
431    checkNonnegative(expectedOldCount, "oldCount");
432    checkNonnegative(newCount, "newCount");
433
434    AtomicInteger existingCounter = safeGet(countMap, element);
435    if (existingCounter == null) {
436      if (expectedOldCount != 0) {
437        return false;
438      } else if (newCount == 0) {
439        return true;
440      } else {
441        // if our write lost the race, it must have lost to a nonzero value, so we can stop
442        return countMap.putIfAbsent(element, new AtomicInteger(newCount)) == null;
443      }
444    }
445    int oldValue = existingCounter.get();
446    if (oldValue == expectedOldCount) {
447      if (oldValue == 0) {
448        if (newCount == 0) {
449          // Just observed a 0; try to remove the entry to clean up the map
450          countMap.remove(element, existingCounter);
451          return true;
452        } else {
453          AtomicInteger newCounter = new AtomicInteger(newCount);
454          return (countMap.putIfAbsent(element, newCounter) == null)
455              || countMap.replace(element, existingCounter, newCounter);
456        }
457      } else {
458        if (existingCounter.compareAndSet(oldValue, newCount)) {
459          if (newCount == 0) {
460            // Just CASed to 0; remove the entry to clean up the map. If the removal fails,
461            // another thread has already replaced it with a new counter, which is fine.
462            countMap.remove(element, existingCounter);
463          }
464          return true;
465        }
466      }
467    }
468    return false;
469  }
470
471  // Views
472
473  @Override
474  public Set<E> elementSet() {
475    Set<E> delegate = countMap.keySet();
476    return new ForwardingSet<E>() {
477      @Override
478      protected Set<E> delegate() {
479        return delegate;
480      }
481
482      @Override
483      public boolean contains(@Nullable Object object) {
484        return object != null && safeContains(delegate, object);
485      }
486
487      @Override
488      public boolean containsAll(Collection<?> collection) {
489        return standardContainsAll(collection);
490      }
491
492      @Override
493      public boolean remove(@Nullable Object object) {
494        return object != null && safeRemove(delegate, object);
495      }
496
497      @Override
498      public boolean removeAll(Collection<?> c) {
499        return standardRemoveAll(c);
500      }
501    };
502  }
503
504  @Override
505  Iterator<E> elementIterator() {
506    throw new AssertionError("should never be called");
507  }
508
509  /**
510   * @deprecated Internal method, use {@link #entrySet()}.
511   */
512  @Deprecated
513  public Set<Entry<E>> createEntrySet() {
514    return entrySet();
515  }
516
517  @Override
518  public Set<Entry<E>> entrySet() {
519    return new EntrySet();
520  }
521
522  @Override
523  int internalDistinctElements() {
524    return countMap.size();
525  }
526
527  @Override
528  public boolean isEmpty() {
529    return countMap.isEmpty();
530  }
531
532  @Override
533  Iterator<Entry<E>> internalEntryIterator() {
534    // AbstractIterator makes this fairly clean, but it doesn't support remove(). To support
535    // remove(), we create an AbstractIterator, and then use ForwardingIterator to delegate to it.
536    Iterator<Entry<E>> readOnlyIterator =
537        new AbstractIterator<Entry<E>>() {
538          final Iterator<Map.Entry<E, AtomicInteger>> mapEntries = countMap.entrySet().iterator();
539
540          @Override
541          protected @Nullable Entry<E> computeNext() {
542            while (true) {
543              if (!mapEntries.hasNext()) {
544                return endOfData();
545              }
546              Map.Entry<E, AtomicInteger> mapEntry = mapEntries.next();
547              int count = mapEntry.getValue().get();
548              if (count != 0) {
549                return Multisets.immutableEntry(mapEntry.getKey(), count);
550              }
551            }
552          }
553        };
554
555    return new ForwardingIterator<Entry<E>>() {
556      @Nullable Entry<E> last;
557
558      @Override
559      protected Iterator<Entry<E>> delegate() {
560        return readOnlyIterator;
561      }
562
563      @Override
564      public Entry<E> next() {
565        last = super.next();
566        return last;
567      }
568
569      @Override
570      public void remove() {
571        checkState(last != null, "no calls to next() since the last call to remove()");
572        ConcurrentHashMultiset.this.setCount(last.getElement(), 0);
573        last = null;
574      }
575    };
576  }
577
578  @Override
579  public Iterator<E> iterator() {
580    return Multisets.iteratorImpl(this);
581  }
582
583  @Override
584  public void clear() {
585    countMap.clear();
586  }
587
588  private final class EntrySet extends AbstractMultiset<E>.EntrySet {
589    @Override
590    ConcurrentHashMultiset<E> multiset() {
591      return ConcurrentHashMultiset.this;
592    }
593
594    /*
595     * Note: the superclass toArray() methods assume that size() gives a correct
596     * answer, which ours does not.
597     */
598
599    @Override
600    public Object[] toArray() {
601      return snapshot().toArray();
602    }
603
604    @Override
605    @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations
606    public <T extends @Nullable Object> T[] toArray(T[] array) {
607      return snapshot().toArray(array);
608    }
609
610    private List<Multiset.Entry<E>> snapshot() {
611      List<Multiset.Entry<E>> list = newArrayListWithExpectedSize(size());
612      // Not Iterables.addAll(list, this), because that'll forward right back here.
613      Iterators.addAll(list, iterator());
614      return list;
615    }
616  }
617
618  /**
619   * @serialData the ConcurrentMap of elements and their counts.
620   */
621    private void writeObject(ObjectOutputStream stream) throws IOException {
622    stream.defaultWriteObject();
623    stream.writeObject(countMap);
624  }
625
626    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
627    stream.defaultReadObject();
628    @SuppressWarnings("unchecked") // reading data stored by writeObject
629    ConcurrentMap<E, AtomicInteger> deserializedCountMap =
630        (ConcurrentMap<E, AtomicInteger>) requireNonNull(stream.readObject());
631    deserializationReplacement = new ConcurrentHashMultiset<>(deserializedCountMap);
632  }
633
634    private Object readResolve() {
635    return requireNonNull(deserializationReplacement); // set by readObject
636  }
637
638  private static final long serialVersionUID = 1;
639}