8303882: Refactor some iterators in jdk.compiler
Co-authored-by: Jan Lahoda <jlahoda@openjdk.org> Reviewed-by: mcimadamore
This commit is contained in:
parent
45809fd0c0
commit
f81e1def8f
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,6 +25,7 @@
|
||||
|
||||
package com.sun.tools.javac.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Function;
|
||||
@ -39,6 +40,15 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
public class Iterators {
|
||||
|
||||
// cache the result of java.util.Collections.emptyIterator(), which
|
||||
// explicitly does not guarantee to return the same instance
|
||||
private static final Iterator<?> EMPTY = Collections.emptyIterator();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Iterator<T> emptyIterator() {
|
||||
return (Iterator<T>) EMPTY;
|
||||
}
|
||||
|
||||
public static <I, O> Iterator<O> createCompoundIterator(Iterable<I> inputs, Function<I, Iterator<O>> converter) {
|
||||
return new CompoundIterator<>(inputs, converter);
|
||||
}
|
||||
@ -47,55 +57,42 @@ public class Iterators {
|
||||
|
||||
private final Iterator<I> inputs;
|
||||
private final Function<I, Iterator<O>> converter;
|
||||
@SuppressWarnings("unchecked")
|
||||
private Iterator<O> currentIterator = EMPTY;
|
||||
private Iterator<O> currentIterator = emptyIterator();
|
||||
|
||||
public CompoundIterator(Iterable<I> inputs, Function<I, Iterator<O>> converter) {
|
||||
this.inputs = inputs.iterator();
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (currentIterator != null && !currentIterator.hasNext()) {
|
||||
update();
|
||||
// if there's no element currently available, advance until there
|
||||
// is one or the input is exhausted
|
||||
for (;;) {
|
||||
if (currentIterator.hasNext())
|
||||
return true;
|
||||
else if (inputs.hasNext())
|
||||
currentIterator = converter.apply(inputs.next());
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return currentIterator != null;
|
||||
}
|
||||
|
||||
public O next() {
|
||||
if (currentIterator == EMPTY && !hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return currentIterator.next();
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
while (inputs.hasNext()) {
|
||||
currentIterator = converter.apply(inputs.next());
|
||||
if (currentIterator.hasNext()) return;
|
||||
}
|
||||
currentIterator = null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final Iterator EMPTY = new Iterator() {
|
||||
public boolean hasNext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object next() {
|
||||
return null;
|
||||
public O next() {
|
||||
// next() cannot assume hasNext() was called immediately before:
|
||||
// next() must itself be able to find the next available element
|
||||
// if there is one
|
||||
while (!currentIterator.hasNext() && inputs.hasNext()) {
|
||||
currentIterator = converter.apply(inputs.next());
|
||||
}
|
||||
return currentIterator.next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// input.next() is assumed to never return null
|
||||
public static <E> Iterator<E> createFilterIterator(Iterator<E> input, Predicate<E> test) {
|
||||
return new Iterator<E>() {
|
||||
return new Iterator<>() {
|
||||
private E current = update();
|
||||
private E update () {
|
||||
while (input.hasNext()) {
|
||||
@ -114,6 +111,9 @@ public class Iterators {
|
||||
|
||||
@Override
|
||||
public E next() {
|
||||
if (current == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
E res = current;
|
||||
current = update();
|
||||
return res;
|
||||
|
@ -438,27 +438,10 @@ public class List<A> extends AbstractCollection<A> implements java.util.List<A>
|
||||
return (List<T>)list;
|
||||
}
|
||||
|
||||
private static final Iterator<?> EMPTYITERATOR = new Iterator<Object>() {
|
||||
public boolean hasNext() {
|
||||
return false;
|
||||
}
|
||||
public Object next() {
|
||||
throw new java.util.NoSuchElementException();
|
||||
}
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <A> Iterator<A> emptyIterator() {
|
||||
return (Iterator<A>)EMPTYITERATOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<A> iterator() {
|
||||
if (tail == null)
|
||||
return emptyIterator();
|
||||
return Iterators.emptyIterator();
|
||||
return new Iterator<A>() {
|
||||
List<A> elems = List.this;
|
||||
public boolean hasNext() {
|
||||
|
196
test/langtools/tools/javac/util/IteratorsTest.java
Normal file
196
test/langtools/tools/javac/util/IteratorsTest.java
Normal file
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8303882
|
||||
* @summary Verify that Iterators method work as expected
|
||||
* @modules jdk.compiler/com.sun.tools.javac.util
|
||||
* @run junit IteratorsTest
|
||||
*/
|
||||
|
||||
import com.sun.tools.javac.util.Iterators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Function;
|
||||
import org.junit.jupiter.api.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
public class IteratorsTest {
|
||||
|
||||
@Test
|
||||
public void consistentNext() {
|
||||
Iterator<?> emptyCompoundIterator = Iterators.createCompoundIterator(List.of(), Function.identity());
|
||||
Assertions.assertThrows(NoSuchElementException.class, emptyCompoundIterator::next);
|
||||
Assertions.assertThrows(NoSuchElementException.class, emptyCompoundIterator::next);
|
||||
}
|
||||
|
||||
// different ways of obtaining an empty iterator are used to make sure
|
||||
// the compound iterator doesn't depend on (checking) the identity of
|
||||
// any one of them
|
||||
@Test
|
||||
public void intermediateEmptyIterators() {
|
||||
List<Iterator<String>> inputs = List.of(
|
||||
Collections.<String>emptyList().iterator(),
|
||||
Collections.emptyListIterator(),
|
||||
Collections.emptyIterator(),
|
||||
List.of("1").iterator(),
|
||||
List.of("2", "3").iterator(),
|
||||
List.<String>of().iterator(),
|
||||
Collections.<String>emptySet().iterator(),
|
||||
List.of("4", "5").iterator(),
|
||||
com.sun.tools.javac.util.List.<String>nil().iterator());
|
||||
Iterator<String> emptyCompoundIterator = Iterators.createCompoundIterator(inputs, Function.identity());
|
||||
var actual = new ArrayList<String>();
|
||||
emptyCompoundIterator.forEachRemaining(actual::add);
|
||||
assertEquals(List.of("1", "2", "3", "4", "5"), actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recursiveEmpty() {
|
||||
Iterable<Iterator<Object>> inner = () -> Iterators.createCompoundIterator(List.of(), i -> Collections.emptyIterator());
|
||||
Iterator<Object> outer = Iterators.createCompoundIterator(inner, Function.identity());
|
||||
assertFalse(outer.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compoundIterator() {
|
||||
TestConverter<String> c = new TestConverter<>(it -> it);
|
||||
TestIterator<String> test1 = new TestIterator<>(List.of("1").iterator());
|
||||
TestIterator<String> test2 = new TestIterator<>(List.of("2").iterator());
|
||||
Iterator<String> compound = Iterators.createCompoundIterator(List.of(test1, test2), c);
|
||||
|
||||
//nothing should be called before the hasNext or next is called:
|
||||
assertAndResetMaxCalls(c, 0);
|
||||
assertAndResetMaxCalls(test1, 0, 0);
|
||||
assertAndResetMaxCalls(test2, 0, 0);
|
||||
|
||||
//when hasNext is called, should invoke the hasNext delegate once:
|
||||
Assertions.assertTrue(compound.hasNext());
|
||||
|
||||
assertAndResetMaxCalls(c, 1);
|
||||
assertAndResetMaxCalls(test1, 1, 0);
|
||||
assertAndResetMaxCalls(test2, 0, 0);
|
||||
|
||||
Assertions.assertTrue(compound.hasNext());
|
||||
|
||||
assertAndResetMaxCalls(c, 0);
|
||||
assertAndResetMaxCalls(test1, 1, 0);
|
||||
assertAndResetMaxCalls(test2, 0, 0);
|
||||
|
||||
//next may invoke hasNext once:
|
||||
Assertions.assertEquals("1", compound.next());
|
||||
|
||||
assertAndResetMaxCalls(c, 0);
|
||||
assertAndResetMaxCalls(test1, 1, 1);
|
||||
assertAndResetMaxCalls(test2, 0, 0);
|
||||
|
||||
Assertions.assertTrue(compound.hasNext());
|
||||
|
||||
assertAndResetMaxCalls(c, 1);
|
||||
assertAndResetMaxCalls(test1, 1, 0);
|
||||
assertAndResetMaxCalls(test2, 1, 0);
|
||||
|
||||
Assertions.assertTrue(compound.hasNext());
|
||||
|
||||
assertAndResetMaxCalls(c, 0);
|
||||
assertAndResetMaxCalls(test1, 0, 0);
|
||||
assertAndResetMaxCalls(test2, 1, 0);
|
||||
|
||||
Assertions.assertEquals("2", compound.next());
|
||||
|
||||
assertAndResetMaxCalls(c, 0);
|
||||
assertAndResetMaxCalls(test1, 0, 0);
|
||||
assertAndResetMaxCalls(test2, 1, 1);
|
||||
|
||||
Assertions.assertFalse(compound.hasNext());
|
||||
|
||||
assertAndResetMaxCalls(c, 0);
|
||||
assertAndResetMaxCalls(test1, 0, 0);
|
||||
assertAndResetMaxCalls(test2, 1, 0);
|
||||
}
|
||||
|
||||
private void assertAndResetMaxCalls(TestIterator<?> test, int maxExpectedHasNextCalls, int maxExpectedNextCalls) {
|
||||
if (test.hasNextCalls > maxExpectedHasNextCalls) {
|
||||
Assertions.fail("too many hasNext invocations: " + test.hasNextCalls +
|
||||
", expected: " + maxExpectedHasNextCalls);
|
||||
}
|
||||
test.hasNextCalls = 0;
|
||||
if (test.nextCalls > maxExpectedNextCalls) {
|
||||
Assertions.fail("too many next invocations: " + test.nextCalls +
|
||||
", expected: " + maxExpectedNextCalls);
|
||||
}
|
||||
test.nextCalls = 0;
|
||||
}
|
||||
|
||||
private void assertAndResetMaxCalls(TestConverter<?> test, int maxExpectedApplyCalls) {
|
||||
if (test.applyCalls > maxExpectedApplyCalls) {
|
||||
Assertions.fail("too many apply invocations: " + test.applyCalls +
|
||||
", expected: " + maxExpectedApplyCalls);
|
||||
}
|
||||
test.applyCalls = 0;
|
||||
}
|
||||
|
||||
static class TestIterator<T> implements Iterator<T> {
|
||||
int hasNextCalls;
|
||||
int nextCalls;
|
||||
final Iterator<T> delegate;
|
||||
|
||||
public TestIterator(Iterator<T> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
hasNextCalls++;
|
||||
return delegate.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
nextCalls++;
|
||||
return delegate.next();
|
||||
}
|
||||
}
|
||||
|
||||
static class TestConverter<T> implements Function<TestIterator<T>, Iterator<T>> {
|
||||
int applyCalls;
|
||||
final Function<TestIterator<T>, Iterator<T>> delegate;
|
||||
|
||||
public TestConverter(Function<TestIterator<T>, Iterator<T>> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> apply(TestIterator<T> t) {
|
||||
applyCalls++;
|
||||
return delegate.apply(t);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user