Skip to content

Commit b347252

Browse files
author
Saurabh Agarwal
authored
Api#2880: Close the underlying iterator in ClosingIterator in hasNext() call (#2881)
1 parent 333e2c1 commit b347252

File tree

2 files changed

+87
-13
lines changed

2 files changed

+87
-13
lines changed

api/src/main/java/org/apache/iceberg/io/ClosingIterator.java

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,38 @@
2323
import java.io.UncheckedIOException;
2424
import java.util.Iterator;
2525

26+
/**
27+
* A convenience wrapper around {@link CloseableIterator}, providing auto-close
28+
* functionality when all of the elements in the iterator are consumed.
29+
*/
2630
public class ClosingIterator<T> implements Iterator<T> {
2731
private final CloseableIterator<T> iterator;
28-
private boolean shouldClose = false;
32+
private boolean isClosed;
2933

3034
public ClosingIterator(CloseableIterator<T> iterator) {
3135
this.iterator = iterator;
32-
3336
}
3437

3538
@Override
3639
public boolean hasNext() {
3740
boolean hasNext = iterator.hasNext();
38-
this.shouldClose = !hasNext;
41+
if (!hasNext && !isClosed) {
42+
close();
43+
}
3944
return hasNext;
4045
}
4146

4247
@Override
4348
public T next() {
44-
T next = iterator.next();
49+
return iterator.next();
50+
}
4551

46-
if (shouldClose) {
47-
// this will only be called once because iterator.next would throw NoSuchElementException
48-
try {
49-
iterator.close();
50-
} catch (IOException e) {
51-
throw new UncheckedIOException(e);
52-
}
52+
private void close() {
53+
try {
54+
iterator.close();
55+
isClosed = true;
56+
} catch (IOException e) {
57+
throw new UncheckedIOException(e);
5358
}
54-
55-
return next;
5659
}
5760
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.io;
21+
22+
import org.junit.Test;
23+
24+
import static org.junit.Assert.assertEquals;
25+
import static org.junit.Assert.assertFalse;
26+
import static org.junit.Assert.assertTrue;
27+
import static org.mockito.Mockito.mock;
28+
import static org.mockito.Mockito.times;
29+
import static org.mockito.Mockito.verify;
30+
import static org.mockito.Mockito.when;
31+
32+
public class TestClosingIterator {
33+
@Test
34+
public void testEmptyIterator() {
35+
CloseableIterator<String> underlying = mock(CloseableIterator.class);
36+
ClosingIterator<String> closingIterator = new ClosingIterator<>(underlying);
37+
assertFalse(closingIterator.hasNext());
38+
}
39+
40+
@Test
41+
public void testHasNextAndNext() {
42+
CloseableIterator<String> underlying = mock(CloseableIterator.class);
43+
when(underlying.hasNext()).thenReturn(true);
44+
when(underlying.next()).thenReturn("hello");
45+
ClosingIterator<String> closingIterator = new ClosingIterator<>(underlying);
46+
assertTrue(closingIterator.hasNext());
47+
assertEquals("hello", closingIterator.next());
48+
}
49+
50+
@Test
51+
public void testUnderlyingIteratorCloseWhenElementsAreExhausted() throws Exception {
52+
CloseableIterator<String> underlying = mock(CloseableIterator.class);
53+
when(underlying.hasNext()).thenReturn(true).thenReturn(false);
54+
when(underlying.next()).thenReturn("hello");
55+
ClosingIterator<String> closingIterator = new ClosingIterator<>(underlying);
56+
assertTrue(closingIterator.hasNext());
57+
assertEquals("hello", closingIterator.next());
58+
59+
assertFalse(closingIterator.hasNext());
60+
verify(underlying, times(1)).close();
61+
}
62+
63+
@Test
64+
public void testCloseCalledOnceForMultipleHasNextCalls() throws Exception {
65+
CloseableIterator<String> underlying = mock(CloseableIterator.class);
66+
ClosingIterator<String> closingIterator = new ClosingIterator<>(underlying);
67+
assertFalse(closingIterator.hasNext());
68+
assertFalse(closingIterator.hasNext());
69+
verify(underlying, times(1)).close();
70+
}
71+
}

0 commit comments

Comments
 (0)