25
25
import com .google .gson .stream .JsonToken ;
26
26
import java .io .IOException ;
27
27
import java .io .Reader ;
28
- import java .util .ArrayList ;
29
28
import java .util .Iterator ;
30
- import java .util .List ;
31
29
import java .util .Map ;
32
30
33
31
/**
@@ -47,35 +45,57 @@ public final class JsonTreeReader extends JsonReader {
47
45
};
48
46
private static final Object SENTINEL_CLOSED = new Object ();
49
47
50
- private final List <Object > stack = new ArrayList <Object >();
48
+ /*
49
+ * The nesting stack. Using a manual array rather than an ArrayList saves 20%.
50
+ */
51
+ private Object [] stack = new Object [32 ];
52
+ private int stackSize = 0 ;
53
+
54
+ /*
55
+ * The path members. It corresponds directly to stack: At indices where the
56
+ * stack contains an object (EMPTY_OBJECT, DANGLING_NAME or NONEMPTY_OBJECT),
57
+ * pathNames contains the name at this scope. Where it contains an array
58
+ * (EMPTY_ARRAY, NONEMPTY_ARRAY) pathIndices contains the current index in
59
+ * that array. Otherwise the value is undefined, and we take advantage of that
60
+ * by incrementing pathIndices when doing so isn't useful.
61
+ */
62
+ private String [] pathNames = new String [32 ];
63
+ private int [] pathIndices = new int [32 ];
51
64
52
65
public JsonTreeReader (JsonElement element ) {
53
66
super (UNREADABLE_READER );
54
- stack . add (element );
67
+ push (element );
55
68
}
56
69
57
70
@ Override public void beginArray () throws IOException {
58
71
expect (JsonToken .BEGIN_ARRAY );
59
72
JsonArray array = (JsonArray ) peekStack ();
60
- stack .add (array .iterator ());
73
+ push (array .iterator ());
74
+ pathIndices [stackSize - 1 ] = 0 ;
61
75
}
62
76
63
77
@ Override public void endArray () throws IOException {
64
78
expect (JsonToken .END_ARRAY );
65
79
popStack (); // empty iterator
66
80
popStack (); // array
81
+ if (stackSize > 0 ) {
82
+ pathIndices [stackSize - 1 ]++;
83
+ }
67
84
}
68
85
69
86
@ Override public void beginObject () throws IOException {
70
87
expect (JsonToken .BEGIN_OBJECT );
71
88
JsonObject object = (JsonObject ) peekStack ();
72
- stack . add (object .entrySet ().iterator ());
89
+ push (object .entrySet ().iterator ());
73
90
}
74
91
75
92
@ Override public void endObject () throws IOException {
76
93
expect (JsonToken .END_OBJECT );
77
94
popStack (); // empty iterator
78
95
popStack (); // object
96
+ if (stackSize > 0 ) {
97
+ pathIndices [stackSize - 1 ]++;
98
+ }
79
99
}
80
100
81
101
@ Override public boolean hasNext () throws IOException {
@@ -84,19 +104,19 @@ public JsonTreeReader(JsonElement element) {
84
104
}
85
105
86
106
@ Override public JsonToken peek () throws IOException {
87
- if (stack . isEmpty () ) {
107
+ if (stackSize == 0 ) {
88
108
return JsonToken .END_DOCUMENT ;
89
109
}
90
110
91
111
Object o = peekStack ();
92
112
if (o instanceof Iterator ) {
93
- boolean isObject = stack . get ( stack . size () - 2 ) instanceof JsonObject ;
113
+ boolean isObject = stack [ stackSize - 2 ] instanceof JsonObject ;
94
114
Iterator <?> iterator = (Iterator <?>) o ;
95
115
if (iterator .hasNext ()) {
96
116
if (isObject ) {
97
117
return JsonToken .NAME ;
98
118
} else {
99
- stack . add (iterator .next ());
119
+ push (iterator .next ());
100
120
return peek ();
101
121
}
102
122
} else {
@@ -127,89 +147,121 @@ public JsonTreeReader(JsonElement element) {
127
147
}
128
148
129
149
private Object peekStack () {
130
- return stack . get ( stack . size () - 1 ) ;
150
+ return stack [ stackSize - 1 ] ;
131
151
}
132
152
133
153
private Object popStack () {
134
- return stack .remove (stack .size () - 1 );
154
+ Object result = stack [--stackSize ];
155
+ stack [stackSize ] = null ;
156
+ return result ;
135
157
}
136
158
137
159
private void expect (JsonToken expected ) throws IOException {
138
160
if (peek () != expected ) {
139
- throw new IllegalStateException ("Expected " + expected + " but was " + peek ());
161
+ throw new IllegalStateException (
162
+ "Expected " + expected + " but was " + peek () + locationString ());
140
163
}
141
164
}
142
165
143
166
@ Override public String nextName () throws IOException {
144
167
expect (JsonToken .NAME );
145
168
Iterator <?> i = (Iterator <?>) peekStack ();
146
169
Map .Entry <?, ?> entry = (Map .Entry <?, ?>) i .next ();
147
- stack .add (entry .getValue ());
148
- return (String ) entry .getKey ();
170
+ String result = (String ) entry .getKey ();
171
+ pathNames [stackSize - 1 ] = result ;
172
+ push (entry .getValue ());
173
+ return result ;
149
174
}
150
175
151
176
@ Override public String nextString () throws IOException {
152
177
JsonToken token = peek ();
153
178
if (token != JsonToken .STRING && token != JsonToken .NUMBER ) {
154
- throw new IllegalStateException ("Expected " + JsonToken .STRING + " but was " + token );
179
+ throw new IllegalStateException (
180
+ "Expected " + JsonToken .STRING + " but was " + token + locationString ());
181
+ }
182
+ String result = ((JsonPrimitive ) popStack ()).getAsString ();
183
+ if (stackSize > 0 ) {
184
+ pathIndices [stackSize - 1 ]++;
155
185
}
156
- return (( JsonPrimitive ) popStack ()). getAsString () ;
186
+ return result ;
157
187
}
158
188
159
189
@ Override public boolean nextBoolean () throws IOException {
160
190
expect (JsonToken .BOOLEAN );
161
- return ((JsonPrimitive ) popStack ()).getAsBoolean ();
191
+ boolean result = ((JsonPrimitive ) popStack ()).getAsBoolean ();
192
+ if (stackSize > 0 ) {
193
+ pathIndices [stackSize - 1 ]++;
194
+ }
195
+ return result ;
162
196
}
163
197
164
198
@ Override public void nextNull () throws IOException {
165
199
expect (JsonToken .NULL );
166
200
popStack ();
201
+ if (stackSize > 0 ) {
202
+ pathIndices [stackSize - 1 ]++;
203
+ }
167
204
}
168
205
169
206
@ Override public double nextDouble () throws IOException {
170
207
JsonToken token = peek ();
171
208
if (token != JsonToken .NUMBER && token != JsonToken .STRING ) {
172
- throw new IllegalStateException ("Expected " + JsonToken .NUMBER + " but was " + token );
209
+ throw new IllegalStateException (
210
+ "Expected " + JsonToken .NUMBER + " but was " + token + locationString ());
173
211
}
174
212
double result = ((JsonPrimitive ) peekStack ()).getAsDouble ();
175
213
if (!isLenient () && (Double .isNaN (result ) || Double .isInfinite (result ))) {
176
214
throw new NumberFormatException ("JSON forbids NaN and infinities: " + result );
177
215
}
178
216
popStack ();
217
+ if (stackSize > 0 ) {
218
+ pathIndices [stackSize - 1 ]++;
219
+ }
179
220
return result ;
180
221
}
181
222
182
223
@ Override public long nextLong () throws IOException {
183
224
JsonToken token = peek ();
184
225
if (token != JsonToken .NUMBER && token != JsonToken .STRING ) {
185
- throw new IllegalStateException ("Expected " + JsonToken .NUMBER + " but was " + token );
226
+ throw new IllegalStateException (
227
+ "Expected " + JsonToken .NUMBER + " but was " + token + locationString ());
186
228
}
187
229
long result = ((JsonPrimitive ) peekStack ()).getAsLong ();
188
230
popStack ();
231
+ if (stackSize > 0 ) {
232
+ pathIndices [stackSize - 1 ]++;
233
+ }
189
234
return result ;
190
235
}
191
236
192
237
@ Override public int nextInt () throws IOException {
193
238
JsonToken token = peek ();
194
239
if (token != JsonToken .NUMBER && token != JsonToken .STRING ) {
195
- throw new IllegalStateException ("Expected " + JsonToken .NUMBER + " but was " + token );
240
+ throw new IllegalStateException (
241
+ "Expected " + JsonToken .NUMBER + " but was " + token + locationString ());
196
242
}
197
243
int result = ((JsonPrimitive ) peekStack ()).getAsInt ();
198
244
popStack ();
245
+ if (stackSize > 0 ) {
246
+ pathIndices [stackSize - 1 ]++;
247
+ }
199
248
return result ;
200
249
}
201
250
202
251
@ Override public void close () throws IOException {
203
- stack . clear () ;
204
- stack . add ( SENTINEL_CLOSED ) ;
252
+ stack = new Object [] { SENTINEL_CLOSED } ;
253
+ stackSize = 1 ;
205
254
}
206
255
207
256
@ Override public void skipValue () throws IOException {
208
257
if (peek () == JsonToken .NAME ) {
209
258
nextName ();
259
+ pathNames [stackSize - 2 ] = "null" ;
210
260
} else {
211
261
popStack ();
262
+ pathNames [stackSize - 1 ] = "null" ;
212
263
}
264
+ pathIndices [stackSize - 1 ]++;
213
265
}
214
266
215
267
@ Override public String toString () {
@@ -220,7 +272,45 @@ public void promoteNameToValue() throws IOException {
220
272
expect (JsonToken .NAME );
221
273
Iterator <?> i = (Iterator <?>) peekStack ();
222
274
Map .Entry <?, ?> entry = (Map .Entry <?, ?>) i .next ();
223
- stack .add (entry .getValue ());
224
- stack .add (new JsonPrimitive ((String )entry .getKey ()));
275
+ push (entry .getValue ());
276
+ push (new JsonPrimitive ((String ) entry .getKey ()));
277
+ }
278
+
279
+ private void push (Object newTop ) {
280
+ if (stackSize == stack .length ) {
281
+ Object [] newStack = new Object [stackSize * 2 ];
282
+ int [] newPathIndices = new int [stackSize * 2 ];
283
+ String [] newPathNames = new String [stackSize * 2 ];
284
+ System .arraycopy (stack , 0 , newStack , 0 , stackSize );
285
+ System .arraycopy (pathIndices , 0 , newPathIndices , 0 , stackSize );
286
+ System .arraycopy (pathNames , 0 , newPathNames , 0 , stackSize );
287
+ stack = newStack ;
288
+ pathIndices = newPathIndices ;
289
+ pathNames = newPathNames ;
290
+ }
291
+ stack [stackSize ++] = newTop ;
292
+ }
293
+
294
+ @ Override public String getPath () {
295
+ StringBuilder result = new StringBuilder ().append ('$' );
296
+ for (int i = 0 ; i < stackSize ; i ++) {
297
+ if (stack [i ] instanceof JsonArray ) {
298
+ if (stack [++i ] instanceof Iterator ) {
299
+ result .append ('[' ).append (pathIndices [i ]).append (']' );
300
+ }
301
+ } else if (stack [i ] instanceof JsonObject ) {
302
+ if (stack [++i ] instanceof Iterator ) {
303
+ result .append ('.' );
304
+ if (pathNames [i ] != null ) {
305
+ result .append (pathNames [i ]);
306
+ }
307
+ }
308
+ }
309
+ }
310
+ return result .toString ();
311
+ }
312
+
313
+ private String locationString () {
314
+ return " at path " + getPath ();
225
315
}
226
316
}
0 commit comments