/* * Copyright (C) 2007 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.collect; import static com.google.common.collect.testing.Helpers.nefariousMapEntry; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Supplier; import com.google.common.testing.SerializableTester; import junit.framework.TestCase; import java.io.Serializable; import java.util.AbstractMap; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.RandomAccess; import java.util.Set; import java.util.SortedSet; /** * Tests for {@code MapConstraints}. * * @author Mike Bostock * @author Jared Levy */ @GwtCompatible(emulated = true) public class MapConstraintsTest extends TestCase { private static final String TEST_KEY = "test"; private static final Integer TEST_VALUE = 42; static final class TestKeyException extends IllegalArgumentException { private static final long serialVersionUID = 0; } static final class TestValueException extends IllegalArgumentException { private static final long serialVersionUID = 0; } static final MapConstraint TEST_CONSTRAINT = new TestConstraint(); private static final class TestConstraint implements MapConstraint, Serializable { @Override public void checkKeyValue(String key, Integer value) { if (TEST_KEY.equals(key)) { throw new TestKeyException(); } if (TEST_VALUE.equals(value)) { throw new TestValueException(); } } private static final long serialVersionUID = 0; } public void testNotNull() { MapConstraint constraint = MapConstraints.notNull(); constraint.checkKeyValue("foo", 1); assertEquals("Not null", constraint.toString()); try { constraint.checkKeyValue(null, 1); fail("NullPointerException expected"); } catch (NullPointerException expected) {} try { constraint.checkKeyValue("foo", null); fail("NullPointerException expected"); } catch (NullPointerException expected) {} try { constraint.checkKeyValue(null, null); fail("NullPointerException expected"); } catch (NullPointerException expected) {} } public void testConstrainedMapLegal() { Map map = Maps.newLinkedHashMap(); Map constrained = MapConstraints.constrainedMap( map, TEST_CONSTRAINT); map.put(TEST_KEY, TEST_VALUE); constrained.put("foo", 1); map.putAll(ImmutableMap.of("bar", 2)); constrained.putAll(ImmutableMap.of("baz", 3)); assertTrue(map.equals(constrained)); assertTrue(constrained.equals(map)); assertEquals(map.entrySet(), constrained.entrySet()); assertEquals(map.keySet(), constrained.keySet()); assertEquals(HashMultiset.create(map.values()), HashMultiset.create(constrained.values())); assertFalse(map.values() instanceof Serializable); assertEquals(map.toString(), constrained.toString()); assertEquals(map.hashCode(), constrained.hashCode()); assertThat(map.entrySet()).has().exactly( Maps.immutableEntry(TEST_KEY, TEST_VALUE), Maps.immutableEntry("foo", 1), Maps.immutableEntry("bar", 2), Maps.immutableEntry("baz", 3)).inOrder(); } public void testConstrainedMapIllegal() { Map map = Maps.newLinkedHashMap(); Map constrained = MapConstraints.constrainedMap( map, TEST_CONSTRAINT); try { constrained.put(TEST_KEY, TEST_VALUE); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.put("baz", TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.put(TEST_KEY, 3); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.putAll(ImmutableMap.of("baz", 3, TEST_KEY, 4)); fail("TestKeyException expected"); } catch (TestKeyException expected) {} assertEquals(Collections.emptySet(), map.entrySet()); assertEquals(Collections.emptySet(), constrained.entrySet()); } public void testConstrainedBiMapLegal() { BiMap map = new AbstractBiMap( Maps.newLinkedHashMap(), Maps.newLinkedHashMap()) {}; BiMap constrained = MapConstraints.constrainedBiMap( map, TEST_CONSTRAINT); map.put(TEST_KEY, TEST_VALUE); constrained.put("foo", 1); map.putAll(ImmutableMap.of("bar", 2)); constrained.putAll(ImmutableMap.of("baz", 3)); assertTrue(map.equals(constrained)); assertTrue(constrained.equals(map)); assertEquals(map.entrySet(), constrained.entrySet()); assertEquals(map.keySet(), constrained.keySet()); assertEquals(map.values(), constrained.values()); assertEquals(map.toString(), constrained.toString()); assertEquals(map.hashCode(), constrained.hashCode()); assertThat(map.entrySet()).has().exactly( Maps.immutableEntry(TEST_KEY, TEST_VALUE), Maps.immutableEntry("foo", 1), Maps.immutableEntry("bar", 2), Maps.immutableEntry("baz", 3)).inOrder(); } public void testConstrainedBiMapIllegal() { BiMap map = new AbstractBiMap( Maps.newLinkedHashMap(), Maps.newLinkedHashMap()) {}; BiMap constrained = MapConstraints.constrainedBiMap( map, TEST_CONSTRAINT); try { constrained.put(TEST_KEY, TEST_VALUE); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.put("baz", TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.put(TEST_KEY, 3); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.putAll(ImmutableMap.of("baz", 3, TEST_KEY, 4)); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.forcePut(TEST_KEY, 3); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.inverse().forcePut(TEST_VALUE, "baz"); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.inverse().forcePut(3, TEST_KEY); fail("TestKeyException expected"); } catch (TestKeyException expected) {} assertEquals(Collections.emptySet(), map.entrySet()); assertEquals(Collections.emptySet(), constrained.entrySet()); } public void testConstrainedMultimapLegal() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap( multimap, TEST_CONSTRAINT); multimap.put(TEST_KEY, TEST_VALUE); constrained.put("foo", 1); multimap.get("bar").add(2); constrained.get("baz").add(3); multimap.get("qux").addAll(Arrays.asList(4)); constrained.get("zig").addAll(Arrays.asList(5)); multimap.putAll("zag", Arrays.asList(6)); constrained.putAll("bee", Arrays.asList(7)); multimap.putAll(new ImmutableMultimap.Builder() .put("bim", 8).build()); constrained.putAll(new ImmutableMultimap.Builder() .put("bop", 9).build()); multimap.putAll(new ImmutableMultimap.Builder() .put("dig", 10).build()); constrained.putAll(new ImmutableMultimap.Builder() .put("dag", 11).build()); assertTrue(multimap.equals(constrained)); assertTrue(constrained.equals(multimap)); assertThat(ImmutableList.copyOf(multimap.entries())).isEqualTo( ImmutableList.copyOf(constrained.entries())); assertThat(constrained.asMap().get("foo")).has().item(1); assertNull(constrained.asMap().get("missing")); assertEquals(multimap.asMap(), constrained.asMap()); assertEquals(multimap.values(), constrained.values()); assertEquals(multimap.keys(), constrained.keys()); assertEquals(multimap.keySet(), constrained.keySet()); assertEquals(multimap.toString(), constrained.toString()); assertEquals(multimap.hashCode(), constrained.hashCode()); assertThat(multimap.entries()).has().exactly( Maps.immutableEntry(TEST_KEY, TEST_VALUE), Maps.immutableEntry("foo", 1), Maps.immutableEntry("bar", 2), Maps.immutableEntry("baz", 3), Maps.immutableEntry("qux", 4), Maps.immutableEntry("zig", 5), Maps.immutableEntry("zag", 6), Maps.immutableEntry("bee", 7), Maps.immutableEntry("bim", 8), Maps.immutableEntry("bop", 9), Maps.immutableEntry("dig", 10), Maps.immutableEntry("dag", 11)).inOrder(); assertFalse(constrained.asMap().values() instanceof Serializable); Iterator> iterator = constrained.asMap().values().iterator(); iterator.next(); iterator.next().add(12); assertTrue(multimap.containsEntry("foo", 12)); } public void testConstrainedTypePreservingList() { ListMultimap multimap = MapConstraints.constrainedListMultimap( LinkedListMultimap.create(), TEST_CONSTRAINT); multimap.put("foo", 1); Map.Entry> entry = multimap.asMap().entrySet().iterator().next(); assertTrue(entry.getValue() instanceof List); assertFalse(multimap.entries() instanceof Set); assertFalse(multimap.get("foo") instanceof RandomAccess); } public void testConstrainedTypePreservingRandomAccessList() { ListMultimap multimap = MapConstraints.constrainedListMultimap( ArrayListMultimap.create(), TEST_CONSTRAINT); multimap.put("foo", 1); Map.Entry> entry = multimap.asMap().entrySet().iterator().next(); assertTrue(entry.getValue() instanceof List); assertFalse(multimap.entries() instanceof Set); assertTrue(multimap.get("foo") instanceof RandomAccess); } public void testConstrainedTypePreservingSet() { SetMultimap multimap = MapConstraints.constrainedSetMultimap( LinkedHashMultimap.create(), TEST_CONSTRAINT); multimap.put("foo", 1); Map.Entry> entry = multimap.asMap().entrySet().iterator().next(); assertTrue(entry.getValue() instanceof Set); } public void testConstrainedTypePreservingSortedSet() { Comparator comparator = Collections.reverseOrder(); SortedSetMultimap delegate = TreeMultimap.create(Ordering.natural(), comparator); SortedSetMultimap multimap = MapConstraints.constrainedSortedSetMultimap(delegate, TEST_CONSTRAINT); multimap.put("foo", 1); Map.Entry> entry = multimap.asMap().entrySet().iterator().next(); assertTrue(entry.getValue() instanceof SortedSet); assertSame(comparator, multimap.valueComparator()); assertSame(comparator, multimap.get("foo").comparator()); } @SuppressWarnings("unchecked") public void testConstrainedMultimapIllegal() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap( multimap, TEST_CONSTRAINT); try { constrained.put(TEST_KEY, 1); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.put("foo", TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.put(TEST_KEY, TEST_VALUE); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.get(TEST_KEY).add(1); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.get("foo").add(TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.get(TEST_KEY).add(TEST_VALUE); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.get(TEST_KEY).addAll(Arrays.asList(1)); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.get("foo").addAll(Arrays.asList(1, TEST_VALUE)); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.get(TEST_KEY).addAll(Arrays.asList(1, TEST_VALUE)); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.putAll(TEST_KEY, Arrays.asList(1)); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.putAll("foo", Arrays.asList(1, TEST_VALUE)); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.putAll(TEST_KEY, Arrays.asList(1, TEST_VALUE)); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.putAll(new ImmutableMultimap.Builder() .put(TEST_KEY, 2).put("foo", 1).build()); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.putAll(new ImmutableMultimap.Builder() .put("bar", TEST_VALUE).put("foo", 1).build()); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.putAll(new ImmutableMultimap.Builder() .put(TEST_KEY, TEST_VALUE).put("foo", 1).build()); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.entries().add(Maps.immutableEntry(TEST_KEY, 1)); fail("UnsupportedOperationException expected"); } catch (UnsupportedOperationException expected) {} try { constrained.entries().addAll(Arrays.asList( Maps.immutableEntry("foo", 1), Maps.immutableEntry(TEST_KEY, 2))); fail("UnsupportedOperationException expected"); } catch (UnsupportedOperationException expected) {} assertTrue(multimap.isEmpty()); assertTrue(constrained.isEmpty()); constrained.put("foo", 1); try { constrained.asMap().get("foo").add(TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.asMap().values().iterator().next().add(TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} try { ((Collection) constrained.asMap().values().toArray()[0]) .add(TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} assertThat(ImmutableList.copyOf(multimap.entries())).isEqualTo( ImmutableList.copyOf(constrained.entries())); assertEquals(multimap.asMap(), constrained.asMap()); assertEquals(multimap.values(), constrained.values()); assertEquals(multimap.keys(), constrained.keys()); assertEquals(multimap.keySet(), constrained.keySet()); assertEquals(multimap.toString(), constrained.toString()); assertEquals(multimap.hashCode(), constrained.hashCode()); } private static class QueueSupplier implements Supplier> { @Override public Queue get() { return new LinkedList(); } } public void testConstrainedMultimapQueue() { Multimap multimap = Multimaps.newMultimap( new HashMap>(), new QueueSupplier()); Multimap constrained = MapConstraints.constrainedMultimap( multimap, TEST_CONSTRAINT); constrained.put("foo", 1); assertTrue(constrained.get("foo").contains(1)); assertTrue(multimap.get("foo").contains(1)); try { constrained.put(TEST_KEY, 1); fail("TestKeyException expected"); } catch (TestKeyException expected) {} try { constrained.put("foo", TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} try { constrained.get("foo").add(TEST_VALUE); fail("TestKeyException expected"); } catch (TestValueException expected) {} try { constrained.get(TEST_KEY).add(1); fail("TestValueException expected"); } catch (TestKeyException expected) {} assertEquals(1, constrained.size()); assertEquals(1, multimap.size()); } public void testMapEntrySetToArray() { Map map = Maps.newLinkedHashMap(); Map constrained = MapConstraints.constrainedMap(map, TEST_CONSTRAINT); map.put("foo", 1); @SuppressWarnings("unchecked") Map.Entry entry = (Map.Entry) constrained.entrySet().toArray()[0]; try { entry.setValue(TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} assertFalse(map.containsValue(TEST_VALUE)); } public void testMapEntrySetContainsNefariousEntry() { Map map = Maps.newTreeMap(); Map constrained = MapConstraints.constrainedMap(map, TEST_CONSTRAINT); map.put("foo", 1); Map.Entry nefariousEntry = nefariousMapEntry(TEST_KEY, TEST_VALUE); Set> entries = constrained.entrySet(); assertFalse(entries.contains(nefariousEntry)); assertFalse(map.containsValue(TEST_VALUE)); assertFalse(entries.containsAll(Collections.singleton(nefariousEntry))); assertFalse(map.containsValue(TEST_VALUE)); } public void testMultimapAsMapEntriesToArray() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT); multimap.put("foo", 1); @SuppressWarnings("unchecked") Map.Entry> entry = (Map.Entry>) constrained.asMap().entrySet().toArray()[0]; try { entry.setValue(Collections.emptySet()); fail("UnsupportedOperationException expected"); } catch (UnsupportedOperationException expected) {} try { entry.getValue().add(TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} assertFalse(multimap.containsValue(TEST_VALUE)); } public void testMultimapAsMapValuesToArray() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT); multimap.put("foo", 1); @SuppressWarnings("unchecked") Collection collection = (Collection) constrained.asMap().values().toArray()[0]; try { collection.add(TEST_VALUE); fail("TestValueException expected"); } catch (TestValueException expected) {} assertFalse(multimap.containsValue(TEST_VALUE)); } public void testMultimapEntriesContainsNefariousEntry() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT); multimap.put("foo", 1); Map.Entry nefariousEntry = nefariousMapEntry(TEST_KEY, TEST_VALUE); Collection> entries = constrained.entries(); assertFalse(entries.contains(nefariousEntry)); assertFalse(multimap.containsValue(TEST_VALUE)); assertFalse(entries.containsAll(Collections.singleton(nefariousEntry))); assertFalse(multimap.containsValue(TEST_VALUE)); } public void testMultimapEntriesRemoveNefariousEntry() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT); multimap.put("foo", 1); Map.Entry nefariousEntry = nefariousMapEntry(TEST_KEY, TEST_VALUE); Collection> entries = constrained.entries(); assertFalse(entries.remove(nefariousEntry)); assertFalse(multimap.containsValue(TEST_VALUE)); assertFalse(entries.removeAll(Collections.singleton(nefariousEntry))); assertFalse(multimap.containsValue(TEST_VALUE)); } public void testMultimapAsMapEntriesContainsNefariousEntry() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT); multimap.put("foo", 1); Map.Entry> nefariousEntry = nefariousMapEntry(TEST_KEY, Collections.singleton(TEST_VALUE)); Set>> entries = constrained.asMap().entrySet(); assertFalse(entries.contains(nefariousEntry)); assertFalse(multimap.containsValue(TEST_VALUE)); assertFalse(entries.containsAll(Collections.singleton(nefariousEntry))); assertFalse(multimap.containsValue(TEST_VALUE)); } public void testMultimapAsMapEntriesRemoveNefariousEntry() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap(multimap, TEST_CONSTRAINT); multimap.put("foo", 1); Map.Entry> nefariousEntry = nefariousMapEntry(TEST_KEY, Collections.singleton(TEST_VALUE)); Set>> entries = constrained.asMap().entrySet(); assertFalse(entries.remove(nefariousEntry)); assertFalse(multimap.containsValue(TEST_VALUE)); assertFalse(entries.removeAll(Collections.singleton(nefariousEntry))); assertFalse(multimap.containsValue(TEST_VALUE)); } public void testNefariousMapPutAll() { Map map = Maps.newLinkedHashMap(); Map constrained = MapConstraints.constrainedMap( map, TEST_CONSTRAINT); Map onceIterable = onceIterableMap("foo", 1); constrained.putAll(onceIterable); assertEquals((Integer) 1, constrained.get("foo")); } public void testNefariousMultimapPutAllIterable() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap( multimap, TEST_CONSTRAINT); Collection onceIterable = ConstraintsTest.onceIterableCollection(1); constrained.putAll("foo", onceIterable); assertEquals(ImmutableList.of(1), constrained.get("foo")); } public void testNefariousMultimapPutAllMultimap() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap( multimap, TEST_CONSTRAINT); Multimap onceIterable = Multimaps.forMap(onceIterableMap("foo", 1)); constrained.putAll(onceIterable); assertEquals(ImmutableList.of(1), constrained.get("foo")); } public void testNefariousMultimapGetAddAll() { Multimap multimap = LinkedListMultimap.create(); Multimap constrained = MapConstraints.constrainedMultimap( multimap, TEST_CONSTRAINT); Collection onceIterable = ConstraintsTest.onceIterableCollection(1); constrained.get("foo").addAll(onceIterable); assertEquals(ImmutableList.of(1), constrained.get("foo")); } /** * Returns a "nefarious" map, which permits only one call to its views' * iterator() methods. This verifies that the constrained map uses a * defensive copy instead of potentially checking the elements in one snapshot * and adding the elements from another. * * @param key the key to be contained in the map * @param value the value to be contained in the map */ static Map onceIterableMap(K key, V value) { final Map.Entry entry = Maps.immutableEntry(key, value); return new AbstractMap() { boolean iteratorCalled; @Override public int size() { /* * We could make the map empty, but that seems more likely to trigger * special cases (so maybe we should test both empty and nonempty...). */ return 1; } @Override public Set> entrySet() { return new ForwardingSet>() { @Override protected Set> delegate() { return Collections.singleton(entry); } @Override public Iterator> iterator() { assertFalse("Expected only one call to iterator()", iteratorCalled); iteratorCalled = true; return super.iterator(); } }; } @Override public Set keySet() { throw new UnsupportedOperationException(); } @Override public Collection values() { throw new UnsupportedOperationException(); } }; } @GwtIncompatible("SerializableTester") public void testSerialization() { // TODO: Test serialization of constrained collections. assertSame(MapConstraints.notNull(), SerializableTester.reserialize(MapConstraints.notNull())); } }