/* * Copyright (C) 2008 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.mapEntry; 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.Equivalence; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableBiMap.Builder; import com.google.common.collect.testing.MapInterfaceTest; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import com.google.common.collect.testing.google.BiMapGenerators.ImmutableBiMapCopyOfEntriesGenerator; import com.google.common.collect.testing.google.BiMapGenerators.ImmutableBiMapCopyOfGenerator; import com.google.common.collect.testing.google.BiMapGenerators.ImmutableBiMapGenerator; import com.google.common.collect.testing.google.BiMapInverseTester; import com.google.common.collect.testing.google.BiMapTestSuiteBuilder; import com.google.common.collect.testing.google.TestStringBiMapGenerator; import com.google.common.testing.CollectorTester; import com.google.common.testing.SerializableTester; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.AbstractMap; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collector; import java.util.stream.Stream; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; /** * Tests for {@link ImmutableBiMap}. * * @author Jared Levy */ @GwtCompatible(emulated = true) public class ImmutableBiMapTest extends TestCase { // TODO: Reduce duplication of ImmutableMapTest code @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(MapTests.class); suite.addTestSuite(InverseMapTests.class); suite.addTestSuite(CreationTests.class); suite.addTestSuite(BiMapSpecificTests.class); suite.addTestSuite(FloodingTest.class); suite.addTest( BiMapTestSuiteBuilder.using(new ImmutableBiMapGenerator()) .named("ImmutableBiMap") .withFeatures( CollectionSize.ANY, CollectionFeature.SERIALIZABLE, CollectionFeature.KNOWN_ORDER, MapFeature.REJECTS_DUPLICATES_AT_CREATION, MapFeature.ALLOWS_ANY_NULL_QUERIES) .suppressing(BiMapInverseTester.getInverseSameAfterSerializingMethods()) .createTestSuite()); suite.addTest( BiMapTestSuiteBuilder.using( new TestStringBiMapGenerator() { @Override protected BiMap create(Entry[] entries) { return ImmutableBiMap.builder() .putAll(Arrays.asList(entries)) .buildJdkBacked(); } }) .named("ImmutableBiMap [JDK backed]") .withFeatures( CollectionSize.ANY, CollectionFeature.SERIALIZABLE, CollectionFeature.KNOWN_ORDER, MapFeature.REJECTS_DUPLICATES_AT_CREATION, MapFeature.ALLOWS_ANY_NULL_QUERIES) .suppressing(BiMapInverseTester.getInverseSameAfterSerializingMethods()) .createTestSuite()); suite.addTest( BiMapTestSuiteBuilder.using(new ImmutableBiMapCopyOfGenerator()) .named("ImmutableBiMap.copyOf[Map]") .withFeatures( CollectionSize.ANY, CollectionFeature.SERIALIZABLE, CollectionFeature.KNOWN_ORDER, MapFeature.ALLOWS_ANY_NULL_QUERIES) .suppressing(BiMapInverseTester.getInverseSameAfterSerializingMethods()) .createTestSuite()); suite.addTest( BiMapTestSuiteBuilder.using(new ImmutableBiMapCopyOfEntriesGenerator()) .named("ImmutableBiMap.copyOf[Iterable]") .withFeatures( CollectionSize.ANY, CollectionFeature.SERIALIZABLE, CollectionFeature.KNOWN_ORDER, MapFeature.REJECTS_DUPLICATES_AT_CREATION, MapFeature.ALLOWS_ANY_NULL_QUERIES) .suppressing(BiMapInverseTester.getInverseSameAfterSerializingMethods()) .createTestSuite()); suite.addTestSuite(ImmutableBiMapTest.class); return suite; } public abstract static class AbstractMapTests extends MapInterfaceTest { public AbstractMapTests() { super(false, false, false, false, false); } @Override protected Map makeEmptyMap() { throw new UnsupportedOperationException(); } private static final Joiner joiner = Joiner.on(", "); @Override protected void assertMoreInvariants(Map map) { BiMap bimap = (BiMap) map; for (Entry entry : map.entrySet()) { assertEquals(entry.getKey() + "=" + entry.getValue(), entry.toString()); assertEquals(entry.getKey(), bimap.inverse().get(entry.getValue())); } 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(Sets.newHashSet(map.entrySet()), map.entrySet()); assertEquals(Sets.newHashSet(map.keySet()), map.keySet()); } } public static class MapTests extends AbstractMapTests { @Override protected Map makeEmptyMap() { return ImmutableBiMap.of(); } @Override protected Map makePopulatedMap() { return ImmutableBiMap.of("one", 1, "two", 2, "three", 3); } @Override protected String getKeyNotInPopulatedMap() { return "minus one"; } @Override protected Integer getValueNotInPopulatedMap() { return -1; } } public static class InverseMapTests extends AbstractMapTests { @Override protected Map makeEmptyMap() { return ImmutableBiMap.of(); } @Override protected Map makePopulatedMap() { return ImmutableBiMap.of(1, "one", 2, "two", 3, "three").inverse(); } @Override protected String getKeyNotInPopulatedMap() { return "minus one"; } @Override protected Integer getValueNotInPopulatedMap() { return -1; } } public static class CreationTests extends TestCase { public void testEmptyBuilder() { ImmutableBiMap map = new Builder().build(); assertEquals(Collections.emptyMap(), map); assertEquals(Collections.emptyMap(), map.inverse()); assertSame(ImmutableBiMap.of(), map); } public void testSingletonBuilder() { ImmutableBiMap map = new Builder().put("one", 1).build(); assertMapEquals(map, "one", 1); assertMapEquals(map.inverse(), 1, "one"); } public void testBuilder_withImmutableEntry() { ImmutableBiMap map = new Builder().put(Maps.immutableEntry("one", 1)).build(); assertMapEquals(map, "one", 1); } public void testBuilder() { ImmutableBiMap map = ImmutableBiMap.builder() .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); assertMapEquals(map.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); } @GwtIncompatible public void testBuilderExactlySizedReusesArray() { ImmutableBiMap.Builder builder = ImmutableBiMap.builderWithExpectedSize(10); Entry[] builderArray = builder.entries; for (int i = 0; i < 10; i++) { builder.put(i, i); } Entry[] builderArrayAfterPuts = builder.entries; RegularImmutableBiMap map = (RegularImmutableBiMap) builder.build(); Entry[] mapInternalArray = map.entries; assertSame(builderArray, builderArrayAfterPuts); assertSame(builderArray, mapInternalArray); } public void testBuilder_orderEntriesByValue() { ImmutableBiMap map = ImmutableBiMap.builder() .orderEntriesByValue(Ordering.natural()) .put("three", 3) .put("one", 1) .put("five", 5) .put("four", 4) .put("two", 2) .build(); assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); assertMapEquals(map.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); } public void testBuilder_orderEntriesByValueAfterExactSizeBuild() { ImmutableBiMap.Builder builder = new ImmutableBiMap.Builder(2).put("four", 4).put("one", 1); ImmutableMap keyOrdered = builder.build(); ImmutableMap valueOrdered = builder.orderEntriesByValue(Ordering.natural()).build(); assertMapEquals(keyOrdered, "four", 4, "one", 1); assertMapEquals(valueOrdered, "one", 1, "four", 4); } public void testBuilder_orderEntriesByValue_usedTwiceFails() { ImmutableBiMap.Builder builder = new Builder().orderEntriesByValue(Ordering.natural()); try { builder.orderEntriesByValue(Ordering.natural()); fail("Expected IllegalStateException"); } catch (IllegalStateException expected) { } } public void testBuilderPutAllWithEmptyMap() { ImmutableBiMap map = new Builder().putAll(Collections.emptyMap()).build(); assertEquals(Collections.emptyMap(), map); } public void testBuilderPutAll() { Map toPut = new LinkedHashMap<>(); toPut.put("one", 1); toPut.put("two", 2); toPut.put("three", 3); Map moreToPut = new LinkedHashMap<>(); moreToPut.put("four", 4); moreToPut.put("five", 5); ImmutableBiMap map = new Builder().putAll(toPut).putAll(moreToPut).build(); assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); assertMapEquals(map.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); } public void testBuilderReuse() { Builder builder = new Builder<>(); ImmutableBiMap mapOne = builder.put("one", 1).put("two", 2).build(); ImmutableBiMap mapTwo = builder.put("three", 3).put("four", 4).build(); assertMapEquals(mapOne, "one", 1, "two", 2); assertMapEquals(mapOne.inverse(), 1, "one", 2, "two"); assertMapEquals(mapTwo, "one", 1, "two", 2, "three", 3, "four", 4); assertMapEquals(mapTwo.inverse(), 1, "one", 2, "two", 3, "three", 4, "four"); } public void testBuilderPutNullKey() { Builder builder = new Builder<>(); try { builder.put(null, 1); fail(); } catch (NullPointerException expected) { } } public void testBuilderPutNullValue() { Builder builder = new Builder<>(); try { builder.put("one", null); fail(); } catch (NullPointerException expected) { } } public void testBuilderPutNullKeyViaPutAll() { Builder builder = new Builder<>(); try { builder.putAll(Collections.singletonMap(null, 1)); fail(); } catch (NullPointerException expected) { } } public void testBuilderPutNullValueViaPutAll() { Builder builder = new Builder<>(); try { builder.putAll(Collections.singletonMap("one", null)); fail(); } catch (NullPointerException expected) { } } public void testPuttingTheSameKeyTwiceThrowsOnBuild() { Builder builder = new Builder() .put("one", 1) .put("one", 1); // throwing on this line would be even better try { builder.build(); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("one"); } } public void testOf() { assertMapEquals(ImmutableBiMap.of("one", 1), "one", 1); assertMapEquals(ImmutableBiMap.of("one", 1).inverse(), 1, "one"); assertMapEquals(ImmutableBiMap.of("one", 1, "two", 2), "one", 1, "two", 2); assertMapEquals(ImmutableBiMap.of("one", 1, "two", 2).inverse(), 1, "one", 2, "two"); assertMapEquals( ImmutableBiMap.of("one", 1, "two", 2, "three", 3), "one", 1, "two", 2, "three", 3); assertMapEquals( ImmutableBiMap.of("one", 1, "two", 2, "three", 3).inverse(), 1, "one", 2, "two", 3, "three"); assertMapEquals( ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4), "one", 1, "two", 2, "three", 3, "four", 4); assertMapEquals( ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4).inverse(), 1, "one", 2, "two", 3, "three", 4, "four"); assertMapEquals( ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5), "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); assertMapEquals( ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5).inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); assertMapEquals( ImmutableBiMap.of( "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6), "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6); assertMapEquals( ImmutableBiMap.of( "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6, "seven", 7), "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6, "seven", 7); assertMapEquals( ImmutableBiMap.of( "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6, "seven", 7, "eight", 8), "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6, "seven", 7, "eight", 8); assertMapEquals( ImmutableBiMap.of( "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6, "seven", 7, "eight", 8, "nine", 9), "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6, "seven", 7, "eight", 8, "nine", 9); assertMapEquals( ImmutableBiMap.of( "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6, "seven", 7, "eight", 8, "nine", 9, "ten", 10), "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, "six", 6, "seven", 7, "eight", 8, "nine", 9, "ten", 10); } public void testOfNullKey() { try { ImmutableBiMap.of(null, 1); fail(); } catch (NullPointerException expected) { } try { ImmutableBiMap.of("one", 1, null, 2); fail(); } catch (NullPointerException expected) { } } public void testOfNullValue() { try { ImmutableBiMap.of("one", null); fail(); } catch (NullPointerException expected) { } try { ImmutableBiMap.of("one", 1, "two", null); fail(); } catch (NullPointerException expected) { } } public void testOfWithDuplicateKey() { try { ImmutableBiMap.of("one", 1, "one", 1); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("one"); } } public void testOfEntries() { assertMapEquals( ImmutableBiMap.ofEntries(entry("one", 1), entry("two", 2)), "one", 1, "two", 2); } public void testOfEntriesNull() { Entry nullKey = entry(null, 23); try { ImmutableBiMap.ofEntries(nullKey); fail(); } catch (NullPointerException expected) { } Entry nullValue = entry(23, null); try { ImmutableBiMap.ofEntries(nullValue); fail(); } catch (NullPointerException expected) { } } private static Entry entry(T key, T value) { return new AbstractMap.SimpleImmutableEntry<>(key, value); } public void testCopyOfEmptyMap() { ImmutableBiMap copy = ImmutableBiMap.copyOf(Collections.emptyMap()); assertEquals(Collections.emptyMap(), copy); assertSame(copy, ImmutableBiMap.copyOf(copy)); assertSame(ImmutableBiMap.of(), copy); } public void testCopyOfSingletonMap() { ImmutableBiMap copy = ImmutableBiMap.copyOf(Collections.singletonMap("one", 1)); assertMapEquals(copy, "one", 1); assertSame(copy, ImmutableBiMap.copyOf(copy)); } public void testCopyOf() { Map original = new LinkedHashMap<>(); original.put("one", 1); original.put("two", 2); original.put("three", 3); ImmutableBiMap copy = ImmutableBiMap.copyOf(original); assertMapEquals(copy, "one", 1, "two", 2, "three", 3); assertSame(copy, ImmutableBiMap.copyOf(copy)); } public void testEmpty() { ImmutableBiMap bimap = ImmutableBiMap.of(); assertEquals(Collections.emptyMap(), bimap); assertEquals(Collections.emptyMap(), bimap.inverse()); } public void testFromHashMap() { Map hashMap = Maps.newLinkedHashMap(); hashMap.put("one", 1); hashMap.put("two", 2); ImmutableBiMap bimap = ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); assertMapEquals(bimap, "one", 1, "two", 2); assertMapEquals(bimap.inverse(), 1, "one", 2, "two"); } public void testFromImmutableMap() { ImmutableBiMap bimap = ImmutableBiMap.copyOf( new ImmutableMap.Builder() .put("one", 1) .put("two", 2) .put("three", 3) .put("four", 4) .put("five", 5) .build()); assertMapEquals(bimap, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); assertMapEquals(bimap.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); } public void testDuplicateValues() { ImmutableMap map = new ImmutableMap.Builder() .put("one", 1) .put("two", 2) .put("uno", 1) .put("dos", 2) .build(); try { ImmutableBiMap.copyOf(map); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("1"); } } public void testToImmutableBiMap() { Collector, ?, ImmutableBiMap> collector = ImmutableBiMap.toImmutableBiMap(Entry::getKey, Entry::getValue); Equivalence> equivalence = Equivalence.equals() .>pairwise() .onResultOf(ImmutableBiMap::entrySet); CollectorTester.of(collector, equivalence) .expectCollects( ImmutableBiMap.of("one", 1, "two", 2, "three", 3), mapEntry("one", 1), mapEntry("two", 2), mapEntry("three", 3)); } public void testToImmutableBiMap_exceptionOnDuplicateKey() { Collector, ?, ImmutableBiMap> collector = ImmutableBiMap.toImmutableBiMap(Entry::getKey, Entry::getValue); try { Stream.of(mapEntry("one", 1), mapEntry("one", 11)).collect(collector); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) { } } } public static class BiMapSpecificTests extends TestCase { public void testForcePut() { BiMap bimap = ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); try { bimap.forcePut("three", 3); fail(); } catch (UnsupportedOperationException expected) { } } public void testKeySet() { ImmutableBiMap bimap = ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4)); Set keys = bimap.keySet(); assertEquals(Sets.newHashSet("one", "two", "three", "four"), keys); assertThat(keys).containsExactly("one", "two", "three", "four").inOrder(); } public void testValues() { ImmutableBiMap bimap = ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4)); Set values = bimap.values(); assertEquals(Sets.newHashSet(1, 2, 3, 4), values); assertThat(values).containsExactly(1, 2, 3, 4).inOrder(); } public void testDoubleInverse() { ImmutableBiMap bimap = ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); assertSame(bimap, bimap.inverse().inverse()); } @GwtIncompatible // SerializableTester public void testEmptySerialization() { ImmutableBiMap bimap = ImmutableBiMap.of(); assertSame(bimap, SerializableTester.reserializeAndAssert(bimap)); } @GwtIncompatible // SerializableTester public void testSerialization() { ImmutableBiMap bimap = ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); ImmutableBiMap copy = SerializableTester.reserializeAndAssert(bimap); assertEquals(Integer.valueOf(1), copy.get("one")); assertEquals("one", copy.inverse().get(1)); assertSame(copy, copy.inverse().inverse()); } @GwtIncompatible // SerializableTester public void testInverseSerialization() { ImmutableBiMap bimap = ImmutableBiMap.copyOf(ImmutableMap.of(1, "one", 2, "two")).inverse(); ImmutableBiMap copy = SerializableTester.reserializeAndAssert(bimap); assertEquals(Integer.valueOf(1), copy.get("one")); assertEquals("one", copy.inverse().get(1)); assertSame(copy, copy.inverse().inverse()); } } private static void assertMapEquals(Map map, Object... alternatingKeysAndValues) { int i = 0; for (Entry entry : map.entrySet()) { assertEquals(alternatingKeysAndValues[i++], entry.getKey()); assertEquals(alternatingKeysAndValues[i++], entry.getValue()); } } public static class FloodingTest extends AbstractHashFloodingTest> { public FloodingTest() { super( EnumSet.allOf(ConstructionPathway.class).stream() .flatMap( path -> Stream.>>of( keys -> path.create( Lists.transform( keys, key -> Maps.immutableEntry(key, new Object()))), keys -> path.create( Lists.transform( keys, key -> Maps.immutableEntry(new Object(), key))), keys -> path.create( Lists.transform(keys, key -> Maps.immutableEntry(key, key))))) .collect(ImmutableList.toImmutableList()), n -> n * Math.log(n), ImmutableList.of( QueryOp.create("BiMap.get", BiMap::get, Math::log), QueryOp.create("BiMap.inverse.get", (bm, o) -> bm.inverse().get(o), Math::log))); } /** All the ways to create an ImmutableBiMap. */ enum ConstructionPathway { COPY_OF_MAP { @Override public ImmutableBiMap create(List> entries) { Map sourceMap = new LinkedHashMap<>(); for (Map.Entry entry : entries) { if (sourceMap.put(entry.getKey(), entry.getValue()) != null) { throw new UnsupportedOperationException("duplicate key"); } } return ImmutableBiMap.copyOf(sourceMap); } }, COPY_OF_ENTRIES { @Override public ImmutableBiMap create(List> entries) { return ImmutableBiMap.copyOf(entries); } }, BUILDER_PUT_ONE_BY_ONE { @Override public ImmutableBiMap create(List> entries) { ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); for (Map.Entry entry : entries) { builder.put(entry.getKey(), entry.getValue()); } return builder.build(); } }, BUILDER_PUT_ALL_MAP { @Override public ImmutableBiMap create(List> entries) { Map sourceMap = new LinkedHashMap<>(); for (Map.Entry entry : entries) { if (sourceMap.put(entry.getKey(), entry.getValue()) != null) { throw new UnsupportedOperationException("duplicate key"); } } ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); builder.putAll(sourceMap); return builder.build(); } }, BUILDER_PUT_ALL_ENTRIES { @Override public ImmutableBiMap create(List> entries) { return ImmutableBiMap.builder().putAll(entries).build(); } }, FORCE_JDK { @Override public ImmutableBiMap create(List> entries) { return ImmutableBiMap.builder().putAll(entries).buildJdkBacked(); } }; @CanIgnoreReturnValue public abstract ImmutableBiMap create(List> entries); } } /** No-op test so that the class has at least one method, making Maven's test runner happy. */ public void testNoop() {} }