001/* 002 * Copyright (C) 2008 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.checkNotNull; 020import static java.util.Objects.requireNonNull; 021 022import com.google.common.annotations.Beta; 023import com.google.common.annotations.GwtCompatible; 024import com.google.common.annotations.GwtIncompatible; 025import com.google.common.annotations.J2ktIncompatible; 026import com.google.errorprone.annotations.CanIgnoreReturnValue; 027import com.google.errorprone.annotations.DoNotCall; 028import com.google.errorprone.annotations.concurrent.LazyInit; 029import com.google.j2objc.annotations.WeakOuter; 030import java.io.InvalidObjectException; 031import java.io.ObjectInputStream; 032import java.io.Serializable; 033import java.util.Arrays; 034import java.util.Collection; 035import java.util.Iterator; 036import java.util.Set; 037import java.util.function.Function; 038import java.util.function.ToIntFunction; 039import java.util.stream.Collector; 040import javax.annotation.CheckForNull; 041import org.checkerframework.checker.nullness.qual.Nullable; 042 043/** 044 * A {@link Multiset} whose contents will never change, with many other important properties 045 * detailed at {@link ImmutableCollection}. 046 * 047 * <p><b>Grouped iteration.</b> In all current implementations, duplicate elements always appear 048 * consecutively when iterating. Elements iterate in order by the <i>first</i> appearance of that 049 * element when the multiset was created. 050 * 051 * <p>See the Guava User Guide article on <a href= 052 * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections</a>. 053 * 054 * @author Jared Levy 055 * @author Louis Wasserman 056 * @since 2.0 057 */ 058@GwtCompatible(serializable = true, emulated = true) 059@SuppressWarnings("serial") // we're overriding default serialization 060@ElementTypesAreNonnullByDefault 061public abstract class ImmutableMultiset<E> extends ImmutableMultisetGwtSerializationDependencies<E> 062 implements Multiset<E> { 063 064 /** 065 * Returns a {@code Collector} that accumulates the input elements into a new {@code 066 * ImmutableMultiset}. Elements iterate in order by the <i>first</i> appearance of that element in 067 * encounter order. 068 * 069 * @since 33.2.0 (available since 21.0 in guava-jre) 070 */ 071 @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) 072 @IgnoreJRERequirement // Users will use this only if they're already using streams. 073 @Beta // TODO: b/288085449 - Remove. 074 public static <E> Collector<E, ?, ImmutableMultiset<E>> toImmutableMultiset() { 075 return CollectCollectors.toImmutableMultiset(Function.identity(), e -> 1); 076 } 077 078 /** 079 * Returns a {@code Collector} that accumulates elements into an {@code ImmutableMultiset} whose 080 * elements are the result of applying {@code elementFunction} to the inputs, with counts equal to 081 * the result of applying {@code countFunction} to the inputs. 082 * 083 * <p>If the mapped elements contain duplicates (according to {@link Object#equals}), the first 084 * occurrence in encounter order appears in the resulting multiset, with count equal to the sum of 085 * the outputs of {@code countFunction.applyAsInt(t)} for each {@code t} mapped to that element. 086 * 087 * @since 33.2.0 (available since 22.0 in guava-jre) 088 */ 089 @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) 090 @IgnoreJRERequirement // Users will use this only if they're already using streams. 091 @Beta // TODO: b/288085449 - Remove. 092 public static <T extends @Nullable Object, E> 093 Collector<T, ?, ImmutableMultiset<E>> toImmutableMultiset( 094 Function<? super T, ? extends E> elementFunction, 095 ToIntFunction<? super T> countFunction) { 096 return CollectCollectors.toImmutableMultiset(elementFunction, countFunction); 097 } 098 099 /** 100 * Returns the empty immutable multiset. 101 * 102 * <p><b>Performance note:</b> the instance returned is a singleton. 103 */ 104 @SuppressWarnings("unchecked") // all supported methods are covariant 105 public static <E> ImmutableMultiset<E> of() { 106 return (ImmutableMultiset<E>) RegularImmutableMultiset.EMPTY; 107 } 108 109 /** 110 * Returns an immutable multiset containing a single element. 111 * 112 * @throws NullPointerException if {@code element} is null 113 * @since 6.0 (source-compatible since 2.0) 114 */ 115 public static <E> ImmutableMultiset<E> of(E element) { 116 return copyFromElements(element); 117 } 118 119 /** 120 * Returns an immutable multiset containing the given elements, in order. 121 * 122 * @throws NullPointerException if any element is null 123 * @since 6.0 (source-compatible since 2.0) 124 */ 125 public static <E> ImmutableMultiset<E> of(E e1, E e2) { 126 return copyFromElements(e1, e2); 127 } 128 129 /** 130 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 131 * described in the class documentation. 132 * 133 * @throws NullPointerException if any element is null 134 * @since 6.0 (source-compatible since 2.0) 135 */ 136 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3) { 137 return copyFromElements(e1, e2, e3); 138 } 139 140 /** 141 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 142 * described in the class documentation. 143 * 144 * @throws NullPointerException if any element is null 145 * @since 6.0 (source-compatible since 2.0) 146 */ 147 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4) { 148 return copyFromElements(e1, e2, e3, e4); 149 } 150 151 /** 152 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 153 * described in the class documentation. 154 * 155 * @throws NullPointerException if any element is null 156 * @since 6.0 (source-compatible since 2.0) 157 */ 158 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4, E e5) { 159 return copyFromElements(e1, e2, e3, e4, e5); 160 } 161 162 /** 163 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 164 * described in the class documentation. 165 * 166 * @throws NullPointerException if any element is null 167 * @since 6.0 (source-compatible since 2.0) 168 */ 169 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) { 170 return new Builder<E>().add(e1).add(e2).add(e3).add(e4).add(e5).add(e6).add(others).build(); 171 } 172 173 /** 174 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 175 * described in the class documentation. 176 * 177 * @throws NullPointerException if any of {@code elements} is null 178 * @since 6.0 179 */ 180 public static <E> ImmutableMultiset<E> copyOf(E[] elements) { 181 return copyFromElements(elements); 182 } 183 184 /** 185 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 186 * described in the class documentation. 187 * 188 * @throws NullPointerException if any of {@code elements} is null 189 */ 190 public static <E> ImmutableMultiset<E> copyOf(Iterable<? extends E> elements) { 191 if (elements instanceof ImmutableMultiset) { 192 @SuppressWarnings("unchecked") // all supported methods are covariant 193 ImmutableMultiset<E> result = (ImmutableMultiset<E>) elements; 194 if (!result.isPartialView()) { 195 return result; 196 } 197 } 198 ImmutableMultiset.Builder<E> builder = 199 new ImmutableMultiset.Builder<E>(Multisets.inferDistinctElements(elements)); 200 builder.addAll(elements); 201 return builder.build(); 202 } 203 204 /** 205 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 206 * described in the class documentation. 207 * 208 * @throws NullPointerException if any of {@code elements} is null 209 */ 210 public static <E> ImmutableMultiset<E> copyOf(Iterator<? extends E> elements) { 211 return new ImmutableMultiset.Builder<E>().addAll(elements).build(); 212 } 213 214 private static <E> ImmutableMultiset<E> copyFromElements(E... elements) { 215 return new ImmutableMultiset.Builder<E>().add(elements).build(); 216 } 217 218 static <E> ImmutableMultiset<E> copyFromEntries( 219 Collection<? extends Entry<? extends E>> entries) { 220 ImmutableMultiset.Builder<E> builder = new ImmutableMultiset.Builder<E>(entries.size()); 221 for (Entry<? extends E> entry : entries) { 222 builder.addCopies(entry.getElement(), entry.getCount()); 223 } 224 return builder.build(); 225 } 226 227 ImmutableMultiset() {} 228 229 @Override 230 public UnmodifiableIterator<E> iterator() { 231 final Iterator<Entry<E>> entryIterator = entrySet().iterator(); 232 return new UnmodifiableIterator<E>() { 233 int remaining; 234 @CheckForNull E element; 235 236 @Override 237 public boolean hasNext() { 238 return (remaining > 0) || entryIterator.hasNext(); 239 } 240 241 @Override 242 public E next() { 243 if (remaining <= 0) { 244 Entry<E> entry = entryIterator.next(); 245 element = entry.getElement(); 246 remaining = entry.getCount(); 247 } 248 remaining--; 249 /* 250 * requireNonNull is safe because `remaining` starts at 0, forcing us to initialize 251 * `element` above. After that, we never clear it. 252 */ 253 return requireNonNull(element); 254 } 255 }; 256 } 257 258 @LazyInit @CheckForNull private transient ImmutableList<E> asList; 259 260 @Override 261 public ImmutableList<E> asList() { 262 ImmutableList<E> result = asList; 263 return (result == null) ? asList = super.asList() : result; 264 } 265 266 @Override 267 public boolean contains(@CheckForNull Object object) { 268 return count(object) > 0; 269 } 270 271 /** 272 * Guaranteed to throw an exception and leave the collection unmodified. 273 * 274 * @throws UnsupportedOperationException always 275 * @deprecated Unsupported operation. 276 */ 277 @CanIgnoreReturnValue 278 @Deprecated 279 @Override 280 @DoNotCall("Always throws UnsupportedOperationException") 281 public final int add(E element, int occurrences) { 282 throw new UnsupportedOperationException(); 283 } 284 285 /** 286 * Guaranteed to throw an exception and leave the collection unmodified. 287 * 288 * @throws UnsupportedOperationException always 289 * @deprecated Unsupported operation. 290 */ 291 @CanIgnoreReturnValue 292 @Deprecated 293 @Override 294 @DoNotCall("Always throws UnsupportedOperationException") 295 public final int remove(@CheckForNull Object element, int occurrences) { 296 throw new UnsupportedOperationException(); 297 } 298 299 /** 300 * Guaranteed to throw an exception and leave the collection unmodified. 301 * 302 * @throws UnsupportedOperationException always 303 * @deprecated Unsupported operation. 304 */ 305 @CanIgnoreReturnValue 306 @Deprecated 307 @Override 308 @DoNotCall("Always throws UnsupportedOperationException") 309 public final int setCount(E element, int count) { 310 throw new UnsupportedOperationException(); 311 } 312 313 /** 314 * Guaranteed to throw an exception and leave the collection unmodified. 315 * 316 * @throws UnsupportedOperationException always 317 * @deprecated Unsupported operation. 318 */ 319 @CanIgnoreReturnValue 320 @Deprecated 321 @Override 322 @DoNotCall("Always throws UnsupportedOperationException") 323 public final boolean setCount(E element, int oldCount, int newCount) { 324 throw new UnsupportedOperationException(); 325 } 326 327 @GwtIncompatible // not present in emulated superclass 328 @Override 329 int copyIntoArray(@Nullable Object[] dst, int offset) { 330 for (Multiset.Entry<E> entry : entrySet()) { 331 Arrays.fill(dst, offset, offset + entry.getCount(), entry.getElement()); 332 offset += entry.getCount(); 333 } 334 return offset; 335 } 336 337 @Override 338 public boolean equals(@CheckForNull Object object) { 339 return Multisets.equalsImpl(this, object); 340 } 341 342 @Override 343 public int hashCode() { 344 return Sets.hashCodeImpl(entrySet()); 345 } 346 347 @Override 348 public String toString() { 349 return entrySet().toString(); 350 } 351 352 /** @since 21.0 (present with return type {@code Set} since 2.0) */ 353 @Override 354 public abstract ImmutableSet<E> elementSet(); 355 356 @LazyInit @CheckForNull private transient ImmutableSet<Entry<E>> entrySet; 357 358 @Override 359 public ImmutableSet<Entry<E>> entrySet() { 360 ImmutableSet<Entry<E>> es = entrySet; 361 return (es == null) ? (entrySet = createEntrySet()) : es; 362 } 363 364 private ImmutableSet<Entry<E>> createEntrySet() { 365 return isEmpty() ? ImmutableSet.<Entry<E>>of() : new EntrySet(); 366 } 367 368 abstract Entry<E> getEntry(int index); 369 370 @WeakOuter 371 private final class EntrySet extends IndexedImmutableSet<Entry<E>> { 372 @Override 373 boolean isPartialView() { 374 return ImmutableMultiset.this.isPartialView(); 375 } 376 377 @Override 378 Entry<E> get(int index) { 379 return getEntry(index); 380 } 381 382 @Override 383 public int size() { 384 return elementSet().size(); 385 } 386 387 @Override 388 public boolean contains(@CheckForNull Object o) { 389 if (o instanceof Entry) { 390 Entry<?> entry = (Entry<?>) o; 391 if (entry.getCount() <= 0) { 392 return false; 393 } 394 int count = count(entry.getElement()); 395 return count == entry.getCount(); 396 } 397 return false; 398 } 399 400 @Override 401 public int hashCode() { 402 return ImmutableMultiset.this.hashCode(); 403 } 404 405 @GwtIncompatible 406 @J2ktIncompatible 407 @Override 408 Object writeReplace() { 409 return new EntrySetSerializedForm<E>(ImmutableMultiset.this); 410 } 411 412 @GwtIncompatible 413 @J2ktIncompatible 414 private void readObject(ObjectInputStream stream) throws InvalidObjectException { 415 throw new InvalidObjectException("Use EntrySetSerializedForm"); 416 } 417 418 @J2ktIncompatible private static final long serialVersionUID = 0; 419 } 420 421 @GwtIncompatible 422 @J2ktIncompatible 423 static class EntrySetSerializedForm<E> implements Serializable { 424 final ImmutableMultiset<E> multiset; 425 426 EntrySetSerializedForm(ImmutableMultiset<E> multiset) { 427 this.multiset = multiset; 428 } 429 430 Object readResolve() { 431 return multiset.entrySet(); 432 } 433 } 434 435 @GwtIncompatible 436 @J2ktIncompatible 437 @Override 438 abstract Object writeReplace(); 439 440 @GwtIncompatible 441 @J2ktIncompatible 442 private void readObject(ObjectInputStream stream) throws InvalidObjectException { 443 throw new InvalidObjectException("Use SerializedForm"); 444 } 445 446 /** 447 * Returns a new builder. The generated builder is equivalent to the builder created by the {@link 448 * Builder} constructor. 449 */ 450 public static <E> Builder<E> builder() { 451 return new Builder<>(); 452 } 453 454 /** 455 * A builder for creating immutable multiset instances, especially {@code public static final} 456 * multisets ("constant multisets"). Example: 457 * 458 * <pre>{@code 459 * public static final ImmutableMultiset<Bean> BEANS = 460 * new ImmutableMultiset.Builder<Bean>() 461 * .addCopies(Bean.COCOA, 4) 462 * .addCopies(Bean.GARDEN, 6) 463 * .addCopies(Bean.RED, 8) 464 * .addCopies(Bean.BLACK_EYED, 10) 465 * .build(); 466 * }</pre> 467 * 468 * <p>Builder instances can be reused; it is safe to call {@link #build} multiple times to build 469 * multiple multisets in series. 470 * 471 * @since 2.0 472 */ 473 public static class Builder<E> extends ImmutableCollection.Builder<E> { 474 /* 475 * `contents` is null only for instances of the subclass, ImmutableSortedMultiset.Builder. That 476 * subclass overrides all the methods that access it here. Thus, all the methods here can safely 477 * assume that this field is non-null. 478 */ 479 @CheckForNull ObjectCountHashMap<E> contents; 480 481 /** 482 * If build() has been called on the current contents multiset, we need to copy it on any future 483 * modifications, or we'll modify the already-built ImmutableMultiset. 484 */ 485 boolean buildInvoked = false; 486 /** 487 * In the event of a setCount(elem, 0) call, we may need to remove elements, which destroys the 488 * insertion order property of ObjectCountHashMap. In that event, we need to convert to a 489 * ObjectCountLinkedHashMap, but we need to know we did that so we can convert back. 490 */ 491 boolean isLinkedHash = false; 492 493 /** 494 * Creates a new builder. The returned builder is equivalent to the builder generated by {@link 495 * ImmutableMultiset#builder}. 496 */ 497 public Builder() { 498 this(4); 499 } 500 501 Builder(int estimatedDistinct) { 502 this.contents = ObjectCountHashMap.createWithExpectedSize(estimatedDistinct); 503 } 504 505 Builder(boolean forSubtype) { 506 // for ImmutableSortedMultiset not to allocate data structures not used there 507 this.contents = null; 508 } 509 510 /** 511 * Adds {@code element} to the {@code ImmutableMultiset}. 512 * 513 * @param element the element to add 514 * @return this {@code Builder} object 515 * @throws NullPointerException if {@code element} is null 516 */ 517 @CanIgnoreReturnValue 518 @Override 519 public Builder<E> add(E element) { 520 return addCopies(element, 1); 521 } 522 523 /** 524 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 525 * 526 * @param elements the elements to add 527 * @return this {@code Builder} object 528 * @throws NullPointerException if {@code elements} is null or contains a null element 529 */ 530 @CanIgnoreReturnValue 531 @Override 532 public Builder<E> add(E... elements) { 533 super.add(elements); 534 return this; 535 } 536 537 /** 538 * Adds a number of occurrences of an element to this {@code ImmutableMultiset}. 539 * 540 * @param element the element to add 541 * @param occurrences the number of occurrences of the element to add. May be zero, in which 542 * case no change will be made. 543 * @return this {@code Builder} object 544 * @throws NullPointerException if {@code element} is null 545 * @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation 546 * would result in more than {@link Integer#MAX_VALUE} occurrences of the element 547 */ 548 @CanIgnoreReturnValue 549 public Builder<E> addCopies(E element, int occurrences) { 550 requireNonNull(contents); // see the comment on the field 551 if (occurrences == 0) { 552 return this; 553 } 554 if (buildInvoked) { 555 contents = new ObjectCountHashMap<E>(contents); 556 isLinkedHash = false; 557 } 558 buildInvoked = false; 559 checkNotNull(element); 560 contents.put(element, occurrences + contents.get(element)); 561 return this; 562 } 563 564 /** 565 * Adds or removes the necessary occurrences of an element such that the element attains the 566 * desired count. 567 * 568 * @param element the element to add or remove occurrences of 569 * @param count the desired count of the element in this multiset 570 * @return this {@code Builder} object 571 * @throws NullPointerException if {@code element} is null 572 * @throws IllegalArgumentException if {@code count} is negative 573 */ 574 @CanIgnoreReturnValue 575 public Builder<E> setCount(E element, int count) { 576 requireNonNull(contents); // see the comment on the field 577 if (count == 0 && !isLinkedHash) { 578 contents = new ObjectCountLinkedHashMap<E>(contents); 579 isLinkedHash = true; 580 // to preserve insertion order through deletions, we have to switch to an actual linked 581 // implementation at least for now, but this should be a super rare case 582 } else if (buildInvoked) { 583 contents = new ObjectCountHashMap<E>(contents); 584 isLinkedHash = false; 585 } 586 buildInvoked = false; 587 checkNotNull(element); 588 if (count == 0) { 589 contents.remove(element); 590 } else { 591 contents.put(checkNotNull(element), count); 592 } 593 return this; 594 } 595 596 /** 597 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 598 * 599 * @param elements the {@code Iterable} to add to the {@code ImmutableMultiset} 600 * @return this {@code Builder} object 601 * @throws NullPointerException if {@code elements} is null or contains a null element 602 */ 603 @CanIgnoreReturnValue 604 @Override 605 public Builder<E> addAll(Iterable<? extends E> elements) { 606 requireNonNull(contents); // see the comment on the field 607 if (elements instanceof Multiset) { 608 Multiset<? extends E> multiset = Multisets.cast(elements); 609 ObjectCountHashMap<? extends E> backingMap = tryGetMap(multiset); 610 if (backingMap != null) { 611 contents.ensureCapacity(Math.max(contents.size(), backingMap.size())); 612 for (int i = backingMap.firstIndex(); i >= 0; i = backingMap.nextIndex(i)) { 613 addCopies(backingMap.getKey(i), backingMap.getValue(i)); 614 } 615 } else { 616 Set<? extends Entry<? extends E>> entries = multiset.entrySet(); 617 contents.ensureCapacity(Math.max(contents.size(), entries.size())); // might overlap 618 for (Entry<? extends E> entry : multiset.entrySet()) { 619 addCopies(entry.getElement(), entry.getCount()); 620 } 621 } 622 } else { 623 super.addAll(elements); 624 } 625 return this; 626 } 627 628 /** 629 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 630 * 631 * @param elements the elements to add to the {@code ImmutableMultiset} 632 * @return this {@code Builder} object 633 * @throws NullPointerException if {@code elements} is null or contains a null element 634 */ 635 @CanIgnoreReturnValue 636 @Override 637 public Builder<E> addAll(Iterator<? extends E> elements) { 638 super.addAll(elements); 639 return this; 640 } 641 642 /** 643 * If the specified collection is backed by an ObjectCountHashMap, it will be much more 644 * efficient to iterate over it by index rather than an entry iterator, which will need to 645 * allocate an object for each entry, so we check for that. 646 */ 647 @CheckForNull 648 static <T> ObjectCountHashMap<T> tryGetMap(Iterable<T> multiset) { 649 if (multiset instanceof RegularImmutableMultiset) { 650 return ((RegularImmutableMultiset<T>) multiset).contents; 651 } else if (multiset instanceof AbstractMapBasedMultiset) { 652 return ((AbstractMapBasedMultiset<T>) multiset).backingMap; 653 } else { 654 return null; 655 } 656 } 657 658 /** 659 * Returns a newly-created {@code ImmutableMultiset} based on the contents of the {@code 660 * Builder}. 661 */ 662 @Override 663 public ImmutableMultiset<E> build() { 664 requireNonNull(contents); // see the comment on the field 665 if (contents.size() == 0) { 666 return of(); 667 } 668 if (isLinkedHash) { 669 // we need ObjectCountHashMap-backed contents, with its keys and values array in direct 670 // insertion order 671 contents = new ObjectCountHashMap<E>(contents); 672 isLinkedHash = false; 673 } 674 buildInvoked = true; 675 // contents is now ObjectCountHashMap, but still guaranteed to be in insertion order! 676 return new RegularImmutableMultiset<E>(contents); 677 } 678 } 679 680 private static final long serialVersionUID = 0xdecaf; 681}