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