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