/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.internal.bind;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* This reader walks the elements of a JsonElement as if it was coming from a
* character stream.
*
* @author Jesse Wilson
*/
public final class JsonTreeReader extends JsonReader {
private static final Reader UNREADABLE_READER = new Reader() {
@Override public int read(char[] buffer, int offset, int count) throws IOException {
throw new AssertionError();
}
@Override public void close() throws IOException {
throw new AssertionError();
}
};
private static final Object SENTINEL_CLOSED = new Object();
private final List<Object> stack = new ArrayList<Object>();
public JsonTreeReader(JsonElement element) {
super(UNREADABLE_READER);
stack.add(element);
}
@Override public void beginArray() throws IOException {
expect(JsonToken.BEGIN_ARRAY);
JsonArray array = (JsonArray) peekStack();
stack.add(array.iterator());
}
@Override public void endArray() throws IOException {
expect(JsonToken.END_ARRAY);
popStack(); // empty iterator
popStack(); // array
}
@Override public void beginObject() throws IOException {
expect(JsonToken.BEGIN_OBJECT);
JsonObject object = (JsonObject) peekStack();
stack.add(object.entrySet().iterator());
}
@Override public void endObject() throws IOException {
expect(JsonToken.END_OBJECT);
popStack(); // empty iterator
popStack(); // object
}
@Override public boolean hasNext() throws IOException {
JsonToken token = peek();
return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
}
@Override public JsonToken peek() throws IOException {
if (stack.isEmpty()) {
return JsonToken.END_DOCUMENT;
}
Object o = peekStack();
if (o instanceof Iterator) {
boolean isObject = stack.get(stack.size() - 2) instanceof JsonObject;
Iterator<?> iterator = (Iterator<?>) o;
if (iterator.hasNext()) {
if (isObject) {
return JsonToken.NAME;
} else {
stack.add(iterator.next());
return peek();
}
} else {
return isObject ? JsonToken.END_OBJECT : JsonToken.END_ARRAY;
}
} else if (o instanceof JsonObject) {
return JsonToken.BEGIN_OBJECT;
} else if (o instanceof JsonArray) {
return JsonToken.BEGIN_ARRAY;
} else if (o instanceof JsonPrimitive) {
JsonPrimitive primitive = (JsonPrimitive) o;
if (primitive.isString()) {
return JsonToken.STRING;
} else if (primitive.isBoolean()) {
return JsonToken.BOOLEAN;
} else if (primitive.isNumber()) {
return JsonToken.NUMBER;
} else {
throw new AssertionError();
}
} else if (o instanceof JsonNull) {
return JsonToken.NULL;
} else if (o == SENTINEL_CLOSED) {
throw new IllegalStateException("JsonReader is closed");
} else {
throw new AssertionError();
}
}
private Object peekStack() {
return stack.get(stack.size() - 1);
}
private Object popStack() {
return stack.remove(stack.size() - 1);
}
private void expect(JsonToken expected) throws IOException {
if (peek() != expected) {
throw new IllegalStateException("Expected " + expected + " but was " + peek());
}
}
@Override public String nextName() throws IOException {
expect(JsonToken.NAME);
Iterator<?> i = (Iterator<?>) peekStack();
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
stack.add(entry.getValue());
return (String) entry.getKey();
}
@Override public String nextString() throws IOException {
JsonToken token = peek();
if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
throw new IllegalStateException("Expected " + JsonToken.STRING + " but was " + token);
}
return ((JsonPrimitive) popStack()).getAsString();
}
@Override public boolean nextBoolean() throws IOException {
expect(JsonToken.BOOLEAN);
return ((JsonPrimitive) popStack()).getAsBoolean();
}
@Override public void nextNull() throws IOException {
expect(JsonToken.NULL);
popStack();
}
@Override public double nextDouble() throws IOException {
JsonToken token = peek();
if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
throw new IllegalStateException("Expected " + JsonToken.NUMBER + " but was " + token);
}
double result = ((JsonPrimitive) peekStack()).getAsDouble();
if (!isLenient() && (Double.isNaN(result) || Double.isInfinite(result))) {
throw new NumberFormatException("JSON forbids NaN and infinities: " + result);
}
popStack();
return result;
}
@Override public long nextLong() throws IOException {
JsonToken token = peek();
if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
throw new IllegalStateException("Expected " + JsonToken.NUMBER + " but was " + token);
}
long result = ((JsonPrimitive) peekStack()).getAsLong();
popStack();
return result;
}
@Override public int nextInt() throws IOException {
JsonToken token = peek();
if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
throw new IllegalStateException("Expected " + JsonToken.NUMBER + " but was " + token);
}
int result = ((JsonPrimitive) peekStack()).getAsInt();
popStack();
return result;
}
@Override public void close() throws IOException {
stack.clear();
stack.add(SENTINEL_CLOSED);
}
@Override public void skipValue() throws IOException {
if (peek() == JsonToken.NAME) {
nextName();
} else {
popStack();
}
}
@Override public String toString() {
return getClass().getSimpleName();
}
public void promoteNameToValue() throws IOException {
expect(JsonToken.NAME);
Iterator<?> i = (Iterator<?>) peekStack();
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
stack.add(entry.getValue());
stack.add(new JsonPrimitive((String)entry.getKey()));
}
}