• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 Google Inc.
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  *      http://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 
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 com.google.gson.stream.MalformedJsonException;
27 import java.io.IOException;
28 import java.io.Reader;
29 import java.util.Arrays;
30 import java.util.Iterator;
31 import java.util.Map;
32 
33 /**
34  * This reader walks the elements of a JsonElement as if it was coming from a
35  * character stream.
36  *
37  * @author Jesse Wilson
38  */
39 public final class JsonTreeReader extends JsonReader {
40   private static final Reader UNREADABLE_READER = new Reader() {
41     @Override public int read(char[] buffer, int offset, int count) {
42       throw new AssertionError();
43     }
44     @Override public void close() {
45       throw new AssertionError();
46     }
47   };
48   private static final Object SENTINEL_CLOSED = new Object();
49 
50   /*
51    * The nesting stack. Using a manual array rather than an ArrayList saves 20%.
52    */
53   private Object[] stack = new Object[32];
54   private int stackSize = 0;
55 
56   /*
57    * The path members. It corresponds directly to stack: At indices where the
58    * stack contains an object (EMPTY_OBJECT, DANGLING_NAME or NONEMPTY_OBJECT),
59    * pathNames contains the name at this scope. Where it contains an array
60    * (EMPTY_ARRAY, NONEMPTY_ARRAY) pathIndices contains the current index in
61    * that array. Otherwise the value is undefined, and we take advantage of that
62    * by incrementing pathIndices when doing so isn't useful.
63    */
64   private String[] pathNames = new String[32];
65   private int[] pathIndices = new int[32];
66 
JsonTreeReader(JsonElement element)67   public JsonTreeReader(JsonElement element) {
68     super(UNREADABLE_READER);
69     push(element);
70   }
71 
beginArray()72   @Override public void beginArray() throws IOException {
73     expect(JsonToken.BEGIN_ARRAY);
74     JsonArray array = (JsonArray) peekStack();
75     push(array.iterator());
76     pathIndices[stackSize - 1] = 0;
77   }
78 
endArray()79   @Override public void endArray() throws IOException {
80     expect(JsonToken.END_ARRAY);
81     popStack(); // empty iterator
82     popStack(); // array
83     if (stackSize > 0) {
84       pathIndices[stackSize - 1]++;
85     }
86   }
87 
beginObject()88   @Override public void beginObject() throws IOException {
89     expect(JsonToken.BEGIN_OBJECT);
90     JsonObject object = (JsonObject) peekStack();
91     push(object.entrySet().iterator());
92   }
93 
endObject()94   @Override public void endObject() throws IOException {
95     expect(JsonToken.END_OBJECT);
96     pathNames[stackSize - 1] = null; // Free the last path name so that it can be garbage collected
97     popStack(); // empty iterator
98     popStack(); // object
99     if (stackSize > 0) {
100       pathIndices[stackSize - 1]++;
101     }
102   }
103 
hasNext()104   @Override public boolean hasNext() throws IOException {
105     JsonToken token = peek();
106     return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY && token != JsonToken.END_DOCUMENT;
107   }
108 
peek()109   @Override public JsonToken peek() throws IOException {
110     if (stackSize == 0) {
111       return JsonToken.END_DOCUMENT;
112     }
113 
114     Object o = peekStack();
115     if (o instanceof Iterator) {
116       boolean isObject = stack[stackSize - 2] instanceof JsonObject;
117       Iterator<?> iterator = (Iterator<?>) o;
118       if (iterator.hasNext()) {
119         if (isObject) {
120           return JsonToken.NAME;
121         } else {
122           push(iterator.next());
123           return peek();
124         }
125       } else {
126         return isObject ? JsonToken.END_OBJECT : JsonToken.END_ARRAY;
127       }
128     } else if (o instanceof JsonObject) {
129       return JsonToken.BEGIN_OBJECT;
130     } else if (o instanceof JsonArray) {
131       return JsonToken.BEGIN_ARRAY;
132     } else if (o instanceof JsonPrimitive) {
133       JsonPrimitive primitive = (JsonPrimitive) o;
134       if (primitive.isString()) {
135         return JsonToken.STRING;
136       } else if (primitive.isBoolean()) {
137         return JsonToken.BOOLEAN;
138       } else if (primitive.isNumber()) {
139         return JsonToken.NUMBER;
140       } else {
141         throw new AssertionError();
142       }
143     } else if (o instanceof JsonNull) {
144       return JsonToken.NULL;
145     } else if (o == SENTINEL_CLOSED) {
146       throw new IllegalStateException("JsonReader is closed");
147     } else {
148       throw new MalformedJsonException("Custom JsonElement subclass " + o.getClass().getName() + " is not supported");
149     }
150   }
151 
peekStack()152   private Object peekStack() {
153     return stack[stackSize - 1];
154   }
155 
popStack()156   private Object popStack() {
157     Object result = stack[--stackSize];
158     stack[stackSize] = null;
159     return result;
160   }
161 
expect(JsonToken expected)162   private void expect(JsonToken expected) throws IOException {
163     if (peek() != expected) {
164       throw new IllegalStateException(
165           "Expected " + expected + " but was " + peek() + locationString());
166     }
167   }
168 
nextName(boolean skipName)169   private String nextName(boolean skipName) throws IOException {
170     expect(JsonToken.NAME);
171     Iterator<?> i = (Iterator<?>) peekStack();
172     Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
173     String result = (String) entry.getKey();
174     pathNames[stackSize - 1] = skipName ? "<skipped>" : result;
175     push(entry.getValue());
176     return result;
177   }
178 
nextName()179   @Override public String nextName() throws IOException {
180     return nextName(false);
181   }
182 
nextString()183   @Override public String nextString() throws IOException {
184     JsonToken token = peek();
185     if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
186       throw new IllegalStateException(
187           "Expected " + JsonToken.STRING + " but was " + token + locationString());
188     }
189     String result = ((JsonPrimitive) popStack()).getAsString();
190     if (stackSize > 0) {
191       pathIndices[stackSize - 1]++;
192     }
193     return result;
194   }
195 
nextBoolean()196   @Override public boolean nextBoolean() throws IOException {
197     expect(JsonToken.BOOLEAN);
198     boolean result = ((JsonPrimitive) popStack()).getAsBoolean();
199     if (stackSize > 0) {
200       pathIndices[stackSize - 1]++;
201     }
202     return result;
203   }
204 
nextNull()205   @Override public void nextNull() throws IOException {
206     expect(JsonToken.NULL);
207     popStack();
208     if (stackSize > 0) {
209       pathIndices[stackSize - 1]++;
210     }
211   }
212 
nextDouble()213   @Override public double nextDouble() throws IOException {
214     JsonToken token = peek();
215     if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
216       throw new IllegalStateException(
217           "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
218     }
219     double result = ((JsonPrimitive) peekStack()).getAsDouble();
220     if (!isLenient() && (Double.isNaN(result) || Double.isInfinite(result))) {
221       throw new MalformedJsonException("JSON forbids NaN and infinities: " + result);
222     }
223     popStack();
224     if (stackSize > 0) {
225       pathIndices[stackSize - 1]++;
226     }
227     return result;
228   }
229 
nextLong()230   @Override public long nextLong() throws IOException {
231     JsonToken token = peek();
232     if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
233       throw new IllegalStateException(
234           "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
235     }
236     long result = ((JsonPrimitive) peekStack()).getAsLong();
237     popStack();
238     if (stackSize > 0) {
239       pathIndices[stackSize - 1]++;
240     }
241     return result;
242   }
243 
nextInt()244   @Override public int nextInt() throws IOException {
245     JsonToken token = peek();
246     if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
247       throw new IllegalStateException(
248           "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
249     }
250     int result = ((JsonPrimitive) peekStack()).getAsInt();
251     popStack();
252     if (stackSize > 0) {
253       pathIndices[stackSize - 1]++;
254     }
255     return result;
256   }
257 
nextJsonElement()258   JsonElement nextJsonElement() throws IOException {
259     final JsonToken peeked = peek();
260     if (peeked == JsonToken.NAME
261         || peeked == JsonToken.END_ARRAY
262         || peeked == JsonToken.END_OBJECT
263         || peeked == JsonToken.END_DOCUMENT) {
264       throw new IllegalStateException("Unexpected " + peeked + " when reading a JsonElement.");
265     }
266     final JsonElement element = (JsonElement) peekStack();
267     skipValue();
268     return element;
269   }
270 
close()271   @Override public void close() throws IOException {
272     stack = new Object[] { SENTINEL_CLOSED };
273     stackSize = 1;
274   }
275 
skipValue()276   @Override public void skipValue() throws IOException {
277     JsonToken peeked = peek();
278     switch (peeked) {
279       case NAME:
280         @SuppressWarnings("unused")
281         String unused = nextName(true);
282         break;
283       case END_ARRAY:
284         endArray();
285         break;
286       case END_OBJECT:
287         endObject();
288         break;
289       case END_DOCUMENT:
290         // Do nothing
291         break;
292       default:
293         popStack();
294         if (stackSize > 0) {
295           pathIndices[stackSize - 1]++;
296         }
297         break;
298     }
299   }
300 
toString()301   @Override public String toString() {
302     return getClass().getSimpleName() + locationString();
303   }
304 
promoteNameToValue()305   public void promoteNameToValue() throws IOException {
306     expect(JsonToken.NAME);
307     Iterator<?> i = (Iterator<?>) peekStack();
308     Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
309     push(entry.getValue());
310     push(new JsonPrimitive((String) entry.getKey()));
311   }
312 
push(Object newTop)313   private void push(Object newTop) {
314     if (stackSize == stack.length) {
315       int newLength = stackSize * 2;
316       stack = Arrays.copyOf(stack, newLength);
317       pathIndices = Arrays.copyOf(pathIndices, newLength);
318       pathNames = Arrays.copyOf(pathNames, newLength);
319     }
320     stack[stackSize++] = newTop;
321   }
322 
getPath(boolean usePreviousPath)323   private String getPath(boolean usePreviousPath) {
324     StringBuilder result = new StringBuilder().append('$');
325     for (int i = 0; i < stackSize; i++) {
326       if (stack[i] instanceof JsonArray) {
327         if (++i < stackSize && stack[i] instanceof Iterator) {
328           int pathIndex = pathIndices[i];
329           // If index is last path element it points to next array element; have to decrement
330           // `- 1` covers case where iterator for next element is on stack
331           // `- 2` covers case where peek() already pushed next element onto stack
332           if (usePreviousPath && pathIndex > 0 && (i == stackSize - 1 || i == stackSize - 2)) {
333             pathIndex--;
334           }
335           result.append('[').append(pathIndex).append(']');
336         }
337       } else if (stack[i] instanceof JsonObject) {
338         if (++i < stackSize && stack[i] instanceof Iterator) {
339           result.append('.');
340           if (pathNames[i] != null) {
341             result.append(pathNames[i]);
342           }
343         }
344       }
345     }
346     return result.toString();
347   }
348 
getPreviousPath()349   @Override public String getPreviousPath() {
350     return getPath(true);
351   }
352 
getPath()353   @Override public String getPath() {
354     return getPath(false);
355   }
356 
locationString()357   private String locationString() {
358     return " at path " + getPath();
359   }
360 }
361