/*
* Copyright (c) 2012, Inversoft Inc., All Rights Reserved
*
* 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 org.primeframework.mvc.json;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* This class provides a JSON generator that uses the builder pattern.
*
* @author Brian Pontarelli
*/
public class JSONBuilder {
public static final char[] HEX_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private final Deque<Boolean> firstField = new ArrayDeque<Boolean>();
private final StringBuilder build;
private final boolean pretty;
public JSONBuilder(StringBuilder build, boolean pretty) {
this.build = build;
this.pretty = pretty;
firstField.push(true);
}
/**
* Starts a JSON object with no name. You need to call {@link #endObject()} when you have finished adding
* everything to this object.
*
* @return This JSONBuilder.
*/
public JSONBuilder startObject() {
addComma();
indent();
build.append("{");
if (pretty) {
build.append("\n");
}
firstField.push(true);
return this;
}
/**
* Starts a JSON object with the given name. You need to call {@link #endObject()} when you have finished adding
* everything to this object.
*
* @param name The name of the object.
* @return This JSONBuilder.
*/
public JSONBuilder startObject(String name) {
addComma();
indent();
appendString(name);
build.append(":{");
if (pretty) {
build.append("\n");
}
firstField.push(true);
return this;
}
/**
* Starts a JSON array. You need to call {@link #endArray()} when you have finished adding everything to this array.
*
* @param name The name of the array inside the current JSON object.
* @return This JSONBuilder.
*/
public JSONBuilder startArray(String name) {
addComma();
indent();
appendString(name);
build.append(":[");
if (pretty) {
build.append("\n");
}
firstField.push(true);
return this;
}
/**
* Adds a String field to the current JSON object.
*
* @param name The name of the field.
* @param value The value of the field.
* @return This JSONBuilder.
*/
public JSONBuilder addStringField(String name, String value) {
if (value == null) {
return this;
}
addComma();
indent();
appendString(name);
build.append(":");
appendString(value);
return this;
}
/**
* Adds an int field to the current JSON object.
*
* @param name The name of the field.
* @param value The value of the field.
* @return This JSONBuilder.
*/
public JSONBuilder addIntField(String name, Integer value) {
if (value == null) {
return this;
}
addComma();
indent();
appendString(name);
build.append(":").append(Integer.toString(value));
return this;
}
/**
* Adds the given enum as an int field using the enums ordinal value.
*
* @param name The field name.
* @param value The enum.
* @return This JSONBuilder.
*/
public JSONBuilder addEnumOrdinalField(String name, Enum<?> value) {
if (value == null) {
return this;
}
addIntField(name, value.ordinal());
return this;
}
/**
* Adds the given boolean field..
*
* @param name The field name.
* @param value The value.
* @return This JSONBuilder.
*/
public JSONBuilder addBooleanField(String name, Boolean value) {
if (value == null) {
return this;
}
addComma();
indent();
appendString(name);
build.append(":").append(value);
return this;
}
/**
* Ends the current JSON object.
*
* @return This JSONBuilder
*/
public JSONBuilder endObject() {
firstField.pop();
if (pretty) {
build.append("\n");
indent();
}
build.append("}");
return this;
}
/**
* Ends the current JSON array.
*
* @return This JSONBuilder
*/
public JSONBuilder endArray() {
firstField.pop();
if (pretty) {
build.append("\n");
indent();
}
build.append("]");
return this;
}
private void indent() {
if (!pretty) {
return;
}
for (int i = 1; i < firstField.size(); i++) {
build.append(" ");
}
}
private void addComma() {
if (!firstField.peek()) {
build.append(",");
if (pretty) {
build.append("\n");
}
} else {
firstField.pop();
firstField.push(false);
}
}
private void appendString(String str) {
build.append("\"");
escape(str, build);
build.append("\"");
}
public static String escape(String str) {
StringBuilder build = new StringBuilder();
escape(str, build);
return build.toString();
}
public static void escape(String str, StringBuilder build) {
char[] ca = str.toCharArray();
for (char c : ca) {
switch (c) {
case '"':
case '\\':
build.append('\\');
build.append(c);
break;
case '\r':
build.append("\\r");
break;
case '\n':
build.append("\\n");
break;
case '\t':
build.append("\\t");
break;
case '\b':
build.append("\\b");
break;
case '\f':
build.append("\\f");
break;
case '/':
build.append("\\/");
break;
default:
if (c <= 0x1F) {
unicodeEscape(c, build);
} else {
build.append(c);
}
}
}
}
public static void unicodeEscape(int ch, StringBuilder build) {
build.append('\\');
build.append('u');
build.append(HEX_CHARS[ch >>> 12]);
build.append(HEX_CHARS[(ch >>> 8) & 0xf]);
build.append(HEX_CHARS[(ch >>> 4) & 0xf]);
build.append(HEX_CHARS[ch & 0xf]);
}
/**
* @return The JSON.
*/
@Override
public String toString() {
return build.toString();
}
}