Skip to content

Commit b3d0fea

Browse files
committed
DATACMNS-1637 - Add default stream() method to CloseableIterator.
CloseableIterator now allows construction of a Stream that is associated with a close hook to release resources after its use. CloseableIterator<Person> iterator = …; try (Stream<Person> stream = iterator.stream()) { assertThat(stream.count()).isEqualTo(3); }
1 parent 82cb36f commit b3d0fea

File tree

2 files changed

+120
-2
lines changed

2 files changed

+120
-2
lines changed

src/main/java/org/springframework/data/util/CloseableIterator.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@
1717

1818
import java.io.Closeable;
1919
import java.util.Iterator;
20+
import java.util.Spliterator;
21+
import java.util.Spliterators;
22+
import java.util.stream.Stream;
23+
import java.util.stream.StreamSupport;
2024

2125
/**
2226
* A {@link CloseableIterator} serves as a bridging data structure for the underlying data store specific results that
23-
* can be wrapped in a Java 8 {@link java.util.stream.Stream}. This allows implementations to clean up any resources
24-
* they need to keep open to iterate over elements.
27+
* can be wrapped in a Java 8 {@link #stream() java.util.stream.Stream}. This allows implementations to clean up any
28+
* resources they need to keep open to iterate over elements.
2529
*
2630
* @author Thomas Darimont
31+
* @author Mark Paluch
2732
* @param <T>
2833
* @since 1.10
2934
*/
@@ -35,4 +40,35 @@ public interface CloseableIterator<T> extends Iterator<T>, Closeable {
3540
*/
3641
@Override
3742
void close();
43+
44+
/**
45+
* Create a {@link Spliterator} over the elements provided by this {@link Iterator}. Implementations should document
46+
* characteristic values reported by the spliterator. Such characteristic values are not required to be reported if
47+
* the spliterator reports {@link Spliterator#SIZED} and this collection contains no elements.
48+
* <p>
49+
* The default implementation should be overridden by subclasses that can return a more efficient spliterator. To
50+
* preserve expected laziness behavior for the {@link #stream()} method, spliterators should either have the
51+
* characteristic of {@code IMMUTABLE} or {@code CONCURRENT}, or be late-binding.
52+
*
53+
* @return a {@link Spliterator} over the elements in this {@link Iterator}.
54+
* @since 2.4
55+
*/
56+
default Spliterator<T> spliterator() {
57+
return Spliterators.spliterator(this, 0, 0);
58+
}
59+
60+
/**
61+
* Return a sequential {@code Stream} with this {@link Iterator} as its source. The resulting stream calls
62+
* {@link #clone()} when {@link Stream#close() closed}. The resulting {@link Stream} must be closed after use, it can
63+
* be declared as a resource in a {@code try}-with-resources statement.
64+
* <p>
65+
* This method should be overridden when the {@link #spliterator()} method cannot return a spliterator that is
66+
* {@code IMMUTABLE}, {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()} for details.)
67+
*
68+
* @return a sequential {@code Stream} over the elements in this {@link Iterator}.
69+
* @since 2.4
70+
*/
71+
default Stream<T> stream() {
72+
return StreamSupport.stream(spliterator(), false).onClose(this::close);
73+
}
3874
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.util;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import java.util.Arrays;
21+
import java.util.Iterator;
22+
import java.util.List;
23+
import java.util.stream.Collectors;
24+
import java.util.stream.Stream;
25+
26+
import org.junit.jupiter.api.Test;
27+
28+
/**
29+
* Unit tests for {@link CloseableIterator}.
30+
*
31+
* @author Mark Paluch
32+
*/
33+
class CloseableIteratorUnitTests {
34+
35+
@Test // DATACMNS-1637
36+
void shouldCreateStream() {
37+
38+
CloseableIteratorImpl<String> iterator = new CloseableIteratorImpl<>(Arrays.asList("1", "2", "3").iterator());
39+
40+
List<String> collection = iterator.stream().map(it -> "hello " + it).collect(Collectors.toList());
41+
42+
assertThat(collection).contains("hello 1", "hello 2", "hello 3");
43+
assertThat(iterator.closed).isFalse();
44+
}
45+
46+
@Test // DATACMNS-1637
47+
void closeStreamShouldCloseIterator() {
48+
49+
CloseableIteratorImpl<String> iterator = new CloseableIteratorImpl<>(Arrays.asList("1", "2", "3").iterator());
50+
51+
try (Stream<String> stream = iterator.stream()) {
52+
assertThat(stream.findFirst()).hasValue("1");
53+
}
54+
55+
assertThat(iterator.closed).isTrue();
56+
}
57+
58+
static class CloseableIteratorImpl<T> implements CloseableIterator<T> {
59+
60+
private final Iterator<T> delegate;
61+
private boolean closed = false;
62+
63+
CloseableIteratorImpl(Iterator<T> delegate) {
64+
this.delegate = delegate;
65+
}
66+
67+
@Override
68+
public void close() {
69+
closed = true;
70+
}
71+
72+
@Override
73+
public boolean hasNext() {
74+
return delegate.hasNext();
75+
}
76+
77+
@Override
78+
public T next() {
79+
return delegate.next();
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)