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.concurrent.LazyInit; 025import com.google.j2objc.annotations.WeakOuter; 026import java.io.Serializable; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Iterator; 030import java.util.Set; 031import org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl; 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 private static <E> ImmutableMultiset<E> copyFromElements(E... elements) { 161 return new ImmutableMultiset.Builder<E>().add(elements).build(); 162 } 163 164 static <E> ImmutableMultiset<E> copyFromEntries( 165 Collection<? extends Entry<? extends E>> entries) { 166 ImmutableMultiset.Builder<E> builder = new ImmutableMultiset.Builder<E>(entries.size()); 167 for (Entry<? extends E> entry : entries) { 168 builder.addCopies(entry.getElement(), entry.getCount()); 169 } 170 return builder.build(); 171 } 172 173 /** 174 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 175 * described in the class documentation. 176 * 177 * @throws NullPointerException if any of {@code elements} is null 178 */ 179 public static <E> ImmutableMultiset<E> copyOf(Iterator<? extends E> elements) { 180 return new ImmutableMultiset.Builder<E>().addAll(elements).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 @MonotonicNonNullDecl 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 public final int add(E element, int occurrences) { 233 throw new UnsupportedOperationException(); 234 } 235 236 /** 237 * Guaranteed to throw an exception and leave the collection unmodified. 238 * 239 * @throws UnsupportedOperationException always 240 * @deprecated Unsupported operation. 241 */ 242 @CanIgnoreReturnValue 243 @Deprecated 244 @Override 245 public final int remove(Object element, int occurrences) { 246 throw new UnsupportedOperationException(); 247 } 248 249 /** 250 * Guaranteed to throw an exception and leave the collection unmodified. 251 * 252 * @throws UnsupportedOperationException always 253 * @deprecated Unsupported operation. 254 */ 255 @CanIgnoreReturnValue 256 @Deprecated 257 @Override 258 public final int setCount(E element, int count) { 259 throw new UnsupportedOperationException(); 260 } 261 262 /** 263 * Guaranteed to throw an exception and leave the collection unmodified. 264 * 265 * @throws UnsupportedOperationException always 266 * @deprecated Unsupported operation. 267 */ 268 @CanIgnoreReturnValue 269 @Deprecated 270 @Override 271 public final boolean setCount(E element, int oldCount, int newCount) { 272 throw new UnsupportedOperationException(); 273 } 274 275 @GwtIncompatible // not present in emulated superclass 276 @Override 277 int copyIntoArray(Object[] dst, int offset) { 278 for (Multiset.Entry<E> entry : entrySet()) { 279 Arrays.fill(dst, offset, offset + entry.getCount(), entry.getElement()); 280 offset += entry.getCount(); 281 } 282 return offset; 283 } 284 285 @Override 286 public boolean equals(@NullableDecl Object object) { 287 return Multisets.equalsImpl(this, object); 288 } 289 290 @Override 291 public int hashCode() { 292 return Sets.hashCodeImpl(entrySet()); 293 } 294 295 @Override 296 public String toString() { 297 return entrySet().toString(); 298 } 299 300 /** @since 21.0 (present with return type {@code Set} since 2.0) */ 301 @Override 302 public abstract ImmutableSet<E> elementSet(); 303 304 @LazyInit private transient ImmutableSet<Entry<E>> entrySet; 305 306 @Override 307 public ImmutableSet<Entry<E>> entrySet() { 308 ImmutableSet<Entry<E>> es = entrySet; 309 return (es == null) ? (entrySet = createEntrySet()) : es; 310 } 311 312 private ImmutableSet<Entry<E>> createEntrySet() { 313 return isEmpty() ? ImmutableSet.<Entry<E>>of() : new EntrySet(); 314 } 315 316 abstract Entry<E> getEntry(int index); 317 318 @WeakOuter 319 private final class EntrySet extends ImmutableSet.Indexed<Entry<E>> { 320 @Override 321 boolean isPartialView() { 322 return ImmutableMultiset.this.isPartialView(); 323 } 324 325 @Override 326 Entry<E> get(int index) { 327 return getEntry(index); 328 } 329 330 @Override 331 public int size() { 332 return elementSet().size(); 333 } 334 335 @Override 336 public boolean contains(Object o) { 337 if (o instanceof Entry) { 338 Entry<?> entry = (Entry<?>) o; 339 if (entry.getCount() <= 0) { 340 return false; 341 } 342 int count = count(entry.getElement()); 343 return count == entry.getCount(); 344 } 345 return false; 346 } 347 348 @Override 349 public int hashCode() { 350 return ImmutableMultiset.this.hashCode(); 351 } 352 353 // We can't label this with @Override, because it doesn't override anything 354 // in the GWT emulated version. 355 // TODO(cpovirk): try making all copies of this method @GwtIncompatible instead 356 Object writeReplace() { 357 return new EntrySetSerializedForm<E>(ImmutableMultiset.this); 358 } 359 360 private static final long serialVersionUID = 0; 361 } 362 363 static class EntrySetSerializedForm<E> implements Serializable { 364 final ImmutableMultiset<E> multiset; 365 366 EntrySetSerializedForm(ImmutableMultiset<E> multiset) { 367 this.multiset = multiset; 368 } 369 370 Object readResolve() { 371 return multiset.entrySet(); 372 } 373 } 374 375 // We can't label this with @Override, because it doesn't override anything 376 // in the GWT emulated version. 377 abstract Object writeReplace(); 378 379 /** 380 * Returns a new builder. The generated builder is equivalent to the builder created by the {@link 381 * Builder} constructor. 382 */ 383 public static <E> Builder<E> builder() { 384 return new Builder<E>(); 385 } 386 387 /** 388 * A builder for creating immutable multiset instances, especially {@code public static final} 389 * multisets ("constant multisets"). Example: 390 * 391 * <pre>{@code 392 * public static final ImmutableMultiset<Bean> BEANS = 393 * new ImmutableMultiset.Builder<Bean>() 394 * .addCopies(Bean.COCOA, 4) 395 * .addCopies(Bean.GARDEN, 6) 396 * .addCopies(Bean.RED, 8) 397 * .addCopies(Bean.BLACK_EYED, 10) 398 * .build(); 399 * }</pre> 400 * 401 * <p>Builder instances can be reused; it is safe to call {@link #build} multiple times to build 402 * multiple multisets in series. 403 * 404 * @since 2.0 405 */ 406 public static class Builder<E> extends ImmutableCollection.Builder<E> { 407 ObjectCountHashMap<E> contents; 408 409 /** 410 * If build() has been called on the current contents multiset, we need to copy it on any future 411 * modifications, or we'll modify the already-built ImmutableMultiset. 412 */ 413 boolean buildInvoked = false; 414 /** 415 * In the event of a setCount(elem, 0) call, we may need to remove elements, which destroys the 416 * insertion order property of ObjectCountHashMap. In that event, we need to convert to a 417 * ObjectCountLinkedHashMap, but we need to know we did that so we can convert back. 418 */ 419 boolean isLinkedHash = false; 420 421 /** 422 * Creates a new builder. The returned builder is equivalent to the builder generated by {@link 423 * ImmutableMultiset#builder}. 424 */ 425 public Builder() { 426 this(4); 427 } 428 429 Builder(int estimatedDistinct) { 430 this.contents = ObjectCountHashMap.createWithExpectedSize(estimatedDistinct); 431 } 432 433 Builder(boolean forSubtype) { 434 // for ImmutableSortedMultiset not to allocate data structures not used there 435 this.contents = null; 436 } 437 438 /** 439 * Adds {@code element} to the {@code ImmutableMultiset}. 440 * 441 * @param element the element to add 442 * @return this {@code Builder} object 443 * @throws NullPointerException if {@code element} is null 444 */ 445 @CanIgnoreReturnValue 446 @Override 447 public Builder<E> add(E element) { 448 return addCopies(element, 1); 449 } 450 451 /** 452 * Adds a number of occurrences of an element to this {@code ImmutableMultiset}. 453 * 454 * @param element the element to add 455 * @param occurrences the number of occurrences of the element to add. May be zero, in which 456 * case no change will be made. 457 * @return this {@code Builder} object 458 * @throws NullPointerException if {@code element} is null 459 * @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation 460 * would result in more than {@link Integer#MAX_VALUE} occurrences of the element 461 */ 462 @CanIgnoreReturnValue 463 public Builder<E> addCopies(E element, int occurrences) { 464 if (occurrences == 0) { 465 return this; 466 } 467 if (buildInvoked) { 468 contents = new ObjectCountHashMap<E>(contents); 469 isLinkedHash = false; 470 } 471 buildInvoked = false; 472 checkNotNull(element); 473 contents.put(element, occurrences + contents.get(element)); 474 return this; 475 } 476 477 /** 478 * Adds or removes the necessary occurrences of an element such that the element attains the 479 * desired count. 480 * 481 * @param element the element to add or remove occurrences of 482 * @param count the desired count of the element in this multiset 483 * @return this {@code Builder} object 484 * @throws NullPointerException if {@code element} is null 485 * @throws IllegalArgumentException if {@code count} is negative 486 */ 487 @CanIgnoreReturnValue 488 public Builder<E> setCount(E element, int count) { 489 if (count == 0 && !isLinkedHash) { 490 contents = new ObjectCountLinkedHashMap<E>(contents); 491 isLinkedHash = true; 492 // to preserve insertion order through deletions, we have to switch to an actual linked 493 // implementation at least for now, but this should be a super rare case 494 } else if (buildInvoked) { 495 contents = new ObjectCountHashMap<E>(contents); 496 isLinkedHash = false; 497 } 498 buildInvoked = false; 499 checkNotNull(element); 500 if (count == 0) { 501 contents.remove(element); 502 } else { 503 contents.put(checkNotNull(element), count); 504 } 505 return this; 506 } 507 508 /** 509 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 510 * 511 * @param elements the elements to add 512 * @return this {@code Builder} object 513 * @throws NullPointerException if {@code elements} is null or contains a null element 514 */ 515 @CanIgnoreReturnValue 516 @Override 517 public Builder<E> add(E... elements) { 518 super.add(elements); 519 return this; 520 } 521 522 /** 523 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 524 * 525 * @param elements the {@code Iterable} to add to the {@code ImmutableMultiset} 526 * @return this {@code Builder} object 527 * @throws NullPointerException if {@code elements} is null or contains a null element 528 */ 529 @CanIgnoreReturnValue 530 @Override 531 public Builder<E> addAll(Iterable<? extends E> elements) { 532 if (elements instanceof Multiset) { 533 Multiset<? extends E> multiset = Multisets.cast(elements); 534 ObjectCountHashMap<? extends E> backingMap = tryGetMap(multiset); 535 if (backingMap != null) { 536 contents.ensureCapacity(Math.max(contents.size(), backingMap.size())); 537 for (int i = backingMap.firstIndex(); i >= 0; i = backingMap.nextIndex(i)) { 538 addCopies(backingMap.getKey(i), backingMap.getValue(i)); 539 } 540 } else { 541 Set<? extends Entry<? extends E>> entries = multiset.entrySet(); 542 contents.ensureCapacity(Math.max(contents.size(), entries.size())); // might overlap 543 for (Entry<? extends E> entry : multiset.entrySet()) { 544 addCopies(entry.getElement(), entry.getCount()); 545 } 546 } 547 } else { 548 super.addAll(elements); 549 } 550 return this; 551 } 552 553 /** 554 * If the specified collection is backed by an ObjectCountHashMap, it will be much more 555 * efficient to iterate over it by index rather than an entry iterator, which will need to 556 * allocate an object for each entry, so we check for that. 557 */ 558 @NullableDecl 559 static <T> ObjectCountHashMap<T> tryGetMap(Iterable<T> multiset) { 560 if (multiset instanceof RegularImmutableMultiset) { 561 return ((RegularImmutableMultiset<T>) multiset).contents; 562 } else if (multiset instanceof AbstractMapBasedMultiset) { 563 return ((AbstractMapBasedMultiset<T>) multiset).backingMap; 564 } else { 565 return null; 566 } 567 } 568 569 /** 570 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 571 * 572 * @param elements the elements to add to the {@code ImmutableMultiset} 573 * @return this {@code Builder} object 574 * @throws NullPointerException if {@code elements} is null or contains a null element 575 */ 576 @CanIgnoreReturnValue 577 @Override 578 public Builder<E> addAll(Iterator<? extends E> elements) { 579 super.addAll(elements); 580 return this; 581 } 582 583 /** 584 * Returns a newly-created {@code ImmutableMultiset} based on the contents of the {@code 585 * Builder}. 586 */ 587 @Override 588 public ImmutableMultiset<E> build() { 589 if (contents.size() == 0) { 590 return of(); 591 } 592 if (isLinkedHash) { 593 // we need ObjectCountHashMap-backed contents, with its keys and values array in direct 594 // insertion order 595 contents = new ObjectCountHashMap<E>(contents); 596 isLinkedHash = false; 597 } 598 buildInvoked = true; 599 // contents is now ObjectCountHashMap, but still guaranteed to be in insertion order! 600 return new RegularImmutableMultiset<E>(contents); 601 } 602 } 603}