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