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 com.google.common.collect.Sets.hashCodeImpl; 021import static java.util.Objects.requireNonNull; 022 023import com.google.common.annotations.GwtCompatible; 024import com.google.common.annotations.GwtIncompatible; 025import com.google.common.annotations.J2ktIncompatible; 026import com.google.common.annotations.VisibleForTesting; 027import com.google.errorprone.annotations.CanIgnoreReturnValue; 028import com.google.errorprone.annotations.DoNotCall; 029import com.google.errorprone.annotations.concurrent.LazyInit; 030import java.io.InvalidObjectException; 031import java.io.ObjectInputStream; 032import java.io.Serializable; 033import java.util.Arrays; 034import java.util.Collection; 035import java.util.Collections; 036import java.util.Iterator; 037import java.util.List; 038import java.util.function.Function; 039import java.util.function.ToIntFunction; 040import java.util.stream.Collector; 041import org.jspecify.annotations.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 059@SuppressWarnings("serial") // we're overriding default serialization 060public abstract class ImmutableMultiset<E> extends ImmutableCollection<E> 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 21.0 068 */ 069 public static <E> Collector<E, ?, ImmutableMultiset<E>> toImmutableMultiset() { 070 return CollectCollectors.toImmutableMultiset(Function.identity(), e -> 1); 071 } 072 073 /** 074 * Returns a {@code Collector} that accumulates elements into an {@code ImmutableMultiset} whose 075 * elements are the result of applying {@code elementFunction} to the inputs, with counts equal to 076 * the result of applying {@code countFunction} to the inputs. 077 * 078 * <p>If the mapped elements contain duplicates (according to {@link Object#equals}), the first 079 * occurrence in encounter order appears in the resulting multiset, with count equal to the sum of 080 * the outputs of {@code countFunction.applyAsInt(t)} for each {@code t} mapped to that element. 081 * 082 * @since 22.0 083 */ 084 public static <T extends @Nullable Object, E> 085 Collector<T, ?, ImmutableMultiset<E>> toImmutableMultiset( 086 Function<? super T, ? extends E> elementFunction, 087 ToIntFunction<? super T> countFunction) { 088 return CollectCollectors.toImmutableMultiset(elementFunction, countFunction); 089 } 090 091 /** 092 * Returns the empty immutable multiset. 093 * 094 * <p><b>Performance note:</b> the instance returned is a singleton. 095 */ 096 @SuppressWarnings("unchecked") // all supported methods are covariant 097 public static <E> ImmutableMultiset<E> of() { 098 return (ImmutableMultiset<E>) RegularImmutableMultiset.EMPTY; 099 } 100 101 /** 102 * Returns an immutable multiset containing a single element. 103 * 104 * @throws NullPointerException if the element is null 105 * @since 6.0 (source-compatible since 2.0) 106 */ 107 public static <E> ImmutableMultiset<E> of(E e1) { 108 return copyFromElements(e1); 109 } 110 111 /** 112 * Returns an immutable multiset containing the given elements, in order. 113 * 114 * @throws NullPointerException if any element is null 115 * @since 6.0 (source-compatible since 2.0) 116 */ 117 public static <E> ImmutableMultiset<E> of(E e1, E e2) { 118 return copyFromElements(e1, e2); 119 } 120 121 /** 122 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 123 * described in the class documentation. 124 * 125 * @throws NullPointerException if any element is null 126 * @since 6.0 (source-compatible since 2.0) 127 */ 128 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3) { 129 return copyFromElements(e1, e2, e3); 130 } 131 132 /** 133 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 134 * described in the class documentation. 135 * 136 * @throws NullPointerException if any element is null 137 * @since 6.0 (source-compatible since 2.0) 138 */ 139 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4) { 140 return copyFromElements(e1, e2, e3, e4); 141 } 142 143 /** 144 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 145 * described in the class documentation. 146 * 147 * @throws NullPointerException if any element is null 148 * @since 6.0 (source-compatible since 2.0) 149 */ 150 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4, E e5) { 151 return copyFromElements(e1, e2, e3, e4, e5); 152 } 153 154 /** 155 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 156 * described in the class documentation. 157 * 158 * @throws NullPointerException if any element is null 159 * @since 6.0 (source-compatible since 2.0) 160 */ 161 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) { 162 return new Builder<E>().add(e1).add(e2).add(e3).add(e4).add(e5).add(e6).add(others).build(); 163 } 164 165 /** 166 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 167 * described in the class documentation. 168 * 169 * @throws NullPointerException if any of {@code elements} is null 170 * @since 6.0 171 */ 172 public static <E> ImmutableMultiset<E> copyOf(E[] elements) { 173 return copyFromElements(elements); 174 } 175 176 /** 177 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 178 * described in the class documentation. 179 * 180 * @throws NullPointerException if any of {@code elements} is null 181 */ 182 public static <E> ImmutableMultiset<E> copyOf(Iterable<? extends E> elements) { 183 if (elements instanceof ImmutableMultiset) { 184 @SuppressWarnings("unchecked") // all supported methods are covariant 185 ImmutableMultiset<E> result = (ImmutableMultiset<E>) elements; 186 if (!result.isPartialView()) { 187 return result; 188 } 189 } 190 191 Multiset<? extends E> multiset = 192 (elements instanceof Multiset) 193 ? (Multiset<? extends E>) elements 194 : LinkedHashMultiset.create(elements); 195 196 return copyFromEntries(multiset.entrySet()); 197 } 198 199 /** 200 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 201 * described in the class documentation. 202 * 203 * @throws NullPointerException if any of {@code elements} is null 204 */ 205 public static <E> ImmutableMultiset<E> copyOf(Iterator<? extends E> elements) { 206 Multiset<E> multiset = LinkedHashMultiset.create(); 207 Iterators.addAll(multiset, elements); 208 return copyFromEntries(multiset.entrySet()); 209 } 210 211 private static <E> ImmutableMultiset<E> copyFromElements(E... elements) { 212 Multiset<E> multiset = LinkedHashMultiset.create(); 213 Collections.addAll(multiset, elements); 214 return copyFromEntries(multiset.entrySet()); 215 } 216 217 static <E> ImmutableMultiset<E> copyFromEntries( 218 Collection<? extends Entry<? extends E>> entries) { 219 if (entries.isEmpty()) { 220 return of(); 221 } else { 222 return RegularImmutableMultiset.create(entries); 223 } 224 } 225 226 ImmutableMultiset() {} 227 228 @Override 229 public final UnmodifiableIterator<E> iterator() { 230 Iterator<Entry<E>> entryIterator = entrySet().iterator(); 231 return new UnmodifiableIterator<E>() { 232 int remaining; 233 @Nullable E element; 234 235 @Override 236 public boolean hasNext() { 237 return (remaining > 0) || entryIterator.hasNext(); 238 } 239 240 @Override 241 public E next() { 242 if (remaining <= 0) { 243 Entry<E> entry = entryIterator.next(); 244 element = entry.getElement(); 245 remaining = entry.getCount(); 246 } 247 remaining--; 248 /* 249 * requireNonNull is safe because `remaining` starts at 0, forcing us to initialize 250 * `element` above. After that, we never clear it. 251 */ 252 return requireNonNull(element); 253 } 254 }; 255 } 256 257 @LazyInit private transient @Nullable ImmutableList<E> asList; 258 259 @Override 260 public final ImmutableList<E> asList() { 261 ImmutableList<E> result = asList; 262 if (result == null) { 263 result = asList = super.asList(); 264 } 265 return result; 266 } 267 268 @Override 269 public boolean contains(@Nullable Object object) { 270 return count(object) > 0; 271 } 272 273 /** 274 * Guaranteed to throw an exception and leave the collection unmodified. 275 * 276 * @throws UnsupportedOperationException always 277 * @deprecated Unsupported operation. 278 */ 279 @CanIgnoreReturnValue 280 @Deprecated 281 @Override 282 @DoNotCall("Always throws UnsupportedOperationException") 283 public final int add(E element, int occurrences) { 284 throw new UnsupportedOperationException(); 285 } 286 287 /** 288 * Guaranteed to throw an exception and leave the collection unmodified. 289 * 290 * @throws UnsupportedOperationException always 291 * @deprecated Unsupported operation. 292 */ 293 @CanIgnoreReturnValue 294 @Deprecated 295 @Override 296 @DoNotCall("Always throws UnsupportedOperationException") 297 public final int remove(@Nullable Object element, int occurrences) { 298 throw new UnsupportedOperationException(); 299 } 300 301 /** 302 * Guaranteed to throw an exception and leave the collection unmodified. 303 * 304 * @throws UnsupportedOperationException always 305 * @deprecated Unsupported operation. 306 */ 307 @CanIgnoreReturnValue 308 @Deprecated 309 @Override 310 @DoNotCall("Always throws UnsupportedOperationException") 311 public final int setCount(E element, int count) { 312 throw new UnsupportedOperationException(); 313 } 314 315 /** 316 * Guaranteed to throw an exception and leave the collection unmodified. 317 * 318 * @throws UnsupportedOperationException always 319 * @deprecated Unsupported operation. 320 */ 321 @CanIgnoreReturnValue 322 @Deprecated 323 @Override 324 @DoNotCall("Always throws UnsupportedOperationException") 325 public final boolean setCount(E element, int oldCount, int newCount) { 326 throw new UnsupportedOperationException(); 327 } 328 329 @GwtIncompatible // not present in emulated superclass 330 @Override 331 final int copyIntoArray(@Nullable Object[] dst, int offset) { 332 for (Entry<E> entry : entrySet()) { 333 Arrays.fill(dst, offset, offset + entry.getCount(), entry.getElement()); 334 offset += entry.getCount(); 335 } 336 return offset; 337 } 338 339 @Override 340 public final boolean equals(@Nullable Object object) { 341 return Multisets.equalsImpl(this, object); 342 } 343 344 @Override 345 public int hashCode() { 346 return hashCodeImpl(entrySet()); 347 } 348 349 @Override 350 public final String toString() { 351 return entrySet().toString(); 352 } 353 354 /** 355 * @since 21.0 (present with return type {@code Set} since 2.0) 356 */ 357 @Override 358 public abstract ImmutableSet<E> elementSet(); 359 360 @Override 361 public final ImmutableSet<Entry<E>> entrySet() { 362 return isEmpty() ? ImmutableSet.of() : new EntrySet(); 363 } 364 365 abstract Entry<E> getEntry(int index); 366 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(@Nullable 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 @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; 415 } 416 417 @GwtIncompatible 418 @J2ktIncompatible 419 private static final 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 private static final long serialVersionUID = -6456416336206982697L; 431 } 432 433 @GwtIncompatible 434 @J2ktIncompatible 435 @Override 436 Object writeReplace() { 437 return new SerializedForm(this); 438 } 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 * {@snippet : 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 * } 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 final Multiset<E> contents; 475 476 /** 477 * Creates a new builder. The returned builder is equivalent to the builder generated by {@link 478 * ImmutableMultiset#builder}. 479 */ 480 public Builder() { 481 this(LinkedHashMultiset.create()); 482 } 483 484 Builder(Multiset<E> contents) { 485 this.contents = contents; 486 } 487 488 /** 489 * Adds {@code element} to the {@code ImmutableMultiset}. 490 * 491 * @param element the element to add 492 * @return this {@code Builder} object 493 * @throws NullPointerException if {@code element} is null 494 */ 495 @CanIgnoreReturnValue 496 @Override 497 public Builder<E> add(E element) { 498 contents.add(checkNotNull(element)); 499 return this; 500 } 501 502 /** 503 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 504 * 505 * @param elements the elements to add 506 * @return this {@code Builder} object 507 * @throws NullPointerException if {@code elements} is null or contains a null element 508 */ 509 @CanIgnoreReturnValue 510 @Override 511 public Builder<E> add(E... elements) { 512 super.add(elements); 513 return this; 514 } 515 516 /** 517 * Adds a number of occurrences of an element to this {@code ImmutableMultiset}. 518 * 519 * @param element the element to add 520 * @param occurrences the number of occurrences of the element to add. May be zero, in which 521 * case no change will be made. 522 * @return this {@code Builder} object 523 * @throws NullPointerException if {@code element} is null 524 * @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation 525 * would result in more than {@link Integer#MAX_VALUE} occurrences of the element 526 */ 527 @CanIgnoreReturnValue 528 public Builder<E> addCopies(E element, int occurrences) { 529 contents.add(checkNotNull(element), occurrences); 530 return this; 531 } 532 533 /** 534 * Adds or removes the necessary occurrences of an element such that the element attains the 535 * desired count. 536 * 537 * @param element the element to add or remove occurrences of 538 * @param count the desired count of the element in this multiset 539 * @return this {@code Builder} object 540 * @throws NullPointerException if {@code element} is null 541 * @throws IllegalArgumentException if {@code count} is negative 542 */ 543 @CanIgnoreReturnValue 544 public Builder<E> setCount(E element, int count) { 545 contents.setCount(checkNotNull(element), count); 546 return this; 547 } 548 549 /** 550 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 551 * 552 * @param elements the {@code Iterable} to add to the {@code ImmutableMultiset} 553 * @return this {@code Builder} object 554 * @throws NullPointerException if {@code elements} is null or contains a null element 555 */ 556 @CanIgnoreReturnValue 557 @Override 558 public Builder<E> addAll(Iterable<? extends E> elements) { 559 if (elements instanceof Multiset) { 560 Multiset<? extends E> multiset = (Multiset<? extends E>) elements; 561 multiset.forEachEntry((e, n) -> contents.add(checkNotNull(e), n)); 562 } else { 563 super.addAll(elements); 564 } 565 return this; 566 } 567 568 /** 569 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 570 * 571 * @param elements the elements to add to the {@code ImmutableMultiset} 572 * @return this {@code Builder} object 573 * @throws NullPointerException if {@code elements} is null or contains a null element 574 */ 575 @CanIgnoreReturnValue 576 @Override 577 public Builder<E> addAll(Iterator<? extends E> elements) { 578 super.addAll(elements); 579 return this; 580 } 581 582 /** 583 * Returns a newly-created {@code ImmutableMultiset} based on the contents of the {@code 584 * Builder}. 585 */ 586 @Override 587 public ImmutableMultiset<E> build() { 588 return copyOf(contents); 589 } 590 591 @VisibleForTesting 592 ImmutableMultiset<E> buildJdkBacked() { 593 if (contents.isEmpty()) { 594 return of(); 595 } 596 return JdkBackedImmutableMultiset.create(contents.entrySet()); 597 } 598 } 599 600 static final class ElementSet<E> extends IndexedImmutableSet<E> { 601 private final List<Entry<E>> entries; 602 // TODO(cpovirk): @Weak? 603 private final Multiset<E> delegate; 604 605 ElementSet(List<Entry<E>> entries, Multiset<E> delegate) { 606 this.entries = entries; 607 this.delegate = delegate; 608 } 609 610 @Override 611 E get(int index) { 612 return entries.get(index).getElement(); 613 } 614 615 @Override 616 public boolean contains(@Nullable Object object) { 617 return delegate.contains(object); 618 } 619 620 @Override 621 boolean isPartialView() { 622 return true; 623 } 624 625 @Override 626 public int size() { 627 return entries.size(); 628 } 629 630 // redeclare to help optimizers with b/310253115 631 @SuppressWarnings("RedundantOverride") 632 @Override 633 @J2ktIncompatible 634 @GwtIncompatible 635 Object writeReplace() { 636 return super.writeReplace(); 637 } 638 } 639 640 @J2ktIncompatible 641 static final class SerializedForm implements Serializable { 642 final Object[] elements; 643 final int[] counts; 644 645 // "extends Object" works around https://github.com/typetools/checker-framework/issues/3013 646 SerializedForm(Multiset<? extends Object> multiset) { 647 int distinct = multiset.entrySet().size(); 648 elements = new Object[distinct]; 649 counts = new int[distinct]; 650 int i = 0; 651 for (Entry<? extends Object> entry : multiset.entrySet()) { 652 elements[i] = entry.getElement(); 653 counts[i] = entry.getCount(); 654 i++; 655 } 656 } 657 658 Object readResolve() { 659 LinkedHashMultiset<Object> multiset = LinkedHashMultiset.create(elements.length); 660 for (int i = 0; i < elements.length; i++) { 661 multiset.add(elements[i], counts[i]); 662 } 663 return copyOf(multiset); 664 } 665 666 @GwtIncompatible private static final long serialVersionUID = 0; 667 } 668 669 @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0xcafebabe; 670}