package com.nominanuda.dataobject;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Stack;
import com.nominanuda.code.ThreadSafe;
import com.nominanuda.lang.Check;
import com.nominanuda.lang.Maths;
import static com.nominanuda.dataobject.DataType.*;
public class JsonPrinter implements JsonContentHandler {
private final Writer w;
private final CommaInsCtx commas = new CommaInsCtx();
private final boolean pretty;
private final boolean unicodeEscapeAll;
private boolean escapeSlash;
public JsonPrinter(Writer writer) {
this(writer, false, false, false);
}
public JsonPrinter(Writer writer, boolean pretty) {
this(writer, pretty, false, false);
}
public JsonPrinter(Writer writer, boolean pretty, boolean unicodeEscapeAll, boolean escapeSlash) {
this.w = Check.notNull(writer);
this.pretty = pretty;
this.unicodeEscapeAll = unicodeEscapeAll;
this.escapeSlash = escapeSlash;
}
public void startJSON() throws RuntimeException {
}
public void endJSON() throws RuntimeException {
try {
w.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public boolean startObject() throws RuntimeException {
try {
commas.startObject();
w.write("{");
indent++;
indent();
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
public boolean endObject() throws RuntimeException {
try {
commas.endObject();
indent--;
indent();
w.write("}");
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
public boolean startObjectEntry(String key) throws RuntimeException {
try {
commas.startObjectEntry(key);
w.write("\"");
stringEncode((String) key, w);
w.write("\":");
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
public boolean endObjectEntry() throws RuntimeException {
commas.endObjectEntry();
return true;
}
public boolean startArray() throws RuntimeException {
try {
commas.startArray();
w.write("[");
indent++;
indent();
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
public boolean endArray() throws RuntimeException {
try {
commas.endArray();
indent--;
indent();
w.write("]");
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
public boolean primitive(Object o) throws RuntimeException {
try {
commas.primitive(o);
if (o == null) {
w.write("null");
} else if (o instanceof Number) {
w.write(numberEncode((Number) o));
} else if (o instanceof String) {
w.write("\"");
stringEncode((String) o, w);
w.write("\"");
} else if (o instanceof Boolean) {
w.write(booleanEncode((Boolean) o));
} else {
throw new IllegalStateException();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
@ThreadSafe public String numberEncode(Number n) {
return Maths.isInteger(n.doubleValue())
? new Long(n.longValue()).toString()
: n.toString();
}
@ThreadSafe public String booleanEncode(Boolean b) {
return b.toString();
}
@ThreadSafe public String stringEncode(String s) {
StringWriter sw = new StringWriter();
try {
stringEncode(s, sw);
} catch (IOException e) {
throw new RuntimeException(e);//never happens
}
return sw.toString();
}
/**
* \" \\ \/ \b \f \n \r \t \u1234 four-hex-digits
*/
@ThreadSafe public void stringEncode(String s, Writer writer) throws IOException {
char[] carr = s.toCharArray();
int len = carr.length;
for(int i = 0; i < len; i++) {
char c = carr[i];
switch (c) {
case '"':
writer.write("\\\"");
break;
case '/':
writer.write(escapeSlash ? "\\/" : "/");
break;
case '\\':
writer.write("\\\\");
break;
case '\b':
writer.write("\\b");
break;
case '\f':
writer.write("\\f");
break;
case '\n':
writer.write("\\n");
break;
case '\r':
writer.write("\\r");
break;
case '\t':
writer.write("\\t");
break;
default:
if(unicodeEscapeAll) {
if(c < 127) {
writer.append(c);
} else {
String hex = Integer.toHexString(c);
writer.write("\\u");
switch (hex.length()) {
case 1:
writer.write("000");
break;
case 2:
writer.write("00");
break;
case 3:
writer.write("0");
break;
default:
break;
}
writer.write(Integer.toHexString(c));
}
} else {
writer.append(c);
}
break;
}
}
}
private class Cx {
public DataType t;
public boolean firstGone;
public Cx(DataType t) {
this.t = t;
this.firstGone = false;
}
}
private int indent = 0;
private final static String[] INDENTS = new String[] {
""," "," "," "," "," "," "
};
//TODO
private void indent() throws RuntimeException {
if(pretty) {
try {
w.write("\n");
w.write(indentSpaces(indent));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private String indentSpaces(int size) {
if(size < INDENTS.length) {
return INDENTS[size];
} else {
char[] carr = new char[size];
for(int i = 0; i < size; i++) {
carr[i] = ' ';
}
return new String(carr);
}
}
private class CommaInsCtx implements JsonContentHandler {
Stack<Cx> stack = new Stack<Cx>();
public void startJSON() throws RuntimeException {
}
public void endJSON() throws RuntimeException {
}
public boolean startObject() throws RuntimeException {
startValue();
stack.push(new Cx(object));
return true;
}
public boolean endObject() throws RuntimeException {
stack.pop();
return true;
}
public boolean startObjectEntry(String key) throws RuntimeException {
Cx cx = stack.peek();
if(cx.firstGone) {
try {
w.write(",");
indent();
} catch (IOException e) {
}
} else {
cx.firstGone = true;
}
return true;
}
public boolean endObjectEntry() throws RuntimeException {
return true;
}
public boolean startArray() throws RuntimeException {
startValue();
stack.push(new Cx(array));
return true;
}
public boolean endArray() throws RuntimeException {
stack.pop();
return true;
}
public boolean primitive(Object value) throws RuntimeException {
startValue();
return true;
}
private void startValue() throws RuntimeException {
if(stack.isEmpty()) {
return;
}
Cx cx = stack.peek();
if(cx.t == array) {
if(cx.firstGone) {
try {
w.write(",");
indent();
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
cx.firstGone = true;
}
}
}
}
}