/*
 * 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<TreeSet<String>> STRING_TREESET_FACTORY = new Supplier<TreeSet<String>>() {
    @Override
    public TreeSet<String> get() {
      return new TreeSet<String>(Ordering.natural().nullsLast());
    }
  };

  static void populateMultimapForGet(
      Multimap<Integer, String> multimap, String[] elements) {
    multimap.put(2, "foo");
    for (String element : elements) {
      multimap.put(3, element);
    }
  }

  static void populateMultimapForKeySet(
      Multimap<String, Integer> multimap, String[] elements) {
    for (String element : elements) {
      multimap.put(element, 2);
      multimap.put(element, 3);
    }
  }

  static void populateMultimapForValues(
      Multimap<Integer, String> multimap, String[] elements) {
    for (int i = 0; i < elements.length; i++) {
      multimap.put(i % 2, elements[i]);
    }
  }

  static void populateMultimapForKeys(
      Multimap<String, Integer> 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<K, V>
      extends ForwardingMultimap<K, V> {
    final Map<K, V> map;
    final SetMultimap<K, V> unusableDelegate;

    static <K, V> PopulatableMapAsMultimap<K, V> create() {
      return new PopulatableMapAsMultimap<K, V>();
    }

    @SuppressWarnings("unchecked")  // all methods throw immediately
    PopulatableMapAsMultimap() {
      this.map = newHashMap();
      this.unusableDelegate = (SetMultimap<K, V>) 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<K, V> delegate() {
      return unusableDelegate;
    }

    @Override public boolean put(K key, V value) {
      map.put(key, value);
      return true;
    }

    SetMultimap<K, V> build() {
      return Multimaps.forMap(map);
    }
  }

  abstract static class TestEntriesGenerator
      implements TestCollectionGenerator<Entry<String, Integer>> {
    @Override
    public SampleElements<Entry<String, Integer>> samples() {
      return new SampleElements<Entry<String, Integer>>(
          Maps.immutableEntry("bar", 1),
          Maps.immutableEntry("bar", 2),
          Maps.immutableEntry("foo", 3),
          Maps.immutableEntry("bar", 3),
          Maps.immutableEntry("cat", 2));
    }

    @Override
    public Collection<Entry<String, Integer>> create(Object... elements) {
      Multimap<String, Integer> multimap = createMultimap();
      for (Object element : elements) {
        @SuppressWarnings("unchecked")
        Entry<String, Integer> entry = (Entry<String, Integer>) element;
        multimap.put(entry.getKey(), entry.getValue());
      }
      return multimap.entries();
    }

    abstract Multimap<String, Integer> createMultimap();

    @Override
    @SuppressWarnings("unchecked")
    public Entry<String, Integer>[] createArray(int length) {
      return (Entry<String, Integer>[]) new Entry<?, ?>[length];
    }

    @Override
    public List<Entry<String, Integer>> order(
        List<Entry<String, Integer>> insertionOrder) {
      return insertionOrder;
    }
  }

  public abstract static class TestEntriesListGenerator
      extends TestEntriesGenerator
      implements TestListGenerator<Entry<String, Integer>> {
    @Override public List<Entry<String, Integer>> create(Object... elements) {
      return (List<Entry<String, Integer>>) super.create(elements);
    }
  }

  private static final Predicate<Map.Entry<Integer, String>> FILTER_GET_PREDICATE
      = new Predicate<Map.Entry<Integer, String>>() {
        @Override public boolean apply(Entry<Integer, String> entry) {
          return !"badvalue".equals(entry.getValue()) && 55556 != entry.getKey();
        }
    };

  private static final Predicate<Map.Entry<String, Integer>> FILTER_KEYSET_PREDICATE
    = new Predicate<Map.Entry<String, Integer>>() {
      @Override public boolean apply(Entry<String, Integer> 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<String, String> create(Entry<String, String>[] entries) {
          ListMultimap<String, String> multimap = Multimaps.synchronizedListMultimap(
              ArrayListMultimap.<String, String> create());
          for (Entry<String, String> 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<String> create(String[] elements) {
            PopulatableMapAsMultimap<Integer, String> 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<String> create(String[] elements) {
            PopulatableMapAsMultimap<String, Integer> 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<String> create(String[] elements) {
            PopulatableMapAsMultimap<String, Integer> 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<M extends Multimap<String, String>> 
      implements TestMultimapGenerator<String, String, M> {

    @Override
    public String[] createKeyArray(int length) {
      return new String[length];
    }

    @Override
    public String[] createValueArray(int length) {
      return new String[length];
    }

    @Override
    public SampleElements<String> sampleKeys() {
      return new SampleElements<String>("one", "two", "three", "four", "five");
    }

    @Override
    public SampleElements<String> sampleValues() {
      return new SampleElements<String>("january", "february", "march", "april", "may");
    }

    @Override
    public Collection<String> createCollection(Iterable<? extends String> values) {
      return Lists.newArrayList(values);
    }

    @Override
    public SampleElements<Entry<String, String>> samples() {
      return new SampleElements<Entry<String, String>>(
          mapEntry("one", "january"),
          mapEntry("two", "february"),
          mapEntry("three", "march"),
          mapEntry("four", "april"),
          mapEntry("five", "may"));
    }

    @Override
    public M create(Object... elements) {
      Multimap<String, String> multimap = ArrayListMultimap.create();
      for (Object o : elements) {
        @SuppressWarnings("unchecked")
        Entry<String, String> entry = (Entry<String, String>) o;
        multimap.put(entry.getKey(), Ascii.toUpperCase(entry.getValue()));
      }
      return transform(multimap);
    }
    
    abstract M transform(Multimap<String, String> multimap);

    @SuppressWarnings("unchecked")
    @Override
    public Entry<String, String>[] createArray(int length) {
      return new Entry[length];
    }

    @Override
    public Iterable<Entry<String, String>> order(List<Entry<String, String>> insertionOrder) {
      return insertionOrder;
    }
    
    static final Function<String, String> FUNCTION = new Function<String, String>() {
      @Override
      public String apply(String value) {
        return Ascii.toLowerCase(value);
      }
    };
    
    static final EntryTransformer<String, String, String> ENTRY_TRANSFORMER =
        new EntryTransformer<String, String, String>() {
      @Override
      public String transformEntry(String key, String value) {
        return Ascii.toLowerCase(value);
      }
    };
  }
  
  static abstract class TransformedListMultimapGenerator 
      extends TransformedMultimapGenerator<ListMultimap<String, String>>
      implements TestListMultimapGenerator<String, String> {
  }
  
  private static Test transformSuite() {
    TestSuite suite = new TestSuite("Multimaps.transform*");
    suite.addTest(MultimapTestSuiteBuilder.using(
        new TransformedMultimapGenerator<Multimap<String,String>>() {
          @Override
          Multimap<String, String> transform(Multimap<String, String> 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<Multimap<String,String>>() {
          @Override
          Multimap<String, String> transform(Multimap<String, String> 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<String, String> transform(Multimap<String, String> multimap) {
            return Multimaps.transformValues((ListMultimap<String, String>) 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<String, String> transform(Multimap<String, String> multimap) {
            return Multimaps.transformEntries(
                (ListMultimap<String, String>) 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<M extends Multimap<String, Integer>>
      implements TestMultimapGenerator<String, Integer, M> {

    @Override
    public SampleElements<Entry<String, Integer>> samples() {
      return new SampleElements<Entry<String, Integer>>(
          mapEntry("one", 114),
          mapEntry("two", 37),
          mapEntry("three", 42),
          mapEntry("four", 19),
          mapEntry("five", 82));
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public Entry<String, Integer>[] createArray(int length) {
      return new Entry[length];
    }

    @Override
    public Iterable<Entry<String, Integer>> order(List<Entry<String, Integer>> 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<String> sampleKeys() {
      return new SampleElements<String>("one", "two", "three", "four", "five");
    }

    @Override
    public SampleElements<Integer> sampleValues() {
      return new SampleElements<Integer>(114, 37, 42, 19, 82);
    }
  }
  
  static abstract class FilteredSetMultimapGenerator 
      extends TestFilteredMultimapGenerator<SetMultimap<String, Integer>>
      implements TestSetMultimapGenerator<String, Integer> {

    
    abstract SetMultimap<String, Integer> filter(SetMultimap<String, Integer> multimap);

    @Override
    public SetMultimap<String, Integer> create(Object... elements) {
      SetMultimap<String, Integer> multimap = LinkedHashMultimap.create();
      for (Object o : elements) {
        @SuppressWarnings("unchecked")
        Entry<String, Integer> entry = (Entry<String, Integer>) o;
        multimap.put(entry.getKey(), entry.getValue());
      }
      return filter(multimap);
    }

    @Override
    public Collection<Integer> createCollection(Iterable<? extends Integer> values) {
      return Sets.newLinkedHashSet(values);
    }
  }
  
  static abstract class FilteredListMultimapGenerator
      extends TestFilteredMultimapGenerator<ListMultimap<String, Integer>>
      implements TestListMultimapGenerator<String, Integer> {

    @Override
    public ListMultimap<String, Integer> create(Object... elements) {
      ListMultimap<String, Integer> multimap = LinkedListMultimap.create();
      for (Object o : elements) {
        @SuppressWarnings("unchecked")
        Entry<String, Integer> entry = (Entry<String, Integer>) o;
        multimap.put(entry.getKey(), entry.getValue());
      }
      return filter(multimap);
    }
    
    abstract ListMultimap<String, Integer> filter(ListMultimap<String, Integer> multimap);

    @Override
    public Collection<Integer> createCollection(Iterable<? extends Integer> values) {
      return Lists.newArrayList(values);
    }
  }
 
  private static Test filterSuite() {   
    TestSuite suite = new TestSuite("Multimaps.filter*");
    suite.addTest(SetMultimapTestSuiteBuilder.using(new FilteredSetMultimapGenerator() {
        @Override
        SetMultimap<String, Integer> filter(SetMultimap<String, Integer> 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<String, Integer> filter(ListMultimap<String, Integer> 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<String, Integer> filter(ListMultimap<String, Integer> 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<String, Integer> filter(SetMultimap<String, Integer> 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<String, Integer> filter(SetMultimap<String, Integer> multimap) {
          ImmutableSetMultimap<String, Integer> 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<String, Integer> filter(SetMultimap<String, Integer> multimap) {
          ImmutableSetMultimap<String, Integer> 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<String, Integer> filter(SetMultimap<String, Integer> multimap) {
          ImmutableSetMultimap<String, Integer> 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<String, Integer> filter(SetMultimap<String, Integer> multimap) {
          ImmutableSetMultimap<String, Integer> 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;
  }
}
