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.collect.testing.Helpers.nefariousMapEntry;
20  import static com.google.common.truth.Truth.assertThat;
21  
22  import com.google.common.annotations.GwtCompatible;
23  import com.google.common.annotations.GwtIncompatible;
24  import com.google.common.base.Supplier;
25  import com.google.common.testing.SerializableTester;
26  
27  import junit.framework.TestCase;
28  
29  import java.io.Serializable;
30  import java.util.AbstractMap;
31  import java.util.Arrays;
32  import java.util.Collection;
33  import java.util.Collections;
34  import java.util.Comparator;
35  import java.util.HashMap;
36  import java.util.Iterator;
37  import java.util.LinkedList;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Queue;
41  import java.util.RandomAccess;
42  import java.util.Set;
43  import java.util.SortedSet;
44  
45  /**
46   * Tests for {@code MapConstraints}.
47   *
48   * @author Mike Bostock
49   * @author Jared Levy
50   */
51  @GwtCompatible(emulated = true)
52  public class MapConstraintsTest extends TestCase {
53  
54    private static final String TEST_KEY = "test";
55  
56    private static final Integer TEST_VALUE = 42;
57  
58    static final class TestKeyException extends IllegalArgumentException {
59      private static final long serialVersionUID = 0;
60    }
61  
62    static final class TestValueException extends IllegalArgumentException {
63      private static final long serialVersionUID = 0;
64    }
65  
66    static final MapConstraint<String, Integer> TEST_CONSTRAINT
67        = new TestConstraint();
68  
69    private static final class TestConstraint
70        implements MapConstraint<String, Integer>, Serializable {
71      @Override
72      public void checkKeyValue(String key, Integer value) {
73        if (TEST_KEY.equals(key)) {
74          throw new TestKeyException();
75        }
76        if (TEST_VALUE.equals(value)) {
77          throw new TestValueException();
78        }
79      }
80      private static final long serialVersionUID = 0;
81    }
82  
83    public void testNotNull() {
84      MapConstraint<Object, Object> constraint = MapConstraints.notNull();
85      constraint.checkKeyValue("foo", 1);
86      assertEquals("Not null", constraint.toString());
87      try {
88        constraint.checkKeyValue(null, 1);
89        fail("NullPointerException expected");
90      } catch (NullPointerException expected) {}
91      try {
92        constraint.checkKeyValue("foo", null);
93        fail("NullPointerException expected");
94      } catch (NullPointerException expected) {}
95      try {
96        constraint.checkKeyValue(null, null);
97        fail("NullPointerException expected");
98      } catch (NullPointerException expected) {}
99    }
100 
101   public void testConstrainedMapLegal() {
102     Map<String, Integer> map = Maps.newLinkedHashMap();
103     Map<String, Integer> constrained = MapConstraints.constrainedMap(
104         map, TEST_CONSTRAINT);
105     map.put(TEST_KEY, TEST_VALUE);
106     constrained.put("foo", 1);
107     map.putAll(ImmutableMap.of("bar", 2));
108     constrained.putAll(ImmutableMap.of("baz", 3));
109     assertTrue(map.equals(constrained));
110     assertTrue(constrained.equals(map));
111     assertEquals(map.entrySet(), constrained.entrySet());
112     assertEquals(map.keySet(), constrained.keySet());
113     assertEquals(HashMultiset.create(map.values()),
114         HashMultiset.create(constrained.values()));
115     assertFalse(map.values() instanceof Serializable);
116     assertEquals(map.toString(), constrained.toString());
117     assertEquals(map.hashCode(), constrained.hashCode());
118     assertThat(map.entrySet()).has().exactly(
119         Maps.immutableEntry(TEST_KEY, TEST_VALUE),
120         Maps.immutableEntry("foo", 1),
121         Maps.immutableEntry("bar", 2),
122         Maps.immutableEntry("baz", 3)).inOrder();
123   }
124 
125   public void testConstrainedMapIllegal() {
126     Map<String, Integer> map = Maps.newLinkedHashMap();
127     Map<String, Integer> constrained = MapConstraints.constrainedMap(
128         map, TEST_CONSTRAINT);
129     try {
130       constrained.put(TEST_KEY, TEST_VALUE);
131       fail("TestKeyException expected");
132     } catch (TestKeyException expected) {}
133     try {
134       constrained.put("baz", TEST_VALUE);
135       fail("TestValueException expected");
136     } catch (TestValueException expected) {}
137     try {
138       constrained.put(TEST_KEY, 3);
139       fail("TestKeyException expected");
140     } catch (TestKeyException expected) {}
141     try {
142       constrained.putAll(ImmutableMap.of("baz", 3, TEST_KEY, 4));
143       fail("TestKeyException expected");
144     } catch (TestKeyException expected) {}
145     assertEquals(Collections.emptySet(), map.entrySet());
146     assertEquals(Collections.emptySet(), constrained.entrySet());
147   }
148 
149   public void testConstrainedBiMapLegal() {
150     BiMap<String, Integer> map = new AbstractBiMap<String, Integer>(
151         Maps.<String, Integer>newLinkedHashMap(),
152         Maps.<Integer, String>newLinkedHashMap()) {};
153     BiMap<String, Integer> constrained = MapConstraints.constrainedBiMap(
154         map, TEST_CONSTRAINT);
155     map.put(TEST_KEY, TEST_VALUE);
156     constrained.put("foo", 1);
157     map.putAll(ImmutableMap.of("bar", 2));
158     constrained.putAll(ImmutableMap.of("baz", 3));
159     assertTrue(map.equals(constrained));
160     assertTrue(constrained.equals(map));
161     assertEquals(map.entrySet(), constrained.entrySet());
162     assertEquals(map.keySet(), constrained.keySet());
163     assertEquals(map.values(), constrained.values());
164     assertEquals(map.toString(), constrained.toString());
165     assertEquals(map.hashCode(), constrained.hashCode());
166     assertThat(map.entrySet()).has().exactly(
167         Maps.immutableEntry(TEST_KEY, TEST_VALUE),
168         Maps.immutableEntry("foo", 1),
169         Maps.immutableEntry("bar", 2),
170         Maps.immutableEntry("baz", 3)).inOrder();
171   }
172 
173   public void testConstrainedBiMapIllegal() {
174     BiMap<String, Integer> map = new AbstractBiMap<String, Integer>(
175         Maps.<String, Integer>newLinkedHashMap(),
176         Maps.<Integer, String>newLinkedHashMap()) {};
177     BiMap<String, Integer> constrained = MapConstraints.constrainedBiMap(
178         map, TEST_CONSTRAINT);
179     try {
180       constrained.put(TEST_KEY, TEST_VALUE);
181       fail("TestKeyException expected");
182     } catch (TestKeyException expected) {}
183     try {
184       constrained.put("baz", TEST_VALUE);
185       fail("TestValueException expected");
186     } catch (TestValueException expected) {}
187     try {
188       constrained.put(TEST_KEY, 3);
189       fail("TestKeyException expected");
190     } catch (TestKeyException expected) {}
191     try {
192       constrained.putAll(ImmutableMap.of("baz", 3, TEST_KEY, 4));
193       fail("TestKeyException expected");
194     } catch (TestKeyException expected) {}
195     try {
196       constrained.forcePut(TEST_KEY, 3);
197       fail("TestKeyException expected");
198     } catch (TestKeyException expected) {}
199     try {
200       constrained.inverse().forcePut(TEST_VALUE, "baz");
201       fail("TestValueException expected");
202     } catch (TestValueException expected) {}
203     try {
204       constrained.inverse().forcePut(3, TEST_KEY);
205       fail("TestKeyException expected");
206     } catch (TestKeyException expected) {}
207     assertEquals(Collections.emptySet(), map.entrySet());
208     assertEquals(Collections.emptySet(), constrained.entrySet());
209   }
210 
211   public void testConstrainedMultimapLegal() {
212     Multimap<String, Integer> multimap = LinkedListMultimap.create();
213     Multimap<String, Integer> constrained = MapConstraints.constrainedMultimap(
214         multimap, TEST_CONSTRAINT);
215     multimap.put(TEST_KEY, TEST_VALUE);
216     constrained.put("foo", 1);
217     multimap.get("bar").add(2);
218     constrained.get("baz").add(3);
219     multimap.get("qux").addAll(Arrays.asList(4));
220     constrained.get("zig").addAll(Arrays.asList(5));
221     multimap.putAll("zag", Arrays.asList(6));
222     constrained.putAll("bee", Arrays.asList(7));
223     multimap.putAll(new ImmutableMultimap.Builder<String, Integer>()
224         .put("bim", 8).build());
225     constrained.putAll(new ImmutableMultimap.Builder<String, Integer>()
226         .put("bop", 9).build());
227     multimap.putAll(new ImmutableMultimap.Builder<String, Integer>()
228         .put("dig", 10).build());
229     constrained.putAll(new ImmutableMultimap.Builder<String, Integer>()
230         .put("dag", 11).build());
231     assertTrue(multimap.equals(constrained));
232     assertTrue(constrained.equals(multimap));
233     assertThat(ImmutableList.copyOf(multimap.entries())).isEqualTo(
234         ImmutableList.copyOf(constrained.entries()));
235     assertThat(constrained.asMap().get("foo")).has().item(1);
236     assertNull(constrained.asMap().get("missing"));
237     assertEquals(multimap.asMap(), constrained.asMap());
238     assertEquals(multimap.values(), constrained.values());
239     assertEquals(multimap.keys(), constrained.keys());
240     assertEquals(multimap.keySet(), constrained.keySet());
241     assertEquals(multimap.toString(), constrained.toString());
242     assertEquals(multimap.hashCode(), constrained.hashCode());
243     assertThat(multimap.entries()).has().exactly(
244         Maps.immutableEntry(TEST_KEY, TEST_VALUE),
245         Maps.immutableEntry("foo", 1),
246         Maps.immutableEntry("bar", 2),
247         Maps.immutableEntry("baz", 3),
248         Maps.immutableEntry("qux", 4),
249         Maps.immutableEntry("zig", 5),
250         Maps.immutableEntry("zag", 6),
251         Maps.immutableEntry("bee", 7),
252         Maps.immutableEntry("bim", 8),
253         Maps.immutableEntry("bop", 9),
254         Maps.immutableEntry("dig", 10),
255         Maps.immutableEntry("dag", 11)).inOrder();
256     assertFalse(constrained.asMap().values() instanceof Serializable);
257     Iterator<Collection<Integer>> iterator =
258         constrained.asMap().values().iterator();
259     iterator.next();
260     iterator.next().add(12);
261     assertTrue(multimap.containsEntry("foo", 12));
262   }
263 
264   public void testConstrainedTypePreservingList() {
265     ListMultimap<String, Integer> multimap
266         = MapConstraints.constrainedListMultimap(
267             LinkedListMultimap.<String, Integer>create(),
268             TEST_CONSTRAINT);
269     multimap.put("foo", 1);
270     Map.Entry<String, Collection<Integer>> entry
271         = multimap.asMap().entrySet().iterator().next();
272     assertTrue(entry.getValue() instanceof List);
273     assertFalse(multimap.entries() instanceof Set);
274     assertFalse(multimap.get("foo") instanceof RandomAccess);
275   }
276 
277   public void testConstrainedTypePreservingRandomAccessList() {
278     ListMultimap<String, Integer> multimap
279         = MapConstraints.constrainedListMultimap(
280             ArrayListMultimap.<String, Integer>create(),
281             TEST_CONSTRAINT);
282     multimap.put("foo", 1);
283     Map.Entry<String, Collection<Integer>> entry
284         = multimap.asMap().entrySet().iterator().next();
285     assertTrue(entry.getValue() instanceof List);
286     assertFalse(multimap.entries() instanceof Set);
287     assertTrue(multimap.get("foo") instanceof RandomAccess);
288   }
289 
290   public void testConstrainedTypePreservingSet() {
291     SetMultimap<String, Integer> multimap
292         = MapConstraints.constrainedSetMultimap(
293             LinkedHashMultimap.<String, Integer>create(),
294             TEST_CONSTRAINT);
295     multimap.put("foo", 1);
296     Map.Entry<String, Collection<Integer>> entry
297         = multimap.asMap().entrySet().iterator().next();
298     assertTrue(entry.getValue() instanceof Set);
299   }
300 
301   public void testConstrainedTypePreservingSortedSet() {
302     Comparator<Integer> comparator = Collections.reverseOrder();
303     SortedSetMultimap<String, Integer> delegate
304         = TreeMultimap.create(Ordering.<String>natural(), comparator);
305     SortedSetMultimap<String, Integer> multimap
306         = MapConstraints.constrainedSortedSetMultimap(delegate,
307             TEST_CONSTRAINT);
308     multimap.put("foo", 1);
309     Map.Entry<String, Collection<Integer>> entry
310         = multimap.asMap().entrySet().iterator().next();
311     assertTrue(entry.getValue() instanceof SortedSet);
312     assertSame(comparator, multimap.valueComparator());
313     assertSame(comparator, multimap.get("foo").comparator());
314   }
315 
316   @SuppressWarnings("unchecked")
317   public void testConstrainedMultimapIllegal() {
318     Multimap<String, Integer> multimap = LinkedListMultimap.create();
319     Multimap<String, Integer> constrained = MapConstraints.constrainedMultimap(
320         multimap, TEST_CONSTRAINT);
321     try {
322       constrained.put(TEST_KEY, 1);
323       fail("TestKeyException expected");
324     } catch (TestKeyException expected) {}
325     try {
326       constrained.put("foo", TEST_VALUE);
327       fail("TestValueException expected");
328     } catch (TestValueException expected) {}
329     try {
330       constrained.put(TEST_KEY, TEST_VALUE);
331       fail("TestKeyException expected");
332     } catch (TestKeyException expected) {}
333     try {
334       constrained.get(TEST_KEY).add(1);
335       fail("TestKeyException expected");
336     } catch (TestKeyException expected) {}
337     try {
338       constrained.get("foo").add(TEST_VALUE);
339       fail("TestValueException expected");
340     } catch (TestValueException expected) {}
341     try {
342       constrained.get(TEST_KEY).add(TEST_VALUE);
343       fail("TestKeyException expected");
344     } catch (TestKeyException expected) {}
345     try {
346       constrained.get(TEST_KEY).addAll(Arrays.asList(1));
347       fail("TestKeyException expected");
348     } catch (TestKeyException expected) {}
349     try {
350       constrained.get("foo").addAll(Arrays.asList(1, TEST_VALUE));
351       fail("TestValueException expected");
352     } catch (TestValueException expected) {}
353     try {
354       constrained.get(TEST_KEY).addAll(Arrays.asList(1, TEST_VALUE));
355       fail("TestKeyException expected");
356     } catch (TestKeyException expected) {}
357     try {
358       constrained.putAll(TEST_KEY, Arrays.asList(1));
359       fail("TestKeyException expected");
360     } catch (TestKeyException expected) {}
361     try {
362       constrained.putAll("foo", Arrays.asList(1, TEST_VALUE));
363       fail("TestValueException expected");
364     } catch (TestValueException expected) {}
365     try {
366       constrained.putAll(TEST_KEY, Arrays.asList(1, TEST_VALUE));
367       fail("TestKeyException expected");
368     } catch (TestKeyException expected) {}
369     try {
370       constrained.putAll(new ImmutableMultimap.Builder<String, Integer>()
371           .put(TEST_KEY, 2).put("foo", 1).build());
372       fail("TestKeyException expected");
373     } catch (TestKeyException expected) {}
374     try {
375       constrained.putAll(new ImmutableMultimap.Builder<String, Integer>()
376           .put("bar", TEST_VALUE).put("foo", 1).build());
377       fail("TestValueException expected");
378     } catch (TestValueException expected) {}
379     try {
380       constrained.putAll(new ImmutableMultimap.Builder<String, Integer>()
381           .put(TEST_KEY, TEST_VALUE).put("foo", 1).build());
382       fail("TestKeyException expected");
383     } catch (TestKeyException expected) {}
384     try {
385       constrained.entries().add(Maps.immutableEntry(TEST_KEY, 1));
386       fail("UnsupportedOperationException expected");
387     } catch (UnsupportedOperationException expected) {}
388     try {
389       constrained.entries().addAll(Arrays.asList(
390           Maps.immutableEntry("foo", 1),
391           Maps.immutableEntry(TEST_KEY, 2)));
392       fail("UnsupportedOperationException expected");
393     } catch (UnsupportedOperationException expected) {}
394     assertTrue(multimap.isEmpty());
395     assertTrue(constrained.isEmpty());
396     constrained.put("foo", 1);
397     try {
398       constrained.asMap().get("foo").add(TEST_VALUE);
399       fail("TestValueException expected");
400     } catch (TestValueException expected) {}
401     try {
402       constrained.asMap().values().iterator().next().add(TEST_VALUE);
403       fail("TestValueException expected");
404     } catch (TestValueException expected) {}
405     try {
406       ((Collection<Integer>) constrained.asMap().values().toArray()[0])
407           .add(TEST_VALUE);
408       fail("TestValueException expected");
409     } catch (TestValueException expected) {}
410     assertThat(ImmutableList.copyOf(multimap.entries())).isEqualTo(
411         ImmutableList.copyOf(constrained.entries()));
412     assertEquals(multimap.asMap(), constrained.asMap());
413     assertEquals(multimap.values(), constrained.values());
414     assertEquals(multimap.keys(), constrained.keys());
415     assertEquals(multimap.keySet(), constrained.keySet());
416     assertEquals(multimap.toString(), constrained.toString());
417     assertEquals(multimap.hashCode(), constrained.hashCode());
418   }
419 
420   private static class QueueSupplier implements Supplier<Queue<Integer>> {
421     @Override
422     public Queue<Integer> get() {
423       return new LinkedList<Integer>();
424     }
425   }
426 
427   public void testConstrainedMultimapQueue() {
428     Multimap<String, Integer> multimap = Multimaps.newMultimap(
429         new HashMap<String, Collection<Integer>>(), new QueueSupplier());
430     Multimap<String, Integer> constrained = MapConstraints.constrainedMultimap(
431         multimap, TEST_CONSTRAINT);
432     constrained.put("foo", 1);
433     assertTrue(constrained.get("foo").contains(1));
434     assertTrue(multimap.get("foo").contains(1));
435     try {
436       constrained.put(TEST_KEY, 1);
437       fail("TestKeyException expected");
438     } catch (TestKeyException expected) {}
439     try {
440       constrained.put("foo", TEST_VALUE);
441       fail("TestValueException expected");
442     } catch (TestValueException expected) {}
443     try {
444       constrained.get("foo").add(TEST_VALUE);
445       fail("TestKeyException expected");
446     } catch (TestValueException expected) {}
447     try {
448       constrained.get(TEST_KEY).add(1);
449       fail("TestValueException expected");
450     } catch (TestKeyException expected) {}
451     assertEquals(1, constrained.size());
452     assertEquals(1, multimap.size());
453   }
454 
455   public void testMapEntrySetToArray() {
456     Map<String, Integer> map = Maps.newLinkedHashMap();
457     Map<String, Integer> constrained
458         = MapConstraints.constrainedMap(map, TEST_CONSTRAINT);
459     map.put("foo", 1);
460     @SuppressWarnings("unchecked")
461     Map.Entry<String, Integer> entry
462         = (Map.Entry) constrained.entrySet().toArray()[0];
463     try {
464       entry.setValue(TEST_VALUE);
465       fail("TestValueException expected");
466     } catch (TestValueException expected) {}
467     assertFalse(map.containsValue(TEST_VALUE));
468   }
469 
470   public void testMapEntrySetContainsNefariousEntry() {
471     Map<String, Integer> map = Maps.newTreeMap();
472     Map<String, Integer> constrained
473         = MapConstraints.constrainedMap(map, TEST_CONSTRAINT);
474     map.put("foo", 1);
475     Map.Entry<String, Integer> nefariousEntry
476         = nefariousMapEntry(TEST_KEY, TEST_VALUE);
477     Set<Map.Entry<String, Integer>> entries = constrained.entrySet();
478     assertFalse(entries.contains(nefariousEntry));
479     assertFalse(map.containsValue(TEST_VALUE));
480     assertFalse(entries.containsAll(Collections.singleton(nefariousEntry)));
481     assertFalse(map.containsValue(TEST_VALUE));
482   }
483 
484   public void testMultimapAsMapEntriesToArray() {
485     Multimap<String, Integer> multimap = LinkedListMultimap.create();
486     Multimap<String, Integer> constrained
487         = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT);
488     multimap.put("foo", 1);
489     @SuppressWarnings("unchecked")
490     Map.Entry<String, Collection<Integer>> entry
491         = (Map.Entry<String, Collection<Integer>>)
492             constrained.asMap().entrySet().toArray()[0];
493     try {
494       entry.setValue(Collections.<Integer>emptySet());
495       fail("UnsupportedOperationException expected");
496     } catch (UnsupportedOperationException expected) {}
497     try {
498       entry.getValue().add(TEST_VALUE);
499       fail("TestValueException expected");
500     } catch (TestValueException expected) {}
501     assertFalse(multimap.containsValue(TEST_VALUE));
502   }
503 
504   public void testMultimapAsMapValuesToArray() {
505     Multimap<String, Integer> multimap = LinkedListMultimap.create();
506     Multimap<String, Integer> constrained
507         = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT);
508     multimap.put("foo", 1);
509     @SuppressWarnings("unchecked")
510     Collection<Integer> collection
511         = (Collection<Integer>) constrained.asMap().values().toArray()[0];
512     try {
513       collection.add(TEST_VALUE);
514       fail("TestValueException expected");
515     } catch (TestValueException expected) {}
516     assertFalse(multimap.containsValue(TEST_VALUE));
517   }
518 
519   public void testMultimapEntriesContainsNefariousEntry() {
520     Multimap<String, Integer> multimap = LinkedListMultimap.create();
521     Multimap<String, Integer> constrained
522         = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT);
523     multimap.put("foo", 1);
524     Map.Entry<String, Integer> nefariousEntry
525         = nefariousMapEntry(TEST_KEY, TEST_VALUE);
526     Collection<Map.Entry<String, Integer>> entries = constrained.entries();
527     assertFalse(entries.contains(nefariousEntry));
528     assertFalse(multimap.containsValue(TEST_VALUE));
529     assertFalse(entries.containsAll(Collections.singleton(nefariousEntry)));
530     assertFalse(multimap.containsValue(TEST_VALUE));
531   }
532 
533   public void testMultimapEntriesRemoveNefariousEntry() {
534     Multimap<String, Integer> multimap = LinkedListMultimap.create();
535     Multimap<String, Integer> constrained
536         = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT);
537     multimap.put("foo", 1);
538     Map.Entry<String, Integer> nefariousEntry
539         = nefariousMapEntry(TEST_KEY, TEST_VALUE);
540     Collection<Map.Entry<String, Integer>> entries = constrained.entries();
541     assertFalse(entries.remove(nefariousEntry));
542     assertFalse(multimap.containsValue(TEST_VALUE));
543     assertFalse(entries.removeAll(Collections.singleton(nefariousEntry)));
544     assertFalse(multimap.containsValue(TEST_VALUE));
545   }
546 
547   public void testMultimapAsMapEntriesContainsNefariousEntry() {
548     Multimap<String, Integer> multimap = LinkedListMultimap.create();
549     Multimap<String, Integer> constrained
550         = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT);
551     multimap.put("foo", 1);
552     Map.Entry<String, ? extends Collection<Integer>> nefariousEntry
553         = nefariousMapEntry(TEST_KEY, Collections.singleton(TEST_VALUE));
554     Set<Map.Entry<String, Collection<Integer>>> entries
555         = constrained.asMap().entrySet();
556     assertFalse(entries.contains(nefariousEntry));
557     assertFalse(multimap.containsValue(TEST_VALUE));
558     assertFalse(entries.containsAll(Collections.singleton(nefariousEntry)));
559     assertFalse(multimap.containsValue(TEST_VALUE));
560   }
561 
562   public void testMultimapAsMapEntriesRemoveNefariousEntry() {
563     Multimap<String, Integer> multimap = LinkedListMultimap.create();
564     Multimap<String, Integer> constrained
565         = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT);
566     multimap.put("foo", 1);
567     Map.Entry<String, ? extends Collection<Integer>> nefariousEntry
568         = nefariousMapEntry(TEST_KEY, Collections.singleton(TEST_VALUE));
569     Set<Map.Entry<String, Collection<Integer>>> entries
570         = constrained.asMap().entrySet();
571     assertFalse(entries.remove(nefariousEntry));
572     assertFalse(multimap.containsValue(TEST_VALUE));
573     assertFalse(entries.removeAll(Collections.singleton(nefariousEntry)));
574     assertFalse(multimap.containsValue(TEST_VALUE));
575   }
576 
577   public void testNefariousMapPutAll() {
578     Map<String, Integer> map = Maps.newLinkedHashMap();
579     Map<String, Integer> constrained = MapConstraints.constrainedMap(
580         map, TEST_CONSTRAINT);
581     Map<String, Integer> onceIterable = onceIterableMap("foo", 1);
582     constrained.putAll(onceIterable);
583     assertEquals((Integer) 1, constrained.get("foo"));
584   }
585 
586   public void testNefariousMultimapPutAllIterable() {
587     Multimap<String, Integer> multimap = LinkedListMultimap.create();
588     Multimap<String, Integer> constrained = MapConstraints.constrainedMultimap(
589         multimap, TEST_CONSTRAINT);
590     Collection<Integer> onceIterable
591         = ConstraintsTest.onceIterableCollection(1);
592     constrained.putAll("foo", onceIterable);
593     assertEquals(ImmutableList.of(1), constrained.get("foo"));
594   }
595 
596   public void testNefariousMultimapPutAllMultimap() {
597     Multimap<String, Integer> multimap = LinkedListMultimap.create();
598     Multimap<String, Integer> constrained = MapConstraints.constrainedMultimap(
599         multimap, TEST_CONSTRAINT);
600     Multimap<String, Integer> onceIterable
601         = Multimaps.forMap(onceIterableMap("foo", 1));
602     constrained.putAll(onceIterable);
603     assertEquals(ImmutableList.of(1), constrained.get("foo"));
604   }
605 
606   public void testNefariousMultimapGetAddAll() {
607     Multimap<String, Integer> multimap = LinkedListMultimap.create();
608     Multimap<String, Integer> constrained = MapConstraints.constrainedMultimap(
609         multimap, TEST_CONSTRAINT);
610     Collection<Integer> onceIterable
611         = ConstraintsTest.onceIterableCollection(1);
612     constrained.get("foo").addAll(onceIterable);
613     assertEquals(ImmutableList.of(1), constrained.get("foo"));
614   }
615 
616   /**
617    * Returns a "nefarious" map, which permits only one call to its views'
618    * iterator() methods. This verifies that the constrained map uses a
619    * defensive copy instead of potentially checking the elements in one snapshot
620    * and adding the elements from another.
621    *
622    * @param key the key to be contained in the map
623    * @param value the value to be contained in the map
624    */
625   static <K, V> Map<K, V> onceIterableMap(K key, V value) {
626     final Map.Entry<K, V> entry = Maps.immutableEntry(key, value);
627     return new AbstractMap<K, V>() {
628       boolean iteratorCalled;
629       @Override public int size() {
630         /*
631          * We could make the map empty, but that seems more likely to trigger
632          * special cases (so maybe we should test both empty and nonempty...).
633          */
634         return 1;
635       }
636       @Override public Set<Entry<K, V>> entrySet() {
637         return new ForwardingSet<Entry<K, V>>() {
638           @Override protected Set<Entry<K, V>> delegate() {
639             return Collections.singleton(entry);
640           }
641           @Override public Iterator<Entry<K, V>> iterator() {
642             assertFalse("Expected only one call to iterator()", iteratorCalled);
643             iteratorCalled = true;
644             return super.iterator();
645           }
646         };
647       }
648       @Override public Set<K> keySet() {
649         throw new UnsupportedOperationException();
650       }
651       @Override public Collection<V> values() {
652         throw new UnsupportedOperationException();
653       }
654     };
655   }
656 
657   @GwtIncompatible("SerializableTester")
658   public void testSerialization() {
659     // TODO: Test serialization of constrained collections.
660     assertSame(MapConstraints.notNull(),
661         SerializableTester.reserialize(MapConstraints.notNull()));
662   }
663 }