Skip to content

Commit ad8c919

Browse files
ItamarBenjaminvelo
authored andcommitted
Makes iterator compatible with Java iterator expected behavior (OpenFeign#1117)
* Makes iterator compatible with Java iterator expected behavior both next() and hasNext() should read from stream if needed. both also inspect 'current' member, next() resets it after consuming. exception is thrown when no more elements are available to return. * Fixing CI - formatting issue
1 parent 7c6b2bb commit ad8c919

2 files changed

Lines changed: 55 additions & 17 deletions

File tree

jackson/src/main/java/feign/jackson/JacksonIteratorDecoder.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,9 @@
1515

1616
import com.fasterxml.jackson.core.JsonParser;
1717
import com.fasterxml.jackson.core.JsonToken;
18-
import com.fasterxml.jackson.databind.DeserializationFeature;
1918
import com.fasterxml.jackson.databind.Module;
20-
import com.fasterxml.jackson.databind.ObjectMapper;
21-
import com.fasterxml.jackson.databind.ObjectReader;
22-
import com.fasterxml.jackson.databind.RuntimeJsonMappingException;
19+
import com.fasterxml.jackson.databind.*;
2320
import feign.Response;
24-
import feign.Util;
2521
import feign.codec.DecodeException;
2622
import feign.codec.Decoder;
2723
import java.io.BufferedReader;
@@ -32,6 +28,7 @@
3228
import java.lang.reflect.Type;
3329
import java.util.Collections;
3430
import java.util.Iterator;
31+
import java.util.NoSuchElementException;
3532
import static feign.Util.ensureClosed;
3633

3734
/**
@@ -131,33 +128,47 @@ static final class JacksonIterator<T> implements Iterator<T>, Closeable {
131128

132129
@Override
133130
public boolean hasNext() {
131+
if (current == null) {
132+
current = readNext();
133+
}
134+
return current != null;
135+
}
136+
137+
private T readNext() {
134138
try {
135139
JsonToken jsonToken = parser.nextToken();
136140
if (jsonToken == null) {
137-
return false;
141+
return null;
138142
}
139143

140144
if (jsonToken == JsonToken.START_ARRAY) {
141145
jsonToken = parser.nextToken();
142146
}
143147

144148
if (jsonToken == JsonToken.END_ARRAY) {
145-
current = null;
146149
ensureClosed(this);
147-
return false;
150+
return null;
148151
}
149152

150-
current = objectReader.readValue(parser);
153+
return objectReader.readValue(parser);
151154
} catch (IOException e) {
152155
// Input Stream closed automatically by parser
153156
throw new DecodeException(response.status(), e.getMessage(), response.request(), e);
154157
}
155-
return current != null;
156158
}
157159

158160
@Override
159161
public T next() {
160-
return current;
162+
if (current != null) {
163+
T tmp = current;
164+
current = null;
165+
return tmp;
166+
}
167+
T next = readNext();
168+
if (next == null) {
169+
throw new NoSuchElementException();
170+
}
171+
return next;
161172
}
162173

163174
@Override

jackson/src/test/java/feign/jackson/JacksonIteratorTest.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,27 @@
1313
*/
1414
package feign.jackson;
1515

16-
import static feign.Util.UTF_8;
17-
import static org.assertj.core.api.Assertions.assertThat;
18-
import static org.hamcrest.core.Is.isA;
1916
import com.fasterxml.jackson.databind.ObjectMapper;
2017
import feign.Request;
2118
import feign.Request.HttpMethod;
2219
import feign.Response;
2320
import feign.Util;
2421
import feign.codec.DecodeException;
2522
import feign.jackson.JacksonIteratorDecoder.JacksonIterator;
23+
import org.junit.Rule;
24+
import org.junit.Test;
25+
import org.junit.rules.ExpectedException;
2626
import java.io.ByteArrayInputStream;
2727
import java.io.IOException;
2828
import java.io.InputStream;
2929
import java.util.Collections;
3030
import java.util.LinkedHashMap;
31+
import java.util.NoSuchElementException;
3132
import java.util.concurrent.atomic.AtomicBoolean;
32-
import org.junit.Rule;
33-
import org.junit.Test;
34-
import org.junit.rules.ExpectedException;
33+
import static feign.Util.UTF_8;
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
36+
import static org.hamcrest.core.Is.isA;
3537

3638
public class JacksonIteratorTest {
3739

@@ -43,6 +45,31 @@ public void shouldDecodePrimitiveArrays() throws IOException {
4345
assertThat(iterator(Integer.class, "[0,1,2,3]")).containsExactly(0, 1, 2, 3);
4446
}
4547

48+
@Test
49+
public void shouldNotSkipElementsOnHasNext() throws IOException {
50+
JacksonIterator<Integer> iterator = iterator(Integer.class, "[0]");
51+
assertThat(iterator.hasNext()).isTrue();
52+
assertThat(iterator.hasNext()).isTrue();
53+
assertThat(iterator.next()).isEqualTo(0);
54+
assertThat(iterator.hasNext()).isFalse();
55+
}
56+
57+
@Test
58+
public void hasNextIsNotMandatory() throws IOException {
59+
JacksonIterator<Integer> iterator = iterator(Integer.class, "[0]");
60+
assertThat(iterator.next()).isEqualTo(0);
61+
assertThat(iterator.hasNext()).isFalse();
62+
}
63+
64+
@Test
65+
public void expectExceptionOnNoElements() throws IOException {
66+
JacksonIterator<Integer> iterator = iterator(Integer.class, "[0]");
67+
assertThat(iterator.next()).isEqualTo(0);
68+
assertThatThrownBy(() -> iterator.next())
69+
.hasMessage(null)
70+
.isInstanceOf(NoSuchElementException.class);
71+
}
72+
4673
@Test
4774
public void shouldDecodeObjects() throws IOException {
4875
assertThat(iterator(User.class, "[{\"login\":\"bob\"},{\"login\":\"joe\"}]"))

0 commit comments

Comments
 (0)