From b3d0fea061bef3b0c2202aa321ca8ffc8505a215 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 22 Sep 2020 15:20:32 +0200 Subject: [PATCH] DATACMNS-1637 - Add default stream() method to CloseableIterator. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CloseableIterator now allows construction of a Stream that is associated with a close hook to release resources after its use. CloseableIterator iterator = …; try (Stream stream = iterator.stream()) { assertThat(stream.count()).isEqualTo(3); } --- .../data/util/CloseableIterator.java | 40 ++++++++- .../data/util/CloseableIteratorUnitTests.java | 82 +++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/springframework/data/util/CloseableIteratorUnitTests.java diff --git a/src/main/java/org/springframework/data/util/CloseableIterator.java b/src/main/java/org/springframework/data/util/CloseableIterator.java index 541097e97..f9ca7afe4 100644 --- a/src/main/java/org/springframework/data/util/CloseableIterator.java +++ b/src/main/java/org/springframework/data/util/CloseableIterator.java @@ -17,13 +17,18 @@ package org.springframework.data.util; import java.io.Closeable; import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * A {@link CloseableIterator} serves as a bridging data structure for the underlying data store specific results that - * can be wrapped in a Java 8 {@link java.util.stream.Stream}. This allows implementations to clean up any resources - * they need to keep open to iterate over elements. + * can be wrapped in a Java 8 {@link #stream() java.util.stream.Stream}. This allows implementations to clean up any + * resources they need to keep open to iterate over elements. * * @author Thomas Darimont + * @author Mark Paluch * @param * @since 1.10 */ @@ -35,4 +40,35 @@ public interface CloseableIterator extends Iterator, Closeable { */ @Override void close(); + + /** + * Create a {@link Spliterator} over the elements provided by this {@link Iterator}. Implementations should document + * characteristic values reported by the spliterator. Such characteristic values are not required to be reported if + * the spliterator reports {@link Spliterator#SIZED} and this collection contains no elements. + *

+ * The default implementation should be overridden by subclasses that can return a more efficient spliterator. To + * preserve expected laziness behavior for the {@link #stream()} method, spliterators should either have the + * characteristic of {@code IMMUTABLE} or {@code CONCURRENT}, or be late-binding. + * + * @return a {@link Spliterator} over the elements in this {@link Iterator}. + * @since 2.4 + */ + default Spliterator spliterator() { + return Spliterators.spliterator(this, 0, 0); + } + + /** + * Return a sequential {@code Stream} with this {@link Iterator} as its source. The resulting stream calls + * {@link #clone()} when {@link Stream#close() closed}. The resulting {@link Stream} must be closed after use, it can + * be declared as a resource in a {@code try}-with-resources statement. + *

+ * This method should be overridden when the {@link #spliterator()} method cannot return a spliterator that is + * {@code IMMUTABLE}, {@code CONCURRENT}, or late-binding. (See {@link #spliterator()} for details.) + * + * @return a sequential {@code Stream} over the elements in this {@link Iterator}. + * @since 2.4 + */ + default Stream stream() { + return StreamSupport.stream(spliterator(), false).onClose(this::close); + } } diff --git a/src/test/java/org/springframework/data/util/CloseableIteratorUnitTests.java b/src/test/java/org/springframework/data/util/CloseableIteratorUnitTests.java new file mode 100644 index 000000000..94af0292b --- /dev/null +++ b/src/test/java/org/springframework/data/util/CloseableIteratorUnitTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 the original author or 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 + * + * https://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.springframework.data.util; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link CloseableIterator}. + * + * @author Mark Paluch + */ +class CloseableIteratorUnitTests { + + @Test // DATACMNS-1637 + void shouldCreateStream() { + + CloseableIteratorImpl iterator = new CloseableIteratorImpl<>(Arrays.asList("1", "2", "3").iterator()); + + List collection = iterator.stream().map(it -> "hello " + it).collect(Collectors.toList()); + + assertThat(collection).contains("hello 1", "hello 2", "hello 3"); + assertThat(iterator.closed).isFalse(); + } + + @Test // DATACMNS-1637 + void closeStreamShouldCloseIterator() { + + CloseableIteratorImpl iterator = new CloseableIteratorImpl<>(Arrays.asList("1", "2", "3").iterator()); + + try (Stream stream = iterator.stream()) { + assertThat(stream.findFirst()).hasValue("1"); + } + + assertThat(iterator.closed).isTrue(); + } + + static class CloseableIteratorImpl implements CloseableIterator { + + private final Iterator delegate; + private boolean closed = false; + + CloseableIteratorImpl(Iterator delegate) { + this.delegate = delegate; + } + + @Override + public void close() { + closed = true; + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public T next() { + return delegate.next(); + } + } +}