1
16
17 package com.google.gson.internal.bind;
18
19 import com.google.gson.JsonArray;
20 import com.google.gson.JsonElement;
21 import com.google.gson.JsonNull;
22 import com.google.gson.JsonObject;
23 import com.google.gson.JsonPrimitive;
24 import com.google.gson.stream.JsonReader;
25 import com.google.gson.stream.JsonToken;
26 import java.io.IOException;
27 import java.io.Reader;
28 import java.util.Iterator;
29 import java.util.Map;
30 import java.util.Arrays;
31
32
38 public final class JsonTreeReader extends JsonReader {
39 private static final Reader UNREADABLE_READER = new Reader() {
40 @Override public int read(char[] buffer, int offset, int count) throws IOException {
41 throw new AssertionError();
42 }
43 @Override public void close() throws IOException {
44 throw new AssertionError();
45 }
46 };
47 private static final Object SENTINEL_CLOSED = new Object();
48
49
52 private Object[] stack = new Object[32];
53 private int stackSize = 0;
54
55
63 private String[] pathNames = new String[32];
64 private int[] pathIndices = new int[32];
65
66 public JsonTreeReader(JsonElement element) {
67 super(UNREADABLE_READER);
68 push(element);
69 }
70
71 @Override public void beginArray() throws IOException {
72 expect(JsonToken.BEGIN_ARRAY);
73 JsonArray array = (JsonArray) peekStack();
74 push(array.iterator());
75 pathIndices[stackSize - 1] = 0;
76 }
77
78 @Override public void endArray() throws IOException {
79 expect(JsonToken.END_ARRAY);
80 popStack();
81 popStack();
82 if (stackSize > 0) {
83 pathIndices[stackSize - 1]++;
84 }
85 }
86
87 @Override public void beginObject() throws IOException {
88 expect(JsonToken.BEGIN_OBJECT);
89 JsonObject object = (JsonObject) peekStack();
90 push(object.entrySet().iterator());
91 }
92
93 @Override public void endObject() throws IOException {
94 expect(JsonToken.END_OBJECT);
95 popStack();
96 popStack();
97 if (stackSize > 0) {
98 pathIndices[stackSize - 1]++;
99 }
100 }
101
102 @Override public boolean hasNext() throws IOException {
103 JsonToken token = peek();
104 return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
105 }
106
107 @Override public JsonToken peek() throws IOException {
108 if (stackSize == 0) {
109 return JsonToken.END_DOCUMENT;
110 }
111
112 Object o = peekStack();
113 if (o instanceof Iterator) {
114 boolean isObject = stack[stackSize - 2] instanceof JsonObject;
115 Iterator<?> iterator = (Iterator<?>) o;
116 if (iterator.hasNext()) {
117 if (isObject) {
118 return JsonToken.NAME;
119 } else {
120 push(iterator.next());
121 return peek();
122 }
123 } else {
124 return isObject ? JsonToken.END_OBJECT : JsonToken.END_ARRAY;
125 }
126 } else if (o instanceof JsonObject) {
127 return JsonToken.BEGIN_OBJECT;
128 } else if (o instanceof JsonArray) {
129 return JsonToken.BEGIN_ARRAY;
130 } else if (o instanceof JsonPrimitive) {
131 JsonPrimitive primitive = (JsonPrimitive) o;
132 if (primitive.isString()) {
133 return JsonToken.STRING;
134 } else if (primitive.isBoolean()) {
135 return JsonToken.BOOLEAN;
136 } else if (primitive.isNumber()) {
137 return JsonToken.NUMBER;
138 } else {
139 throw new AssertionError();
140 }
141 } else if (o instanceof JsonNull) {
142 return JsonToken.NULL;
143 } else if (o == SENTINEL_CLOSED) {
144 throw new IllegalStateException("JsonReader is closed");
145 } else {
146 throw new AssertionError();
147 }
148 }
149
150 private Object peekStack() {
151 return stack[stackSize - 1];
152 }
153
154 private Object popStack() {
155 Object result = stack[--stackSize];
156 stack[stackSize] = null;
157 return result;
158 }
159
160 private void expect(JsonToken expected) throws IOException {
161 if (peek() != expected) {
162 throw new IllegalStateException(
163 "Expected " + expected + " but was " + peek() + locationString());
164 }
165 }
166
167 @Override public String nextName() throws IOException {
168 expect(JsonToken.NAME);
169 Iterator<?> i = (Iterator<?>) peekStack();
170 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
171 String result = (String) entry.getKey();
172 pathNames[stackSize - 1] = result;
173 push(entry.getValue());
174 return result;
175 }
176
177 @Override public String nextString() throws IOException {
178 JsonToken token = peek();
179 if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
180 throw new IllegalStateException(
181 "Expected " + JsonToken.STRING + " but was " + token + locationString());
182 }
183 String result = ((JsonPrimitive) popStack()).getAsString();
184 if (stackSize > 0) {
185 pathIndices[stackSize - 1]++;
186 }
187 return result;
188 }
189
190 @Override public boolean nextBoolean() throws IOException {
191 expect(JsonToken.BOOLEAN);
192 boolean result = ((JsonPrimitive) popStack()).getAsBoolean();
193 if (stackSize > 0) {
194 pathIndices[stackSize - 1]++;
195 }
196 return result;
197 }
198
199 @Override public void nextNull() throws IOException {
200 expect(JsonToken.NULL);
201 popStack();
202 if (stackSize > 0) {
203 pathIndices[stackSize - 1]++;
204 }
205 }
206
207 @Override public double nextDouble() throws IOException {
208 JsonToken token = peek();
209 if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
210 throw new IllegalStateException(
211 "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
212 }
213 double result = ((JsonPrimitive) peekStack()).getAsDouble();
214 if (!isLenient() && (Double.isNaN(result) || Double.isInfinite(result))) {
215 throw new NumberFormatException("JSON forbids NaN and infinities: " + result);
216 }
217 popStack();
218 if (stackSize > 0) {
219 pathIndices[stackSize - 1]++;
220 }
221 return result;
222 }
223
224 @Override public long nextLong() throws IOException {
225 JsonToken token = peek();
226 if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
227 throw new IllegalStateException(
228 "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
229 }
230 long result = ((JsonPrimitive) peekStack()).getAsLong();
231 popStack();
232 if (stackSize > 0) {
233 pathIndices[stackSize - 1]++;
234 }
235 return result;
236 }
237
238 @Override public int nextInt() throws IOException {
239 JsonToken token = peek();
240 if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
241 throw new IllegalStateException(
242 "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
243 }
244 int result = ((JsonPrimitive) peekStack()).getAsInt();
245 popStack();
246 if (stackSize > 0) {
247 pathIndices[stackSize - 1]++;
248 }
249 return result;
250 }
251
252 @Override public void close() throws IOException {
253 stack = new Object[] { SENTINEL_CLOSED };
254 stackSize = 1;
255 }
256
257 @Override public void skipValue() throws IOException {
258 if (peek() == JsonToken.NAME) {
259 nextName();
260 pathNames[stackSize - 2] = "null";
261 } else {
262 popStack();
263 if (stackSize > 0) {
264 pathNames[stackSize - 1] = "null";
265 }
266 }
267 if (stackSize > 0) {
268 pathIndices[stackSize - 1]++;
269 }
270 }
271
272 @Override public String toString() {
273 return getClass().getSimpleName();
274 }
275
276 public void promoteNameToValue() throws IOException {
277 expect(JsonToken.NAME);
278 Iterator<?> i = (Iterator<?>) peekStack();
279 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
280 push(entry.getValue());
281 push(new JsonPrimitive((String) entry.getKey()));
282 }
283
284 private void push(Object newTop) {
285 if (stackSize == stack.length) {
286 int newLength = stackSize * 2;
287 stack = Arrays.copyOf(stack, newLength);
288 pathIndices = Arrays.copyOf(pathIndices, newLength);
289 pathNames = Arrays.copyOf(pathNames, newLength);
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();
315 }
316 }
317