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