View Javadoc
1   /*
2    * Copyright (C) 2008 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.collect;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  
21  import com.google.common.annotations.GwtCompatible;
22  import com.google.common.annotations.GwtIncompatible;
23  import com.google.common.collect.Multiset.Entry;
24  import com.google.common.primitives.Ints;
25  
26  import java.io.Serializable;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Iterator;
30  
31  import javax.annotation.Nullable;
32  
33  /**
34   * An immutable hash-based multiset. Does not permit null elements.
35   *
36   * <p>Its iterator orders elements according to the first appearance of the
37   * element among the items passed to the factory method or builder. When the
38   * multiset contains multiple instances of an element, those instances are
39   * consecutive in the iteration order.
40   *
41   * <p>See the Guava User Guide article on <a href=
42   * "http://code.google.com/p/guava-libraries/wiki/ImmutableCollectionsExplained">
43   * immutable collections</a>.
44   *
45   * @author Jared Levy
46   * @author Louis Wasserman
47   * @since 2.0 (imported from Google Collections Library)
48   */
49  @GwtCompatible(serializable = true, emulated = true)
50  @SuppressWarnings("serial") // we're overriding default serialization
51  // TODO(user): write an efficient asList() implementation
52  public abstract class ImmutableMultiset<E> extends ImmutableCollection<E>
53      implements Multiset<E> {
54  
55    private static final ImmutableMultiset<Object> EMPTY =
56        new RegularImmutableMultiset<Object>(ImmutableMap.<Object, Integer>of(), 0);
57  
58    /**
59     * Returns the empty immutable multiset.
60     */
61    @SuppressWarnings("unchecked") // all supported methods are covariant
62    public static <E> ImmutableMultiset<E> of() {
63      return (ImmutableMultiset<E>) EMPTY;
64    }
65  
66    /**
67     * Returns an immutable multiset containing a single element.
68     *
69     * @throws NullPointerException if {@code element} is null
70     * @since 6.0 (source-compatible since 2.0)
71     */
72    @SuppressWarnings("unchecked") // generic array created but never written
73    public static <E> ImmutableMultiset<E> of(E element) {
74      return copyOfInternal(element);
75    }
76  
77    /**
78     * Returns an immutable multiset containing the given elements, in order.
79     *
80     * @throws NullPointerException if any element is null
81     * @since 6.0 (source-compatible since 2.0)
82     */
83    @SuppressWarnings("unchecked") //
84    public static <E> ImmutableMultiset<E> of(E e1, E e2) {
85      return copyOfInternal(e1, e2);
86    }
87  
88    /**
89     * Returns an immutable multiset containing the given elements, in order.
90     *
91     * @throws NullPointerException if any element is null
92     * @since 6.0 (source-compatible since 2.0)
93     */
94    @SuppressWarnings("unchecked") //
95    public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3) {
96      return copyOfInternal(e1, e2, e3);
97    }
98  
99    /**
100    * Returns an immutable multiset containing the given elements, in order.
101    *
102    * @throws NullPointerException if any element is null
103    * @since 6.0 (source-compatible since 2.0)
104    */
105   @SuppressWarnings("unchecked") //
106   public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4) {
107     return copyOfInternal(e1, e2, e3, e4);
108   }
109 
110   /**
111    * Returns an immutable multiset containing the given elements, in order.
112    *
113    * @throws NullPointerException if any element is null
114    * @since 6.0 (source-compatible since 2.0)
115    */
116   @SuppressWarnings("unchecked") //
117   public static <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4, E e5) {
118     return copyOfInternal(e1, e2, e3, e4, e5);
119   }
120 
121   /**
122    * Returns an immutable multiset containing the given elements, in order.
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(
129       E e1, E e2, E e3, E e4, E e5, E e6, E... others) {
130     return new Builder<E>()
131         .add(e1)
132         .add(e2)
133         .add(e3)
134         .add(e4)
135         .add(e5)
136         .add(e6)
137         .add(others)
138         .build();
139   }
140 
141   /**
142    * Returns an immutable multiset containing the given elements.
143    *
144    * <p>The multiset is ordered by the first occurrence of each element. For
145    * example, {@code ImmutableMultiset.copyOf([2, 3, 1, 3])} yields a multiset
146    * with elements in the order {@code 2, 3, 3, 1}.
147    *
148    * @throws NullPointerException if any of {@code elements} is null
149    * @since 6.0
150    */
151   public static <E> ImmutableMultiset<E> copyOf(E[] elements) {
152     return copyOf(Arrays.asList(elements));
153   }
154 
155   /**
156    * Returns an immutable multiset containing the given elements.
157    *
158    * <p>The multiset is ordered by the first occurrence of each element. For
159    * example, {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3))} yields
160    * a multiset with elements in the order {@code 2, 3, 3, 1}.
161    *
162    * <p>Despite the method name, this method attempts to avoid actually copying
163    * the data when it is safe to do so. The exact circumstances under which a
164    * copy will or will not be performed are undocumented and subject to change.
165    *
166    * <p><b>Note:</b> Despite what the method name suggests, if {@code elements}
167    * is an {@code ImmutableMultiset}, no copy will actually be performed, and
168    * the given multiset itself will be returned.
169    *
170    * @throws NullPointerException if any of {@code elements} is null
171    */
172   public static <E> ImmutableMultiset<E> copyOf(
173       Iterable<? extends E> elements) {
174     if (elements instanceof ImmutableMultiset) {
175       @SuppressWarnings("unchecked") // all supported methods are covariant
176       ImmutableMultiset<E> result = (ImmutableMultiset<E>) elements;
177       if (!result.isPartialView()) {
178         return result;
179       }
180     }
181 
182     Multiset<? extends E> multiset = (elements instanceof Multiset)
183         ? Multisets.cast(elements)
184         : LinkedHashMultiset.create(elements);
185 
186     return copyOfInternal(multiset);
187   }
188 
189   private static <E> ImmutableMultiset<E> copyOfInternal(E... elements) {
190     return copyOf(Arrays.asList(elements));
191   }
192 
193   private static <E> ImmutableMultiset<E> copyOfInternal(
194       Multiset<? extends E> multiset) {
195     return copyFromEntries(multiset.entrySet());
196   }
197 
198   static <E> ImmutableMultiset<E> copyFromEntries(
199       Collection<? extends Entry<? extends E>> entries) {
200     long size = 0;
201     ImmutableMap.Builder<E, Integer> builder = ImmutableMap.builder();
202     for (Entry<? extends E> entry : entries) {
203       int count = entry.getCount();
204       if (count > 0) {
205         // Since ImmutableMap.Builder throws an NPE if an element is null, no
206         // other null checks are needed.
207         builder.put(entry.getElement(), count);
208         size += count;
209       }
210     }
211 
212     if (size == 0) {
213       return of();
214     }
215     return new RegularImmutableMultiset<E>(
216         builder.build(), Ints.saturatedCast(size));
217   }
218 
219   /**
220    * Returns an immutable multiset containing the given elements.
221    *
222    * <p>The multiset is ordered by the first occurrence of each element. For
223    * example,
224    * {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3).iterator())}
225    * yields a multiset with elements in the order {@code 2, 3, 3, 1}.
226    *
227    * @throws NullPointerException if any of {@code elements} is null
228    */
229   public static <E> ImmutableMultiset<E> copyOf(
230       Iterator<? extends E> elements) {
231     Multiset<E> multiset = LinkedHashMultiset.create();
232     Iterators.addAll(multiset, elements);
233     return copyOfInternal(multiset);
234   }
235 
236   ImmutableMultiset() {}
237 
238   @Override public UnmodifiableIterator<E> iterator() {
239     final Iterator<Entry<E>> entryIterator = entrySet().iterator();
240     return new UnmodifiableIterator<E>() {
241       int remaining;
242       E element;
243 
244       @Override
245       public boolean hasNext() {
246         return (remaining > 0) || entryIterator.hasNext();
247       }
248 
249       @Override
250       public E next() {
251         if (remaining <= 0) {
252           Entry<E> entry = entryIterator.next();
253           element = entry.getElement();
254           remaining = entry.getCount();
255         }
256         remaining--;
257         return element;
258       }
259     };
260   }
261 
262   @Override
263   public boolean contains(@Nullable Object object) {
264     return count(object) > 0;
265   }
266 
267   @Override
268   public boolean containsAll(Collection<?> targets) {
269     return elementSet().containsAll(targets);
270   }
271 
272   /**
273    * Guaranteed to throw an exception and leave the collection unmodified.
274    *
275    * @throws UnsupportedOperationException always
276    * @deprecated Unsupported operation.
277    */
278   @Deprecated
279   @Override
280   public final int add(E element, int occurrences) {
281     throw new UnsupportedOperationException();
282   }
283 
284   /**
285    * Guaranteed to throw an exception and leave the collection unmodified.
286    *
287    * @throws UnsupportedOperationException always
288    * @deprecated Unsupported operation.
289    */
290   @Deprecated
291   @Override
292   public final int remove(Object element, int occurrences) {
293     throw new UnsupportedOperationException();
294   }
295 
296   /**
297    * Guaranteed to throw an exception and leave the collection unmodified.
298    *
299    * @throws UnsupportedOperationException always
300    * @deprecated Unsupported operation.
301    */
302   @Deprecated
303   @Override
304   public final int setCount(E element, int count) {
305     throw new UnsupportedOperationException();
306   }
307 
308   /**
309    * Guaranteed to throw an exception and leave the collection unmodified.
310    *
311    * @throws UnsupportedOperationException always
312    * @deprecated Unsupported operation.
313    */
314   @Deprecated
315   @Override
316   public final boolean setCount(E element, int oldCount, int newCount) {
317     throw new UnsupportedOperationException();
318   }
319 
320   @GwtIncompatible("not present in emulated superclass")
321   @Override
322   int copyIntoArray(Object[] dst, int offset) {
323     for (Multiset.Entry<E> entry : entrySet()) {
324       Arrays.fill(dst, offset, offset + entry.getCount(), entry.getElement());
325       offset += entry.getCount();
326     }
327     return offset;
328   }
329 
330   @Override public boolean equals(@Nullable Object object) {
331     return Multisets.equalsImpl(this, object);
332   }
333 
334   @Override public int hashCode() {
335     return Sets.hashCodeImpl(entrySet());
336   }
337 
338   @Override public String toString() {
339     return entrySet().toString();
340   }
341 
342   private transient ImmutableSet<Entry<E>> entrySet;
343 
344   @Override
345   public ImmutableSet<Entry<E>> entrySet() {
346     ImmutableSet<Entry<E>> es = entrySet;
347     return (es == null) ? (entrySet = createEntrySet()) : es;
348   }
349 
350   private final ImmutableSet<Entry<E>> createEntrySet() {
351     return isEmpty() ? ImmutableSet.<Entry<E>>of() : new EntrySet();
352   }
353 
354   abstract Entry<E> getEntry(int index);
355 
356   private final class EntrySet extends ImmutableSet<Entry<E>> {
357     @Override
358     boolean isPartialView() {
359       return ImmutableMultiset.this.isPartialView();
360     }
361 
362     @Override
363     public UnmodifiableIterator<Entry<E>> iterator() {
364       return asList().iterator();
365     }
366 
367     @Override
368     ImmutableList<Entry<E>> createAsList() {
369       return new ImmutableAsList<Entry<E>>() {
370         @Override
371         public Entry<E> get(int index) {
372           return getEntry(index);
373         }
374 
375         @Override
376         ImmutableCollection<Entry<E>> delegateCollection() {
377           return EntrySet.this;
378         }
379       };
380     }
381 
382     @Override
383     public int size() {
384       return elementSet().size();
385     }
386 
387     @Override
388     public boolean contains(Object o) {
389       if (o instanceof Entry) {
390         Entry<?> entry = (Entry<?>) o;
391         if (entry.getCount() <= 0) {
392           return false;
393         }
394         int count = count(entry.getElement());
395         return count == entry.getCount();
396       }
397       return false;
398     }
399 
400     @Override
401     public int hashCode() {
402       return ImmutableMultiset.this.hashCode();
403     }
404 
405     // We can't label this with @Override, because it doesn't override anything
406     // in the GWT emulated version.
407     // TODO(cpovirk): try making all copies of this method @GwtIncompatible instead
408     Object writeReplace() {
409       return new EntrySetSerializedForm<E>(ImmutableMultiset.this);
410     }
411 
412     private static final long serialVersionUID = 0;
413   }
414 
415   static class EntrySetSerializedForm<E> implements Serializable {
416     final ImmutableMultiset<E> multiset;
417 
418     EntrySetSerializedForm(ImmutableMultiset<E> multiset) {
419       this.multiset = multiset;
420     }
421 
422     Object readResolve() {
423       return multiset.entrySet();
424     }
425   }
426 
427   private static class SerializedForm implements Serializable {
428     final Object[] elements;
429     final int[] counts;
430 
431     SerializedForm(Multiset<?> multiset) {
432       int distinct = multiset.entrySet().size();
433       elements = new Object[distinct];
434       counts = new int[distinct];
435       int i = 0;
436       for (Entry<?> entry : multiset.entrySet()) {
437         elements[i] = entry.getElement();
438         counts[i] = entry.getCount();
439         i++;
440       }
441     }
442 
443     Object readResolve() {
444       LinkedHashMultiset<Object> multiset =
445           LinkedHashMultiset.create(elements.length);
446       for (int i = 0; i < elements.length; i++) {
447         multiset.add(elements[i], counts[i]);
448       }
449       return ImmutableMultiset.copyOf(multiset);
450     }
451 
452     private static final long serialVersionUID = 0;
453   }
454 
455   // We can't label this with @Override, because it doesn't override anything
456   // in the GWT emulated version.
457   Object writeReplace() {
458     return new SerializedForm(this);
459   }
460 
461   /**
462    * Returns a new builder. The generated builder is equivalent to the builder
463    * created by the {@link Builder} constructor.
464    */
465   public static <E> Builder<E> builder() {
466     return new Builder<E>();
467   }
468 
469   /**
470    * A builder for creating immutable multiset instances, especially {@code
471    * public static final} multisets ("constant multisets"). Example:
472    * <pre> {@code
473    *
474    *   public static final ImmutableMultiset<Bean> BEANS =
475    *       new ImmutableMultiset.Builder<Bean>()
476    *           .addCopies(Bean.COCOA, 4)
477    *           .addCopies(Bean.GARDEN, 6)
478    *           .addCopies(Bean.RED, 8)
479    *           .addCopies(Bean.BLACK_EYED, 10)
480    *           .build();}</pre>
481    *
482    * <p>Builder instances can be reused; it is safe to call {@link #build} multiple
483    * times to build multiple multisets in series.
484    *
485    * @since 2.0 (imported from Google Collections Library)
486    */
487   public static class Builder<E> extends ImmutableCollection.Builder<E> {
488     final Multiset<E> contents;
489 
490     /**
491      * Creates a new builder. The returned builder is equivalent to the builder
492      * generated by {@link ImmutableMultiset#builder}.
493      */
494     public Builder() {
495       this(LinkedHashMultiset.<E>create());
496     }
497 
498     Builder(Multiset<E> contents) {
499       this.contents = contents;
500     }
501 
502     /**
503      * Adds {@code element} to the {@code ImmutableMultiset}.
504      *
505      * @param element the element to add
506      * @return this {@code Builder} object
507      * @throws NullPointerException if {@code element} is null
508      */
509     @Override public Builder<E> add(E element) {
510       contents.add(checkNotNull(element));
511       return this;
512     }
513 
514     /**
515      * Adds a number of occurrences of an element to this {@code
516      * ImmutableMultiset}.
517      *
518      * @param element the element to add
519      * @param occurrences the number of occurrences of the element to add. May
520      *     be zero, in which case no change will be made.
521      * @return this {@code Builder} object
522      * @throws NullPointerException if {@code element} is null
523      * @throws IllegalArgumentException if {@code occurrences} is negative, or
524      *     if this operation would result in more than {@link Integer#MAX_VALUE}
525      *     occurrences of the element
526      */
527     public Builder<E> addCopies(E element, int occurrences) {
528       contents.add(checkNotNull(element), occurrences);
529       return this;
530     }
531 
532     /**
533      * Adds or removes the necessary occurrences of an element such that the
534      * element attains the desired count.
535      *
536      * @param element the element to add or remove occurrences of
537      * @param count the desired count of the element in this multiset
538      * @return this {@code Builder} object
539      * @throws NullPointerException if {@code element} is null
540      * @throws IllegalArgumentException if {@code count} is negative
541      */
542     public Builder<E> setCount(E element, int count) {
543       contents.setCount(checkNotNull(element), count);
544       return this;
545     }
546 
547     /**
548      * Adds each element of {@code elements} to the {@code ImmutableMultiset}.
549      *
550      * @param elements the elements to add
551      * @return this {@code Builder} object
552      * @throws NullPointerException if {@code elements} is null or contains a
553      *     null element
554      */
555     @Override public Builder<E> add(E... elements) {
556       super.add(elements);
557       return this;
558     }
559 
560     /**
561      * Adds each element of {@code elements} to the {@code ImmutableMultiset}.
562      *
563      * @param elements the {@code Iterable} to add to the {@code
564      *     ImmutableMultiset}
565      * @return this {@code Builder} object
566      * @throws NullPointerException if {@code elements} is null or contains a
567      *     null element
568      */
569     @Override public Builder<E> addAll(Iterable<? extends E> elements) {
570       if (elements instanceof Multiset) {
571         Multiset<? extends E> multiset = Multisets.cast(elements);
572         for (Entry<? extends E> entry : multiset.entrySet()) {
573           addCopies(entry.getElement(), entry.getCount());
574         }
575       } else {
576         super.addAll(elements);
577       }
578       return this;
579     }
580 
581     /**
582      * Adds each element of {@code elements} to the {@code ImmutableMultiset}.
583      *
584      * @param elements the elements to add to the {@code ImmutableMultiset}
585      * @return this {@code Builder} object
586      * @throws NullPointerException if {@code elements} is null or contains a
587      *     null element
588      */
589     @Override public Builder<E> addAll(Iterator<? extends E> elements) {
590       super.addAll(elements);
591       return this;
592     }
593 
594     /**
595      * Returns a newly-created {@code ImmutableMultiset} based on the contents
596      * of the {@code Builder}.
597      */
598     @Override public ImmutableMultiset<E> build() {
599       return copyOf(contents);
600     }
601   }
602 }