/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.react.bridge; import java.io.Closeable; import java.io.IOException; import java.io.Writer; import java.util.ArrayDeque; import java.util.Deque; /** * Simple Json generator that does no validation. */ public class JsonWriter implements Closeable { private final Writer mWriter; private final Deque<Scope> mScopes; public JsonWriter(Writer writer) { mWriter = writer; mScopes = new ArrayDeque<>(); } public JsonWriter beginArray() throws IOException { open(Scope.EMPTY_ARRAY, '['); return this; } public JsonWriter endArray() throws IOException { close(']'); return this; } public JsonWriter beginObject() throws IOException { open(Scope.EMPTY_OBJECT, '{'); return this; } public JsonWriter endObject() throws IOException { close('}'); return this; } public JsonWriter name(String name) throws IOException { if (name == null) { throw new NullPointerException("name can not be null"); } beforeName(); string(name); mWriter.write(':'); return this; } public JsonWriter value(String value) throws IOException { if (value == null) { return nullValue(); } beforeValue(); string(value); return this; } public JsonWriter nullValue() throws IOException { beforeValue(); mWriter.write("null"); return this; } public JsonWriter rawValue(String json) throws IOException { beforeValue(); mWriter.write(json); return this; } public JsonWriter value(boolean value) throws IOException { beforeValue(); mWriter.write(value ? "true" : "false"); return this; } public JsonWriter value(double value) throws IOException { beforeValue(); mWriter.append(Double.toString(value)); return this; } public JsonWriter value(long value) throws IOException { beforeValue(); mWriter.write(Long.toString(value)); return this; } public JsonWriter value(Number value) throws IOException { if (value == null) { return nullValue(); } beforeValue(); mWriter.append(value.toString()); return this; } @Override public void close() throws IOException { mWriter.close(); } private void beforeValue() throws IOException { Scope scope = mScopes.peek(); switch (scope) { case EMPTY_ARRAY: replace(Scope.ARRAY); break; case EMPTY_OBJECT: throw new IllegalArgumentException(Scope.EMPTY_OBJECT.name()); case ARRAY: mWriter.write(','); break; case OBJECT: break; default: throw new IllegalStateException("Unknown scope: " + scope); } } private void beforeName() throws IOException { Scope scope = mScopes.peek(); switch (scope) { case EMPTY_ARRAY: case ARRAY: throw new IllegalStateException("name not allowed in array"); case EMPTY_OBJECT: replace(Scope.OBJECT); break; case OBJECT: mWriter.write(','); break; default: throw new IllegalStateException("Unknown scope: " + scope); } } private void open(Scope scope, char bracket) throws IOException { mScopes.push(scope); mWriter.write(bracket); } private void close(char bracket) throws IOException { mScopes.pop(); mWriter.write(bracket); } private void string(String string) throws IOException { mWriter.write('"'); for (int i = 0, length = string.length(); i < length; i++) { char c = string.charAt(i); switch (c) { case '\t': mWriter.write("\\t"); break; case '\b': mWriter.write("\\b"); break; case '\n': mWriter.write("\\n"); break; case '\r': mWriter.write("\\r"); break; case '\f': mWriter.write("\\f"); break; case '"': case '\\': mWriter.write('\\'); mWriter.write(c); break; case '\u2028': case '\u2029': mWriter.write(String.format("\\u%04x", (int) c)); break; default: if (c <= 0x1F) { mWriter.write(String.format("\\u%04x", (int) c)); } else { mWriter.write(c); } break; } } mWriter.write('"'); } private void replace(Scope scope) { mScopes.pop(); mScopes.push(scope); } private enum Scope { EMPTY_OBJECT, OBJECT, EMPTY_ARRAY, ARRAY } }