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