• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Maps.immutableEntry;
21 import static com.google.common.collect.Sets.newHashSet;
22 import static com.google.common.collect.testing.Helpers.mapEntry;
23 import static com.google.common.collect.testing.Helpers.nefariousMapEntry;
24 import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE;
25 import static com.google.common.truth.Truth.assertThat;
26 import static java.util.Arrays.asList;
27 
28 import com.google.common.annotations.GwtCompatible;
29 import com.google.common.annotations.GwtIncompatible;
30 import com.google.common.base.Equivalence;
31 import com.google.common.base.Function;
32 import com.google.common.base.Functions;
33 import com.google.common.base.Predicates;
34 import com.google.common.base.Supplier;
35 import com.google.common.collect.Maps.EntryTransformer;
36 import com.google.common.collect.testing.IteratorTester;
37 import com.google.common.collect.testing.google.UnmodifiableCollectionTests;
38 import com.google.common.primitives.Chars;
39 import com.google.common.testing.CollectorTester;
40 import com.google.common.testing.EqualsTester;
41 import com.google.common.testing.NullPointerTester;
42 import com.google.common.testing.SerializableTester;
43 import java.io.Serializable;
44 import java.util.Arrays;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.Comparator;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.LinkedList;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Map.Entry;
55 import java.util.NavigableSet;
56 import java.util.Queue;
57 import java.util.RandomAccess;
58 import java.util.Set;
59 import java.util.SortedMap;
60 import java.util.SortedSet;
61 import java.util.TreeSet;
62 import java.util.function.BiPredicate;
63 import java.util.stream.Collector;
64 import java.util.stream.Stream;
65 import junit.framework.TestCase;
66 import org.checkerframework.checker.nullness.qual.Nullable;
67 
68 /**
69  * Unit test for {@code Multimaps}.
70  *
71  * @author Jared Levy
72  */
73 @GwtCompatible(emulated = true)
74 public class MultimapsTest extends TestCase {
75 
76   private static final Comparator<Integer> INT_COMPARATOR =
77       Ordering.<Integer>natural().reverse().nullsFirst();
78 
testMultimapCollectorGenerics()79   public void testMultimapCollectorGenerics() {
80     ListMultimap<Integer, String> unused =
81         Stream.of("foo", "bar", "quux")
82             .collect(
83                 Multimaps.toMultimap(
84                     String::length, s -> s, MultimapBuilder.treeKeys().arrayListValues()::build));
85   }
86 
testToMultimap()87   public void testToMultimap() {
88     Collector<Entry<String, Integer>, ?, TreeMultimap<String, Integer>> collector =
89         Multimaps.toMultimap(Entry::getKey, Entry::getValue, TreeMultimap::create);
90     BiPredicate<Multimap<?, ?>, Multimap<?, ?>> equivalence =
91         Equivalence.equals()
92             .onResultOf((Multimap<?, ?> mm) -> ImmutableList.copyOf(mm.asMap().entrySet()))
93             .and(Equivalence.equals());
94     TreeMultimap<String, Integer> empty = TreeMultimap.create();
95     TreeMultimap<String, Integer> filled = TreeMultimap.create();
96     filled.put("a", 1);
97     filled.put("a", 2);
98     filled.put("b", 2);
99     filled.put("c", 3);
100     CollectorTester.of(collector, equivalence)
101         .expectCollects(empty)
102         .expectCollects(
103             filled, mapEntry("a", 1), mapEntry("a", 2), mapEntry("b", 2), mapEntry("c", 3));
104   }
105 
testFlatteningToMultimap()106   public void testFlatteningToMultimap() {
107     Collector<String, ?, ListMultimap<Character, Character>> collector =
108         Multimaps.flatteningToMultimap(
109             str -> str.charAt(0),
110             str -> Chars.asList(str.substring(1).toCharArray()).stream(),
111             MultimapBuilder.linkedHashKeys().arrayListValues()::build);
112     BiPredicate<Multimap<?, ?>, Multimap<?, ?>> equivalence =
113         Equivalence.equals()
114             .onResultOf((Multimap<?, ?> mm) -> ImmutableList.copyOf(mm.asMap().entrySet()))
115             .and(Equivalence.equals());
116     ListMultimap<Character, Character> empty =
117         MultimapBuilder.linkedHashKeys().arrayListValues().build();
118     ListMultimap<Character, Character> filled =
119         MultimapBuilder.linkedHashKeys().arrayListValues().build();
120     filled.putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'));
121     filled.putAll('a', Arrays.asList('p', 'p', 'l', 'e'));
122     filled.putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'));
123     filled.putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'));
124     filled.putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'));
125     CollectorTester.of(collector, equivalence)
126         .expectCollects(empty)
127         .expectCollects(filled, "banana", "apple", "carrot", "asparagus", "cherry");
128   }
129 
130   @SuppressWarnings("deprecation")
testUnmodifiableListMultimapShortCircuit()131   public void testUnmodifiableListMultimapShortCircuit() {
132     ListMultimap<String, Integer> mod = ArrayListMultimap.create();
133     ListMultimap<String, Integer> unmod = Multimaps.unmodifiableListMultimap(mod);
134     assertNotSame(mod, unmod);
135     assertSame(unmod, Multimaps.unmodifiableListMultimap(unmod));
136     ImmutableListMultimap<String, Integer> immutable =
137         ImmutableListMultimap.of("a", 1, "b", 2, "a", 3);
138     assertSame(immutable, Multimaps.unmodifiableListMultimap(immutable));
139     assertSame(
140         immutable, Multimaps.unmodifiableListMultimap((ListMultimap<String, Integer>) immutable));
141   }
142 
143   @SuppressWarnings("deprecation")
testUnmodifiableSetMultimapShortCircuit()144   public void testUnmodifiableSetMultimapShortCircuit() {
145     SetMultimap<String, Integer> mod = HashMultimap.create();
146     SetMultimap<String, Integer> unmod = Multimaps.unmodifiableSetMultimap(mod);
147     assertNotSame(mod, unmod);
148     assertSame(unmod, Multimaps.unmodifiableSetMultimap(unmod));
149     ImmutableSetMultimap<String, Integer> immutable =
150         ImmutableSetMultimap.of("a", 1, "b", 2, "a", 3);
151     assertSame(immutable, Multimaps.unmodifiableSetMultimap(immutable));
152     assertSame(
153         immutable, Multimaps.unmodifiableSetMultimap((SetMultimap<String, Integer>) immutable));
154   }
155 
156   @SuppressWarnings("deprecation")
testUnmodifiableMultimapShortCircuit()157   public void testUnmodifiableMultimapShortCircuit() {
158     Multimap<String, Integer> mod = HashMultimap.create();
159     Multimap<String, Integer> unmod = Multimaps.unmodifiableMultimap(mod);
160     assertNotSame(mod, unmod);
161     assertSame(unmod, Multimaps.unmodifiableMultimap(unmod));
162     ImmutableMultimap<String, Integer> immutable = ImmutableMultimap.of("a", 1, "b", 2, "a", 3);
163     assertSame(immutable, Multimaps.unmodifiableMultimap(immutable));
164     assertSame(immutable, Multimaps.unmodifiableMultimap((Multimap<String, Integer>) immutable));
165   }
166 
167   @GwtIncompatible // slow (~10s)
testUnmodifiableArrayListMultimap()168   public void testUnmodifiableArrayListMultimap() {
169     checkUnmodifiableMultimap(ArrayListMultimap.<String, Integer>create(), true);
170   }
171 
172   @GwtIncompatible // SerializableTester
testSerializingUnmodifiableArrayListMultimap()173   public void testSerializingUnmodifiableArrayListMultimap() {
174     Multimap<String, Integer> unmodifiable =
175         prepareUnmodifiableTests(ArrayListMultimap.<String, Integer>create(), true, null, null);
176     SerializableTester.reserializeAndAssert(unmodifiable);
177   }
178 
testUnmodifiableArrayListMultimapRandomAccess()179   public void testUnmodifiableArrayListMultimapRandomAccess() {
180     ListMultimap<String, Integer> delegate = ArrayListMultimap.create();
181     delegate.put("foo", 1);
182     delegate.put("foo", 3);
183     ListMultimap<String, Integer> multimap = Multimaps.unmodifiableListMultimap(delegate);
184     assertTrue(multimap.get("foo") instanceof RandomAccess);
185     assertTrue(multimap.get("bar") instanceof RandomAccess);
186   }
187 
testUnmodifiableLinkedListMultimapRandomAccess()188   public void testUnmodifiableLinkedListMultimapRandomAccess() {
189     ListMultimap<String, Integer> delegate = LinkedListMultimap.create();
190     delegate.put("foo", 1);
191     delegate.put("foo", 3);
192     ListMultimap<String, Integer> multimap = Multimaps.unmodifiableListMultimap(delegate);
193     assertFalse(multimap.get("foo") instanceof RandomAccess);
194     assertFalse(multimap.get("bar") instanceof RandomAccess);
195   }
196 
197   @GwtIncompatible // slow (~10s)
testUnmodifiableHashMultimap()198   public void testUnmodifiableHashMultimap() {
199     checkUnmodifiableMultimap(HashMultimap.<String, Integer>create(), false);
200   }
201 
202   @GwtIncompatible // SerializableTester
testSerializingUnmodifiableHashMultimap()203   public void testSerializingUnmodifiableHashMultimap() {
204     Multimap<String, Integer> unmodifiable =
205         prepareUnmodifiableTests(HashMultimap.<String, Integer>create(), false, null, null);
206     SerializableTester.reserializeAndAssert(unmodifiable);
207   }
208 
209   @GwtIncompatible // slow (~10s)
testUnmodifiableTreeMultimap()210   public void testUnmodifiableTreeMultimap() {
211     checkUnmodifiableMultimap(TreeMultimap.<String, Integer>create(), false, "null", 42);
212   }
213 
214   @GwtIncompatible // SerializableTester
testSerializingUnmodifiableTreeMultimap()215   public void testSerializingUnmodifiableTreeMultimap() {
216     Multimap<String, Integer> unmodifiable =
217         prepareUnmodifiableTests(TreeMultimap.<String, Integer>create(), false, "null", 42);
218     SerializableTester.reserializeAndAssert(unmodifiable);
219   }
220 
221   @GwtIncompatible // slow (~10s)
testUnmodifiableSynchronizedArrayListMultimap()222   public void testUnmodifiableSynchronizedArrayListMultimap() {
223     checkUnmodifiableMultimap(
224         Multimaps.synchronizedListMultimap(ArrayListMultimap.<String, Integer>create()), true);
225   }
226 
227   @GwtIncompatible // SerializableTester
testSerializingUnmodifiableSynchronizedArrayListMultimap()228   public void testSerializingUnmodifiableSynchronizedArrayListMultimap() {
229     Multimap<String, Integer> unmodifiable =
230         prepareUnmodifiableTests(
231             Multimaps.synchronizedListMultimap(ArrayListMultimap.<String, Integer>create()),
232             true,
233             null,
234             null);
235     SerializableTester.reserializeAndAssert(unmodifiable);
236   }
237 
238   @GwtIncompatible // slow (~10s)
testUnmodifiableSynchronizedHashMultimap()239   public void testUnmodifiableSynchronizedHashMultimap() {
240     checkUnmodifiableMultimap(
241         Multimaps.synchronizedSetMultimap(HashMultimap.<String, Integer>create()), false);
242   }
243 
244   @GwtIncompatible // SerializableTester
testSerializingUnmodifiableSynchronizedHashMultimap()245   public void testSerializingUnmodifiableSynchronizedHashMultimap() {
246     Multimap<String, Integer> unmodifiable =
247         prepareUnmodifiableTests(
248             Multimaps.synchronizedSetMultimap(HashMultimap.<String, Integer>create()),
249             false,
250             null,
251             null);
252     SerializableTester.reserializeAndAssert(unmodifiable);
253   }
254 
255   @GwtIncompatible // slow (~10s)
testUnmodifiableSynchronizedTreeMultimap()256   public void testUnmodifiableSynchronizedTreeMultimap() {
257     TreeMultimap<String, Integer> delegate =
258         TreeMultimap.create(Ordering.<String>natural(), INT_COMPARATOR);
259     SortedSetMultimap<String, Integer> multimap = Multimaps.synchronizedSortedSetMultimap(delegate);
260     checkUnmodifiableMultimap(multimap, false, "null", 42);
261     assertSame(INT_COMPARATOR, multimap.valueComparator());
262   }
263 
264   @GwtIncompatible // SerializableTester
testSerializingUnmodifiableSynchronizedTreeMultimap()265   public void testSerializingUnmodifiableSynchronizedTreeMultimap() {
266     TreeMultimap<String, Integer> delegate =
267         TreeMultimap.create(Ordering.<String>natural(), INT_COMPARATOR);
268     SortedSetMultimap<String, Integer> multimap = Multimaps.synchronizedSortedSetMultimap(delegate);
269     Multimap<String, Integer> unmodifiable = prepareUnmodifiableTests(multimap, false, "null", 42);
270     SerializableTester.reserializeAndAssert(unmodifiable);
271     assertSame(INT_COMPARATOR, multimap.valueComparator());
272   }
273 
testUnmodifiableMultimapIsView()274   public void testUnmodifiableMultimapIsView() {
275     Multimap<String, Integer> mod = HashMultimap.create();
276     Multimap<String, Integer> unmod = Multimaps.unmodifiableMultimap(mod);
277     assertEquals(mod, unmod);
278     mod.put("foo", 1);
279     assertTrue(unmod.containsEntry("foo", 1));
280     assertEquals(mod, unmod);
281   }
282 
283   @SuppressWarnings("unchecked")
testUnmodifiableMultimapEntries()284   public void testUnmodifiableMultimapEntries() {
285     Multimap<String, Integer> mod = HashMultimap.create();
286     Multimap<String, Integer> unmod = Multimaps.unmodifiableMultimap(mod);
287     mod.put("foo", 1);
288     Entry<String, Integer> entry = unmod.entries().iterator().next();
289     try {
290       entry.setValue(2);
291       fail("UnsupportedOperationException expected");
292     } catch (UnsupportedOperationException expected) {
293     }
294     entry = (Entry<String, Integer>) unmod.entries().toArray()[0];
295     try {
296       entry.setValue(2);
297       fail("UnsupportedOperationException expected");
298     } catch (UnsupportedOperationException expected) {
299     }
300     Entry<String, Integer>[] array = (Entry<String, Integer>[]) new Entry<?, ?>[2];
301     assertSame(array, unmod.entries().toArray(array));
302     try {
303       array[0].setValue(2);
304       fail("UnsupportedOperationException expected");
305     } catch (UnsupportedOperationException expected) {
306     }
307     assertFalse(unmod.entries().contains(nefariousMapEntry("pwnd", 2)));
308     assertFalse(unmod.keys().contains("pwnd"));
309   }
310 
311   /**
312    * The supplied multimap will be mutated and an unmodifiable instance used in its stead. The
313    * multimap must support null keys and values.
314    */
checkUnmodifiableMultimap( Multimap<String, Integer> multimap, boolean permitsDuplicates)315   private static void checkUnmodifiableMultimap(
316       Multimap<String, Integer> multimap, boolean permitsDuplicates) {
317     checkUnmodifiableMultimap(multimap, permitsDuplicates, null, null);
318   }
319 
320   /**
321    * The supplied multimap will be mutated and an unmodifiable instance used in its stead. If the
322    * multimap does not support null keys or values, alternatives may be specified for tests
323    * involving nulls.
324    */
checkUnmodifiableMultimap( Multimap<String, Integer> multimap, boolean permitsDuplicates, @Nullable String nullKey, @Nullable Integer nullValue)325   private static void checkUnmodifiableMultimap(
326       Multimap<String, Integer> multimap,
327       boolean permitsDuplicates,
328       @Nullable String nullKey,
329       @Nullable Integer nullValue) {
330     Multimap<String, Integer> unmodifiable =
331         prepareUnmodifiableTests(multimap, permitsDuplicates, nullKey, nullValue);
332 
333     UnmodifiableCollectionTests.assertMultimapIsUnmodifiable(unmodifiable, "test", 123);
334 
335     assertUnmodifiableIterableInTandem(unmodifiable.keys(), multimap.keys());
336 
337     assertUnmodifiableIterableInTandem(unmodifiable.keySet(), multimap.keySet());
338 
339     assertUnmodifiableIterableInTandem(unmodifiable.entries(), multimap.entries());
340 
341     assertUnmodifiableIterableInTandem(
342         unmodifiable.asMap().entrySet(), multimap.asMap().entrySet());
343 
344     assertEquals(multimap.toString(), unmodifiable.toString());
345     assertEquals(multimap.hashCode(), unmodifiable.hashCode());
346     assertEquals(multimap, unmodifiable);
347 
348     assertThat(unmodifiable.asMap().get("bar")).containsExactly(5, -1);
349     assertNull(unmodifiable.asMap().get("missing"));
350 
351     assertFalse(unmodifiable.entries() instanceof Serializable);
352   }
353 
354   /** Prepares the multimap for unmodifiable tests, returning an unmodifiable view of the map. */
prepareUnmodifiableTests( Multimap<String, Integer> multimap, boolean permitsDuplicates, @Nullable String nullKey, @Nullable Integer nullValue)355   private static Multimap<String, Integer> prepareUnmodifiableTests(
356       Multimap<String, Integer> multimap,
357       boolean permitsDuplicates,
358       @Nullable String nullKey,
359       @Nullable Integer nullValue) {
360     multimap.clear();
361     multimap.put("foo", 1);
362     multimap.put("foo", 2);
363     multimap.put("foo", 3);
364     multimap.put("bar", 5);
365     multimap.put("bar", -1);
366     multimap.put(nullKey, nullValue);
367     multimap.put("foo", nullValue);
368     multimap.put(nullKey, 5);
369     multimap.put("foo", 2);
370 
371     if (permitsDuplicates) {
372       assertEquals(9, multimap.size());
373     } else {
374       assertEquals(8, multimap.size());
375     }
376 
377     Multimap<String, Integer> unmodifiable;
378     if (multimap instanceof SortedSetMultimap) {
379       unmodifiable =
380           Multimaps.unmodifiableSortedSetMultimap((SortedSetMultimap<String, Integer>) multimap);
381     } else if (multimap instanceof SetMultimap) {
382       unmodifiable = Multimaps.unmodifiableSetMultimap((SetMultimap<String, Integer>) multimap);
383     } else if (multimap instanceof ListMultimap) {
384       unmodifiable = Multimaps.unmodifiableListMultimap((ListMultimap<String, Integer>) multimap);
385     } else {
386       unmodifiable = Multimaps.unmodifiableMultimap(multimap);
387     }
388     return unmodifiable;
389   }
390 
assertUnmodifiableIterableInTandem( Iterable<T> unmodifiable, Iterable<T> modifiable)391   private static <T> void assertUnmodifiableIterableInTandem(
392       Iterable<T> unmodifiable, Iterable<T> modifiable) {
393     UnmodifiableCollectionTests.assertIteratorIsUnmodifiable(unmodifiable.iterator());
394     UnmodifiableCollectionTests.assertIteratorsInOrder(
395         unmodifiable.iterator(), modifiable.iterator());
396   }
397 
testInvertFrom()398   public void testInvertFrom() {
399     ImmutableMultimap<Integer, String> empty = ImmutableMultimap.of();
400 
401     // typical usage example - sad that ArrayListMultimap.create() won't work
402     Multimap<String, Integer> multimap =
403         Multimaps.invertFrom(empty, ArrayListMultimap.<String, Integer>create());
404     assertTrue(multimap.isEmpty());
405 
406     ImmutableMultimap<Integer, String> single =
407         new ImmutableMultimap.Builder<Integer, String>().put(1, "one").put(2, "two").build();
408 
409     // copy into existing multimap
410     assertSame(multimap, Multimaps.invertFrom(single, multimap));
411 
412     ImmutableMultimap<String, Integer> expected =
413         new ImmutableMultimap.Builder<String, Integer>().put("one", 1).put("two", 2).build();
414 
415     assertEquals(expected, multimap);
416   }
417 
testAsMap_multimap()418   public void testAsMap_multimap() {
419     Multimap<String, Integer> multimap =
420         Multimaps.newMultimap(new HashMap<String, Collection<Integer>>(), new QueueSupplier());
421     Map<String, Collection<Integer>> map = Multimaps.asMap(multimap);
422     assertSame(multimap.asMap(), map);
423   }
424 
testAsMap_listMultimap()425   public void testAsMap_listMultimap() {
426     ListMultimap<String, Integer> listMultimap = ArrayListMultimap.create();
427     Map<String, List<Integer>> map = Multimaps.asMap(listMultimap);
428     assertSame(listMultimap.asMap(), map);
429   }
430 
testAsMap_setMultimap()431   public void testAsMap_setMultimap() {
432     SetMultimap<String, Integer> setMultimap = LinkedHashMultimap.create();
433     Map<String, Set<Integer>> map = Multimaps.asMap(setMultimap);
434     assertSame(setMultimap.asMap(), map);
435   }
436 
testAsMap_sortedSetMultimap()437   public void testAsMap_sortedSetMultimap() {
438     SortedSetMultimap<String, Integer> sortedSetMultimap = TreeMultimap.create();
439     Map<String, SortedSet<Integer>> map = Multimaps.asMap(sortedSetMultimap);
440     assertSame(sortedSetMultimap.asMap(), map);
441   }
442 
testForMap()443   public void testForMap() {
444     Map<String, Integer> map = Maps.newHashMap();
445     map.put("foo", 1);
446     map.put("bar", 2);
447     Multimap<String, Integer> multimap = HashMultimap.create();
448     multimap.put("foo", 1);
449     multimap.put("bar", 2);
450     Multimap<String, Integer> multimapView = Multimaps.forMap(map);
451     new EqualsTester().addEqualityGroup(multimap, multimapView).addEqualityGroup(map).testEquals();
452     Multimap<String, Integer> multimap2 = HashMultimap.create();
453     multimap2.put("foo", 1);
454     assertFalse(multimapView.equals(multimap2));
455     multimap2.put("bar", 1);
456     assertFalse(multimapView.equals(multimap2));
457     ListMultimap<String, Integer> listMultimap =
458         new ImmutableListMultimap.Builder<String, Integer>().put("foo", 1).put("bar", 2).build();
459     assertFalse("SetMultimap equals ListMultimap", multimapView.equals(listMultimap));
460     assertEquals(multimap.hashCode(), multimapView.hashCode());
461     assertEquals(multimap.size(), multimapView.size());
462     assertTrue(multimapView.containsKey("foo"));
463     assertTrue(multimapView.containsValue(1));
464     assertTrue(multimapView.containsEntry("bar", 2));
465     assertEquals(Collections.singleton(1), multimapView.get("foo"));
466     assertEquals(Collections.singleton(2), multimapView.get("bar"));
467     try {
468       multimapView.put("baz", 3);
469       fail("UnsupportedOperationException expected");
470     } catch (UnsupportedOperationException expected) {
471     }
472     try {
473       multimapView.putAll("baz", Collections.singleton(3));
474       fail("UnsupportedOperationException expected");
475     } catch (UnsupportedOperationException expected) {
476     }
477     try {
478       multimapView.putAll(multimap);
479       fail("UnsupportedOperationException expected");
480     } catch (UnsupportedOperationException expected) {
481     }
482     try {
483       multimapView.replaceValues("foo", Collections.<Integer>emptySet());
484       fail("UnsupportedOperationException expected");
485     } catch (UnsupportedOperationException expected) {
486     }
487     multimapView.remove("bar", 2);
488     assertFalse(multimapView.containsKey("bar"));
489     assertFalse(map.containsKey("bar"));
490     assertEquals(map.keySet(), multimapView.keySet());
491     assertEquals(map.keySet(), multimapView.keys().elementSet());
492     assertThat(multimapView.keys()).contains("foo");
493     assertThat(multimapView.values()).contains(1);
494     assertThat(multimapView.entries()).contains(Maps.immutableEntry("foo", 1));
495     assertThat(multimapView.asMap().entrySet())
496         .contains(Maps.immutableEntry("foo", (Collection<Integer>) Collections.singleton(1)));
497     multimapView.clear();
498     assertFalse(multimapView.containsKey("foo"));
499     assertFalse(map.containsKey("foo"));
500     assertTrue(map.isEmpty());
501     assertTrue(multimapView.isEmpty());
502     multimap.clear();
503     assertEquals(multimap.toString(), multimapView.toString());
504     assertEquals(multimap.hashCode(), multimapView.hashCode());
505     assertEquals(multimap.size(), multimapView.size());
506     assertEquals(multimapView, ArrayListMultimap.create());
507   }
508 
509   @GwtIncompatible // SerializableTester
testForMapSerialization()510   public void testForMapSerialization() {
511     Map<String, Integer> map = Maps.newHashMap();
512     map.put("foo", 1);
513     map.put("bar", 2);
514     Multimap<String, Integer> multimapView = Multimaps.forMap(map);
515     SerializableTester.reserializeAndAssert(multimapView);
516   }
517 
testForMapRemoveAll()518   public void testForMapRemoveAll() {
519     Map<String, Integer> map = Maps.newHashMap();
520     map.put("foo", 1);
521     map.put("bar", 2);
522     map.put("cow", 3);
523     Multimap<String, Integer> multimap = Multimaps.forMap(map);
524     assertEquals(3, multimap.size());
525     assertEquals(Collections.emptySet(), multimap.removeAll("dog"));
526     assertEquals(3, multimap.size());
527     assertTrue(multimap.containsKey("bar"));
528     assertEquals(Collections.singleton(2), multimap.removeAll("bar"));
529     assertEquals(2, multimap.size());
530     assertFalse(multimap.containsKey("bar"));
531   }
532 
testForMapAsMap()533   public void testForMapAsMap() {
534     Map<String, Integer> map = Maps.newHashMap();
535     map.put("foo", 1);
536     map.put("bar", 2);
537     Map<String, Collection<Integer>> asMap = Multimaps.forMap(map).asMap();
538     assertEquals(Collections.singleton(1), asMap.get("foo"));
539     assertNull(asMap.get("cow"));
540     assertTrue(asMap.containsKey("foo"));
541     assertFalse(asMap.containsKey("cow"));
542 
543     Set<Entry<String, Collection<Integer>>> entries = asMap.entrySet();
544     assertFalse(entries.contains((Object) 4.5));
545     assertFalse(entries.remove((Object) 4.5));
546     assertFalse(entries.contains(Maps.immutableEntry("foo", Collections.singletonList(1))));
547     assertFalse(entries.remove(Maps.immutableEntry("foo", Collections.singletonList(1))));
548     assertFalse(entries.contains(Maps.immutableEntry("foo", Sets.newLinkedHashSet(asList(1, 2)))));
549     assertFalse(entries.remove(Maps.immutableEntry("foo", Sets.newLinkedHashSet(asList(1, 2)))));
550     assertFalse(entries.contains(Maps.immutableEntry("foo", Collections.singleton(2))));
551     assertFalse(entries.remove(Maps.immutableEntry("foo", Collections.singleton(2))));
552     assertTrue(map.containsKey("foo"));
553     assertTrue(entries.contains(Maps.immutableEntry("foo", Collections.singleton(1))));
554     assertTrue(entries.remove(Maps.immutableEntry("foo", Collections.singleton(1))));
555     assertFalse(map.containsKey("foo"));
556   }
557 
testForMapGetIteration()558   public void testForMapGetIteration() {
559     IteratorTester<Integer> tester =
560         new IteratorTester<Integer>(
561             4, MODIFIABLE, newHashSet(1), IteratorTester.KnownOrder.KNOWN_ORDER) {
562           private Multimap<String, Integer> multimap;
563 
564           @Override
565           protected Iterator<Integer> newTargetIterator() {
566             Map<String, Integer> map = Maps.newHashMap();
567             map.put("foo", 1);
568             map.put("bar", 2);
569             multimap = Multimaps.forMap(map);
570             return multimap.get("foo").iterator();
571           }
572 
573           @Override
574           protected void verify(List<Integer> elements) {
575             assertEquals(newHashSet(elements), multimap.get("foo"));
576           }
577         };
578 
579     tester.test();
580   }
581 
582   private enum Color {
583     BLUE,
584     RED,
585     YELLOW,
586     GREEN
587   }
588 
589   private abstract static class CountingSupplier<E> implements Supplier<E>, Serializable {
590     int count;
591 
getImpl()592     abstract E getImpl();
593 
594     @Override
get()595     public E get() {
596       count++;
597       return getImpl();
598     }
599   }
600 
601   private static class QueueSupplier extends CountingSupplier<Queue<Integer>> {
602     @Override
getImpl()603     public Queue<Integer> getImpl() {
604       return new LinkedList<>();
605     }
606 
607     private static final long serialVersionUID = 0;
608   }
609 
testNewMultimapWithCollectionRejectingNegativeElements()610   public void testNewMultimapWithCollectionRejectingNegativeElements() {
611     CountingSupplier<Set<Integer>> factory =
612         new SetSupplier() {
613           @Override
614           public Set<Integer> getImpl() {
615             final Set<Integer> backing = super.getImpl();
616             return new ForwardingSet<Integer>() {
617               @Override
618               protected Set<Integer> delegate() {
619                 return backing;
620               }
621 
622               @Override
623               public boolean add(Integer element) {
624                 checkArgument(element >= 0);
625                 return super.add(element);
626               }
627 
628               @Override
629               public boolean addAll(Collection<? extends Integer> collection) {
630                 return standardAddAll(collection);
631               }
632             };
633           }
634         };
635 
636     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
637     Multimap<Color, Integer> multimap = Multimaps.newMultimap(map, factory);
638     try {
639       multimap.put(Color.BLUE, -1);
640       fail("Expected IllegalArgumentException");
641     } catch (IllegalArgumentException expected) {
642     }
643     multimap.put(Color.RED, 1);
644     multimap.put(Color.BLUE, 2);
645     try {
646       multimap.put(Color.GREEN, -1);
647       fail("Expected IllegalArgumentException");
648     } catch (IllegalArgumentException expected) {
649     }
650     assertThat(multimap.entries())
651         .containsExactly(Maps.immutableEntry(Color.RED, 1), Maps.immutableEntry(Color.BLUE, 2));
652   }
653 
testNewMultimap()654   public void testNewMultimap() {
655     // The ubiquitous EnumArrayBlockingQueueMultimap
656     CountingSupplier<Queue<Integer>> factory = new QueueSupplier();
657 
658     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
659     Multimap<Color, Integer> multimap = Multimaps.newMultimap(map, factory);
660     assertEquals(0, factory.count);
661     multimap.putAll(Color.BLUE, asList(3, 1, 4));
662     assertEquals(1, factory.count);
663     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
664     assertEquals(2, factory.count);
665     assertEquals("[3, 1, 4]", multimap.get(Color.BLUE).toString());
666 
667     Multimap<Color, Integer> ummodifiable = Multimaps.unmodifiableMultimap(multimap);
668     assertEquals("[3, 1, 4]", ummodifiable.get(Color.BLUE).toString());
669 
670     Collection<Integer> collection = multimap.get(Color.BLUE);
671     // Explicitly call `equals`; `assertEquals` might return fast
672     assertTrue(collection.equals(collection));
673 
674     assertFalse(multimap.keySet() instanceof SortedSet);
675     assertFalse(multimap.asMap() instanceof SortedMap);
676   }
677 
testNewMultimapValueCollectionMatchesNavigableSet()678   public void testNewMultimapValueCollectionMatchesNavigableSet() {
679     Supplier<TreeSet<Integer>> factory = new SortedSetSupplier();
680     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
681     Multimap<Color, Integer> multimap = Multimaps.newMultimap(map, factory);
682     assertTrue(multimap.get(Color.BLUE) instanceof NavigableSet);
683   }
684 
testNewMultimapValueCollectionMatchesList()685   public void testNewMultimapValueCollectionMatchesList() {
686     Supplier<LinkedList<Integer>> factory = new ListSupplier();
687     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
688     Multimap<Color, Integer> multimap = Multimaps.newMultimap(map, factory);
689     assertTrue(multimap.get(Color.BLUE) instanceof List);
690   }
691 
692   @GwtIncompatible // SerializableTester
testNewMultimapSerialization()693   public void testNewMultimapSerialization() {
694     CountingSupplier<Queue<Integer>> factory = new QueueSupplier();
695     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
696     Multimap<Color, Integer> multimap = Multimaps.newMultimap(map, factory);
697     multimap.putAll(Color.BLUE, asList(3, 1, 4));
698     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
699     SerializableTester.reserializeAndAssert(multimap);
700   }
701 
702   private static class ListSupplier extends CountingSupplier<LinkedList<Integer>> {
703     @Override
getImpl()704     public LinkedList<Integer> getImpl() {
705       return new LinkedList<>();
706     }
707 
708     private static final long serialVersionUID = 0;
709   }
710 
testNewListMultimap()711   public void testNewListMultimap() {
712     CountingSupplier<LinkedList<Integer>> factory = new ListSupplier();
713     Map<Color, Collection<Integer>> map = Maps.newTreeMap();
714     ListMultimap<Color, Integer> multimap = Multimaps.newListMultimap(map, factory);
715     assertEquals(0, factory.count);
716     multimap.putAll(Color.BLUE, asList(3, 1, 4, 1));
717     assertEquals(1, factory.count);
718     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
719     assertEquals(2, factory.count);
720     assertEquals("{BLUE=[3, 1, 4, 1], RED=[2, 7, 1, 8]}", multimap.toString());
721     assertFalse(multimap.get(Color.BLUE) instanceof RandomAccess);
722 
723     assertTrue(multimap.keySet() instanceof SortedSet);
724     assertTrue(multimap.asMap() instanceof SortedMap);
725   }
726 
727   @GwtIncompatible // SerializableTester
testNewListMultimapSerialization()728   public void testNewListMultimapSerialization() {
729     CountingSupplier<LinkedList<Integer>> factory = new ListSupplier();
730     Map<Color, Collection<Integer>> map = Maps.newTreeMap();
731     ListMultimap<Color, Integer> multimap = Multimaps.newListMultimap(map, factory);
732     multimap.putAll(Color.BLUE, asList(3, 1, 4, 1));
733     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
734     SerializableTester.reserializeAndAssert(multimap);
735   }
736 
737   private static class SetSupplier extends CountingSupplier<Set<Integer>> {
738     @Override
getImpl()739     public Set<Integer> getImpl() {
740       return new HashSet<>(4);
741     }
742 
743     private static final long serialVersionUID = 0;
744   }
745 
testNewSetMultimap()746   public void testNewSetMultimap() {
747     CountingSupplier<Set<Integer>> factory = new SetSupplier();
748     Map<Color, Collection<Integer>> map = Maps.newHashMap();
749     SetMultimap<Color, Integer> multimap = Multimaps.newSetMultimap(map, factory);
750     assertEquals(0, factory.count);
751     multimap.putAll(Color.BLUE, asList(3, 1, 4));
752     assertEquals(1, factory.count);
753     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
754     assertEquals(2, factory.count);
755     assertEquals(Sets.newHashSet(4, 3, 1), multimap.get(Color.BLUE));
756   }
757 
758   @GwtIncompatible // SerializableTester
testNewSetMultimapSerialization()759   public void testNewSetMultimapSerialization() {
760     CountingSupplier<Set<Integer>> factory = new SetSupplier();
761     Map<Color, Collection<Integer>> map = Maps.newHashMap();
762     SetMultimap<Color, Integer> multimap = Multimaps.newSetMultimap(map, factory);
763     multimap.putAll(Color.BLUE, asList(3, 1, 4));
764     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
765     SerializableTester.reserializeAndAssert(multimap);
766   }
767 
768   private static class SortedSetSupplier extends CountingSupplier<TreeSet<Integer>> {
769     @Override
getImpl()770     public TreeSet<Integer> getImpl() {
771       return Sets.newTreeSet(INT_COMPARATOR);
772     }
773 
774     private static final long serialVersionUID = 0;
775   }
776 
testNewSortedSetMultimap()777   public void testNewSortedSetMultimap() {
778     CountingSupplier<TreeSet<Integer>> factory = new SortedSetSupplier();
779     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
780     SortedSetMultimap<Color, Integer> multimap = Multimaps.newSortedSetMultimap(map, factory);
781     // newSortedSetMultimap calls the factory once to determine the comparator.
782     assertEquals(1, factory.count);
783     multimap.putAll(Color.BLUE, asList(3, 1, 4));
784     assertEquals(2, factory.count);
785     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
786     assertEquals(3, factory.count);
787     assertEquals("[4, 3, 1]", multimap.get(Color.BLUE).toString());
788     assertEquals(INT_COMPARATOR, multimap.valueComparator());
789   }
790 
791   @GwtIncompatible // SerializableTester
testNewSortedSetMultimapSerialization()792   public void testNewSortedSetMultimapSerialization() {
793     CountingSupplier<TreeSet<Integer>> factory = new SortedSetSupplier();
794     Map<Color, Collection<Integer>> map = Maps.newEnumMap(Color.class);
795     SortedSetMultimap<Color, Integer> multimap = Multimaps.newSortedSetMultimap(map, factory);
796     multimap.putAll(Color.BLUE, asList(3, 1, 4));
797     multimap.putAll(Color.RED, asList(2, 7, 1, 8));
798     SerializableTester.reserializeAndAssert(multimap);
799     assertEquals(INT_COMPARATOR, multimap.valueComparator());
800   }
801 
testIndex()802   public void testIndex() {
803     final Multimap<String, Object> stringToObject =
804         new ImmutableMultimap.Builder<String, Object>()
805             .put("1", 1)
806             .put("1", 1L)
807             .put("1", "1")
808             .put("2", 2)
809             .put("2", 2L)
810             .build();
811 
812     ImmutableMultimap<String, Object> outputMap =
813         Multimaps.index(stringToObject.values(), Functions.toStringFunction());
814     assertEquals(stringToObject, outputMap);
815   }
816 
testIndexIterator()817   public void testIndexIterator() {
818     final Multimap<String, Object> stringToObject =
819         new ImmutableMultimap.Builder<String, Object>()
820             .put("1", 1)
821             .put("1", 1L)
822             .put("1", "1")
823             .put("2", 2)
824             .put("2", 2L)
825             .build();
826 
827     ImmutableMultimap<String, Object> outputMap =
828         Multimaps.index(stringToObject.values().iterator(), Functions.toStringFunction());
829     assertEquals(stringToObject, outputMap);
830   }
831 
testIndex_ordering()832   public void testIndex_ordering() {
833     final Multimap<Integer, String> expectedIndex =
834         new ImmutableListMultimap.Builder<Integer, String>()
835             .put(4, "Inky")
836             .put(6, "Blinky")
837             .put(5, "Pinky")
838             .put(5, "Pinky")
839             .put(5, "Clyde")
840             .build();
841 
842     final List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
843     final Function<String, Integer> stringLengthFunction =
844         new Function<String, Integer>() {
845           @Override
846           public Integer apply(String input) {
847             return input.length();
848           }
849         };
850 
851     Multimap<Integer, String> index = Multimaps.index(badGuys, stringLengthFunction);
852 
853     assertEquals(expectedIndex, index);
854   }
855 
testIndex_nullValue()856   public void testIndex_nullValue() {
857     List<Integer> values = Arrays.asList(1, null);
858     try {
859       Multimaps.index(values, Functions.identity());
860       fail();
861     } catch (NullPointerException expected) {
862     }
863   }
864 
testIndex_nullKey()865   public void testIndex_nullKey() {
866     List<Integer> values = Arrays.asList(1, 2);
867     try {
868       Multimaps.index(values, Functions.constant(null));
869       fail();
870     } catch (NullPointerException expected) {
871     }
872   }
873 
874   @GwtIncompatible(value = "untested")
testTransformValues()875   public void testTransformValues() {
876     SetMultimap<String, Integer> multimap =
877         ImmutableSetMultimap.of("a", 2, "b", -3, "b", 3, "a", 4, "c", 6);
878     Function<Integer, Integer> square =
879         new Function<Integer, Integer>() {
880           @Override
881           public Integer apply(Integer in) {
882             return in * in;
883           }
884         };
885     Multimap<String, Integer> transformed = Multimaps.transformValues(multimap, square);
886     assertThat(transformed.entries())
887         .containsExactly(
888             immutableEntry("a", 4),
889             immutableEntry("a", 16),
890             immutableEntry("b", 9),
891             immutableEntry("b", 9),
892             immutableEntry("c", 36))
893         .inOrder();
894   }
895 
896   @GwtIncompatible(value = "untested")
testTransformValuesIsView()897   public void testTransformValuesIsView() {
898     Multimap<String, String> multimap = LinkedListMultimap.create();
899     multimap.put("a", "a");
900     Multimap<String, Integer> transformed =
901         Multimaps.transformValues(
902             multimap,
903             new Function<String, Integer>() {
904 
905               @Override
906               public Integer apply(String str) {
907                 return str.length();
908               }
909             });
910     Entry<String, String> entry = multimap.entries().iterator().next();
911     entry.setValue("bbb");
912     assertThat(transformed.entries()).containsExactly(immutableEntry("a", 3));
913   }
914 
915   @GwtIncompatible(value = "untested")
testTransformListValues()916   public void testTransformListValues() {
917     ListMultimap<String, Integer> multimap =
918         ImmutableListMultimap.of("a", 2, "b", -3, "b", 3, "a", 4, "c", 6);
919     Function<Integer, Integer> square =
920         new Function<Integer, Integer>() {
921           @Override
922           public Integer apply(Integer in) {
923             return in * in;
924           }
925         };
926     ListMultimap<String, Integer> transformed = Multimaps.transformValues(multimap, square);
927     assertThat(transformed.entries())
928         .containsExactly(
929             immutableEntry("a", 4),
930             immutableEntry("a", 16),
931             immutableEntry("b", 9),
932             immutableEntry("b", 9),
933             immutableEntry("c", 36))
934         .inOrder();
935   }
936 
937   @GwtIncompatible(value = "untested")
testTransformEntries()938   public void testTransformEntries() {
939     SetMultimap<String, Integer> multimap = ImmutableSetMultimap.of("a", 1, "a", 4, "b", -6);
940     EntryTransformer<String, Integer, String> transformer =
941         new EntryTransformer<String, Integer, String>() {
942           @Override
943           public String transformEntry(String key, Integer value) {
944             return (value >= 0) ? key : "no" + key;
945           }
946         };
947     Multimap<String, String> transformed = Multimaps.transformEntries(multimap, transformer);
948     assertThat(transformed.entries())
949         .containsExactly(
950             immutableEntry("a", "a"), immutableEntry("a", "a"), immutableEntry("b", "nob"))
951         .inOrder();
952   }
953 
954   @GwtIncompatible(value = "untested")
testTransformListEntries()955   public void testTransformListEntries() {
956     ListMultimap<String, Integer> multimap =
957         ImmutableListMultimap.of("a", 1, "a", 4, "b", 6, "a", 4);
958     EntryTransformer<String, Integer, String> transformer =
959         new EntryTransformer<String, Integer, String>() {
960           @Override
961           public String transformEntry(String key, Integer value) {
962             return key + value;
963           }
964         };
965     ListMultimap<String, String> transformed = Multimaps.transformEntries(multimap, transformer);
966     assertEquals(ImmutableListMultimap.of("a", "a1", "a", "a4", "a", "a4", "b", "b6"), transformed);
967     assertEquals("{a=[a1, a4, a4], b=[b6]}", transformed.toString());
968   }
969 
testSynchronizedMultimapSampleCodeCompilation()970   public <K, V> void testSynchronizedMultimapSampleCodeCompilation() {
971     K key = null;
972 
973     Multimap<K, V> multimap = Multimaps.synchronizedMultimap(HashMultimap.<K, V>create());
974     Collection<V> values = multimap.get(key); // Needn't be in synchronized block
975     synchronized (multimap) { // Synchronizing on multimap, not values!
976       Iterator<V> i = values.iterator(); // Must be in synchronized block
977       while (i.hasNext()) {
978         foo(i.next());
979       }
980     }
981   }
982 
foo(Object o)983   private static void foo(Object o) {}
984 
testFilteredKeysSetMultimapReplaceValues()985   public void testFilteredKeysSetMultimapReplaceValues() {
986     SetMultimap<String, Integer> multimap = LinkedHashMultimap.create();
987     multimap.put("foo", 1);
988     multimap.put("bar", 2);
989     multimap.put("baz", 3);
990     multimap.put("bar", 4);
991 
992     SetMultimap<String, Integer> filtered =
993         Multimaps.filterKeys(multimap, Predicates.in(ImmutableSet.of("foo", "bar")));
994 
995     assertEquals(ImmutableSet.of(), filtered.replaceValues("baz", ImmutableSet.<Integer>of()));
996 
997     try {
998       filtered.replaceValues("baz", ImmutableSet.of(5));
999       fail("Expected IllegalArgumentException");
1000     } catch (IllegalArgumentException expected) {
1001     }
1002   }
1003 
testFilteredKeysSetMultimapGetBadValue()1004   public void testFilteredKeysSetMultimapGetBadValue() {
1005     SetMultimap<String, Integer> multimap = LinkedHashMultimap.create();
1006     multimap.put("foo", 1);
1007     multimap.put("bar", 2);
1008     multimap.put("baz", 3);
1009     multimap.put("bar", 4);
1010 
1011     SetMultimap<String, Integer> filtered =
1012         Multimaps.filterKeys(multimap, Predicates.in(ImmutableSet.of("foo", "bar")));
1013     Set<Integer> bazSet = filtered.get("baz");
1014     assertThat(bazSet).isEmpty();
1015     try {
1016       bazSet.add(5);
1017       fail("Expected IllegalArgumentException");
1018     } catch (IllegalArgumentException expected) {
1019     }
1020     try {
1021       bazSet.addAll(ImmutableSet.of(6, 7));
1022       fail("Expected IllegalArgumentException");
1023     } catch (IllegalArgumentException expected) {
1024     }
1025   }
1026 
testFilteredKeysListMultimapGetBadValue()1027   public void testFilteredKeysListMultimapGetBadValue() {
1028     ListMultimap<String, Integer> multimap = ArrayListMultimap.create();
1029     multimap.put("foo", 1);
1030     multimap.put("bar", 2);
1031     multimap.put("baz", 3);
1032     multimap.put("bar", 4);
1033 
1034     ListMultimap<String, Integer> filtered =
1035         Multimaps.filterKeys(multimap, Predicates.in(ImmutableSet.of("foo", "bar")));
1036     List<Integer> bazList = filtered.get("baz");
1037     assertThat(bazList).isEmpty();
1038     try {
1039       bazList.add(5);
1040       fail("Expected IllegalArgumentException");
1041     } catch (IllegalArgumentException expected) {
1042     }
1043     try {
1044       bazList.add(0, 6);
1045       fail("Expected IllegalArgumentException");
1046     } catch (IllegalArgumentException expected) {
1047     }
1048     try {
1049       bazList.addAll(ImmutableList.of(7, 8));
1050       fail("Expected IllegalArgumentException");
1051     } catch (IllegalArgumentException expected) {
1052     }
1053     try {
1054       bazList.addAll(0, ImmutableList.of(9, 10));
1055       fail("Expected IllegalArgumentException");
1056     } catch (IllegalArgumentException expected) {
1057     }
1058   }
1059 
1060   @GwtIncompatible // NullPointerTester
1061   @AndroidIncompatible // see ImmutableTableTest.testNullPointerInstance
testNullPointers()1062   public void testNullPointers() {
1063     new NullPointerTester().testAllPublicStaticMethods(Multimaps.class);
1064   }
1065 }
1066