View Javadoc
1   /*
2    * Copyright (C) 2007 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.checkArgument;
20  import static com.google.common.collect.Sets.newHashSet;
21  import static com.google.common.collect.testing.Helpers.nefariousMapEntry;
22  import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE;
23  import static com.google.common.truth.Truth.assertThat;
24  import static java.util.Arrays.asList;
25  
26  import com.google.common.annotations.GwtCompatible;
27  import com.google.common.base.Function;
28  import com.google.common.base.Functions;
29  import com.google.common.base.Predicates;
30  import com.google.common.base.Supplier;
31  import com.google.common.collect.Maps.EntryTransformer;
32  import com.google.common.collect.testing.IteratorTester;
33  import com.google.common.collect.testing.google.UnmodifiableCollectionTests;
34  
35  import junit.framework.TestCase;
36  
37  import java.io.Serializable;
38  import java.util.Arrays;
39  import java.util.Collection;
40  import java.util.Collections;
41  import java.util.Comparator;
42  import java.util.HashMap;
43  import java.util.HashSet;
44  import java.util.Iterator;
45  import java.util.LinkedList;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.Map.Entry;
49  import java.util.Queue;
50  import java.util.RandomAccess;
51  import java.util.Set;
52  import java.util.SortedMap;
53  import java.util.SortedSet;
54  import java.util.TreeSet;
55  
56  import javax.annotation.Nullable;
57  
58  /**
59   * Unit test for {@code Multimaps}.
60   *
61   * @author Jared Levy
62   */
63  @GwtCompatible(emulated = true)
64  public class MultimapsTest extends TestCase {
65  
66    private static final Comparator<Integer> INT_COMPARATOR =
67        Ordering.<Integer>natural().reverse().nullsFirst();
68  
69    private static final EntryTransformer<Object, Object, Object> ALWAYS_NULL =
70        new EntryTransformer<Object, Object, Object>() {
71          @Override
72          public Object transformEntry(Object k, Object v1) {
73            return null;
74          }
75        };
76  
77    @SuppressWarnings("deprecation")
78    public void testUnmodifiableListMultimapShortCircuit() {
79      ListMultimap<String, Integer> mod = ArrayListMultimap.create();
80      ListMultimap<String, Integer> unmod = Multimaps.unmodifiableListMultimap(mod);
81      assertNotSame(mod, unmod);
82      assertSame(unmod, Multimaps.unmodifiableListMultimap(unmod));
83      ImmutableListMultimap<String, Integer> immutable =
84          ImmutableListMultimap.of("a", 1, "b", 2, "a", 3);
85      assertSame(immutable, Multimaps.unmodifiableListMultimap(immutable));
86      assertSame(
87          immutable, Multimaps.unmodifiableListMultimap((ListMultimap<String, Integer>) immutable));
88    }
89  
90    @SuppressWarnings("deprecation")
91    public void testUnmodifiableSetMultimapShortCircuit() {
92      SetMultimap<String, Integer> mod = HashMultimap.create();
93      SetMultimap<String, Integer> unmod = Multimaps.unmodifiableSetMultimap(mod);
94      assertNotSame(mod, unmod);
95      assertSame(unmod, Multimaps.unmodifiableSetMultimap(unmod));
96      ImmutableSetMultimap<String, Integer> immutable =
97          ImmutableSetMultimap.of("a", 1, "b", 2, "a", 3);
98      assertSame(immutable, Multimaps.unmodifiableSetMultimap(immutable));
99      assertSame(
100         immutable, Multimaps.unmodifiableSetMultimap((SetMultimap<String, Integer>) immutable));
101   }
102 
103   @SuppressWarnings("deprecation")
104   public void testUnmodifiableMultimapShortCircuit() {
105     Multimap<String, Integer> mod = HashMultimap.create();
106     Multimap<String, Integer> unmod = Multimaps.unmodifiableMultimap(mod);
107     assertNotSame(mod, unmod);
108     assertSame(unmod, Multimaps.unmodifiableMultimap(unmod));
109     ImmutableMultimap<String, Integer> immutable = ImmutableMultimap.of("a", 1, "b", 2, "a", 3);
110     assertSame(immutable, Multimaps.unmodifiableMultimap(immutable));
111     assertSame(immutable, Multimaps.unmodifiableMultimap((Multimap<String, Integer>) immutable));
112   }
113 
114   public void testUnmodifiableArrayListMultimapRandomAccess() {
115     ListMultimap<String, Integer> delegate = ArrayListMultimap.create();
116     delegate.put("foo", 1);
117     delegate.put("foo", 3);
118     ListMultimap<String, Integer> multimap
119         = Multimaps.unmodifiableListMultimap(delegate);
120     assertTrue(multimap.get("foo") instanceof RandomAccess);
121     assertTrue(multimap.get("bar") instanceof RandomAccess);
122   }
123 
124   public void testUnmodifiableLinkedListMultimapRandomAccess() {
125     ListMultimap<String, Integer> delegate = LinkedListMultimap.create();
126     delegate.put("foo", 1);
127     delegate.put("foo", 3);
128     ListMultimap<String, Integer> multimap
129         = Multimaps.unmodifiableListMultimap(delegate);
130     assertFalse(multimap.get("foo") instanceof RandomAccess);
131     assertFalse(multimap.get("bar") instanceof RandomAccess);
132   }
133 
134   public void testUnmodifiableMultimapIsView() {
135     Multimap<String, Integer> mod = HashMultimap.create();
136     Multimap<String, Integer> unmod = Multimaps.unmodifiableMultimap(mod);
137     assertEquals(mod, unmod);
138     mod.put("foo", 1);
139     assertTrue(unmod.containsEntry("foo", 1));
140     assertEquals(mod, unmod);
141   }
142 
143   @SuppressWarnings("unchecked")
144   public void testUnmodifiableMultimapEntries() {
145     Multimap<String, Integer> mod = HashMultimap.create();
146     Multimap<String, Integer> unmod = Multimaps.unmodifiableMultimap(mod);
147     mod.put("foo", 1);
148     Entry<String, Integer> entry = unmod.entries().iterator().next();
149     try {
150       entry.setValue(2);
151       fail("UnsupportedOperationException expected");
152     } catch (UnsupportedOperationException expected) {}
153     entry = (Entry<String, Integer>) unmod.entries().toArray()[0];
154     try {
155       entry.setValue(2);
156       fail("UnsupportedOperationException expected");
157     } catch (UnsupportedOperationException expected) {}
158     Entry<String, Integer>[] array
159         = (Entry<String, Integer>[]) new Entry<?, ?>[2];
160     assertSame(array, unmod.entries().toArray(array));
161     try {
162       array[0].setValue(2);
163       fail("UnsupportedOperationException expected");
164     } catch (UnsupportedOperationException expected) {}
165     assertFalse(unmod.entries().contains(nefariousMapEntry("pwnd", 2)));
166     assertFalse(unmod.keys().contains("pwnd"));
167   }
168 
169   /**
170    * The supplied multimap will be mutated and an unmodifiable instance used
171    * in its stead. The multimap must support null keys and values.
172    */
173   private static void checkUnmodifiableMultimap(
174       Multimap<String, Integer> multimap, boolean permitsDuplicates) {
175     checkUnmodifiableMultimap(multimap, permitsDuplicates, null, null);
176   }
177 
178   /**
179    * The supplied multimap will be mutated and an unmodifiable instance used
180    * in its stead. If the multimap does not support null keys or values,
181    * alternatives may be specified for tests involving nulls.
182    */
183   private static void checkUnmodifiableMultimap(
184       Multimap<String, Integer> multimap, boolean permitsDuplicates,
185       @Nullable String nullKey, @Nullable Integer nullValue) {
186     Multimap<String, Integer> unmodifiable =
187         prepareUnmodifiableTests(multimap, permitsDuplicates, nullKey, nullValue);
188 
189     UnmodifiableCollectionTests.assertMultimapIsUnmodifiable(
190         unmodifiable, "test", 123);
191 
192     assertUnmodifiableIterableInTandem(
193         unmodifiable.keys(), multimap.keys());
194 
195     assertUnmodifiableIterableInTandem(
196         unmodifiable.keySet(), multimap.keySet());
197 
198     assertUnmodifiableIterableInTandem(
199         unmodifiable.entries(), multimap.entries());
200 
201     assertUnmodifiableIterableInTandem(
202         unmodifiable.asMap().entrySet(), multimap.asMap().entrySet());
203 
204     assertEquals(multimap.toString(), unmodifiable.toString());
205     assertEquals(multimap.hashCode(), unmodifiable.hashCode());
206     assertEquals(multimap, unmodifiable);
207 
208     assertThat(unmodifiable.asMap().get("bar")).has().exactly(5, -1);
209     assertNull(unmodifiable.asMap().get("missing"));
210 
211     assertFalse(unmodifiable.entries() instanceof Serializable);
212   }
213 
214   /**
215    * Prepares the multimap for unmodifiable tests, returning an unmodifiable view
216    * of the map.
217    */
218   private static Multimap<String, Integer> prepareUnmodifiableTests(
219       Multimap<String, Integer> multimap, boolean permitsDuplicates,
220       @Nullable String nullKey, @Nullable Integer nullValue) {
221     multimap.clear();
222     multimap.put("foo", 1);
223     multimap.put("foo", 2);
224     multimap.put("foo", 3);
225     multimap.put("bar", 5);
226     multimap.put("bar", -1);
227     multimap.put(nullKey, nullValue);
228     multimap.put("foo", nullValue);
229     multimap.put(nullKey, 5);
230     multimap.put("foo", 2);
231 
232     if (permitsDuplicates) {
233       assertEquals(9, multimap.size());
234     } else {
235       assertEquals(8, multimap.size());
236     }
237 
238     Multimap<String, Integer> unmodifiable;
239     if (multimap instanceof SortedSetMultimap) {
240       unmodifiable = Multimaps.unmodifiableSortedSetMultimap(
241           (SortedSetMultimap<String, Integer>) multimap);
242     } else if (multimap instanceof SetMultimap) {
243       unmodifiable = Multimaps.unmodifiableSetMultimap(
244           (SetMultimap<String, Integer>) multimap);
245     } else if (multimap instanceof ListMultimap) {
246       unmodifiable = Multimaps.unmodifiableListMultimap(
247           (ListMultimap<String, Integer>) multimap);
248     } else {
249       unmodifiable = Multimaps.unmodifiableMultimap(multimap);
250     }
251     return unmodifiable;
252   }
253 
254   private static <T> void assertUnmodifiableIterableInTandem(
255       Iterable<T> unmodifiable, Iterable<T> modifiable) {
256     UnmodifiableCollectionTests.assertIteratorIsUnmodifiable(
257         unmodifiable.iterator());
258     UnmodifiableCollectionTests.assertIteratorsInOrder(
259         unmodifiable.iterator(), modifiable.iterator());
260   }
261 
262   public void testInvertFrom() {
263     ImmutableMultimap<Integer, String> empty = ImmutableMultimap.of();
264 
265     // typical usage example - sad that ArrayListMultimap.create() won't work
266     Multimap<String, Integer> multimap = Multimaps.invertFrom(empty,
267         ArrayListMultimap.<String, Integer>create());
268     assertTrue(multimap.isEmpty());
269 
270     ImmutableMultimap<Integer, String> single
271         = new ImmutableMultimap.Builder<Integer, String>()
272             .put(1, "one")
273             .put(2, "two")
274             .build();
275 
276     // copy into existing multimap
277     assertSame(multimap, Multimaps.invertFrom(single, multimap));
278 
279     ImmutableMultimap<String, Integer> expected
280         = new ImmutableMultimap.Builder<String, Integer>()
281         .put("one", 1)
282         .put("two", 2)
283         .build();
284 
285     assertEquals(expected, multimap);
286   }
287 
288   public void testAsMap_multimap() {
289     Multimap<String, Integer> multimap = Multimaps.newMultimap(
290         new HashMap<String, Collection<Integer>>(), new QueueSupplier());
291     Map<String, Collection<Integer>> map = Multimaps.asMap(multimap);
292     assertSame(multimap.asMap(), map);
293   }
294 
295   public void testAsMap_listMultimap() {
296     ListMultimap<String, Integer> listMultimap = ArrayListMultimap.create();
297     Map<String, List<Integer>> map = Multimaps.asMap(listMultimap);
298     assertSame(listMultimap.asMap(), map);
299   }
300 
301   public void testAsMap_setMultimap() {
302     SetMultimap<String, Integer> setMultimap = LinkedHashMultimap.create();
303     Map<String, Set<Integer>> map = Multimaps.asMap(setMultimap);
304     assertSame(setMultimap.asMap(), map);
305   }
306 
307   public void testAsMap_sortedSetMultimap() {
308     SortedSetMultimap<String, Integer> sortedSetMultimap =
309         TreeMultimap.create();
310     Map<String, SortedSet<Integer>> map = Multimaps.asMap(sortedSetMultimap);
311     assertSame(sortedSetMultimap.asMap(), map);
312   }
313 
314   public void testForMap() {
315     Map<String, Integer> map = Maps.newHashMap();
316     map.put("foo", 1);
317     map.put("bar", 2);
318     Multimap<String, Integer> multimap = HashMultimap.create();
319     multimap.put("foo", 1);
320     multimap.put("bar", 2);
321     Multimap<String, Integer> multimapView = Multimaps.forMap(map);
322     assertTrue(multimap.equals(multimapView));
323     assertTrue(multimapView.equals(multimap));
324     assertTrue(multimapView.equals(multimapView));
325     assertFalse(multimapView.equals(map));
326     Multimap<String, Integer> multimap2 = HashMultimap.create();
327     multimap2.put("foo", 1);
328     assertFalse(multimapView.equals(multimap2));
329     multimap2.put("bar", 1);
330     assertFalse(multimapView.equals(multimap2));
331     ListMultimap<String, Integer> listMultimap
332         = new ImmutableListMultimap.Builder<String, Integer>()
333             .put("foo", 1).put("bar", 2).build();
334     assertFalse("SetMultimap equals ListMultimap",
335         multimapView.equals(listMultimap));
336     assertEquals(multimap.toString(), multimapView.toString());
337     assertEquals(multimap.hashCode(), multimapView.hashCode());
338     assertEquals(multimap.size(), multimapView.size());
339     assertTrue(multimapView.containsKey("foo"));
340     assertTrue(multimapView.containsValue(1));
341     assertTrue(multimapView.containsEntry("bar", 2));
342     assertEquals(Collections.singleton(1), multimapView.get("foo"));
343     assertEquals(Collections.singleton(2), multimapView.get("bar"));
344     try {
345       multimapView.put("baz", 3);
346       fail("UnsupportedOperationException expected");
347     } catch (UnsupportedOperationException expected) {}
348     try {
349       multimapView.putAll("baz", Collections.singleton(3));
350       fail("UnsupportedOperationException expected");
351     } catch (UnsupportedOperationException expected) {}
352     try {
353       multimapView.putAll(multimap);
354       fail("UnsupportedOperationException expected");
355     } catch (UnsupportedOperationException expected) {}
356     try {
357       multimapView.replaceValues("foo", Collections.<Integer>emptySet());
358       fail("UnsupportedOperationException expected");
359     } catch (UnsupportedOperationException expected) {}
360     multimapView.remove("bar", 2);
361     assertFalse(multimapView.containsKey("bar"));
362     assertFalse(map.containsKey("bar"));
363     assertEquals(map.keySet(), multimapView.keySet());
364     assertEquals(map.keySet(), multimapView.keys().elementSet());
365     assertThat(multimapView.keys()).has().item("foo");
366     assertThat(multimapView.values()).has().item(1);
367     assertThat(multimapView.entries()).has().item(
368         Maps.immutableEntry("foo", 1));
369     assertThat(multimapView.asMap().entrySet()).has().item(
370         Maps.immutableEntry(
371             "foo", (Collection<Integer>) Collections.singleton(1)));
372     multimapView.clear();
373     assertFalse(multimapView.containsKey("foo"));
374     assertFalse(map.containsKey("foo"));
375     assertTrue(map.isEmpty());
376     assertTrue(multimapView.isEmpty());
377     multimap.clear();
378     assertEquals(multimap.toString(), multimapView.toString());
379     assertEquals(multimap.hashCode(), multimapView.hashCode());
380     assertEquals(multimap.size(), multimapView.size());
381     assertEquals(multimapView, ArrayListMultimap.create());
382   }
383 
384   public void testForMapRemoveAll() {
385     Map<String, Integer> map = Maps.newHashMap();
386     map.put("foo", 1);
387     map.put("bar", 2);
388     map.put("cow", 3);
389     Multimap<String, Integer> multimap = Multimaps.forMap(map);
390     assertEquals(3, multimap.size());
391     assertEquals(Collections.emptySet(), multimap.removeAll("dog"));
392     assertEquals(3, multimap.size());
393     assertTrue(multimap.containsKey("bar"));
394     assertEquals(Collections.singleton(2), multimap.removeAll("bar"));
395     assertEquals(2, multimap.size());
396     assertFalse(multimap.containsKey("bar"));
397   }
398 
399   public void testForMapAsMap() {
400     Map<String, Integer> map = Maps.newHashMap();
401     map.put("foo", 1);
402     map.put("bar", 2);
403     Map<String, Collection<Integer>> asMap = Multimaps.forMap(map).asMap();
404     assertEquals(Collections.singleton(1), asMap.get("foo"));
405     assertNull(asMap.get("cow"));
406     assertTrue(asMap.containsKey("foo"));
407     assertFalse(asMap.containsKey("cow"));
408 
409     Set<Entry<String, Collection<Integer>>> entries = asMap.entrySet();
410     assertFalse(entries.contains(4.5));
411     assertFalse(entries.remove(4.5));
412     assertFalse(entries.contains(Maps.immutableEntry("foo",
413         Collections.singletonList(1))));
414     assertFalse(entries.remove(Maps.immutableEntry("foo",
415         Collections.singletonList(1))));
416     assertFalse(entries.contains(Maps.immutableEntry("foo",
417         Sets.newLinkedHashSet(asList(1, 2)))));
418     assertFalse(entries.remove(Maps.immutableEntry("foo",
419         Sets.newLinkedHashSet(asList(1, 2)))));
420     assertFalse(entries.contains(Maps.immutableEntry("foo",
421         Collections.singleton(2))));
422     assertFalse(entries.remove(Maps.immutableEntry("foo",
423         Collections.singleton(2))));
424     assertTrue(map.containsKey("foo"));
425     assertTrue(entries.contains(Maps.immutableEntry("foo",
426         Collections.singleton(1))));
427     assertTrue(entries.remove(Maps.immutableEntry("foo",
428         Collections.singleton(1))));
429     assertFalse(map.containsKey("foo"));
430   }
431 
432   public void testForMapGetIteration() {
433     IteratorTester<Integer> tester =
434         new IteratorTester<Integer>(4, MODIFIABLE, newHashSet(1),
435             IteratorTester.KnownOrder.KNOWN_ORDER) {
436           private Multimap<String, Integer> multimap;
437 
438           @Override protected Iterator<Integer> newTargetIterator() {
439             Map<String, Integer> map = Maps.newHashMap();
440             map.put("foo", 1);
441             map.put("bar", 2);
442             multimap = Multimaps.forMap(map);
443             return multimap.get("foo").iterator();
444           }
445 
446           @Override protected void verify(List<Integer> elements) {
447             assertEquals(newHashSet(elements), multimap.get("foo"));
448           }
449         };
450 
451     tester.test();
452   }
453 
454   private enum Color {BLUE, RED, YELLOW, GREEN}
455 
456   private abstract static class CountingSupplier<E>
457       implements Supplier<E>, Serializable {
458     int count;
459 
460     abstract E getImpl();
461 
462     @Override
463     public E get() {
464       count++;
465       return getImpl();
466     }
467   }
468 
469   private static class QueueSupplier extends CountingSupplier<Queue<Integer>> {
470     @Override public Queue<Integer> getImpl() {
471       return new LinkedList<Integer>();
472     }
473     private static final long serialVersionUID = 0;
474   }
475 
476   public void testNewMultimapWithCollectionRejectingNegativeElements() {
477     CountingSupplier<Set<Integer>> factory = new SetSupplier() {
478       @Override
479       public Set<Integer> getImpl() {
480         final Set<Integer> backing = super.getImpl();
481         return new ForwardingSet<Integer>() {
482           @Override
483           protected Set<Integer> delegate() {
484             return backing;
485           }
486 
487           @Override
488           public boolean add(Integer element) {
489             checkArgument(element >= 0);
490             return super.add(element);
491           }
492 
493           @Override
494           public boolean addAll(Collection<? extends Integer> collection) {
495             return standardAddAll(collection);
496           }
497         };
498       }
499     };
500 
501     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
502     Multimap<Color, Integer> multimap = Multimaps.newMultimap(map, factory);
503     try {
504       multimap.put(Color.BLUE, -1);
505       fail("Expected IllegalArgumentException");
506     } catch (IllegalArgumentException expected) {
507       // expected
508     }
509     multimap.put(Color.RED, 1);
510     multimap.put(Color.BLUE, 2);
511     try {
512       multimap.put(Color.GREEN, -1);
513       fail("Expected IllegalArgumentException");
514     } catch (IllegalArgumentException expected) {
515       // expected
516     }
517     assertThat(multimap.entries()).has().exactly(
518         Maps.immutableEntry(Color.RED, 1),
519         Maps.immutableEntry(Color.BLUE, 2));
520   }
521 
522   public void testNewMultimap() {
523     // The ubiquitous EnumArrayBlockingQueueMultimap
524     CountingSupplier<Queue<Integer>> factory = new QueueSupplier();
525 
526     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
527     Multimap<Color, Integer> multimap = Multimaps.newMultimap(map, factory);
528     assertEquals(0, factory.count);
529     multimap.putAll(Color.BLUE, asList(3, 1, 4));
530     assertEquals(1, factory.count);
531     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
532     assertEquals(2, factory.count);
533     assertEquals("[3, 1, 4]", multimap.get(Color.BLUE).toString());
534 
535     Multimap<Color, Integer> ummodifiable =
536         Multimaps.unmodifiableMultimap(multimap);
537     assertEquals("[3, 1, 4]", ummodifiable.get(Color.BLUE).toString());
538 
539     Collection<Integer> collection = multimap.get(Color.BLUE);
540     assertEquals(collection, collection);
541 
542     assertFalse(multimap.keySet() instanceof SortedSet);
543     assertFalse(multimap.asMap() instanceof SortedMap);
544   }
545 
546   private static class ListSupplier extends
547       CountingSupplier<LinkedList<Integer>> {
548     @Override public LinkedList<Integer> getImpl() {
549       return new LinkedList<Integer>();
550     }
551     private static final long serialVersionUID = 0;
552   }
553 
554   public void testNewListMultimap() {
555     CountingSupplier<LinkedList<Integer>> factory = new ListSupplier();
556     Map<Color, Collection<Integer>> map = Maps.newTreeMap();
557     ListMultimap<Color, Integer> multimap =
558         Multimaps.newListMultimap(map, factory);
559     assertEquals(0, factory.count);
560     multimap.putAll(Color.BLUE, asList(3, 1, 4, 1));
561     assertEquals(1, factory.count);
562     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
563     assertEquals(2, factory.count);
564     assertEquals("{BLUE=[3, 1, 4, 1], RED=[2, 7, 1, 8]}", multimap.toString());
565     assertFalse(multimap.get(Color.BLUE) instanceof RandomAccess);
566 
567     assertTrue(multimap.keySet() instanceof SortedSet);
568     assertTrue(multimap.asMap() instanceof SortedMap);
569   }
570 
571   private static class SetSupplier extends CountingSupplier<Set<Integer>> {
572     @Override public Set<Integer> getImpl() {
573       return new HashSet<Integer>(4);
574     }
575     private static final long serialVersionUID = 0;
576   }
577 
578   public void testNewSetMultimap() {
579     CountingSupplier<Set<Integer>> factory = new SetSupplier();
580     Map<Color, Collection<Integer>> map = Maps.newHashMap();
581     SetMultimap<Color, Integer> multimap =
582         Multimaps.newSetMultimap(map, factory);
583     assertEquals(0, factory.count);
584     multimap.putAll(Color.BLUE, asList(3, 1, 4));
585     assertEquals(1, factory.count);
586     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
587     assertEquals(2, factory.count);
588     assertEquals(Sets.newHashSet(4, 3, 1), multimap.get(Color.BLUE));
589   }
590 
591   private static class SortedSetSupplier extends
592       CountingSupplier<TreeSet<Integer>> {
593     @Override public TreeSet<Integer> getImpl() {
594       return Sets.newTreeSet(INT_COMPARATOR);
595     }
596     private static final long serialVersionUID = 0;
597   }
598 
599   public void testNewSortedSetMultimap() {
600     CountingSupplier<TreeSet<Integer>> factory = new SortedSetSupplier();
601     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
602     SortedSetMultimap<Color, Integer> multimap =
603         Multimaps.newSortedSetMultimap(map, factory);
604     // newSortedSetMultimap calls the factory once to determine the comparator.
605     assertEquals(1, factory.count);
606     multimap.putAll(Color.BLUE, asList(3, 1, 4));
607     assertEquals(2, factory.count);
608     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
609     assertEquals(3, factory.count);
610     assertEquals("[4, 3, 1]", multimap.get(Color.BLUE).toString());
611     assertEquals(INT_COMPARATOR, multimap.valueComparator());
612   }
613 
614   public void testIndex() {
615     final Multimap<String, Object> stringToObject =
616         new ImmutableMultimap.Builder<String, Object>()
617             .put("1", 1)
618             .put("1", 1L)
619             .put("1", "1")
620             .put("2", 2)
621             .put("2", 2L)
622             .build();
623 
624     ImmutableMultimap<String, Object> outputMap =
625         Multimaps.index(stringToObject.values(),
626             Functions.toStringFunction());
627     assertEquals(stringToObject, outputMap);
628   }
629 
630   public void testIndexIterator() {
631     final Multimap<String, Object> stringToObject =
632         new ImmutableMultimap.Builder<String, Object>()
633             .put("1", 1)
634             .put("1", 1L)
635             .put("1", "1")
636             .put("2", 2)
637             .put("2", 2L)
638             .build();
639 
640     ImmutableMultimap<String, Object> outputMap =
641         Multimaps.index(stringToObject.values().iterator(),
642             Functions.toStringFunction());
643     assertEquals(stringToObject, outputMap);
644   }
645 
646   public void testIndex_ordering() {
647     final Multimap<Integer, String> expectedIndex =
648         new ImmutableListMultimap.Builder<Integer, String>()
649             .put(4, "Inky")
650             .put(6, "Blinky")
651             .put(5, "Pinky")
652             .put(5, "Pinky")
653             .put(5, "Clyde")
654             .build();
655 
656     final List<String> badGuys =
657         Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
658     final Function<String, Integer> stringLengthFunction =
659         new Function<String, Integer>() {
660           @Override
661           public Integer apply(String input) {
662             return input.length();
663           }
664         };
665 
666     Multimap<Integer, String> index =
667         Multimaps.index(badGuys, stringLengthFunction);
668 
669     assertEquals(expectedIndex, index);
670   }
671 
672   public void testIndex_nullValue() {
673     List<Integer> values = Arrays.asList(1, null);
674     try {
675       Multimaps.index(values, Functions.identity());
676       fail();
677     } catch (NullPointerException e) {}
678   }
679 
680   public void testIndex_nullKey() {
681     List<Integer> values = Arrays.asList(1, 2);
682     try {
683       Multimaps.index(values, Functions.constant(null));
684       fail();
685     } catch (NullPointerException e) {}
686   }
687 
688   public <K, V> void testSynchronizedMultimapSampleCodeCompilation() {
689     K key = null;
690 
691     Multimap<K, V> multimap = Multimaps.synchronizedMultimap(
692         HashMultimap.<K, V>create());
693     Collection<V> values = multimap.get(key);  // Needn't be in synchronized block
694     synchronized (multimap) {  // Synchronizing on multimap, not values!
695       Iterator<V> i = values.iterator(); // Must be in synchronized block
696       while (i.hasNext()) {
697         foo(i.next());
698       }
699     }
700   }
701 
702   private static void foo(Object o) {}
703   
704   public void testFilteredKeysSetMultimapReplaceValues() {
705     SetMultimap<String, Integer> multimap = LinkedHashMultimap.create();
706     multimap.put("foo", 1);
707     multimap.put("bar", 2);
708     multimap.put("baz", 3);
709     multimap.put("bar", 4);
710     
711     SetMultimap<String, Integer> filtered = Multimaps.filterKeys(
712         multimap, Predicates.in(ImmutableSet.of("foo", "bar")));
713     
714     assertEquals(
715         ImmutableSet.of(),
716         filtered.replaceValues("baz", ImmutableSet.<Integer>of()));
717     
718     try {
719       filtered.replaceValues("baz", ImmutableSet.of(5));
720       fail("Expected IllegalArgumentException");
721     } catch (IllegalArgumentException expected) {
722     }
723   }
724   
725   public void testFilteredKeysSetMultimapGetBadValue() {
726     SetMultimap<String, Integer> multimap = LinkedHashMultimap.create();
727     multimap.put("foo", 1);
728     multimap.put("bar", 2);
729     multimap.put("baz", 3);
730     multimap.put("bar", 4);
731     
732     SetMultimap<String, Integer> filtered = Multimaps.filterKeys(
733         multimap, Predicates.in(ImmutableSet.of("foo", "bar")));
734     Set<Integer> bazSet = filtered.get("baz");
735     assertThat(bazSet).isEmpty();
736     try {
737       bazSet.add(5);
738       fail("Expected IllegalArgumentException");
739     } catch (IllegalArgumentException expected) {
740     }
741     try {
742       bazSet.addAll(ImmutableSet.of(6, 7));
743       fail("Expected IllegalArgumentException");
744     } catch (IllegalArgumentException expected) {
745     }
746   }
747   
748   public void testFilteredKeysListMultimapGetBadValue() {
749     ListMultimap<String, Integer> multimap = ArrayListMultimap.create();
750     multimap.put("foo", 1);
751     multimap.put("bar", 2);
752     multimap.put("baz", 3);
753     multimap.put("bar", 4);
754     
755     ListMultimap<String, Integer> filtered = Multimaps.filterKeys(
756         multimap, Predicates.in(ImmutableSet.of("foo", "bar")));
757     List<Integer> bazList = filtered.get("baz");
758     assertThat(bazList).isEmpty();
759     try {
760       bazList.add(5);
761       fail("Expected IllegalArgumentException");
762     } catch (IllegalArgumentException expected) {
763     }
764     try {
765       bazList.add(0, 6);
766       fail("Expected IllegalArgumentException");
767     } catch (IllegalArgumentException expected) {
768     }
769     try {
770       bazList.addAll(ImmutableList.of(7, 8));
771       fail("Expected IllegalArgumentException");
772     } catch (IllegalArgumentException expected) {
773     }
774     try {
775       bazList.addAll(0, ImmutableList.of(9, 10));
776       fail("Expected IllegalArgumentException");
777     } catch (IllegalArgumentException expected) {
778     }
779   }
780 }
781