package com.dubture.getcomposer.json;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gson.Gson;
public class JsonFormatter {
public static String format(final Object object) {
final JsonVisitor visitor = new JsonVisitor(1, '\t');
visitor.visit(object, 0);
return JsonFormatter.postProcessing(visitor.toString());
}
private static String postProcessing(String json) {
json = json.replace("[\n{", "[{");
json = json.replace("},\n{", "}, {");
return json;
}
private static class JsonVisitor {
private Gson gson = new Gson();
private final StringBuilder builder = new StringBuilder();
private final int indentationSize;
private final char indentationChar;
public JsonVisitor(final int indentationSize, final char indentationChar) {
this.indentationSize = indentationSize;
this.indentationChar = indentationChar;
}
private void visit(final List<Object> array, final int indent) {
final int length = array.size();
if (length == 0) {
write("[]", 0);
} else {
writeln("[", 0);
for (int i = 0; i < length; i++) {
visit(array.get(i), indent + 1);
if (i < length - 1) {
writeln(",", 0);
}
}
writeln("", 0);
write("]", indent);
}
}
private void visit(final Map<String, Object> obj, int indent) {
final int length = obj.size();
if (length == 0) {
write("{}", 0);
} else {
writeln("{", 0);
final Iterator<String> keys = ((Set<String>)obj.keySet()).iterator();
while (keys.hasNext()) {
final String key = keys.next();
write("\"" + escape(key) + "\" : ", indent + 1);
visit(obj.get(key), indent + 1);
if (keys.hasNext()) {
writeln(",", 0);
}
}
writeln("", 0);
write("}", indent);
}
}
@SuppressWarnings("unchecked")
private void visit(final Object object, int indent) {
if (object instanceof List) {
visit((List<Object>) object, indent);
} else if (object instanceof Map) {
visit((Map<String, Object>) object, indent);
} else {
if (builder.charAt(builder.length() - 1) != '\n') {
indent = 0;
}
if (object instanceof String) {
write("\"" + escape(String.valueOf(object)) + "\"", indent);
} else if (object instanceof Boolean || object instanceof Number) {
write(String.valueOf(object), indent);
} else {
write(gson.toJson(String.valueOf(object)), indent);
}
}
}
private void writeln(final String data, final int indent) {
write(data, indent);
builder.append('\n');
}
private void write(final String data, final int indent) {
for (int i = 0; i < (indent * indentationSize); i++) {
builder.append(indentationChar);
}
builder.append(data);
}
@Override
public String toString() {
return builder.toString();
}
private static String escape(String s) {
if (s == null || s.isEmpty()) {
return "";
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
switch(ch) {
case '"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
case '\b':
sb.append("\\b");
break;
case '\f':
sb.append("\\f");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
// see https://github.com/pulse00/Composer-Eclipse-Plugin/issues/51
/*
case '/':
sb.append("\\/");
break;
*/
default:
// Reference: http://www.unicode.org/versions/Unicode5.1.0/
if((ch>='\u0000' && ch<='\u001F') || (ch>='\u007F' && ch<='\u009F') || (ch>='\u2000' && ch<='\u20FF')){
String ss=Integer.toHexString(ch);
sb.append("\\u");
for(int k=0;k<4-ss.length();k++){
sb.append('0');
}
sb.append(ss.toUpperCase());
}
else{
sb.append(ch);
}
}
}
return sb.toString();
}
}
}