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