/*
* Copyright (C) 2008 Google Inc.
*
* 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 com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.testing.CollectionTestSuiteBuilder;
import com.google.common.collect.testing.MapInterfaceTest;
import com.google.common.collect.testing.MinimalSet;
import com.google.common.collect.testing.ReserializingTestCollectionGenerator;
import com.google.common.collect.testing.ReserializingTestSetGenerator;
import com.google.common.collect.testing.SampleElements;
import com.google.common.collect.testing.SampleElements.Colliders;
import com.google.common.collect.testing.SampleElements.Unhashables;
import com.google.common.collect.testing.SetTestSuiteBuilder;
import com.google.common.collect.testing.TestCollectionGenerator;
import com.google.common.collect.testing.TestMapEntrySetGenerator;
import com.google.common.collect.testing.TestStringSetGenerator;
import com.google.common.collect.testing.TestUnhashableCollectionGenerator;
import com.google.common.collect.testing.UnhashableObject;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;
import com.google.common.collect.testing.features.SetFeature;
import com.google.common.testutils.NullPointerTester;
import com.google.common.testutils.SerializableTester;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Tests for {@link ImmutableMap}.
*
* @author Kevin Bourrillion
* @author Jesse Wilson
*/
public class ImmutableMapTest extends TestCase {
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTestSuite(ImmutableMapTest.class);
suite.addTest(SetTestSuiteBuilder.using(keySetGenerator())
.withFeatures(
CollectionSize.ANY,
CollectionFeature.KNOWN_ORDER,
SetFeature.REJECTS_DUPLICATES_AT_CREATION)
.named("ImmutableMap.keySet")
.createTestSuite());
suite.addTest(SetTestSuiteBuilder.using(entrySetGenerator())
.withFeatures(
CollectionSize.ANY,
CollectionFeature.KNOWN_ORDER,
SetFeature.REJECTS_DUPLICATES_AT_CREATION)
.named("ImmutableMap.entrySet")
.createTestSuite());
suite.addTest(CollectionTestSuiteBuilder.using(valuesGenerator())
.withFeatures(CollectionSize.ANY, CollectionFeature.KNOWN_ORDER)
.named("ImmutableMap.values")
.createTestSuite());
suite.addTest(SetTestSuiteBuilder.using(
ReserializingTestSetGenerator.newInstance(keySetGenerator()))
.withFeatures(
CollectionSize.ANY,
CollectionFeature.KNOWN_ORDER,
SetFeature.REJECTS_DUPLICATES_AT_CREATION)
.named("ImmutableMap.keySet, reserialized")
.createTestSuite());
suite.addTest(SetTestSuiteBuilder.using(
ReserializingTestSetGenerator.newInstance(entrySetGenerator()))
.withFeatures(
CollectionSize.ANY,
CollectionFeature.KNOWN_ORDER,
SetFeature.REJECTS_DUPLICATES_AT_CREATION)
.named("ImmutableMap.entrySet, reserialized")
.createTestSuite());
suite.addTest(CollectionTestSuiteBuilder.using(
ReserializingTestCollectionGenerator.newInstance(valuesGenerator()))
.withFeatures(CollectionSize.ANY, CollectionFeature.KNOWN_ORDER)
.named("ImmutableMap.values, reserialized")
.createTestSuite());
suite.addTest(CollectionTestSuiteBuilder.using(unhashableValuesGenerator())
.withFeatures(CollectionSize.ANY, CollectionFeature.KNOWN_ORDER)
.named("ImmutableMap.values, unhashable")
.createTestSuite());
return suite;
}
static TestMapEntrySetGenerator<String, String> entrySetGenerator() {
SampleElements.Strings sampleStrings = new SampleElements.Strings();
return new TestMapEntrySetGenerator<String, String>(
sampleStrings, sampleStrings) {
@Override public Set<Entry<String, String>> createFromEntries(
Entry<String, String>[] entries) {
Builder<String, String> builder = ImmutableMap.builder();
for (Entry<String, String> entry : entries) {
builder.put(entry.getKey(), entry.getValue());
}
return builder.build().entrySet();
}
};
}
static TestStringSetGenerator keySetGenerator() {
return new TestStringSetGenerator() {
@Override protected Set<String> create(String[] elements) {
Builder<String, Integer> builder = ImmutableMap.builder();
for (String key : elements) {
builder.put(key, key.length());
}
return builder.build().keySet();
}
};
}
static TestCollectionGenerator<String> valuesGenerator() {
return new TestCollectionGenerator<String>() {
public SampleElements<String> samples() {
return new SampleElements.Strings();
}
public Collection<String> create(Object... elements) {
Builder<Object, String> builder = ImmutableMap.builder();
for (Object key : elements) {
builder.put(key, key.toString());
}
return builder.build().values();
}
public String[] createArray(int length) {
return new String[length];
}
public List<String> order(List<String> insertionOrder) {
return insertionOrder;
}
};
}
static TestCollectionGenerator<UnhashableObject> unhashableValuesGenerator() {
return
new TestUnhashableCollectionGenerator<Collection<UnhashableObject>>() {
@Override public Collection<UnhashableObject> create(
UnhashableObject[] elements) {
Builder<Integer, UnhashableObject> builder = ImmutableMap.builder();
int key = 1;
for (UnhashableObject value : elements) {
builder.put(key++, value);
}
return builder.build().values();
}
};
}
public abstract static class AbstractMapTests<K, V>
extends MapInterfaceTest<K, V> {
public AbstractMapTests() {
super(false, false, false, false, false);
}
@Override protected Map<K, V> makeEmptyMap() {
throw new UnsupportedOperationException();
}
private static final Joiner joiner = Joiner.on(", ");
@Override protected void assertMoreInvariants(Map<K, V> map) {
// TODO: can these be moved to MapInterfaceTest?
for (Entry<K, V> entry : map.entrySet()) {
assertEquals(entry.getKey() + "=" + entry.getValue(),
entry.toString());
}
assertEquals("{" + joiner.join(map.entrySet()) + "}",
map.toString());
assertEquals("[" + joiner.join(map.entrySet()) + "]",
map.entrySet().toString());
assertEquals("[" + joiner.join(map.keySet()) + "]",
map.keySet().toString());
assertEquals("[" + joiner.join(map.values()) + "]",
map.values().toString());
assertEquals(MinimalSet.from(map.entrySet()), map.entrySet());
assertEquals(Sets.newHashSet(map.keySet()), map.keySet());
}
}
public static class MapTests extends AbstractMapTests<String, Integer> {
@Override protected Map<String, Integer> makeEmptyMap() {
return ImmutableMap.of();
}
@Override protected Map<String, Integer> makePopulatedMap() {
return ImmutableMap.of("one", 1, "two", 2, "three", 3);
}
@Override protected String getKeyNotInPopulatedMap() {
return "minus one";
}
@Override protected Integer getValueNotInPopulatedMap() {
return -1;
}
}
public static class SingletonMapTests
extends AbstractMapTests<String, Integer> {
@Override protected Map<String, Integer> makePopulatedMap() {
return ImmutableMap.of("one", 1);
}
@Override protected String getKeyNotInPopulatedMap() {
return "minus one";
}
@Override protected Integer getValueNotInPopulatedMap() {
return -1;
}
}
public static class ReserializedMapTests
extends AbstractMapTests<String, Integer> {
@Override protected Map<String, Integer> makePopulatedMap() {
return SerializableTester.reserialize(
ImmutableMap.of("one", 1, "two", 2, "three", 3));
}
@Override protected String getKeyNotInPopulatedMap() {
return "minus one";
}
@Override protected Integer getValueNotInPopulatedMap() {
return -1;
}
}
public static class MapTestsWithBadHashes
extends AbstractMapTests<Object, Integer> {
@Override protected Map<Object, Integer> makeEmptyMap() {
throw new UnsupportedOperationException();
}
@Override protected Map<Object, Integer> makePopulatedMap() {
Colliders colliders = new Colliders();
return ImmutableMap.of(
colliders.e0, 0,
colliders.e1, 1,
colliders.e2, 2,
colliders.e3, 3);
}
@Override protected Object getKeyNotInPopulatedMap() {
return new Colliders().e4;
}
@Override protected Integer getValueNotInPopulatedMap() {
return 4;
}
}
public static class MapTestsWithUnhashableValues
extends AbstractMapTests<Integer, UnhashableObject> {
@Override protected Map<Integer, UnhashableObject> makeEmptyMap() {
return ImmutableMap.of();
}
@Override protected Map<Integer, UnhashableObject> makePopulatedMap() {
Unhashables unhashables = new Unhashables();
return ImmutableMap.of(
0, unhashables.e0, 1, unhashables.e1, 2, unhashables.e2);
}
@Override protected Integer getKeyNotInPopulatedMap() {
return 3;
}
@Override protected UnhashableObject getValueNotInPopulatedMap() {
return new Unhashables().e3;
}
}
public static class MapTestsWithSingletonUnhashableValue
extends MapTestsWithUnhashableValues {
@Override protected Map<Integer, UnhashableObject> makePopulatedMap() {
Unhashables unhashables = new Unhashables();
return ImmutableMap.of(0, unhashables.e0);
}
}
public static class CreationTests extends TestCase {
public void testEmptyBuilder() {
ImmutableMap<String, Integer> map
= new Builder<String, Integer>().build();
assertEquals(Collections.<String, Integer>emptyMap(), map);
}
public void testSingletonBuilder() {
ImmutableMap<String, Integer> map = new Builder<String, Integer>()
.put("one", 1)
.build();
assertMapEquals(map, "one", 1);
}
public void testBuilder() {
ImmutableMap<String, Integer> map = new Builder<String, Integer>()
.put("one", 1)
.put("two", 2)
.put("three", 3)
.put("four", 4)
.put("five", 5)
.build();
assertMapEquals(map,
"one", 1, "two", 2, "three", 3, "four", 4, "five", 5);
}
public void testBuilderPutAllWithEmptyMap() {
ImmutableMap<String, Integer> map = new Builder<String, Integer>()
.putAll(Collections.<String, Integer>emptyMap())
.build();
assertEquals(Collections.<String, Integer>emptyMap(), map);
}
public void testBuilderPutAll() {
Map<String, Integer> toPut = new LinkedHashMap<String, Integer>();
toPut.put("one", 1);
toPut.put("two", 2);
toPut.put("three", 3);
Map<String, Integer> moreToPut = new LinkedHashMap<String, Integer>();
moreToPut.put("four", 4);
moreToPut.put("five", 5);
ImmutableMap<String, Integer> map = new Builder<String, Integer>()
.putAll(toPut)
.putAll(moreToPut)
.build();
assertMapEquals(map,
"one", 1, "two", 2, "three", 3, "four", 4, "five", 5);
}
public void testBuilderReuse() {
Builder<String, Integer> builder = new Builder<String, Integer>();
ImmutableMap<String, Integer> mapOne = builder
.put("one", 1)
.put("two", 2)
.build();
ImmutableMap<String, Integer> mapTwo = builder
.put("three", 3)
.put("four", 4)
.build();
assertMapEquals(mapOne, "one", 1, "two", 2);
assertMapEquals(mapTwo, "one", 1, "two", 2, "three", 3, "four", 4);
}
public void testBuilderPutNullKey() {
Builder<String, Integer> builder = new Builder<String, Integer>();
try {
builder.put(null, 1);
fail();
} catch (NullPointerException expected) {
}
}
public void testBuilderPutNullValue() {
Builder<String, Integer> builder = new Builder<String, Integer>();
try {
builder.put("one", null);
fail();
} catch (NullPointerException expected) {
}
}
public void testBuilderPutNullKeyViaPutAll() {
Builder<String, Integer> builder = new Builder<String, Integer>();
try {
builder.putAll(Collections.<String, Integer>singletonMap(null, 1));
fail();
} catch (NullPointerException expected) {
}
}
public void testBuilderPutNullValueViaPutAll() {
Builder<String, Integer> builder = new Builder<String, Integer>();
try {
builder.putAll(Collections.<String, Integer>singletonMap("one", null));
fail();
} catch (NullPointerException expected) {
}
}
public void testPuttingTheSameKeyTwiceThrowsOnBuild() {
Builder<String, Integer> builder = new Builder<String, Integer>()
.put("one", 1)
.put("one", 1); // throwing on this line would be even better
try {
builder.build();
fail();
} catch (IllegalArgumentException expected) {
assertEquals("duplicate key: one", expected.getMessage());
}
}
public void testOf() {
assertMapEquals(
ImmutableMap.of("one", 1),
"one", 1);
assertMapEquals(
ImmutableMap.of("one", 1, "two", 2),
"one", 1, "two", 2);
assertMapEquals(
ImmutableMap.of("one", 1, "two", 2, "three", 3),
"one", 1, "two", 2, "three", 3);
assertMapEquals(
ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4),
"one", 1, "two", 2, "three", 3, "four", 4);
assertMapEquals(
ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5),
"one", 1, "two", 2, "three", 3, "four", 4, "five", 5);
}
public void testOfNullKey() {
try {
ImmutableMap.of(null, 1);
fail();
} catch (NullPointerException expected) {
}
try {
ImmutableMap.of("one", 1, null, 2);
fail();
} catch (NullPointerException expected) {
}
}
public void testOfNullValue() {
try {
ImmutableMap.of("one", null);
fail();
} catch (NullPointerException expected) {
}
try {
ImmutableMap.of("one", 1, "two", null);
fail();
} catch (NullPointerException expected) {
}
}
public void testOfWithDuplicateKey() {
try {
ImmutableMap.of("one", 1, "one", 1);
fail();
} catch (IllegalArgumentException expected) {
assertEquals("duplicate key: one", expected.getMessage());
}
}
public void testCopyOfEmptyMap() {
ImmutableMap<String, Integer> copy
= ImmutableMap.copyOf(Collections.<String, Integer>emptyMap());
assertEquals(Collections.<String, Integer>emptyMap(), copy);
assertSame(copy, ImmutableMap.copyOf(copy));
}
public void testCopyOfSingletonMap() {
ImmutableMap<String, Integer> copy
= ImmutableMap.copyOf(Collections.singletonMap("one", 1));
assertMapEquals(copy, "one", 1);
assertSame(copy, ImmutableMap.copyOf(copy));
}
public void testCopyOf() {
Map<String, Integer> original = new LinkedHashMap<String, Integer>();
original.put("one", 1);
original.put("two", 2);
original.put("three", 3);
ImmutableMap<String, Integer> copy = ImmutableMap.copyOf(original);
assertMapEquals(copy, "one", 1, "two", 2, "three", 3);
assertSame(copy, ImmutableMap.copyOf(copy));
}
}
public void testNullPointers() throws Exception {
NullPointerTester tester = new NullPointerTester();
tester.testAllPublicStaticMethods(ImmutableMap.class);
tester.testAllPublicInstanceMethods(
new ImmutableMap.Builder<Object, Object>());
if (false) {
// these tests aren't included due to a bug in NullPointerTester
// TODO: fix that bug, add these tests
tester.testAllPublicInstanceMethods(ImmutableMap.of());
tester.testAllPublicInstanceMethods(ImmutableMap.of("one", 1));
tester.testAllPublicInstanceMethods(
ImmutableMap.of("one", 1, "two", 2, "three", 3));
}
}
private static <K, V> void assertMapEquals(Map<K, V> map,
Object... alternatingKeysAndValues) {
assertEquals(map.size(), alternatingKeysAndValues.length / 2);
int i = 0;
for (Entry<K, V> entry : map.entrySet()) {
assertEquals(alternatingKeysAndValues[i++], entry.getKey());
assertEquals(alternatingKeysAndValues[i++], entry.getValue());
}
}
private static class IntHolder implements Serializable {
public int value;
public IntHolder(int value) {
this.value = value;
}
@Override public boolean equals(Object o) {
return (o instanceof IntHolder) && ((IntHolder) o).value == value;
}
@Override public int hashCode() {
return value;
}
private static final long serialVersionUID = 5;
}
public void testMutableValues() {
IntHolder holderA = new IntHolder(1);
IntHolder holderB = new IntHolder(2);
Map<String, IntHolder> map = ImmutableMap.of("a", holderA, "b", holderB);
holderA.value = 3;
assertTrue(map.entrySet().contains(
Maps.immutableEntry("a", new IntHolder(3))));
Map<String, Integer> intMap = ImmutableMap.of("a", 3, "b", 2);
assertEquals(intMap.hashCode(), map.entrySet().hashCode());
assertEquals(intMap.hashCode(), map.hashCode());
}
public void testViewSerialization() {
Map<String, Integer> map = ImmutableMap.of("one", 1, "two", 2, "three", 3);
SerializableTester.reserializeAndAssert(map.entrySet());
SerializableTester.reserializeAndAssert(map.keySet());
assertEquals(Lists.newArrayList(map.values()),
Lists.newArrayList(SerializableTester.reserialize(map.values())));
}
}