[COLLECTIONS-471] Added BoundedIterator.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1546220 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Thomas Neidhart 2013-11-27 22:54:05 +00:00
parent 59899e1c82
commit ba8758326c
5 changed files with 585 additions and 2 deletions

View File

@ -384,6 +384,9 @@
<contributor>
<name>Leo Sutic</name>
</contributor>
<contributor>
<name>Radford Tam</name>
</contributor>
<contributor>
<name>Chris Tilden</name>
</contributor>

View File

@ -22,8 +22,9 @@
<body>
<release version="4.0.1" date="TBD" description="">
<!-- <action issue="COLLECTIONS-XXX" dev="XXX" type="XXX">
</action> -->
<action issue="COLLECTIONS-471" dev="tn" type="add" due-to="Radford Tam">
Added new decorator "BoundedIterator".
</action>
</release>
<release version="4.0" date="2013-11-27" description="
This is a major release: It combines bug fixes, new features and

View File

@ -31,6 +31,7 @@ import java.util.Map;
import org.apache.commons.collections4.iterators.ArrayIterator;
import org.apache.commons.collections4.iterators.ArrayListIterator;
import org.apache.commons.collections4.iterators.BoundedIterator;
import org.apache.commons.collections4.iterators.CollatingIterator;
import org.apache.commons.collections4.iterators.EmptyIterator;
import org.apache.commons.collections4.iterators.EmptyListIterator;
@ -432,6 +433,44 @@ public class IteratorUtils {
return new ArrayListIterator<E>(array, start, end);
}
// Bounded
//-----------------------------------------------------------------------
/**
* Decorates the specified iterator to return at most the given number
* of elements.
*
* @param <E> the element type
* @param iterator the iterator to decorate
* @param max the maximum number of elements returned by this iterator
* @return a new bounded iterator
* @throws IllegalArgumentException if the iterator is null or either offset or max is negative
* @since 4.0.1
*/
public static <E> BoundedIterator<E> boundedIterator(final Iterator<? extends E> iterator, long max) {
return BoundedIterator.boundedIterator(iterator, max);
}
/**
* Decorates the specified iterator to return at most the given number
* of elements, skipping all elements until the iterator reaches the
* position at {@code offset}.
* <p>
* The iterator is immediately advanced until it reaches the position at
* {@code offset}, incurring O(n) time.
*
* @param <E> the element type
* @param iterator the iterator to decorate
* @param offset the index of the first element of the decorated iterator to return
* @param max the maximum number of elements returned by this iterator
* @return a new bounded iterator
* @throws IllegalArgumentException if the iterator is null or either offset or max is negative
* @since 4.0.1
*/
public static <E> BoundedIterator<E> boundedIterator(final Iterator<? extends E> iterator,
long offset, long max) {
return BoundedIterator.boundedIterator(iterator, offset, max);
}
// Unmodifiable
//-----------------------------------------------------------------------
/**

View File

@ -0,0 +1,170 @@
/*
* 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.apache.commons.collections4.iterators;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Decorates another iterator to return elements in a specific range.
* <p>
* The decorated iterator is bounded in the range [offset, offset+max).
* The {@code offset} corresponds to the position of the first element to
* be returned from the decorated iterator, and {@code max} is the maximum
* number of elements to be returned at most.
* <p>
* In case an offset parameter other than 0 is provided, the decorated
* iterator is immediately advanced to this position, skipping all elements
* before that position.
*
* @since 4.0.1
* @version $Id$
*/
public class BoundedIterator<E> implements Iterator<E> {
/** The iterator being decorated. */
private final Iterator<? extends E> iterator;
/** The offset to bound the first element return */
private final long offset;
/** The max number of elements to return */
private final long max;
/** The position of the current element */
private long pos;
//-----------------------------------------------------------------------
/**
* Decorates the specified iterator to return at most the given number
* of elements.
*
* @param <E> the element type
* @param iterator the iterator to decorate
* @param max the maximum number of elements returned by this iterator
* @return a new bounded iterator
* @throws IllegalArgumentException if the iterator is null or max is negative
*/
public static <E> BoundedIterator<E> boundedIterator(final Iterator<? extends E> iterator,
final long max) {
return boundedIterator(iterator, 0, max);
}
/**
* Decorates the specified iterator to return at most the given number
* of elements, skipping all elements until the iterator reaches the position
* at {@code offset}.
* <p>
* The iterator is immediately advanced until it reaches the position at {@code offset},
* incurring O(n) time.
*
* @param <E> the element type
* @param iterator the iterator to decorate
* @param offset the index of the first element of the decorated iterator to return
* @param max the maximum number of elements returned by this iterator
* @return a new bounded iterator
* @throws IllegalArgumentException if the iterator is null or either offset or max is negative
*/
public static <E> BoundedIterator<E> boundedIterator(final Iterator<? extends E> iterator,
final long offset, final long max) {
return new BoundedIterator<E>(iterator, 0, max);
}
//-----------------------------------------------------------------------
/**
* Decorates the specified iterator to return at most the given number of elements,
* skipping all elements until the iterator reaches the position at {@code offset}.
* <p>
* The iterator is immediately advanced until it reaches the position at {@code offset},
* incurring O(n) time.
*
* @param iterator the iterator to be decorated
* @param offset the index of the first element of the decorated iterator to return
* @param max the maximum number of elements of the decorated iterator to return
* @throws IllegalArgumentException if iterator is null, or either offset or max is negative
*/
public BoundedIterator(final Iterator<? extends E> iterator, final long offset, final long max) {
if (iterator == null) {
throw new IllegalArgumentException("Iterator must not be null");
}
if (offset < 0) {
throw new IllegalArgumentException("Offset parameter must not be negative.");
}
if (max < 0) {
throw new IllegalArgumentException("Max parameter must not be negative.");
}
this.iterator = iterator;
this.offset = offset;
this.max = max;
pos = 0;
init();
}
/**
* Advances the underlying iterator to the beginning of the bounded range.
*/
private void init() {
while (pos < offset && iterator.hasNext()) {
iterator.next();
pos++;
}
}
//-----------------------------------------------------------------------
public boolean hasNext() {
if (!checkBounds()) {
return false;
}
return iterator.hasNext();
}
/**
* Checks whether the iterator is still within its bounded range.
* @return {@code true} if the iterator is within its bounds, {@code false} otherwise
*/
private boolean checkBounds() {
if (pos - offset + 1 > max) {
return false;
}
return true;
}
public E next() {
if (!checkBounds()) {
throw new NoSuchElementException();
}
final E next = iterator.next();
pos++;
return next;
}
/**
* {@inheritDoc}
* <p>
* In case an offset other than 0 was specified, the underlying iterator will be advanced
* to this position upon creation. A call to {@link #remove()} will still result in an
* {@link IllegalStateException} if no explicit call to {@link #next()} has been made prior
* to calling {@link #remove()}.
*/
public void remove() {
if (pos <= offset) {
throw new IllegalStateException("remove() can not be called before calling next()");
}
iterator.remove();
}
}

View File

@ -0,0 +1,370 @@
/*
* 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.apache.commons.collections4.iterators;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.junit.Test;
/**
* A unit test to test the basic functions of {@link BoundedIterator}.
*
* @version $Id$
*/
public class BoundedIteratorTest<E> extends AbstractIteratorTest<E> {
/** Test array of size 7 */
private String[] testArray = {
"a", "b", "c", "d", "e", "f", "g"
};
private List<E> testList;
public BoundedIteratorTest(final String testName) {
super(testName);
}
@SuppressWarnings("unchecked")
@Override
public void setUp()
throws Exception {
super.setUp();
testList = Arrays.asList((E[]) testArray);
}
@Override
public Iterator<E> makeEmptyIterator() {
return BoundedIterator.boundedIterator(Collections.<E>emptyList().iterator(), 10);
}
@Override
public Iterator<E> makeObject() {
return BoundedIterator.boundedIterator(new ArrayList<E>(testList).iterator(), 1, testList.size() - 1);
}
// ---------------- Tests ---------------------
/**
* Test a decorated iterator bounded such that the first element returned is
* at an index greater its first element, and the last element returned is
* at an index less than its last element.
*/
@Test
public void testBounded() {
Iterator<E> iter = new BoundedIterator<E>(testList.iterator(), 2, 4);
assertTrue(iter.hasNext());
assertEquals("c", iter.next());
assertTrue(iter.hasNext());
assertEquals("d", iter.next());
assertTrue(iter.hasNext());
assertEquals("e", iter.next());
assertTrue(iter.hasNext());
assertEquals("f", iter.next());
assertFalse(iter.hasNext());
try {
iter.next();
fail("Expected NoSuchElementException.");
} catch (NoSuchElementException nsee) { /* Success case */
}
}
/**
* Test a decorated iterator bounded such that the <code>offset</code> is
* zero and the <code>max</code> is its size, in that the BoundedIterator
* should return all the same elements as its decorated iterator.
*/
@Test
public void testSameAsDecorated() {
Iterator<E> iter = new BoundedIterator<E>(testList.iterator(), 0,
testList.size());
assertTrue(iter.hasNext());
assertEquals("a", iter.next());
assertTrue(iter.hasNext());
assertEquals("b", iter.next());
assertTrue(iter.hasNext());
assertEquals("c", iter.next());
assertTrue(iter.hasNext());
assertEquals("d", iter.next());
assertTrue(iter.hasNext());
assertEquals("e", iter.next());
assertTrue(iter.hasNext());
assertEquals("f", iter.next());
assertTrue(iter.hasNext());
assertEquals("g", iter.next());
assertFalse(iter.hasNext());
try {
iter.next();
fail("Expected NoSuchElementException.");
} catch (NoSuchElementException nsee) { /* Success case */
}
}
/**
* Test a decorated iterator bounded to a <code>max</code> of 0. The
* BoundedIterator should behave as if there are no more elements to return,
* since it is technically an empty iterator.
*/
@Test
public void testEmptyBounded() {
Iterator<E> iter = new BoundedIterator<E>(testList.iterator(), 3, 0);
assertFalse(iter.hasNext());
try {
iter.next();
fail("Expected NoSuchElementException.");
} catch (NoSuchElementException nsee) { /* Success case */
}
}
/**
* Test the case if a negative <code>offset</code> is passed to the
* constructor. {@link IllegalArgumentException} is expected.
*/
@Test
public void testNegativeOffset() {
try {
new BoundedIterator<E>(testList.iterator(), -1, 4);
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException iae) { /* Success case */
}
}
/**
* Test the case if a negative <code>max</code> is passed to the
* constructor. {@link IllegalArgumentException} is expected.
*/
@Test
public void testNegativeMax() {
try {
new BoundedIterator<E>(testList.iterator(), 3, -1);
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException iae) { /* Success case */
}
}
/**
* Test the case if the <code>offset</code> passed to the constructor is
* greater than the decorated iterator's size. The BoundedIterator should
* behave as if there are no more elements to return.
*/
@Test
public void testOffsetGreaterThanSize() {
Iterator<E> iter = new BoundedIterator<E>(testList.iterator(), 10, 4);
assertFalse(iter.hasNext());
try {
iter.next();
fail("Expected NoSuchElementException.");
} catch (NoSuchElementException nsee) { /* Success case */
}
}
/**
* Test the case if the <code>max</code> passed to the constructor is
* greater than the size of the decorated iterator. The last element
* returned should be the same as the last element of the decorated
* iterator.
*/
@Test
public void testMaxGreaterThanSize() {
Iterator<E> iter = new BoundedIterator<E>(testList.iterator(), 1, 10);
assertTrue(iter.hasNext());
assertEquals("b", iter.next());
assertTrue(iter.hasNext());
assertEquals("c", iter.next());
assertTrue(iter.hasNext());
assertEquals("d", iter.next());
assertTrue(iter.hasNext());
assertEquals("e", iter.next());
assertTrue(iter.hasNext());
assertEquals("f", iter.next());
assertTrue(iter.hasNext());
assertEquals("g", iter.next());
assertFalse(iter.hasNext());
try {
iter.next();
fail("Expected NoSuchElementException.");
} catch (NoSuchElementException nsee) { /* Success case */
}
}
/**
* Test the <code>remove()</code> method being called without
* <code>next()</code> being called first.
*/
@Test
public void testRemoveWithoutCallingNext() {
List<E> testListCopy = new ArrayList<E>(testList);
Iterator<E> iter = new BoundedIterator<E>(testListCopy.iterator(), 1, 5);
try {
iter.remove();
fail("Expected IllegalStateException.");
} catch (IllegalStateException ise) { /* Success case */
}
}
/**
* Test the <code>remove()</code> method being called twice without calling
* <code>next()</code> in between.
*/
@Test
public void testRemoveCalledTwice() {
List<E> testListCopy = new ArrayList<E>(testList);
Iterator<E> iter = new BoundedIterator<E>(testListCopy.iterator(), 1, 5);
assertTrue(iter.hasNext());
assertEquals("b", iter.next());
iter.remove();
try {
iter.remove();
fail("Expected IllegalStateException.");
} catch (IllegalStateException ise) { /* Success case */
}
}
/**
* Test removing the first element. Verify that the element is removed from
* the underlying collection.
*/
@Test
public void testRemoveFirst() {
List<E> testListCopy = new ArrayList<E>(testList);
Iterator<E> iter = new BoundedIterator<E>(testListCopy.iterator(), 1, 5);
assertTrue(iter.hasNext());
assertEquals("b", iter.next());
iter.remove();
assertFalse(testListCopy.contains("b"));
assertTrue(iter.hasNext());
assertEquals("c", iter.next());
assertTrue(iter.hasNext());
assertEquals("d", iter.next());
assertTrue(iter.hasNext());
assertEquals("e", iter.next());
assertTrue(iter.hasNext());
assertEquals("f", iter.next());
assertFalse(iter.hasNext());
try {
iter.next();
fail("Expected NoSuchElementException.");
} catch (NoSuchElementException nsee) { /* Success case */
}
}
/**
* Test removing an element in the middle of the iterator. Verify that the
* element is removed from the underlying collection.
*/
@Test
public void testRemoveMiddle() {
List<E> testListCopy = new ArrayList<E>(testList);
Iterator<E> iter = new BoundedIterator<E>(testListCopy.iterator(), 1, 5);
assertTrue(iter.hasNext());
assertEquals("b", iter.next());
assertTrue(iter.hasNext());
assertEquals("c", iter.next());
assertTrue(iter.hasNext());
assertEquals("d", iter.next());
iter.remove();
assertFalse(testListCopy.contains("d"));
assertTrue(iter.hasNext());
assertEquals("e", iter.next());
assertTrue(iter.hasNext());
assertEquals("f", iter.next());
assertFalse(iter.hasNext());
try {
iter.next();
fail("Expected NoSuchElementException.");
} catch (NoSuchElementException nsee) { /* Success case */
}
}
/**
* Test removing the last element. Verify that the element is removed from
* the underlying collection.
*/
@Test
public void testRemoveLast() {
List<E> testListCopy = new ArrayList<E>(testList);
Iterator<E> iter = new BoundedIterator<E>(testListCopy.iterator(), 1, 5);
assertTrue(iter.hasNext());
assertEquals("b", iter.next());
assertTrue(iter.hasNext());
assertEquals("c", iter.next());
assertTrue(iter.hasNext());
assertEquals("d", iter.next());
assertTrue(iter.hasNext());
assertEquals("e", iter.next());
assertTrue(iter.hasNext());
assertEquals("f", iter.next());
assertFalse(iter.hasNext());
try {
iter.next();
fail("Expected NoSuchElementException.");
} catch (NoSuchElementException nsee) { /* Success case */
}
iter.remove();
assertFalse(testListCopy.contains("f"));
assertFalse(iter.hasNext());
try {
iter.next();
fail("Expected NoSuchElementException.");
} catch (NoSuchElementException nsee) { /* Success case */
}
}
/**
* Test the case if the decorated iterator does not support the
* <code>remove()</code> method and throws an {@link UnsupportedOperationException}.
*/
@Test
public void testRemoveUnsupported() {
Iterator<E> mockIterator = new AbstractIteratorDecorator<E>(testList.iterator()) {
public void remove() {
throw new UnsupportedOperationException();
}
};
Iterator<E> iter = new BoundedIterator<E>(mockIterator, 1, 5);
assertTrue(iter.hasNext());
assertEquals("b", iter.next());
try {
iter.remove();
fail("Expected UnsupportedOperationException.");
} catch (UnsupportedOperationException usoe) { /* Success case */
}
}
}