package org.codefx.libfx.collection.transform; import java.util.AbstractMap.SimpleEntry; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import junit.framework.Test; import junit.framework.TestSuite; import org.codefx.libfx.collection.transform.ElementTypes.Cat; import org.codefx.libfx.collection.transform.ElementTypes.Feline; import org.codefx.libfx.collection.transform.ElementTypes.Mammal; import com.google.common.collect.testing.MapTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.TestMapGenerator; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.Feature; import com.google.common.collect.testing.features.MapFeature; /** * Tests {@link TransformingMap}. */ public class TransformingMapTest { /** * JUnit-3-style method to create the tests run for this class. * * @return the tests to run */ public static Test suite() { TestSuite suite = new TestSuite("org.codefx.libfx.collection.transform.TransformingMap"); suite.addTest(backingMapHasSupertype()); suite.addTest(backingMapHasSubtype()); return suite; } private static Feature<?>[] features() { return new Feature<?>[] { // since 'TransformedMap' passes all calls along, // the features are determined by the backing data structure (which is a 'HashMap') CollectionSize.ANY, MapFeature.ALLOWS_ANY_NULL_QUERIES, MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION, MapFeature.SUPPORTS_PUT, MapFeature.SUPPORTS_REMOVE, CollectionFeature.SUPPORTS_ITERATOR_REMOVE, }; } /** * Creates a test for a feline map which us backed by a mammal map (i.e. a supertype). * * @return the test case */ private static Test backingMapHasSupertype() { return MapTestSuiteBuilder .using(new TransformingMapGenerator(Mammal.class)) .named("backed by supertype") .withFeatures(features()) .createTestSuite(); } /** * Creates a test for a feline map which us backed by a cat map (i.e. a subtype). * * @return the test case */ private static Test backingMapHasSubtype() { return MapTestSuiteBuilder .using(new TransformingMapGenerator(Cat.class)) .named("backed by subtype") .withFeatures(features()) .createTestSuite(); } private static class TransformingMapGenerator implements TestMapGenerator<Feline, Feline> { private final Class<?> backingMapGenericType; public TransformingMapGenerator(Class<?> backingMapGenericType) { this.backingMapGenericType = backingMapGenericType; } @Override public SampleElements<Entry<Feline, Feline>> samples() { return new SampleElements<Entry<Feline, Feline>>( new SimpleEntry<>(new Feline("A"), new Feline("1")), new SimpleEntry<>(new Feline("B"), new Feline("2")), new SimpleEntry<>(new Feline("C"), new Feline("3")), new SimpleEntry<>(new Feline("D"), new Feline("4")), new SimpleEntry<>(new Feline("E"), new Feline("5"))); } @Override @SuppressWarnings("unchecked") public Entry<Feline, Feline>[] createArray(int length) { return new Entry[length]; } @Override public Feline[] createKeyArray(int length) { return new Feline[length]; } @Override public Feline[] createValueArray(int length) { return new Feline[length]; } @Override public Iterable<Entry<Feline, Feline>> order(List<Entry<Feline, Feline>> insertionOrder) { return insertionOrder; } @Override public Map<Feline, Feline> create(Object... entries) { if (backingMapGenericType.equals(Mammal.class)) return createBackedByMammalMap(entries); if (backingMapGenericType.equals(Cat.class)) return createBackedByCatMap(entries); throw new UnsupportedOperationException(); } private static Map<Feline, Feline> createBackedByMammalMap(Object[] entries) { Map<Mammal, Mammal> mammals = new HashMap<>(); for (Object entry : entries) { String keyName = ((Feline) ((Entry<?, ?>) entry).getKey()).getName(); String valueName = ((Feline) ((Entry<?, ?>) entry).getValue()).getName(); mammals.put(new Mammal(keyName), new Mammal(valueName)); } // Because 'Feline' does not uphold the Liskov Substitution Principle (by having its own 'toString' // method) felines can not masquerade as mammals. Hence create a new mammal for each feline. return TransformingMapBuilder .forTypes(Mammal.class, Feline.class, Mammal.class, Feline.class) .toOuterKey(mammal -> new Feline(mammal.getName())) .toInnerKey(feline -> new Cat(feline.getName())) .toOuterValue(mammal -> new Feline(mammal.getName())) .toInnerValue(feline -> new Cat(feline.getName())) .transformMap(mammals); } private static Map<Feline, Feline> createBackedByCatMap(Object[] entries) { Map<Cat, Cat> cats = new HashMap<>(); for (Object entry : entries) { String keyName = ((Feline) ((Entry<?, ?>) entry).getKey()).getName(); String valueName = ((Feline) ((Entry<?, ?>) entry).getValue()).getName(); cats.put(new Cat(keyName), new Cat(valueName)); } // Because 'Cat' does not uphold the Liskov Substitution Principle (by having its own 'toString' // method) cats can not masquerade as felines. Hence create a new feline for each cat. return TransformingMapBuilder .forTypes(Cat.class, Feline.class, Cat.class, Feline.class) .toOuterKey(cat -> new Feline(cat.getName())) .toInnerKey(feline -> new Cat(feline.getName())) .toOuterValue(cat -> new Feline(cat.getName())) .toInnerValue(feline -> new Cat(feline.getName())) .transformMap(cats); } } }