// CHECKSTYLE_OFF: Copyrighted to ASF
/*
 * Copyright 1999-2004 The Apache Software Foundation
 *
 * 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.
 */
// CHECKSTYLE_ON
package com.google.gwt.emultest.java.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * Tests base {@link java.util.Collection} methods and contracts.
 *
 * <p>You should create a concrete subclass of this class to test any custom {@link Collection}
 * implementation. At minimum, you'll have to implement the {@link #makeCollection()} method. You
 * might want to override some of the additional protected methods as well:
 *
 * <p><B>Element Population Methods</B>
 *
 * <p>Override these if your collection restricts what kind of elements are allowed (for instance,
 * if <Code>null</Code> is not permitted):
 *
 * <UL>
 *   <Li>{@link #getFullElements()}
 *   <Li>{@link #getOtherElements()}
 * </UL>
 *
 * <B>Supported Operation Methods</B>
 *
 * <p>Override these if your collection doesn't support certain operations:
 *
 * <UL>
 *   <LI>{@link #isAddSupported()}
 *   <LI>{@link #isRemoveSupported()}
 *   <li>{@link #areEqualElementsDistinguishable()}
 * </UL>
 *
 * <B>Fixture Methods</B>
 *
 * <p>Fixtures are used to verify that the operation results in correct state for the collection.
 * Basically, the operation is performed against your collection implementation, and an identical
 * operation is performed against a <I>confirmed</I> collection implementation. A confirmed
 * collection implementation is something like <Code>java.util.ArrayList</Code>, which is known to
 * conform exactly to its collection interface's contract. After the operation takes place on both
 * your collection implementation and the confirmed collection implementation, the two collections
 * are compared to see if their state is identical. The comparison is usually much more involved
 * than a simple <Code>equals</Code> test. This verification is used to ensure proper modifications
 * are made along with ensuring that the collection does not change when read-only modifications are
 * made.
 *
 * <p>The {@link #collection} field holds an instance of your collection implementation; the {@link
 * #confirmed} field holds an instance of the confirmed collection implementation. The {@link
 * #resetEmpty()} and {@link #resetFull()} methods set these fields to empty or full collections, so
 * that tests can proceed from a known state.
 *
 * <p>After a modification operation to both {@link #collection} and {@link #confirmed}, the {@link
 * #verify()} method is invoked to compare the results. You may want to override {@link #verify()}
 * to perform additional verifications. For instance, when testing the collection views of a map,
 * {@link TestMap} would override {@link #verify()} to make sure the map is changed after the
 * collection view is changed.
 *
 * <p>If you're extending this class directly, you will have to provide implementations for the
 * following:
 *
 * <UL>
 *   <LI>{@link #makeConfirmedCollection()}
 *   <LI>{@link #makeConfirmedFullCollection()}
 * </UL>
 *
 * Those methods should provide a confirmed collection implementation that's compatible with your
 * collection implementation.
 *
 * <p>If you're extending {@link TestList}, {@link TestSet}, or {@link TestBag}, you probably don't
 * have to worry about the above methods, because those three classes already override the methods
 * to provide standard JDK confirmed collections.
 *
 * <p><B>Other notes</B>
 *
 * <p>If your {@link Collection} fails one of these tests by design, you may still use this base set
 * of cases. Simply override the test case (method) your {@link Collection} fails. For instance, the
 * {@link #testIteratorFailFast()} method is provided since most collections have fail-fast
 * iterators; however, that's not strictly required by the collection contract, so you may want to
 * override that method to do nothing.
 *
 * <p>
 */
@SuppressWarnings({"unchecked", "rawtypes"})
abstract class TestCollection extends TestObject {

  //
  // NOTE:
  //
  // Collection doesn't define any semantics for equals, and recommends you
  // use reference-based default behavior of Object.equals.  (And a test for
  // that already exists in TestObject).  Tests for equality of lists, sets
  // and bags will have to be written in test subclasses.  Thus, there is no
  // tests on Collection.equals nor any for Collection.hashCode.
  //

  // These fields are used by reset() and verify(), and any test
  // method that tests a modification.

  /** A collection instance that will be used for testing. */
  protected Collection collection;

  /**
   * Confirmed collection. This is an instance of a collection that is confirmed to conform exactly
   * to the java.util.Collection contract. Modification operations are tested by performing a mod on
   * your collection, performing the exact same mod on an equivalent confirmed collection, and then
   * calling verify() to make sure your collection still matches the confirmed collection.
   */
  protected Collection confirmed;

  /**
   * Resets the {@link #collection} and {@link #confirmed} fields to empty collections. Invoke this
   * method before performing a modification test.
   */
  protected void resetEmpty() {
    this.collection = makeCollection();
    this.confirmed = makeConfirmedCollection();
  }

  /**
   * Resets the {@link #collection} and {@link #confirmed} fields to full collections. Invoke this
   * method before performing a modification test.
   */
  protected void resetFull() {
    this.collection = makeFullCollection();
    this.confirmed = makeConfirmedFullCollection();
  }

  /**
   * Specifies whether equal elements in the collection are, in fact, distinguishable with
   * information not readily available. That is, if a particular value is to be removed from the
   * collection, then there is one and only one value that can be removed, even if there are other
   * elements which are equal to it.
   *
   * <p>In most collection cases, elements are not distinguishable (equal is equal), thus this
   * method defaults to return false. In some cases, however, they are. For example, the collection
   * returned from the map's values() collection view are backed by the map, so while there may be
   * two values that are equal, their associated keys are not. Since the keys are distinguishable,
   * the values are.
   *
   * <p>This flag is used to skip some verifications for iterator.remove() where it is impossible to
   * perform an equivalent modification on the confirmed collection because it is not possible to
   * determine which value in the confirmed collection to actually remove. Tests that override the
   * default (i.e. where equal elements are distinguishable), should provide additional tests on
   * iterator.remove() to make sure the proper elements are removed when remove() is called on the
   * iterator.
   */
  protected boolean areEqualElementsDistinguishable() {
    return false;
  }

  /** Verifies that {@link #collection} and {@link #confirmed} have identical state. */
  protected void verify() {
    int confirmedSize = confirmed.size();
    assertEquals(
        "Collection size should match confirmed collection's", confirmedSize, collection.size());
    assertEquals(
        "Collection isEmpty() result should match confirmed " + " collection's",
        confirmed.isEmpty(),
        collection.isEmpty());

    // verify the collections are the same by attempting to match each
    // object in the collection and confirmed collection.  To account for
    // duplicates and differing orders, each confirmed element is copied
    // into an array and a flag is maintained for each element to determine
    // whether it has been matched once and only once.  If all elements in
    // the confirmed collection are matched once and only once and there
    // aren't any elements left to be matched in the collection,
    // verification is a success.

    // copy each collection value into an array
    Object[] confirmedValues = new Object[confirmedSize];

    Iterator iter;

    iter = confirmed.iterator();
    int pos = 0;
    while (iter.hasNext()) {
      confirmedValues[pos++] = iter.next();
    }

    // allocate an array of boolean flags for tracking values that have
    // been matched once and only once.
    boolean[] matched = new boolean[confirmedSize];

    // now iterate through the values of the collection and try to match
    // the value with one in the confirmed array.
    iter = collection.iterator();
    while (iter.hasNext()) {
      Object o = iter.next();
      boolean match = false;
      for (int i = 0; i < confirmedSize; i++) {
        if (matched[i]) {
          // skip values already matched
          continue;
        }
        if (o == confirmedValues[i] || (o != null && o.equals(confirmedValues[i]))) {
          // values matched
          matched[i] = true;
          match = true;
          break;
        }
      }
      // no match found!
      if (!match) {
        fail(
            "Collection should not contain a value that the "
                + "confirmed collection does not have.");
      }
    }

    // make sure there aren't any unmatched values
    for (int i = 0; i < confirmedSize; i++) {
      if (!matched[i]) {
        // the collection didn't match all the confirmed values
        fail("Collection should contain all values that are in the " + "confirmed collection");
      }
    }
  }

  /**
   * Returns a confirmed empty collection. For instance, a {@link java.util.ArrayList} for lists or
   * a {@link java.util.HashSet} for sets.
   *
   * @return a confirmed empty collection
   */
  protected abstract Collection makeConfirmedCollection();

  /**
   * Returns a confirmed full collection. For instance, a {@link java.util.ArrayList} for lists or a
   * {@link java.util.HashSet} for sets. The returned collection should contain the elements
   * returned by {@link #getFullElements()}.
   *
   * @return a confirmed full collection
   */
  protected abstract Collection makeConfirmedFullCollection();

  /**
   * Returns true if the collections produced by {@link #makeCollection()} and {@link
   * #makeFullCollection()} support the <Code>add</Code> and <Code>addAll</Code> operations.
   *
   * <p>Default implementation returns true. Override if your collection class does not support add
   * or addAll.
   */
  protected boolean isAddSupported() {
    return true;
  }

  /**
   * Returns true if the collections produced by {@link #makeCollection()} and {@link
   * #makeFullCollection()} support the <Code>remove</Code>, <Code>removeAll</Code>, <Code>retainAll
   * </Code>, <Code>clear</Code> and <Code>iterator().remove()</Code> methods. Default
   * implementation returns true. Override if your collection class does not support removal
   * operations.
   */
  protected boolean isRemoveSupported() {
    return true;
  }

  /**
   * Returns an array of objects that are contained in a collection produced by {@link
   * #makeFullCollection()}. Every element in the returned array <I>must</I> be an element in a full
   * collection.
   *
   * <p>The default implementation returns a heterogeneous array of objects with some duplicates and
   * with the null element. Override if you require specific testing elements. Note that if you
   * override {@link #makeFullCollection()}, you <I>must</I> override this method to reflect the
   * contents of a full collection.
   */
  protected Object[] getFullElements() {
    ArrayList list = new ArrayList();

    list.addAll(Arrays.asList(getFullNonNullElements()));
    list.add(4, null);
    return list.toArray();
  }

  /**
   * Returns an array of elements that are <I>not</I> contained in a full collection. Every element
   * in the returned array must not exist in a collection returned by {@link #makeFullCollection()}.
   * The default implementation returns a heterogeneous array of elements without null. Note that
   * some of the tests add these elements to an empty or full collection, so if your collection
   * restricts certain kinds of elements, you should override this method.
   */
  protected Object[] getOtherElements() {
    return getOtherNonNullElements();
  }

  /** Returns a new, empty {@link Collection} to be used for testing. */
  protected abstract Collection makeCollection();

  /**
   * Returns a full collection to be used for testing. The collection returned by this method should
   * contain every element returned by {@link #getFullElements()}. The default implementation, in
   * fact, simply invokes <Code>addAll</Code> on an empty collection with the results of {@link
   * #getFullElements()}. Override this default if your collection doesn't support addAll.
   */
  protected Collection makeFullCollection() {
    Collection c = makeCollection();
    c.addAll(Arrays.asList(getFullElements()));
    return c;
  }

  /** Returns an empty collection for Object tests. */
  @Override
  public Object makeObject() {
    return makeCollection();
  }

  /** Tests {@link Collection#add(Object)}. */
  public void testCollectionAdd() {
    if (!isAddSupported()) {
      return;
    }

    Object[] elements = getFullElements();
    for (int i = 0; i < elements.length; i++) {
      resetEmpty();
      boolean r = collection.add(elements[i]);
      confirmed.add(elements[i]);
      verify();
      assertTrue("Empty collection changed after add", r);
      assertTrue("Collection size is 1 after first add", collection.size() == 1);
    }

    resetEmpty();
    int size = 0;
    for (int i = 0; i < elements.length; i++) {
      boolean r = collection.add(elements[i]);
      confirmed.add(elements[i]);
      verify();
      if (r) {
        size++;
      }
      assertEquals("Collection size should grow after add", size, collection.size());
      assertTrue("Collection should contain added element", collection.contains(elements[i]));
    }
  }

  /** Tests {@link Collection#addAll(Collection)}. */
  public void testCollectionAddAll() {
    if (!isAddSupported()) {
      return;
    }

    resetEmpty();
    Object[] elements = getFullElements();
    boolean r = collection.addAll(Arrays.asList(elements));
    confirmed.addAll(Arrays.asList(elements));
    verify();
    assertTrue("Empty collection should change after addAll", r);
    for (int i = 0; i < elements.length; i++) {
      assertTrue("Collection should contain added element", collection.contains(elements[i]));
    }

    resetFull();
    int size = collection.size();
    elements = getOtherElements();
    r = collection.addAll(Arrays.asList(elements));
    confirmed.addAll(Arrays.asList(elements));
    verify();
    assertTrue("Full collection should change after addAll", r);
    for (int i = 0; i < elements.length; i++) {
      assertTrue("Full collection should contain added element", collection.contains(elements[i]));
    }
    assertEquals("Size should increase after addAll", size + elements.length, collection.size());

    resetFull();
    size = collection.size();
    r = collection.addAll(Arrays.asList(getFullElements()));
    confirmed.addAll(Arrays.asList(getFullElements()));
    verify();
    if (r) {
      assertTrue("Size should increase if addAll returns true", size < collection.size());
    } else {
      assertEquals("Size should not change if addAll returns false", size, collection.size());
    }
  }

  /**
   *  If {@link #isAddSupported()} returns false, tests that add operations
   *  raise <Code>UnsupportedOperationException.
   */
  public void testUnsupportedAdd() {
    if (isAddSupported()) {
      return;
    }

    resetEmpty();
    try {
      collection.add(new Object());
      fail("Empty collection should not support add.");
    } catch (UnsupportedOperationException e) {
      // expected
    }
    // make sure things didn't change even if the expected exception was
    // thrown.
    verify();

    try {
      collection.addAll(Arrays.asList(getFullElements()));
      fail("Empty collection should not support addAll.");
    } catch (UnsupportedOperationException e) {
      // expected
    }
    // make sure things didn't change even if the expected exception was
    // thrown.
    verify();

    resetFull();
    try {
      collection.add(new Object());
      fail("Full collection should not support add.");
    } catch (UnsupportedOperationException e) {
      // expected
    }
    // make sure things didn't change even if the expected exception was
    // thrown.
    verify();

    try {
      collection.addAll(Arrays.asList(getOtherElements()));
      fail("Full collection should not support addAll.");
    } catch (UnsupportedOperationException e) {
      // expected
    }
    // make sure things didn't change even if the expected exception was
    // thrown.
    verify();
  }

  /** Test {@link Collection#clear()}. */
  public void testCollectionClear() {
    if (!isRemoveSupported()) {
      return;
    }

    resetEmpty();
    collection.clear(); // just to make sure it doesn't raise anything
    verify();

    resetFull();
    collection.clear();
    confirmed.clear();
    verify();
  }

  /** Tests {@link Collection#contains(Object)}. */
  public void testCollectionContains() {
    Object[] elements;

    resetEmpty();
    elements = getFullElements();
    for (int i = 0; i < elements.length; i++) {
      assertFalse("Empty collection shouldn't contain element", collection.contains(elements[i]));
    }
    // make sure calls to "contains" don't change anything
    verify();

    elements = getOtherElements();
    for (int i = 0; i < elements.length; i++) {
      assertTrue("Empty collection shouldn't contain element", !collection.contains(elements[i]));
    }
    // make sure calls to "contains" don't change anything
    verify();

    resetFull();
    elements = getFullElements();
    for (int i = 0; i < elements.length; i++) {
      assertTrue("Full collection should contain element.", collection.contains(elements[i]));
    }
    // make sure calls to "contains" don't change anything
    verify();

    resetFull();
    elements = getOtherElements();
    for (int i = 0; i < elements.length; i++) {
      assertTrue("Full collection shouldn't contain element", !collection.contains(elements[i]));
    }
  }

  /** Tests {@link Collection#containsAll(Collection)}. */
  @SuppressWarnings("ModifyingCollectionWithItself")
  public void testCollectionContainsAll() {
    resetEmpty();
    Collection col = new HashSet();
    assertTrue(
        "Every Collection should contain all elements of an " + "empty Collection.",
        collection.containsAll(col));
    col.addAll(Arrays.asList(getOtherElements()));
    assertTrue(
        "Empty Collection shouldn't contain all elements of " + "a non-empty Collection.",
        !collection.containsAll(col));
    // make sure calls to "containsAll" don't change anything
    verify();

    resetFull();
    assertTrue("Full collection shouldn't contain other elements", !collection.containsAll(col));

    col.clear();
    col.addAll(Arrays.asList(getFullElements()));
    assertTrue("Full collection should containAll full elements", collection.containsAll(col));
    // make sure calls to "containsAll" don't change anything
    verify();

    assertTrue("Full collection should containAll itself", collection.containsAll(collection));

    // make sure calls to "containsAll" don't change anything
    verify();

    col = new ArrayList();
    col.addAll(Arrays.asList(getFullElements()));
    col.addAll(Arrays.asList(getFullElements()));
    assertTrue(
        "Full collection should containAll duplicate full " + "elements",
        collection.containsAll(col));

    // make sure calls to "containsAll" don't change anything
    verify();
  }

  /** Tests {@link Collection#isEmpty()}. */
  public void testCollectionIsEmpty() {
    resetEmpty();
    assertEquals("New Collection should be empty.", true, collection.isEmpty());
    // make sure calls to "isEmpty() don't change anything
    verify();

    resetFull();
    assertEquals("Full collection shouldn't be empty", false, collection.isEmpty());
    // make sure calls to "isEmpty() don't change anything
    verify();
  }

  /** Tests the read-only functionality of {@link Collection#iterator()}. */
  public void testCollectionIterator() {
    resetEmpty();
    Iterator it1 = collection.iterator();
    assertEquals("Iterator for empty Collection shouldn't have next.", false, it1.hasNext());
    try {
      it1.next();
      fail(
          "Iterator at end of Collection should throw "
              + "NoSuchElementException when next is called.");
    } catch (NoSuchElementException e) {
      // expected
    }
    // make sure nothing has changed after non-modification
    verify();

    resetFull();
    it1 = collection.iterator();
    for (int i = 0; i < collection.size(); i++) {
      assertTrue("Iterator for full collection should haveNext", it1.hasNext());
      it1.next();
    }
    assertTrue("Iterator should be finished", !it1.hasNext());

    ArrayList list = new ArrayList();
    it1 = collection.iterator();
    for (int i = 0; i < collection.size(); i++) {
      Object next = it1.next();
      assertTrue(
          "Collection should contain element returned by " + "its iterator",
          collection.contains(next));
      list.add(next);
    }
    try {
      it1.next();
      fail("iterator.next() should raise NoSuchElementException " + "after it finishes");
    } catch (NoSuchElementException e) {
      // expected
    }
    // make sure nothing has changed after non-modification
    verify();
  }

  /** Tests removals from {@link Collection#iterator()}. */
  @SuppressWarnings("ReturnValueIgnored")
  public void testCollectionIteratorRemove() {
    if (!isRemoveSupported()) {
      return;
    }

    resetEmpty();
    try {
      collection.iterator().remove();
      fail("New iterator.remove should raise IllegalState");
    } catch (IllegalStateException e) {
      // expected
    }
    verify();

    try {
      Iterator iter = collection.iterator();
      iter.hasNext();
      iter.remove();
      fail("New iterator.remove should raise IllegalState " + "even after hasNext");
    } catch (IllegalStateException e) {
      // expected
    }
    verify();

    resetFull();
    int size = collection.size();
    Iterator iter = collection.iterator();
    while (iter.hasNext()) {
      Object o = iter.next();
      iter.remove();

      // if the elements aren't distinguishable, we can just remove a
      // matching element from the confirmed collection and verify
      // contents are still the same.  Otherwise, we don't have the
      // ability to distinguish the elements and determine which to
      // remove from the confirmed collection (in which case, we don't
      // verify because we don't know how).
      //
      // see areEqualElementsDistinguishable()
      if (!areEqualElementsDistinguishable()) {
        confirmed.remove(o);
        verify();
      }

      size--;
      assertEquals(
          "Collection should shrink by one after " + "iterator.remove", size, collection.size());
    }
    assertTrue("Collection should be empty after iterator purge", collection.isEmpty());

    resetFull();
    iter = collection.iterator();
    iter.next();
    iter.remove();
    try {
      iter.remove();
      fail("Second iter.remove should raise IllegalState");
    } catch (IllegalStateException e) {
      // expected
    }
  }

  /** Tests {@link Collection#remove(Object)}. */
  public void testCollectionRemove() {
    if (!isRemoveSupported()) {
      return;
    }

    resetEmpty();
    Object[] elements = getFullElements();
    for (int i = 0; i < elements.length; i++) {
      assertTrue("Shouldn't remove nonexistent element", !collection.remove(elements[i]));
      verify();
    }

    Object[] other = getOtherElements();

    resetFull();
    for (int i = 0; i < other.length; i++) {
      assertTrue("Shouldn't remove nonexistent other element", !collection.remove(other[i]));
      verify();
    }

    int size = collection.size();
    for (int i = 0; i < elements.length; i++) {
      resetFull();
      assertTrue("Collection should remove extant element", collection.remove(elements[i]));

      // if the elements aren't distinguishable, we can just remove a
      // matching element from the confirmed collection and verify
      // contents are still the same.  Otherwise, we don't have the
      // ability to distinguish the elements and determine which to
      // remove from the confirmed collection (in which case, we don't
      // verify because we don't know how).
      //
      // see areEqualElementsDistinguishable()
      if (!areEqualElementsDistinguishable()) {
        confirmed.remove(elements[i]);
        verify();
      }

      assertEquals("Collection should shrink after remove", size - 1, collection.size());
    }
  }

  /** Tests {@link Collection#removeAll(Collection)}. */
  public void testCollectionRemoveAll() {
    if (!isRemoveSupported()) {
      return;
    }

    resetEmpty();
    assertTrue(
        "Empty collection removeAll should return false for " + "empty input",
        !collection.removeAll(Collections.EMPTY_SET));
    verify();

    assertTrue(
        "Empty collection removeAll should return false for " + "nonempty input",
        !collection.removeAll(new ArrayList(collection)));
    verify();

    resetFull();
    assertTrue(
        "Full collection removeAll should return false for " + "empty input",
        !collection.removeAll(Collections.EMPTY_SET));
    verify();

    assertTrue(
        "Full collection removeAll should return false for " + "other elements",
        !collection.removeAll(Arrays.asList(getOtherElements())));
    verify();

    assertTrue(
        "Full collection removeAll should return true for " + "full elements",
        collection.removeAll(new HashSet(collection)));
    confirmed.removeAll(new HashSet(confirmed));
    verify();

    resetFull();

    int size = collection.size();
    Object[] s = getFullElements();
    List l = new ArrayList();
    l.add(s[2]);
    l.add(s[3]);
    l.add(s[3]);

    assertTrue("Full collection removeAll should work", collection.removeAll(l));
    confirmed.removeAll(l);
    verify();

    assertTrue("Collection should shrink after removeAll", collection.size() < size);
    Iterator iter = l.iterator();
    while (iter.hasNext()) {
      assertTrue("Collection shouldn't contain removed element", !collection.contains(iter.next()));
    }
  }

  /** Tests {@link Collection#retainAll(Collection)}. */
  public void testCollectionRetainAll() {
    if (!isRemoveSupported()) {
      return;
    }

    resetEmpty();
    List elements = Arrays.asList(getFullElements());
    List other = Arrays.asList(getOtherElements());
    Set empty = new HashSet();

    assertTrue("Empty retainAll() should return false", !collection.retainAll(empty));
    verify();

    assertTrue("Empty retainAll() should return false", !collection.retainAll(elements));
    verify();

    resetFull();
    assertTrue("Collection should change from retainAll empty", collection.retainAll(empty));
    confirmed.retainAll(empty);
    verify();

    resetFull();
    assertTrue("Collection changed from retainAll other", collection.retainAll(other));
    confirmed.retainAll(other);
    verify();

    resetFull();
    int size = collection.size();
    assertTrue(
        "Collection shouldn't change from retainAll elements", !collection.retainAll(elements));
    verify();
    assertEquals("Collection size shouldn't change", size, collection.size());

    resetFull();
    HashSet set = new HashSet(elements);
    size = collection.size();
    assertTrue(
        "Collection shouldn't change from retainAll without " + "duplicate elements",
        !collection.retainAll(set));
    verify();
    assertEquals(
        "Collection size didn't change from nonduplicate " + "retainAll", size, collection.size());
  }

  /** Tests {@link Collection#size()}. */
  public void testCollectionSize() {
    resetEmpty();
    assertEquals("Size of new Collection is 0.", 0, collection.size());

    resetFull();
    assertTrue("Size of full collection should be greater than zero", collection.size() > 0);
  }

  /** Tests <Code>toString</Code> on a collection. */
  public void testCollectionToString() {
    resetEmpty();
    assertTrue("toString shouldn't return null", collection.toString() != null);

    resetFull();
    assertTrue("toString shouldn't return null", collection.toString() != null);
  }

  /**
   * If isRemoveSupported() returns false, tests to see that remove operations raise an
   * UnsupportedOperationException.
   */
  public void testUnsupportedRemove() {
    if (isRemoveSupported()) {
      return;
    }

    resetEmpty();
    try {
      collection.clear();
      fail("clear should raise UnsupportedOperationException");
    } catch (UnsupportedOperationException e) {
      // expected
    }
    verify();

    try {
      collection.remove(null);
      fail("remove should raise UnsupportedOperationException");
    } catch (UnsupportedOperationException e) {
      // expected
    }
    verify();

    try {
      collection.removeAll(null);
      fail("removeAll should raise UnsupportedOperationException");
    } catch (UnsupportedOperationException e) {
      // expected
    }
    verify();

    try {
      collection.retainAll(null);
      fail("removeAll should raise UnsupportedOperationException");
    } catch (UnsupportedOperationException e) {
      // expected
    }
    verify();

    resetFull();
    try {
      Iterator iterator = collection.iterator();
      iterator.next();
      iterator.remove();
      fail("iterator.remove should raise UnsupportedOperationException");
    } catch (UnsupportedOperationException e) {
      // expected
    }
    verify();
  }

  /**
   * Returns a list of elements suitable for return by {@link #getFullElements()}. The array
   * returned by this method does not include null, but does include a variety of objects of
   * different types. Override getFullElements to return the results of this method if your
   * collection does not support the null element.
   */
  public static Object[] getFullNonNullElements() {
    return new Object[] {
      "",
      "One",
      new Integer(2),
      "Three",
      new Integer(4),
      "One",
      new Double(5),
      new Float(6),
      "Seven",
      "Eight",
      "Nine",
      new Integer(10),
      new Short((short) 11),
      new Long(12),
      "Thirteen",
      "14",
      "15",
      new Byte((byte) 16)
    };
  }

  /**
   * Returns the default list of objects returned by {@link #getOtherElements()}. Includes many
   * objects of different types.
   */
  public static Object[] getOtherNonNullElements() {
    return new Object[] {
      new Integer(0),
      new Float(0),
      new Double(0),
      "Zero",
      new Short((short) 0),
      new Byte((byte) 0),
      new Long(0),
      new Character('\u0000'),
      "0"
    };
  }

  /**
   * Returns a list of string elements suitable for return by {@link #getFullElements()}. Override
   * getFullElements to return the results of this method if your collection does not support
   * heterogeneous elements or the null element.
   */
  public static Object[] getFullNonNullStringElements() {
    return new Object[] {
      "If",
      "the",
      "dull",
      "substance",
      "of",
      "my",
      "flesh",
      "were",
      "thought",
      "Injurious",
      "distance",
      "could",
      "not",
      "stop",
      "my",
      "way",
    };
  }

  /**
   * Returns a list of string elements suitable for return by {@link #getOtherElements()}. Override
   * getOtherElements to return the results of this method if your collection does not support
   * heterogeneous elements or the null element.
   */
  public static Object[] getOtherNonNullStringElements() {
    return new Object[] {
      "For", "then", "despite", /* of */ "space", "I", "would", "be", "brought",
      "From", "limits", "far", "remote", "where", "thou", "dost", "stay"
    };
  }
}
