/*
 * 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.testing.testers;

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.features.CollectionSize.ONE;
import static com.google.common.collect.testing.features.CollectionSize.ZERO;
import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows;
import static java.util.Arrays.asList;

import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.testing.AbstractCollectionTester;
import com.google.common.collect.testing.MinimalCollection;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.junit.Ignore;

/**
 * A generic JUnit test which tests {@code retainAll} operations on a collection. Can't be invoked
 * directly; please see {@link com.google.common.collect.testing.CollectionTestSuiteBuilder}.
 *
 * @author Chris Povirk
 */
@GwtCompatible
@Ignore("test runners must not instantiate and run this directly, only via suites we build")
// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests.
@SuppressWarnings("JUnit4ClassUsedInJUnit3")
public class CollectionRetainAllTester<E> extends AbstractCollectionTester<E> {

  /** A collection of elements to retain, along with a description for use in failure messages. */
  private final class Target {
    private final Collection<E> toRetain;
    private final String description;

    private Target(Collection<E> toRetain, String description) {
      this.toRetain = toRetain;
      this.description = description;
    }

    @Override
    public String toString() {
      return description;
    }
  }

  private Target empty;
  private Target disjoint;
  private Target superset;
  private Target nonEmptyProperSubset;
  private Target sameElements;
  private Target partialOverlap;
  private Target containsDuplicates;
  private Target nullSingleton;

  @Override
  public void setUp() throws Exception {
    super.setUp();

    empty = new Target(emptyCollection(), "empty");
    /*
     * We test that nullSingleton.retainAll(disjointList) does NOT throw a
     * NullPointerException when disjointList does not, so we can't use
     * MinimalCollection, which throws NullPointerException on calls to
     * contains(null).
     */
    List<E> disjointList = asList(e3(), e4());
    disjoint = new Target(disjointList, "disjoint");
    superset = new Target(MinimalCollection.of(e0(), e1(), e2(), e3(), e4()), "superset");
    nonEmptyProperSubset = new Target(MinimalCollection.of(e1()), "subset");
    sameElements = new Target(asList(createSamplesArray()), "sameElements");
    containsDuplicates =
        new Target(MinimalCollection.of(e0(), e0(), e3(), e3()), "containsDuplicates");
    partialOverlap = new Target(MinimalCollection.of(e2(), e3()), "partialOverlap");
    nullSingleton = new Target(Collections.<E>singleton(null), "nullSingleton");
  }

  // retainAll(empty)

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(ZERO)
  public void testRetainAll_emptyPreviouslyEmpty() {
    expectReturnsFalse(empty);
    expectUnchanged();
  }

  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
  @CollectionSize.Require(ZERO)
  public void testRetainAll_emptyPreviouslyEmptyUnsupported() {
    expectReturnsFalseOrThrows(empty);
    expectUnchanged();
  }

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = ZERO)
  public void testRetainAll_emptyPreviouslyNonEmpty() {
    expectReturnsTrue(empty);
    expectContents();
    expectMissing(e0(), e1(), e2());
  }

  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = ZERO)
  public void testRetainAll_emptyPreviouslyNonEmptyUnsupported() {
    expectThrows(empty);
    expectUnchanged();
  }

  // retainAll(disjoint)

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(ZERO)
  public void testRetainAll_disjointPreviouslyEmpty() {
    expectReturnsFalse(disjoint);
    expectUnchanged();
  }

  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
  @CollectionSize.Require(ZERO)
  public void testRetainAll_disjointPreviouslyEmptyUnsupported() {
    expectReturnsFalseOrThrows(disjoint);
    expectUnchanged();
  }

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = ZERO)
  public void testRetainAll_disjointPreviouslyNonEmpty() {
    expectReturnsTrue(disjoint);
    expectContents();
    expectMissing(e0(), e1(), e2());
  }

  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = ZERO)
  public void testRetainAll_disjointPreviouslyNonEmptyUnsupported() {
    expectThrows(disjoint);
    expectUnchanged();
  }

  // retainAll(superset)

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  public void testRetainAll_superset() {
    expectReturnsFalse(superset);
    expectUnchanged();
  }

  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
  public void testRetainAll_supersetUnsupported() {
    expectReturnsFalseOrThrows(superset);
    expectUnchanged();
  }

  // retainAll(subset)

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testRetainAll_subset() {
    expectReturnsTrue(nonEmptyProperSubset);
    expectContents(nonEmptyProperSubset.toRetain);
  }

  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testRetainAll_subsetUnsupported() {
    expectThrows(nonEmptyProperSubset);
    expectUnchanged();
  }

  // retainAll(sameElements)

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  public void testRetainAll_sameElements() {
    expectReturnsFalse(sameElements);
    expectUnchanged();
  }

  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
  public void testRetainAll_sameElementsUnsupported() {
    expectReturnsFalseOrThrows(sameElements);
    expectUnchanged();
  }

  // retainAll(partialOverlap)

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testRetainAll_partialOverlap() {
    expectReturnsTrue(partialOverlap);
    expectContents(e2());
  }

  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testRetainAll_partialOverlapUnsupported() {
    expectThrows(partialOverlap);
    expectUnchanged();
  }

  // retainAll(containsDuplicates)

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(ONE)
  public void testRetainAll_containsDuplicatesSizeOne() {
    expectReturnsFalse(containsDuplicates);
    expectContents(e0());
  }

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testRetainAll_containsDuplicatesSizeSeveral() {
    expectReturnsTrue(containsDuplicates);
    expectContents(e0());
  }

  // retainAll(nullSingleton)

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(ZERO)
  public void testRetainAll_nullSingletonPreviouslyEmpty() {
    expectReturnsFalse(nullSingleton);
    expectUnchanged();
  }

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = ZERO)
  public void testRetainAll_nullSingletonPreviouslyNonEmpty() {
    expectReturnsTrue(nullSingleton);
    expectContents();
  }

  @CollectionFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_VALUES})
  @CollectionSize.Require(ONE)
  public void testRetainAll_nullSingletonPreviouslySingletonWithNull() {
    initCollectionWithNullElement();
    expectReturnsFalse(nullSingleton);
    expectContents(createArrayWithNullElement());
  }

  @CollectionFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_VALUES})
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testRetainAll_nullSingletonPreviouslySeveralWithNull() {
    initCollectionWithNullElement();
    expectReturnsTrue(nullSingleton);
    expectContents(nullSingleton.toRetain);
  }

  // nullSingleton.retainAll()

  @CollectionFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_VALUES})
  @CollectionSize.Require(absent = ZERO)
  public void testRetainAll_containsNonNullWithNull() {
    initCollectionWithNullElement();
    expectReturnsTrue(disjoint);
    expectContents();
  }

  // retainAll(null)

  /*
   * AbstractCollection fails the retainAll(null) test when the subject
   * collection is empty, but we'd still like to test retainAll(null) when we
   * can. We split the test into empty and non-empty cases. This allows us to
   * suppress only the former.
   */

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(ZERO)
  public void testRetainAll_nullCollectionReferenceEmptySubject() {
    try {
      collection.retainAll(null);
      // Returning successfully is not ideal, but tolerated.
    } catch (NullPointerException tolerated) {
    }
  }

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = ZERO)
  public void testRetainAll_nullCollectionReferenceNonEmptySubject() {
    assertThrows(NullPointerException.class, () -> collection.retainAll(null));
  }

  private void expectReturnsTrue(Target target) {
    String message = Platform.format("retainAll(%s) should return true", target);
    assertTrue(message, collection.retainAll(target.toRetain));
  }

  private void expectReturnsFalse(Target target) {
    String message = Platform.format("retainAll(%s) should return false", target);
    assertFalse(message, collection.retainAll(target.toRetain));
  }

  private void expectThrows(Target target) {
    try {
      collection.retainAll(target.toRetain);
      String message = Platform.format("retainAll(%s) should throw", target);
      fail(message);
    } catch (UnsupportedOperationException expected) {
    }
  }

  private void expectReturnsFalseOrThrows(Target target) {
    String message = Platform.format("retainAll(%s) should return false or throw", target);
    try {
      assertFalse(message, collection.retainAll(target.toRetain));
    } catch (UnsupportedOperationException tolerated) {
    }
  }
}
