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