/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.geotoolkit.test;
import java.util.Random;
import java.util.List;
import java.util.ListIterator;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.sis.test.TestCase;
import static org.opengis.test.Assert.*;
/**
* Base class for testing {@link ListIterator} implementations.
*
* @param <E> The type of elements in the iterator to test.
*
* @author Martin Desruisseaux (Geomatys)
* @since 3.22
* @version 3.22
* @module
*/
public abstract strictfp class ListIteratorTestCase<E> extends TestCase {
/**
* The iterator to test, which must be supplied by the sub-class.
*/
protected ListIterator<E> iterator;
/**
* A reference collection containing all the data to be traversed by the {@linkplain #iterator}.
* It should be an instance from the standard standard Java collection framework - not an instance
* of a class to be tested - because this collection will be used as a reference implementation.
*/
protected Collection<E> data;
/**
* A random number generator. This generator shall be initialized (if needed) by the subclass
* as below:
*
* {@preformat java
* random = TestUtilities.createRandomNumberGenerator("myTestMethod");
* }
*/
protected Random random;
/**
* {@code true} for testing forward iteration using {@link ListIterator#next()}.
* At least one of {@code testForward} and {@link #testBackward} must be {@code true}.
*/
protected boolean testForward;
/**
* {@code true} for testing backward iteration using {@link ListIterator#next()}.
* At least one of {@link #testForward} and {@code testBackward} must be {@code true}.
*/
protected boolean testBackward;
/**
* {@code true} for testing {@link ListIterator#remove()}.
*/
protected boolean testRemove;
/**
* Creates a new test case. Subclasses shall initialize the protected fields either in their
* constructor, in method annotated by {@code BeforeTest} or at the beginning of the test method.
*/
protected ListIteratorTestCase() {
}
/**
* Returns the data as a {@link List}, by casting the {@linkplain #collection} if possible
* or copying it otherwise.
*/
private List<E> asList() {
if (data instanceof List<?>) {
return (List<E>) data;
} else {
return new ArrayList<>(data);
}
}
/**
* Runs the tests on the current {@linkplain #iterator}. This method goes forward or
* backward randomly, and compares the following methods against the expected values:
*
* <ul>
* <li>{@link ListIterator#nextIndex()}</li>
* <li>{@link ListIterator#previousIndex()}</li>
* <li>{@link ListIterator#next()}</li>
* <li>{@link ListIterator#previous()}</li>
* </ul>
*
* Additional optional tests:
*
* <ul>
* <li>{@link ListIterator#remove()} if {@link #testRemove} is {@code true}.</li>
* </ul>
*
* @param index Expected initial position of the iterator.
* @param numIterations Maximal number of iterations to perform.
*/
protected void runTest(int index, final int numIterations) {
final boolean testForward = this.testForward;
final boolean testBackward = this.testBackward;
final boolean testRemove = this.testRemove;
final ListIterator<E> iterator = this.iterator; // Protect from changes.
assertTrue("At least one of 'testForward' and 'testBackward' shall be true.", testForward | testBackward);
final List<E> asList = asList();
int count = asList.size();
boolean forward = testForward;
for (int iter=0; iter<numIterations; iter++) {
/*
* Get the direction of traversal. We will perform on average 4 steps
* forward or backward before to reverse the direction of traversal.
*/
if (index == 0) {
assertFalse("ListIterator[0].hasPrevious()", iterator.hasPrevious());
if (!testForward) break;
forward = true;
} else if (index == count) {
assertFalse("ListIterator[end].hasNext()", iterator.hasNext());
if (!testBackward) break;
forward = false;
} else if ((testForward & testBackward) && random.nextInt(5) == 0) {
forward = !forward;
}
/*
* Move the iterator to the next or previous position and check.
*/
final String message;
final int indexOfExpected;
final E actual;
if (forward) {
message = "On iter=" + iter + ", ListIterator[" + index + "].next";
assertEquals(message, index, iterator.nextIndex());
assertTrue(message, iterator.hasNext());
actual = iterator.next();
assertEquals(message, index, iterator.previousIndex());
indexOfExpected = index++;
} else {
indexOfExpected = --index;
message = "On iter=" + iter + ", ListIterator[" + index + "].previous";
assertEquals(message, index, iterator.previousIndex());
assertTrue(message, iterator.hasPrevious());
actual = iterator.previous();
assertEquals(message, index, iterator.nextIndex());
}
assertEquals(message, asList.get(indexOfExpected), actual);
/*
* Optionally tests removal operations, if enabled.
* We will remove only 1/3 of data on average.
*/
if (testRemove && random.nextInt(3) == 0) {
iterator.remove();
if (forward) {
index--;
}
assertEquals(message, index, iterator.nextIndex());
assertEquals(message, index-1, iterator.previousIndex());
if (data != asList) {
asList.clear();
asList.addAll(data);
}
assertEquals(--count, asList.size());
if (count == 0) {
break;
}
}
}
}
}