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