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.Collections; 031import java.util.Iterator; 032import javax.annotation.Nullable; 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 040 * that 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"> 044 * immutable collections</a>. 045 * 046 * @author Jared Levy 047 * @author Louis Wasserman 048 * @since 2.0 049 */ 050@GwtCompatible(serializable = true, emulated = true) 051@SuppressWarnings("serial") // we're overriding default serialization 052// TODO(lowasser): write an efficient asList() implementation 053public abstract class ImmutableMultiset<E> extends ImmutableCollection<E> implements Multiset<E> { 054 /** 055 * Returns the empty immutable multiset. 056 */ 057 @SuppressWarnings("unchecked") // all supported methods are covariant 058 public static <E> ImmutableMultiset<E> of() { 059 return (ImmutableMultiset<E>) RegularImmutableMultiset.EMPTY; 060 } 061 062 /** 063 * Returns an immutable multiset containing a single element. 064 * 065 * @throws NullPointerException if {@code element} is null 066 * @since 6.0 (source-compatible since 2.0) 067 */ 068 @SuppressWarnings("unchecked") // generic array created but never written 069 public static <E> ImmutableMultiset<E> of(E element) { 070 return copyFromElements(element); 071 } 072 073 /** 074 * Returns an immutable multiset containing the given elements, in order. 075 * 076 * @throws NullPointerException if any element is null 077 * @since 6.0 (source-compatible since 2.0) 078 */ 079 @SuppressWarnings("unchecked") // 080 public static <E> ImmutableMultiset<E> of(E e1, E e2) { 081 return copyFromElements(e1, e2); 082 } 083 084 /** 085 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 086 * described in the class documentation. 087 * 088 * @throws NullPointerException if any element is null 089 * @since 6.0 (source-compatible since 2.0) 090 */ 091 @SuppressWarnings("unchecked") // 092 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3) { 093 return copyFromElements(e1, e2, e3); 094 } 095 096 /** 097 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 098 * described in the class documentation. 099 * 100 * @throws NullPointerException if any element is null 101 * @since 6.0 (source-compatible since 2.0) 102 */ 103 @SuppressWarnings("unchecked") // 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 @SuppressWarnings("unchecked") // 116 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4, E e5) { 117 return copyFromElements(e1, e2, e3, e4, e5); 118 } 119 120 /** 121 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 122 * described in the class documentation. 123 * 124 * @throws NullPointerException if any element is null 125 * @since 6.0 (source-compatible since 2.0) 126 */ 127 @SuppressWarnings("unchecked") // 128 public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) { 129 return new Builder<E>().add(e1).add(e2).add(e3).add(e4).add(e5).add(e6).add(others).build(); 130 } 131 132 /** 133 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 134 * described in the class documentation. 135 * 136 * @throws NullPointerException if any of {@code elements} is null 137 * @since 6.0 138 */ 139 public static <E> ImmutableMultiset<E> copyOf(E[] elements) { 140 return copyFromElements(elements); 141 } 142 143 /** 144 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 145 * described in the class documentation. 146 * 147 * @throws NullPointerException if any of {@code elements} is null 148 */ 149 public static <E> ImmutableMultiset<E> copyOf(Iterable<? extends E> elements) { 150 if (elements instanceof ImmutableMultiset) { 151 @SuppressWarnings("unchecked") // all supported methods are covariant 152 ImmutableMultiset<E> result = (ImmutableMultiset<E>) elements; 153 if (!result.isPartialView()) { 154 return result; 155 } 156 } 157 158 Multiset<? extends E> multiset = 159 (elements instanceof Multiset) 160 ? Multisets.cast(elements) 161 : LinkedHashMultiset.create(elements); 162 163 return copyFromEntries(multiset.entrySet()); 164 } 165 166 private static <E> ImmutableMultiset<E> copyFromElements(E... elements) { 167 Multiset<E> multiset = LinkedHashMultiset.create(); 168 Collections.addAll(multiset, elements); 169 return copyFromEntries(multiset.entrySet()); 170 } 171 172 static <E> ImmutableMultiset<E> copyFromEntries( 173 Collection<? extends Entry<? extends E>> entries) { 174 if (entries.isEmpty()) { 175 return of(); 176 } else { 177 return new RegularImmutableMultiset<E>(entries); 178 } 179 } 180 181 /** 182 * Returns an immutable multiset containing the given elements, in the "grouped iteration order" 183 * described in the class documentation. 184 * 185 * @throws NullPointerException if any of {@code elements} is null 186 */ 187 public static <E> ImmutableMultiset<E> copyOf(Iterator<? extends E> elements) { 188 Multiset<E> multiset = LinkedHashMultiset.create(); 189 Iterators.addAll(multiset, elements); 190 return copyFromEntries(multiset.entrySet()); 191 } 192 193 ImmutableMultiset() {} 194 195 @Override 196 public UnmodifiableIterator<E> iterator() { 197 final Iterator<Entry<E>> entryIterator = entrySet().iterator(); 198 return new UnmodifiableIterator<E>() { 199 int remaining; 200 E element; 201 202 @Override 203 public boolean hasNext() { 204 return (remaining > 0) || entryIterator.hasNext(); 205 } 206 207 @Override 208 public E next() { 209 if (remaining <= 0) { 210 Entry<E> entry = entryIterator.next(); 211 element = entry.getElement(); 212 remaining = entry.getCount(); 213 } 214 remaining--; 215 return element; 216 } 217 }; 218 } 219 220 @LazyInit 221 private transient ImmutableList<E> asList; 222 223 @Override 224 public ImmutableList<E> asList() { 225 ImmutableList<E> result = asList; 226 return (result == null) ? asList = createAsList() : result; 227 } 228 229 ImmutableList<E> createAsList() { 230 if (isEmpty()) { 231 return ImmutableList.of(); 232 } 233 return new RegularImmutableAsList<E>(this, toArray()); 234 } 235 236 @Override 237 public boolean contains(@Nullable Object object) { 238 return count(object) > 0; 239 } 240 241 /** 242 * Guaranteed to throw an exception and leave the collection unmodified. 243 * 244 * @throws UnsupportedOperationException always 245 * @deprecated Unsupported operation. 246 */ 247 @CanIgnoreReturnValue 248 @Deprecated 249 @Override 250 public final int add(E element, int occurrences) { 251 throw new UnsupportedOperationException(); 252 } 253 254 /** 255 * Guaranteed to throw an exception and leave the collection unmodified. 256 * 257 * @throws UnsupportedOperationException always 258 * @deprecated Unsupported operation. 259 */ 260 @CanIgnoreReturnValue 261 @Deprecated 262 @Override 263 public final int remove(Object element, int occurrences) { 264 throw new UnsupportedOperationException(); 265 } 266 267 /** 268 * Guaranteed to throw an exception and leave the collection unmodified. 269 * 270 * @throws UnsupportedOperationException always 271 * @deprecated Unsupported operation. 272 */ 273 @CanIgnoreReturnValue 274 @Deprecated 275 @Override 276 public final int setCount(E element, int count) { 277 throw new UnsupportedOperationException(); 278 } 279 280 /** 281 * Guaranteed to throw an exception and leave the collection unmodified. 282 * 283 * @throws UnsupportedOperationException always 284 * @deprecated Unsupported operation. 285 */ 286 @CanIgnoreReturnValue 287 @Deprecated 288 @Override 289 public final boolean setCount(E element, int oldCount, int newCount) { 290 throw new UnsupportedOperationException(); 291 } 292 293 @GwtIncompatible // not present in emulated superclass 294 @Override 295 int copyIntoArray(Object[] dst, int offset) { 296 for (Multiset.Entry<E> entry : entrySet()) { 297 Arrays.fill(dst, offset, offset + entry.getCount(), entry.getElement()); 298 offset += entry.getCount(); 299 } 300 return offset; 301 } 302 303 @Override 304 public boolean equals(@Nullable Object object) { 305 return Multisets.equalsImpl(this, object); 306 } 307 308 @Override 309 public int hashCode() { 310 return Sets.hashCodeImpl(entrySet()); 311 } 312 313 @Override 314 public String toString() { 315 return entrySet().toString(); 316 } 317 318 @LazyInit 319 private transient ImmutableSet<Entry<E>> entrySet; 320 321 @Override 322 public ImmutableSet<Entry<E>> entrySet() { 323 ImmutableSet<Entry<E>> es = entrySet; 324 return (es == null) ? (entrySet = createEntrySet()) : es; 325 } 326 327 private final ImmutableSet<Entry<E>> createEntrySet() { 328 return isEmpty() ? ImmutableSet.<Entry<E>>of() : new EntrySet(); 329 } 330 331 abstract Entry<E> getEntry(int index); 332 333 @WeakOuter 334 private final class EntrySet extends ImmutableSet.Indexed<Entry<E>> { 335 @Override 336 boolean isPartialView() { 337 return ImmutableMultiset.this.isPartialView(); 338 } 339 340 @Override 341 Entry<E> get(int index) { 342 return getEntry(index); 343 } 344 345 @Override 346 public int size() { 347 return elementSet().size(); 348 } 349 350 @Override 351 public boolean contains(Object o) { 352 if (o instanceof Entry) { 353 Entry<?> entry = (Entry<?>) o; 354 if (entry.getCount() <= 0) { 355 return false; 356 } 357 int count = count(entry.getElement()); 358 return count == entry.getCount(); 359 } 360 return false; 361 } 362 363 @Override 364 public int hashCode() { 365 return ImmutableMultiset.this.hashCode(); 366 } 367 368 // We can't label this with @Override, because it doesn't override anything 369 // in the GWT emulated version. 370 // TODO(cpovirk): try making all copies of this method @GwtIncompatible instead 371 Object writeReplace() { 372 return new EntrySetSerializedForm<E>(ImmutableMultiset.this); 373 } 374 375 private static final long serialVersionUID = 0; 376 } 377 378 static class EntrySetSerializedForm<E> implements Serializable { 379 final ImmutableMultiset<E> multiset; 380 381 EntrySetSerializedForm(ImmutableMultiset<E> multiset) { 382 this.multiset = multiset; 383 } 384 385 Object readResolve() { 386 return multiset.entrySet(); 387 } 388 } 389 390 private static class SerializedForm implements Serializable { 391 final Object[] elements; 392 final int[] counts; 393 394 SerializedForm(Multiset<?> multiset) { 395 int distinct = multiset.entrySet().size(); 396 elements = new Object[distinct]; 397 counts = new int[distinct]; 398 int i = 0; 399 for (Entry<?> entry : multiset.entrySet()) { 400 elements[i] = entry.getElement(); 401 counts[i] = entry.getCount(); 402 i++; 403 } 404 } 405 406 Object readResolve() { 407 LinkedHashMultiset<Object> multiset = LinkedHashMultiset.create(elements.length); 408 for (int i = 0; i < elements.length; i++) { 409 multiset.add(elements[i], counts[i]); 410 } 411 return ImmutableMultiset.copyOf(multiset); 412 } 413 414 private static final long serialVersionUID = 0; 415 } 416 417 // We can't label this with @Override, because it doesn't override anything 418 // in the GWT emulated version. 419 Object writeReplace() { 420 return new SerializedForm(this); 421 } 422 423 /** 424 * Returns a new builder. The generated builder is equivalent to the builder 425 * created by the {@link Builder} constructor. 426 */ 427 public static <E> Builder<E> builder() { 428 return new Builder<E>(); 429 } 430 431 /** 432 * A builder for creating immutable multiset instances, especially {@code 433 * public static final} multisets ("constant multisets"). Example: 434 * <pre> {@code 435 * 436 * public static final ImmutableMultiset<Bean> BEANS = 437 * new ImmutableMultiset.Builder<Bean>() 438 * .addCopies(Bean.COCOA, 4) 439 * .addCopies(Bean.GARDEN, 6) 440 * .addCopies(Bean.RED, 8) 441 * .addCopies(Bean.BLACK_EYED, 10) 442 * .build();}</pre> 443 * 444 * <p>Builder instances can be reused; it is safe to call {@link #build} multiple 445 * times to build multiple multisets in series. 446 * 447 * @since 2.0 448 */ 449 public static class Builder<E> extends ImmutableCollection.Builder<E> { 450 final Multiset<E> contents; 451 452 /** 453 * Creates a new builder. The returned builder is equivalent to the builder 454 * generated by {@link ImmutableMultiset#builder}. 455 */ 456 public Builder() { 457 this(LinkedHashMultiset.<E>create()); 458 } 459 460 Builder(Multiset<E> contents) { 461 this.contents = contents; 462 } 463 464 /** 465 * Adds {@code element} to the {@code ImmutableMultiset}. 466 * 467 * @param element the element to add 468 * @return this {@code Builder} object 469 * @throws NullPointerException if {@code element} is null 470 */ 471 @CanIgnoreReturnValue 472 @Override 473 public Builder<E> add(E element) { 474 contents.add(checkNotNull(element)); 475 return this; 476 } 477 478 /** 479 * Adds a number of occurrences of an element to this {@code 480 * ImmutableMultiset}. 481 * 482 * @param element the element to add 483 * @param occurrences the number of occurrences of the element to add. May 484 * be zero, in which 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 488 * if this operation would result in more than {@link Integer#MAX_VALUE} 489 * occurrences of the element 490 */ 491 @CanIgnoreReturnValue 492 public Builder<E> addCopies(E element, int occurrences) { 493 contents.add(checkNotNull(element), occurrences); 494 return this; 495 } 496 497 /** 498 * Adds or removes the necessary occurrences of an element such that the 499 * element attains the desired count. 500 * 501 * @param element the element to add or remove occurrences of 502 * @param count the desired count of the element in this multiset 503 * @return this {@code Builder} object 504 * @throws NullPointerException if {@code element} is null 505 * @throws IllegalArgumentException if {@code count} is negative 506 */ 507 @CanIgnoreReturnValue 508 public Builder<E> setCount(E element, int count) { 509 contents.setCount(checkNotNull(element), count); 510 return this; 511 } 512 513 /** 514 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 515 * 516 * @param elements the elements to add 517 * @return this {@code Builder} object 518 * @throws NullPointerException if {@code elements} is null or contains a 519 * null element 520 */ 521 @CanIgnoreReturnValue 522 @Override 523 public Builder<E> add(E... elements) { 524 super.add(elements); 525 return this; 526 } 527 528 /** 529 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 530 * 531 * @param elements the {@code Iterable} to add to the {@code 532 * ImmutableMultiset} 533 * @return this {@code Builder} object 534 * @throws NullPointerException if {@code elements} is null or contains a 535 * null element 536 */ 537 @CanIgnoreReturnValue 538 @Override 539 public Builder<E> addAll(Iterable<? extends E> elements) { 540 if (elements instanceof Multiset) { 541 Multiset<? extends E> multiset = Multisets.cast(elements); 542 for (Entry<? extends E> entry : multiset.entrySet()) { 543 addCopies(entry.getElement(), entry.getCount()); 544 } 545 } else { 546 super.addAll(elements); 547 } 548 return this; 549 } 550 551 /** 552 * Adds each element of {@code elements} to the {@code ImmutableMultiset}. 553 * 554 * @param elements the elements to add to the {@code ImmutableMultiset} 555 * @return this {@code Builder} object 556 * @throws NullPointerException if {@code elements} is null or contains a 557 * 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 * Returns a newly-created {@code ImmutableMultiset} based on the contents 568 * of the {@code Builder}. 569 */ 570 @Override 571 public ImmutableMultiset<E> build() { 572 return copyOf(contents); 573 } 574 } 575}