/******************************************************************************* * Copyright 2011 See AUTHORS file. * * 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.badlogic.gdx.utils; import java.io.IOException; import java.io.Writer; import java.util.regex.Pattern; /** * Builder style API for emitting JSON. * * @author Nathan Sweet */ public class JsonWriter extends Writer { Writer writer; private final Array<JsonObject> stack = new Array(); private JsonObject current; private boolean named; private OutputType outputType = OutputType.json; public JsonWriter(Writer writer) { this.writer = writer; } public void setOutputType(OutputType outputType) { this.outputType = outputType; } public JsonWriter name(String name) throws IOException { if (current == null || current.array) throw new IllegalStateException("Current item must be an object."); if (!current.needsComma) current.needsComma = true; else writer.write(','); writer.write(outputType.quoteName(name)); writer.write(':'); named = true; return this; } public JsonWriter object() throws IOException { if (current != null) { if (current.array) { if (!current.needsComma) current.needsComma = true; else writer.write(','); } else { if (!named && !current.array) throw new IllegalStateException("Name must be set."); named = false; } } stack.add(current = new JsonObject(false)); return this; } public JsonWriter array() throws IOException { if (current != null) { if (current.array) { if (!current.needsComma) current.needsComma = true; else writer.write(','); } else { if (!named && !current.array) throw new IllegalStateException("Name must be set."); named = false; } } stack.add(current = new JsonObject(true)); return this; } public JsonWriter value(Object value) throws IOException { if (current != null) { if (current.array) { if (!current.needsComma) current.needsComma = true; else writer.write(','); } else { if (!named) throw new IllegalStateException("Name must be set."); named = false; } } writer.write(outputType.quoteValue(value)); return this; } public JsonWriter object(String name) throws IOException { return name(name).object(); } public JsonWriter array(String name) throws IOException { return name(name).array(); } public JsonWriter set(String name, Object value) throws IOException { return name(name).value(value); } public JsonWriter pop() throws IOException { if (named) throw new IllegalStateException("Expected an object, array, or value since a name was set."); stack.pop().close(); current = stack.size == 0 ? null : stack.peek(); return this; } public void write(char[] cbuf, int off, int len) throws IOException { writer.write(cbuf, off, len); } public void flush() throws IOException { writer.flush(); } public void close() throws IOException { while (stack.size > 0) pop(); writer.close(); } private class JsonObject { final boolean array; boolean needsComma; JsonObject(boolean array) throws IOException { this.array = array; writer.write(array ? '[' : '{'); } void close() throws IOException { writer.write(array ? ']' : '}'); } } static public enum OutputType { /** Normal JSON, with all its quotes. */ json, /** Like JSON, but names are only quoted if necessary. */ javascript, /** * Like JSON, but names and values are only quoted if necessary. This is best for object serialization, because * it has a difference between a String field being null and "null". */ minimal; static private Pattern javascriptPattern = Pattern.compile("[a-zA-Z_$][a-zA-Z_$0-9]*"); static private Pattern minimalValuePattern = Pattern.compile("[a-zA-Z_$][^:}\\], ]*"); static private Pattern minimalNamePattern = Pattern.compile("[a-zA-Z0-9_$][^:}\\], ]*"); public String quoteValue(Object value) { if (this != OutputType.json && (value == null || value instanceof Number || value instanceof Boolean)) return String.valueOf(value); String string = String.valueOf(value).replace("\\", "\\\\"); if (this == OutputType.minimal && !string.equals("true") && !string.equals("false") && !string.equals("null") && minimalValuePattern.matcher(string).matches()) return string; return '"' + string.replace("\"", "\\\"") + '"'; } public String quoteName(String value) { value = value.replace("\\", "\\\\"); switch (this) { case minimal: if (minimalNamePattern.matcher(value).matches()) return value; return '"' + value.replace("\"", "\\\"") + '"'; case javascript: if (javascriptPattern.matcher(value).matches()) return value; return '"' + value.replace("\"", "\\\"") + '"'; default: return '"' + value.replace("\"", "\\\"") + '"'; } } } }