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
17package com.google.common.collect;
18
19import static com.google.common.collect.testing.Helpers.nefariousMapEntry;
20import static org.truth0.Truth.ASSERT;
21
22import com.google.common.annotations.GwtCompatible;
23import com.google.common.annotations.GwtIncompatible;
24import com.google.common.base.Supplier;
25import com.google.common.testing.SerializableTester;
26
27import junit.framework.TestCase;
28
29import java.io.Serializable;
30import java.util.AbstractMap;
31import java.util.Arrays;
32import java.util.Collection;
33import java.util.Collections;
34import java.util.Comparator;
35import java.util.HashMap;
36import java.util.Iterator;
37import java.util.LinkedList;
38import java.util.List;
39import java.util.Map;
40import java.util.Queue;
41import java.util.RandomAccess;
42import java.util.Set;
43import java.util.SortedSet;
44
45/**
46 * Tests for {@code MapConstraints}.
47 *
48 * @author Mike Bostock
49 * @author Jared Levy
50 */
51@GwtCompatible(emulated = true)
52public 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    ASSERT.that(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    ASSERT.that(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    ASSERT.that(ImmutableList.copyOf(multimap.entries()))
234        .is(ImmutableList.copyOf(constrained.entries()));
235    ASSERT.that(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    ASSERT.that(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    ASSERT.that(ImmutableList.copyOf(multimap.entries()))
411        .is(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}
664