• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 Google Inc.
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 com.google.common.annotations.GwtCompatible;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import com.google.common.collect.Serialization.FieldSetter;
22 
23 import java.io.IOException;
24 import java.io.InvalidObjectException;
25 import java.io.ObjectInputStream;
26 import java.io.ObjectOutputStream;
27 import java.util.Arrays;
28 import java.util.Iterator;
29 import java.util.Map;
30 import java.util.Set;
31 
32 import javax.annotation.Nullable;
33 
34 /**
35  * An immutable hash-based multiset. Does not permit null elements.
36  *
37  * <p>Its iterator orders elements according to the first appearance of the
38  * element among the items passed to the factory method or builder. When the
39  * multiset contains multiple instances of an element, those instances are
40  * consecutive in the iteration order.
41  *
42  * @author Jared Levy
43  * @since 2010.01.04 <b>stable</b> (imported from Google Collections Library)
44  */
45 @GwtCompatible(serializable = true)
46 public class ImmutableMultiset<E> extends ImmutableCollection<E>
47     implements Multiset<E> {
48 
49   /**
50    * Returns the empty immutable multiset.
51    */
52   @SuppressWarnings("unchecked") // all supported methods are covariant
of()53   public static <E> ImmutableMultiset<E> of() {
54     // BEGIN android-changed
55     return (ImmutableMultiset) EmptyImmutableMultiset.INSTANCE;
56     // END android-changed
57   }
58 
59   /**
60    * Returns an immutable multiset containing the given elements.
61    *
62    * <p>The multiset is ordered by the first occurrence of each element. For
63    * example, {@code ImmutableMultiset.of(2, 3, 1, 3)} yields a multiset with
64    * elements in the order {@code 2, 3, 3, 1}.
65    *
66    * @throws NullPointerException if any of {@code elements} is null
67    */
of(E... elements)68   public static <E> ImmutableMultiset<E> of(E... elements) {
69     return copyOf(Arrays.asList(elements));
70   }
71 
72   /**
73    * Returns an immutable multiset containing the given elements.
74    *
75    * <p>The multiset is ordered by the first occurrence of each element. For
76    * example, {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3))} yields
77    * a multiset with elements in the order {@code 2, 3, 3, 1}.
78    *
79    * <p>Note that if {@code c} is a {@code Collection<String>}, then {@code
80    * ImmutableMultiset.copyOf(c)} returns an {@code ImmutableMultiset<String>}
81    * containing each of the strings in {@code c}, while
82    * {@code ImmutableMultiset.of(c)} returns an
83    * {@code ImmutableMultiset<Collection<String>>} containing one element (the
84    * given collection itself).
85    *
86    * <p><b>Note:</b> Despite what the method name suggests, if {@code elements}
87    * is an {@code ImmutableMultiset}, no copy will actually be performed, and
88    * the given multiset itself will be returned.
89    *
90    * @throws NullPointerException if any of {@code elements} is null
91    */
copyOf( Iterable<? extends E> elements)92   public static <E> ImmutableMultiset<E> copyOf(
93       Iterable<? extends E> elements) {
94     if (elements instanceof ImmutableMultiset) {
95       @SuppressWarnings("unchecked") // all supported methods are covariant
96       ImmutableMultiset<E> result = (ImmutableMultiset<E>) elements;
97       return result;
98     }
99 
100     @SuppressWarnings("unchecked") // the cast causes a warning
101     Multiset<? extends E> multiset = (elements instanceof Multiset)
102         ? (Multiset<? extends E>) elements
103         : LinkedHashMultiset.create(elements);
104 
105     return copyOfInternal(multiset);
106   }
107 
copyOfInternal( Multiset<? extends E> multiset)108   private static <E> ImmutableMultiset<E> copyOfInternal(
109       Multiset<? extends E> multiset) {
110     long size = 0;
111     ImmutableMap.Builder<E, Integer> builder = ImmutableMap.builder();
112 
113     for (Entry<? extends E> entry : multiset.entrySet()) {
114       int count = entry.getCount();
115       if (count > 0) {
116         // Since ImmutableMap.Builder throws an NPE if an element is null, no
117         // other null checks are needed.
118         builder.put(entry.getElement(), count);
119         size += count;
120       }
121     }
122 
123     if (size == 0) {
124       return of();
125     }
126     return new ImmutableMultiset<E>(
127         builder.build(), (int) Math.min(size, Integer.MAX_VALUE));
128   }
129 
130   /**
131    * Returns an immutable multiset containing the given elements.
132    *
133    * <p>The multiset is ordered by the first occurrence of each element. For
134    * example,
135    * {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3).iterator())}
136    * yields a multiset with elements in the order {@code 2, 3, 3, 1}.
137    *
138    * @throws NullPointerException if any of {@code elements} is null
139    */
copyOf( Iterator<? extends E> elements)140   public static <E> ImmutableMultiset<E> copyOf(
141       Iterator<? extends E> elements) {
142     Multiset<E> multiset = LinkedHashMultiset.create();
143     Iterators.addAll(multiset, elements);
144     return copyOfInternal(multiset);
145   }
146 
147   private final transient ImmutableMap<E, Integer> map;
148   private final transient int size;
149 
150   // These constants allow the deserialization code to set final fields. This
151   // holder class makes sure they are not initialized unless an instance is
152   // deserialized.
153   @SuppressWarnings("unchecked")
154   // eclipse doesn't like the raw types here, but they're harmless
155   private static class FieldSettersHolder {
156     static final FieldSetter<ImmutableMultiset> MAP_FIELD_SETTER
157         = Serialization.getFieldSetter(ImmutableMultiset.class, "map");
158     static final FieldSetter<ImmutableMultiset> SIZE_FIELD_SETTER
159         = Serialization.getFieldSetter(ImmutableMultiset.class, "size");
160   }
161 
ImmutableMultiset(ImmutableMap<E, Integer> map, int size)162   ImmutableMultiset(ImmutableMap<E, Integer> map, int size) {
163     this.map = map;
164     this.size = size;
165   }
166 
count(@ullable Object element)167   public int count(@Nullable Object element) {
168     Integer value = map.get(element);
169     return (value == null) ? 0 : value;
170   }
171 
iterator()172   @Override public UnmodifiableIterator<E> iterator() {
173     final Iterator<Map.Entry<E, Integer>> mapIterator
174         = map.entrySet().iterator();
175 
176     return new UnmodifiableIterator<E>() {
177       int remaining;
178       E element;
179 
180       public boolean hasNext() {
181         return (remaining > 0) || mapIterator.hasNext();
182       }
183 
184       public E next() {
185         if (remaining <= 0) {
186           Map.Entry<E, Integer> entry = mapIterator.next();
187           element = entry.getKey();
188           remaining = entry.getValue();
189         }
190         remaining--;
191         return element;
192       }
193     };
194   }
195 
size()196   public int size() {
197     return size;
198   }
199 
contains(@ullable Object element)200   @Override public boolean contains(@Nullable Object element) {
201     return map.containsKey(element);
202   }
203 
204   /**
205    * Guaranteed to throw an exception and leave the collection unmodified.
206    *
207    * @throws UnsupportedOperationException always
208    */
add(E element, int occurrences)209   public int add(E element, int occurrences) {
210     throw new UnsupportedOperationException();
211   }
212 
213   /**
214    * Guaranteed to throw an exception and leave the collection unmodified.
215    *
216    * @throws UnsupportedOperationException always
217    */
remove(Object element, int occurrences)218   public int remove(Object element, int occurrences) {
219     throw new UnsupportedOperationException();
220   }
221 
222   /**
223    * Guaranteed to throw an exception and leave the collection unmodified.
224    *
225    * @throws UnsupportedOperationException always
226    */
setCount(E element, int count)227   public int setCount(E element, int count) {
228     throw new UnsupportedOperationException();
229   }
230 
231   /**
232    * Guaranteed to throw an exception and leave the collection unmodified.
233    *
234    * @throws UnsupportedOperationException always
235    */
setCount(E element, int oldCount, int newCount)236   public boolean setCount(E element, int oldCount, int newCount) {
237     throw new UnsupportedOperationException();
238   }
239 
equals(@ullable Object object)240   @Override public boolean equals(@Nullable Object object) {
241     if (object == this) {
242       return true;
243     }
244     if (object instanceof Multiset) {
245       Multiset<?> that = (Multiset<?>) object;
246       if (this.size() != that.size()) {
247         return false;
248       }
249       for (Entry<?> entry : that.entrySet()) {
250         if (count(entry.getElement()) != entry.getCount()) {
251           return false;
252         }
253       }
254       return true;
255     }
256     return false;
257   }
258 
hashCode()259   @Override public int hashCode() {
260     // could cache this, but not considered worthwhile to do so
261     return map.hashCode();
262   }
263 
toString()264   @Override public String toString() {
265     return entrySet().toString();
266   }
267 
268   // TODO: Serialization of the element set should serialize the multiset, and
269   // deserialization should call multiset.elementSet(). Then
270   // reserialized(multiset).elementSet() == reserialized(multiset.elementSet())
271   // Currently, those object references differ.
elementSet()272   public Set<E> elementSet() {
273     return map.keySet();
274   }
275 
276   private transient ImmutableSet<Entry<E>> entrySet;
277 
entrySet()278   public Set<Entry<E>> entrySet() {
279     ImmutableSet<Entry<E>> es = entrySet;
280     return (es == null) ? (entrySet = new EntrySet<E>(this)) : es;
281   }
282 
283   private static class EntrySet<E> extends ImmutableSet<Entry<E>> {
284     final ImmutableMultiset<E> multiset;
285 
EntrySet(ImmutableMultiset<E> multiset)286     public EntrySet(ImmutableMultiset<E> multiset) {
287       this.multiset = multiset;
288     }
289 
iterator()290     @Override public UnmodifiableIterator<Entry<E>> iterator() {
291       final Iterator<Map.Entry<E, Integer>> mapIterator
292           = multiset.map.entrySet().iterator();
293       return new UnmodifiableIterator<Entry<E>>() {
294         public boolean hasNext() {
295           return mapIterator.hasNext();
296         }
297         public Entry<E> next() {
298           Map.Entry<E, Integer> mapEntry = mapIterator.next();
299           return
300               Multisets.immutableEntry(mapEntry.getKey(), mapEntry.getValue());
301         }
302       };
303     }
304 
305     public int size() {
306       return multiset.map.size();
307     }
308 
309     @Override public boolean contains(Object o) {
310       if (o instanceof Entry) {
311         Entry<?> entry = (Entry<?>) o;
312         if (entry.getCount() <= 0) {
313           return false;
314         }
315         int count = multiset.count(entry.getElement());
316         return count == entry.getCount();
317       }
318       return false;
319     }
320 
321     @Override public int hashCode() {
322       return multiset.map.hashCode();
323     }
324 
325     @Override Object writeReplace() {
326       return this;
327     }
328 
329     private static final long serialVersionUID = 0;
330   }
331 
332   /**
333    * @serialData the number of distinct elements, the first element, its count,
334    *     the second element, its count, and so on
335    */
336   private void writeObject(ObjectOutputStream stream) throws IOException {
337     stream.defaultWriteObject();
338     Serialization.writeMultiset(this, stream);
339   }
340 
341   private void readObject(ObjectInputStream stream)
342       throws IOException, ClassNotFoundException {
343     stream.defaultReadObject();
344     int entryCount = stream.readInt();
345     ImmutableMap.Builder<E, Integer> builder = ImmutableMap.builder();
346     long tmpSize = 0;
347     for (int i = 0; i < entryCount; i++) {
348       @SuppressWarnings("unchecked") // reading data stored by writeMultiset
349       E element = (E) stream.readObject();
350       int count = stream.readInt();
351       if (count <= 0) {
352         throw new InvalidObjectException("Invalid count " + count);
353       }
354       builder.put(element, count);
355       tmpSize += count;
356     }
357 
358     FieldSettersHolder.MAP_FIELD_SETTER.set(this, builder.build());
359     FieldSettersHolder.SIZE_FIELD_SETTER.set(
360         this, (int) Math.min(tmpSize, Integer.MAX_VALUE));
361   }
362 
363   @Override Object writeReplace() {
364     return this;
365   }
366 
367   private static final long serialVersionUID = 0;
368 
369   /**
370    * Returns a new builder. The generated builder is equivalent to the builder
371    * created by the {@link Builder} constructor.
372    */
373   public static <E> Builder<E> builder() {
374     return new Builder<E>();
375   }
376 
377   /**
378    * A builder for creating immutable multiset instances, especially
379    * {@code public static final} multisets ("constant multisets").
380    *
381    * <p>Example:
382    * <pre>   {@code
383    *   public static final ImmutableMultiset<Bean> BEANS
384    *       = new ImmutableMultiset.Builder<Bean>()
385    *           .addCopies(Bean.COCOA, 4)
386    *           .addCopies(Bean.GARDEN, 6)
387    *           .addCopies(Bean.RED, 8)
388    *           .addCopies(Bean.BLACK_EYED, 10)
389    *           .build();}</pre>
390    *
391    * <p>Builder instances can be reused - it is safe to call {@link #build}
392    * multiple times to build multiple multisets in series. Each multiset
393    * is a superset of the multiset created before it.
394    */
395   public static final class Builder<E> extends ImmutableCollection.Builder<E> {
396     private final Multiset<E> contents = LinkedHashMultiset.create();
397 
398     /**
399      * Creates a new builder. The returned builder is equivalent to the builder
400      * generated by {@link ImmutableMultiset#builder}.
401      */
402     public Builder() {}
403 
404     /**
405      * Adds {@code element} to the {@code ImmutableMultiset}.
406      *
407      * @param element the element to add
408      * @return this {@code Builder} object
409      * @throws NullPointerException if {@code element} is null
410      */
411     @Override public Builder<E> add(E element) {
412       contents.add(checkNotNull(element));
413       return this;
414     }
415 
416     /**
417      * Adds a number of occurrences of an element to this {@code
418      * ImmutableMultiset}.
419      *
420      * @param element the element to add
421      * @param occurrences the number of occurrences of the element to add. May
422      *     be zero, in which case no change will be made.
423      * @return this {@code Builder} object
424      * @throws NullPointerException if {@code element} is null
425      * @throws IllegalArgumentException if {@code occurrences} is negative, or
426      *     if this operation would result in more than {@link Integer#MAX_VALUE}
427      *     occurrences of the element
428      */
429     public Builder<E> addCopies(E element, int occurrences) {
430       contents.add(checkNotNull(element), occurrences);
431       return this;
432     }
433 
434     /**
435      * Adds or removes the necessary occurrences of an element such that the
436      * element attains the desired count.
437      *
438      * @param element the element to add or remove occurrences of
439      * @param count the desired count of the element in this multiset
440      * @return this {@code Builder} object
441      * @throws NullPointerException if {@code element} is null
442      * @throws IllegalArgumentException if {@code count} is negative
443      */
444     public Builder<E> setCount(E element, int count) {
445       contents.setCount(checkNotNull(element), count);
446       return this;
447     }
448 
449     /**
450      * Adds each element of {@code elements} to the {@code ImmutableMultiset}.
451      *
452      * @param elements the elements to add
453      * @return this {@code Builder} object
454      * @throws NullPointerException if {@code elements} is null or contains a
455      *     null element
456      */
457     @Override public Builder<E> add(E... elements) {
458       super.add(elements);
459       return this;
460     }
461 
462     /**
463      * Adds each element of {@code elements} to the {@code ImmutableMultiset}.
464      *
465      * @param elements the {@code Iterable} to add to the {@code
466      *     ImmutableMultiset}
467      * @return this {@code Builder} object
468      * @throws NullPointerException if {@code elements} is null or contains a
469      *     null element
470      */
471     @Override public Builder<E> addAll(Iterable<? extends E> elements) {
472       if (elements instanceof Multiset) {
473         @SuppressWarnings("unchecked")
474         Multiset<? extends E> multiset = (Multiset<? extends E>) elements;
475         for (Entry<? extends E> entry : multiset.entrySet()) {
476           addCopies(entry.getElement(), entry.getCount());
477         }
478       } else {
479         super.addAll(elements);
480       }
481       return this;
482     }
483 
484     /**
485      * Adds each element of {@code elements} to the {@code ImmutableMultiset}.
486      *
487      * @param elements the elements to add to the {@code ImmutableMultiset}
488      * @return this {@code Builder} object
489      * @throws NullPointerException if {@code elements} is null or contains a
490      *     null element
491      */
492     @Override public Builder<E> addAll(Iterator<? extends E> elements) {
493       super.addAll(elements);
494       return this;
495     }
496 
497     /**
498      * Returns a newly-created {@code ImmutableMultiset} based on the contents
499      * of the {@code Builder}.
500      */
501     @Override public ImmutableMultiset<E> build() {
502       return copyOf(contents);
503     }
504   }
505 }
506