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.common.collect.Multiset.Entry; 024import com.google.errorprone.annotations.CanIgnoreReturnValue; 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 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 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 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 final 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 AbstractObjectCountMap<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 for (Entry<? extends E> entry : multiset.entrySet()) { 535 addCopies(entry.getElement(), entry.getCount()); 536 } 537 } else { 538 super.addAll(elements); 539 } 540 return this; 541 } 542 543 /** 544 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 545 * 546 * @param elements the elements to add to the {@code ImmutableMultiset} 547 * @return this {@code Builder} object 548 * @throws NullPointerException if {@code elements} is null or contains a null element 549 */ 550 @CanIgnoreReturnValue 551 @Override 552 public Builder<E> addAll(Iterator<? extends E> elements) { 553 super.addAll(elements); 554 return this; 555 } 556 557 /** 558 * Returns a newly-created {@code ImmutableMultiset} based on the contents of the {@code 559 * Builder}. 560 */ 561 @Override 562 public ImmutableMultiset<E> build() { 563 if (contents.isEmpty()) { 564 return of(); 565 } 566 if (isLinkedHash) { 567 // we need ObjectCountHashMap-backed contents, with its keys and values array in direct 568 // insertion order 569 contents = new ObjectCountHashMap<E>(contents); 570 isLinkedHash = false; 571 } 572 buildInvoked = true; 573 // contents is now ObjectCountHashMap, but still guaranteed to be in insertion order! 574 return new RegularImmutableMultiset<E>((ObjectCountHashMap<E>) contents); 575 } 576 } 577}