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