/* * 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.Maps.newHashMap; import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.google.AbstractMultisetSetCountTester.getSetCountDuplicateInitializingMethods; import static com.google.common.collect.testing.google.MultisetCountTester.getCountDuplicateInitializingMethods; import static com.google.common.collect.testing.google.MultisetIteratorTester.getIteratorDuplicateInitializingMethods; import static com.google.common.collect.testing.google.MultisetRemoveTester.getRemoveDuplicateInitializingMethods; import static java.lang.reflect.Proxy.newProxyInstance; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Ascii; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.collect.Maps.EntryTransformer; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.SetTestSuiteBuilder; import com.google.common.collect.testing.TestCollectionGenerator; import com.google.common.collect.testing.TestListGenerator; import com.google.common.collect.testing.TestStringSetGenerator; 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; import com.google.common.collect.testing.google.ListMultimapTestSuiteBuilder; import com.google.common.collect.testing.google.MultimapFeature; import com.google.common.collect.testing.google.MultimapTestSuiteBuilder; import com.google.common.collect.testing.google.MultisetTestSuiteBuilder; import com.google.common.collect.testing.google.SetMultimapTestSuiteBuilder; import com.google.common.collect.testing.google.TestListMultimapGenerator; import com.google.common.collect.testing.google.TestMultimapGenerator; import com.google.common.collect.testing.google.TestSetMultimapGenerator; import com.google.common.collect.testing.google.TestStringListMultimapGenerator; import com.google.common.collect.testing.google.TestStringMultisetGenerator; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; /** * Run collection tests on wrappers from {@link Multimaps}. * * @author Jared Levy */ @GwtIncompatible("suite") // TODO(cpovirk): set up collect/gwt/suites version public class MultimapsCollectionTest extends TestCase { private static final Feature[] FOR_MAP_FEATURES_ONE = { CollectionSize.ONE, ALLOWS_NULL_VALUES, SUPPORTS_REMOVE, CollectionFeature.SUPPORTS_ITERATOR_REMOVE }; private static final Feature[] FOR_MAP_FEATURES_ANY = { CollectionSize.ANY, ALLOWS_NULL_VALUES, SUPPORTS_REMOVE, CollectionFeature.SUPPORTS_ITERATOR_REMOVE, MultisetTestSuiteBuilder.NoRecurse.NO_ENTRY_SET, // Cannot create entries with count > 1 }; static final Supplier> STRING_TREESET_FACTORY = new Supplier>() { @Override public TreeSet get() { return new TreeSet(Ordering.natural().nullsLast()); } }; static void populateMultimapForGet( Multimap multimap, String[] elements) { multimap.put(2, "foo"); for (String element : elements) { multimap.put(3, element); } } static void populateMultimapForKeySet( Multimap multimap, String[] elements) { for (String element : elements) { multimap.put(element, 2); multimap.put(element, 3); } } static void populateMultimapForValues( Multimap multimap, String[] elements) { for (int i = 0; i < elements.length; i++) { multimap.put(i % 2, elements[i]); } } static void populateMultimapForKeys( Multimap multimap, String[] elements) { for (int i = 0; i < elements.length; i++) { multimap.put(elements[i], i); } } /** * Implements {@code Multimap.put()} -- and no other methods -- for a {@code * Map} by ignoring all but the latest value for each key. This class exists * only so that we can use * {@link MultimapsCollectionTest#populateMultimapForGet(Multimap, String[])} * and similar methods to populate a map to be passed to * {@link Multimaps#forMap(Map)}. All tests should run against the result of * {@link #build()}. */ private static final class PopulatableMapAsMultimap extends ForwardingMultimap { final Map map; final SetMultimap unusableDelegate; static PopulatableMapAsMultimap create() { return new PopulatableMapAsMultimap(); } @SuppressWarnings("unchecked") // all methods throw immediately PopulatableMapAsMultimap() { this.map = newHashMap(); this.unusableDelegate = (SetMultimap) newProxyInstance( SetMultimap.class.getClassLoader(), new Class[] {SetMultimap.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { throw new UnsupportedOperationException(); } }); } @Override protected Multimap delegate() { return unusableDelegate; } @Override public boolean put(K key, V value) { map.put(key, value); return true; } SetMultimap build() { return Multimaps.forMap(map); } } abstract static class TestEntriesGenerator implements TestCollectionGenerator> { @Override public SampleElements> samples() { return new SampleElements>( Maps.immutableEntry("bar", 1), Maps.immutableEntry("bar", 2), Maps.immutableEntry("foo", 3), Maps.immutableEntry("bar", 3), Maps.immutableEntry("cat", 2)); } @Override public Collection> create(Object... elements) { Multimap multimap = createMultimap(); for (Object element : elements) { @SuppressWarnings("unchecked") Entry entry = (Entry) element; multimap.put(entry.getKey(), entry.getValue()); } return multimap.entries(); } abstract Multimap createMultimap(); @Override @SuppressWarnings("unchecked") public Entry[] createArray(int length) { return (Entry[]) new Entry[length]; } @Override public List> order( List> insertionOrder) { return insertionOrder; } } public abstract static class TestEntriesListGenerator extends TestEntriesGenerator implements TestListGenerator> { @Override public List> create(Object... elements) { return (List>) super.create(elements); } } private static final Predicate> FILTER_GET_PREDICATE = new Predicate>() { @Override public boolean apply(Entry entry) { return !"badvalue".equals(entry.getValue()) && 55556 != entry.getKey(); } }; private static final Predicate> FILTER_KEYSET_PREDICATE = new Predicate>() { @Override public boolean apply(Entry entry) { return !"badkey".equals(entry.getKey()) && 55556 != entry.getValue(); } }; public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest(transformSuite()); suite.addTest(filterSuite()); suite.addTest(ListMultimapTestSuiteBuilder.using(new TestStringListMultimapGenerator() { @Override protected ListMultimap create(Entry[] entries) { ListMultimap multimap = Multimaps.synchronizedListMultimap( ArrayListMultimap. create()); for (Entry entry : entries) { multimap.put(entry.getKey(), entry.getValue()); } return multimap; } }) .named("synchronized ArrayListMultimap") .withFeatures( MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_NULL_VALUES, MapFeature.ALLOWS_ANY_NULL_QUERIES, MapFeature.GENERAL_PURPOSE, MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION, CollectionFeature.SUPPORTS_ITERATOR_REMOVE, CollectionSize.ANY) .createTestSuite()); suite.addTest(SetTestSuiteBuilder.using( new TestStringSetGenerator() { @Override protected Set create(String[] elements) { PopulatableMapAsMultimap multimap = PopulatableMapAsMultimap.create(); populateMultimapForGet(multimap, elements); return multimap.build().get(3); } }) .named("Multimaps.forMap.get") .withFeatures(FOR_MAP_FEATURES_ONE) .createTestSuite()); suite.addTest(SetTestSuiteBuilder.using( new TestStringSetGenerator() { @Override protected Set create(String[] elements) { PopulatableMapAsMultimap multimap = PopulatableMapAsMultimap.create(); populateMultimapForKeySet(multimap, elements); return multimap.build().keySet(); } }) .named("Multimaps.forMap.keySet") .withFeatures(FOR_MAP_FEATURES_ANY) .createTestSuite()); // TODO: use collection testers on Multimaps.forMap.values suite.addTest(MultisetTestSuiteBuilder.using( new TestStringMultisetGenerator() { @Override protected Multiset create(String[] elements) { PopulatableMapAsMultimap multimap = PopulatableMapAsMultimap.create(); populateMultimapForKeys(multimap, elements); return multimap.build().keys(); } }) .named("Multimaps.forMap.keys") .withFeatures(FOR_MAP_FEATURES_ANY) .suppressing(getCountDuplicateInitializingMethods()) .suppressing(getSetCountDuplicateInitializingMethods()) .suppressing(getIteratorDuplicateInitializingMethods()) .suppressing(getRemoveDuplicateInitializingMethods()) .createTestSuite()); // TODO: use collection testers on Multimaps.forMap.entries return suite; } static abstract class TransformedMultimapGenerator> implements TestMultimapGenerator { @Override public String[] createKeyArray(int length) { return new String[length]; } @Override public String[] createValueArray(int length) { return new String[length]; } @Override public SampleElements sampleKeys() { return new SampleElements("one", "two", "three", "four", "five"); } @Override public SampleElements sampleValues() { return new SampleElements("january", "february", "march", "april", "may"); } @Override public Collection createCollection(Iterable values) { return Lists.newArrayList(values); } @Override public SampleElements> samples() { return new SampleElements>( mapEntry("one", "january"), mapEntry("two", "february"), mapEntry("three", "march"), mapEntry("four", "april"), mapEntry("five", "may")); } @Override public M create(Object... elements) { Multimap multimap = ArrayListMultimap.create(); for (Object o : elements) { @SuppressWarnings("unchecked") Entry entry = (Entry) o; multimap.put(entry.getKey(), Ascii.toUpperCase(entry.getValue())); } return transform(multimap); } abstract M transform(Multimap multimap); @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { return new Entry[length]; } @Override public Iterable> order(List> insertionOrder) { return insertionOrder; } static final Function FUNCTION = new Function() { @Override public String apply(String value) { return Ascii.toLowerCase(value); } }; static final EntryTransformer ENTRY_TRANSFORMER = new EntryTransformer() { @Override public String transformEntry(String key, String value) { return Ascii.toLowerCase(value); } }; } static abstract class TransformedListMultimapGenerator extends TransformedMultimapGenerator> implements TestListMultimapGenerator { } private static Test transformSuite() { TestSuite suite = new TestSuite("Multimaps.transform*"); suite.addTest(MultimapTestSuiteBuilder.using( new TransformedMultimapGenerator>() { @Override Multimap transform(Multimap multimap) { return Multimaps.transformValues(multimap, FUNCTION); } }) .named("Multimaps.transformValues[Multimap]") .withFeatures( CollectionSize.ANY, MapFeature.SUPPORTS_REMOVE, CollectionFeature.SUPPORTS_ITERATOR_REMOVE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(MultimapTestSuiteBuilder.using( new TransformedMultimapGenerator>() { @Override Multimap transform(Multimap multimap) { return Multimaps.transformEntries(multimap, ENTRY_TRANSFORMER); } }) .named("Multimaps.transformEntries[Multimap]") .withFeatures( CollectionSize.ANY, MapFeature.SUPPORTS_REMOVE, CollectionFeature.SUPPORTS_ITERATOR_REMOVE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(ListMultimapTestSuiteBuilder.using(new TransformedListMultimapGenerator() { @Override ListMultimap transform(Multimap multimap) { return Multimaps.transformValues((ListMultimap) multimap, FUNCTION); } }) .named("Multimaps.transformValues[ListMultimap]") .withFeatures( CollectionSize.ANY, MapFeature.SUPPORTS_REMOVE, CollectionFeature.SUPPORTS_ITERATOR_REMOVE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(ListMultimapTestSuiteBuilder.using(new TransformedListMultimapGenerator() { @Override ListMultimap transform(Multimap multimap) { return Multimaps.transformEntries( (ListMultimap) multimap, ENTRY_TRANSFORMER); } }) .named("Multimaps.transformEntries[ListMultimap]") .withFeatures( CollectionSize.ANY, MapFeature.SUPPORTS_REMOVE, CollectionFeature.SUPPORTS_ITERATOR_REMOVE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); // TODO: use collection testers on Multimaps.forMap.entries return suite; } static abstract class TestFilteredMultimapGenerator> implements TestMultimapGenerator { @Override public SampleElements> samples() { return new SampleElements>( mapEntry("one", 114), mapEntry("two", 37), mapEntry("three", 42), mapEntry("four", 19), mapEntry("five", 82)); } @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { return new Entry[length]; } @Override public Iterable> order(List> insertionOrder) { return insertionOrder; } @Override public String[] createKeyArray(int length) { return new String[length]; } @Override public Integer[] createValueArray(int length) { return new Integer[length]; } @Override public SampleElements sampleKeys() { return new SampleElements("one", "two", "three", "four", "five"); } @Override public SampleElements sampleValues() { return new SampleElements(114, 37, 42, 19, 82); } } static abstract class FilteredSetMultimapGenerator extends TestFilteredMultimapGenerator> implements TestSetMultimapGenerator { abstract SetMultimap filter(SetMultimap multimap); @Override public SetMultimap create(Object... elements) { SetMultimap multimap = LinkedHashMultimap.create(); for (Object o : elements) { @SuppressWarnings("unchecked") Entry entry = (Entry) o; multimap.put(entry.getKey(), entry.getValue()); } return filter(multimap); } @Override public Collection createCollection(Iterable values) { return Sets.newLinkedHashSet(values); } } static abstract class FilteredListMultimapGenerator extends TestFilteredMultimapGenerator> implements TestListMultimapGenerator { @Override public ListMultimap create(Object... elements) { ListMultimap multimap = LinkedListMultimap.create(); for (Object o : elements) { @SuppressWarnings("unchecked") Entry entry = (Entry) o; multimap.put(entry.getKey(), entry.getValue()); } return filter(multimap); } abstract ListMultimap filter(ListMultimap multimap); @Override public Collection createCollection(Iterable values) { return Lists.newArrayList(values); } } private static Test filterSuite() { TestSuite suite = new TestSuite("Multimaps.filter*"); suite.addTest(SetMultimapTestSuiteBuilder.using(new FilteredSetMultimapGenerator() { @Override SetMultimap filter(SetMultimap multimap) { multimap.put("foo", 17); multimap.put("bar", 32); multimap.put("foo", 16); return Multimaps.filterKeys(multimap, Predicates.not(Predicates.in(ImmutableSet.of("foo", "bar")))); } }) .named("Multimaps.filterKeys[SetMultimap, Predicate]") .withFeatures( CollectionSize.ANY, MultimapFeature.VALUE_COLLECTIONS_SUPPORT_ITERATOR_REMOVE, MapFeature.GENERAL_PURPOSE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_NULL_VALUES, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(ListMultimapTestSuiteBuilder.using(new FilteredListMultimapGenerator() { @Override ListMultimap filter(ListMultimap multimap) { multimap.put("foo", 17); multimap.put("bar", 32); multimap.put("foo", 16); return Multimaps.filterKeys(multimap, Predicates.not(Predicates.in(ImmutableSet.of("foo", "bar")))); } }) .named("Multimaps.filterKeys[ListMultimap, Predicate]") .withFeatures( CollectionSize.ANY, MultimapFeature.VALUE_COLLECTIONS_SUPPORT_ITERATOR_REMOVE, MapFeature.GENERAL_PURPOSE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_NULL_VALUES, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(ListMultimapTestSuiteBuilder.using(new FilteredListMultimapGenerator() { @Override ListMultimap filter(ListMultimap multimap) { multimap.put("foo", 17); multimap.put("bar", 32); multimap.put("foo", 16); multimap = Multimaps.filterKeys(multimap, Predicates.not(Predicates.equalTo("foo"))); return Multimaps.filterKeys(multimap, Predicates.not(Predicates.equalTo("bar"))); } }) .named("Multimaps.filterKeys[Multimaps.filterKeys[ListMultimap], Predicate]") .withFeatures( CollectionSize.ANY, MultimapFeature.VALUE_COLLECTIONS_SUPPORT_ITERATOR_REMOVE, MapFeature.GENERAL_PURPOSE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_NULL_VALUES, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(SetMultimapTestSuiteBuilder.using(new FilteredSetMultimapGenerator() { @Override SetMultimap filter(SetMultimap multimap) { multimap.put("one", 314); multimap.put("two", 159); multimap.put("one", 265); return Multimaps.filterValues(multimap, Predicates.not(Predicates.in(ImmutableSet.of(314, 159, 265)))); } }) .named("Multimaps.filterValues[SetMultimap, Predicate]") .withFeatures( CollectionSize.ANY, MapFeature.GENERAL_PURPOSE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_NULL_VALUES, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(SetMultimapTestSuiteBuilder.using(new FilteredSetMultimapGenerator() { @Override SetMultimap filter(SetMultimap multimap) { ImmutableSetMultimap badEntries = ImmutableSetMultimap.of("foo", 314, "one", 159, "two", 265, "bar", 358); multimap.putAll(badEntries); return Multimaps.filterEntries(multimap, Predicates.not(Predicates.in(badEntries.entries()))); } }) .named("Multimaps.filterEntries[SetMultimap, Predicate]") .withFeatures( CollectionSize.ANY, MapFeature.GENERAL_PURPOSE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_NULL_VALUES, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(SetMultimapTestSuiteBuilder.using(new FilteredSetMultimapGenerator() { @Override SetMultimap filter(SetMultimap multimap) { ImmutableSetMultimap badEntries = ImmutableSetMultimap.of("foo", 314, "one", 159, "two", 265, "bar", 358); multimap.putAll(badEntries); multimap = Multimaps.filterKeys(multimap, Predicates.not(Predicates.in(ImmutableSet.of("foo", "bar")))); return Multimaps.filterEntries(multimap, Predicates.not(Predicates.in(badEntries.entries()))); } }) .named("Multimaps.filterEntries[Multimaps.filterKeys[SetMultimap]]") .withFeatures( CollectionSize.ANY, MapFeature.GENERAL_PURPOSE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_NULL_VALUES, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(SetMultimapTestSuiteBuilder.using(new FilteredSetMultimapGenerator() { @Override SetMultimap filter(SetMultimap multimap) { ImmutableSetMultimap badEntries = ImmutableSetMultimap.of("foo", 314, "one", 159, "two", 265, "bar", 358); multimap.putAll(badEntries); multimap = Multimaps.filterEntries(multimap, Predicates.not(Predicates.in(ImmutableMap.of("one", 159, "two", 265).entrySet()))); return Multimaps.filterKeys(multimap, Predicates.not(Predicates.in(ImmutableSet.of("foo", "bar")))); } }) .named("Multimaps.filterKeys[Multimaps.filterEntries[SetMultimap]]") .withFeatures( CollectionSize.ANY, MapFeature.GENERAL_PURPOSE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_NULL_VALUES, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); suite.addTest(SetMultimapTestSuiteBuilder.using(new FilteredSetMultimapGenerator() { @Override SetMultimap filter(SetMultimap multimap) { ImmutableSetMultimap badEntries = ImmutableSetMultimap.of("foo", 314, "bar", 358); multimap.putAll(badEntries); multimap = Multimaps.filterKeys(multimap, Predicates.not(Predicates.equalTo("foo"))); multimap = Multimaps.filterKeys(multimap, Predicates.not(Predicates.equalTo("bar"))); return multimap; } }) .named("Multimaps.filterKeys[Multimaps.filterKeys[SetMultimap]]") .withFeatures( CollectionSize.ANY, MultimapFeature.VALUE_COLLECTIONS_SUPPORT_ITERATOR_REMOVE, MapFeature.GENERAL_PURPOSE, MapFeature.ALLOWS_NULL_KEYS, MapFeature.ALLOWS_NULL_VALUES, MapFeature.ALLOWS_ANY_NULL_QUERIES) .createTestSuite()); return suite; } }