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