package restservices.util;
import java.io.OutputStream;
import java.util.Stack;
import org.apache.commons.lang3.StringEscapeUtils;
import com.mendix.thirdparty.org.json.JSONArray;
import com.mendix.thirdparty.org.json.JSONObject;
import restservices.RestServices;
public class DataWriter {
private static class State {
public State()
{
// empty constructor
}
boolean hasSomething = false;
boolean isArray = false;
boolean isObject = false;
boolean isKey = false;
boolean isListItem = false;
String key = null;
}
public static final int JSON = 0;
public static final int XML = 1;
public static final int HTML = 2;
private int mode;
private Stack<State> states = new Stack<State>();
private OutputStream writer;
public DataWriter(OutputStream writer, int mode) {
this.mode = mode;
this.writer = writer;
states.push(new State()); //root state to avoid NPE's
}
public DataWriter array() {
writeValueStart();
states.push(new State());
state().isArray = true;
if (mode == JSON)
write("[");
else if (mode == HTML)
write("<ol>");
return this;
}
public DataWriter endArray() {
writeValueEnd();
assrt(state().isArray, "unexpected endArray");
if (mode == JSON)
write("]");
else if (mode == HTML)
write("</ol>");
states.pop();
return this;
}
public DataWriter object() {
writeValueStart();
states.push(new State());
state().isObject = true;
if (mode == JSON)
write("{");
else if (mode == HTML)
write("\n<table class=\"table-nested-").write(states.size() % 4 == 0 ? "even" : "odd").write("\">");
return this;
}
public DataWriter endObject() {
writeValueEnd();
assrt(state().isObject, "unexpected endArray");
if (mode == JSON)
write("}");
else if (mode == HTML)
write("\n</table>");
states.pop();
writeValueEnd();
return this;
}
public DataWriter key(String keyName) {
writeValueEnd();
assrt(state().isObject, "Key can only be used in state 'beginObject'");
writeValueStart();
State s = new State();
s.isKey = true;
s.key = mode == XML ? keyName.replaceAll("[^a-zA-Z0-9_]", "_") : keyName;
states.push(s);
if (mode == JSON)
write(JSONObject.quote(s.key)).write(":");
else if (mode == XML)
write("<").write(s.key).write(">");
else if (mode == HTML)
write("\n<tr><td>").write(StringEscapeUtils.escapeHtml4(s.key)).write("</td><td>");
return this;
}
public DataWriter value(Object value) {
if (value == null || value == JSONObject.NULL)
writeString(null);
else if (value instanceof String)
writeString((String) value);
else if (value instanceof JSONObject)
writeJSONObject((JSONObject)value);
else if (value instanceof JSONArray)
writeJSONArray((JSONArray) value);
else if (value instanceof Long)
value((long)(Long) value);
else if (value instanceof Double)
value((double)(Double) value);
else if (value instanceof Integer)
value((long)(Integer) value);
else if (value instanceof Float)
value((double)(Float) value);
else if (value instanceof Boolean)
value((boolean)(Boolean)value);
else
assrt(false, "Expected String, Number, JSONObject or JSONArray");
return this;
}
private DataWriter writeString(String value) {
writeValueStart();
if (value == null) {
if (mode == JSON)
write("null");
else if (mode == HTML)
write("<p class='null'><none></p>");
}
else {
if (mode == JSON)
write(JSONObject.valueToString(value));
else if (mode == XML)
write(StringEscapeUtils.escapeXml(value));
else if (mode == HTML)
write(Utils.autoGenerateLink(StringEscapeUtils.escapeHtml4(value)));
}
writeValueEnd();
return this;
}
public DataWriter value(long value) {
return value(Long.toString(value));
}
public DataWriter value(double value) {
return value(Double.toString(value));
}
public DataWriter writeNull() {
return value(null);
}
public DataWriter value(boolean value) {
return value(value ? "true" : "false");
}
private DataWriter writeJSONObject(JSONObject json) {
if (mode == JSON) {
writeValueStart();
write(json.toString(2));
writeValueEnd();
}
else {
object();
String[] names = JSONObject.getNames(json); //MWE json bug, empty object returns null instead of empty array...
if (names != null) for(String key : names)
key(key).value(json.get(key));
endObject();
}
return this;
}
private DataWriter writeJSONArray(JSONArray json) {
if (mode == JSON) {
writeValueStart();
write(json.toString(2));
writeValueEnd();
}
else {
array();
for(int i = 0, l = json.length(); i < l; i++)
value(json.get(i));
endArray();
}
return this;
}
private void writeValueStart() {
if (mode == JSON && (state().isArray || state().isObject) && state().hasSomething)
write(",");
state().hasSomething = true;
if (state().isArray) {
states.push(new State());
state().isListItem = true;
if (mode == XML)
write("<item>");
else if (mode == HTML)
write("\n<li>");
}
}
private void writeValueEnd() {
if (state().isKey){
if (mode == XML)
write("</").write(state().key).write(">");
else if (mode == HTML)
write("</td></tr>");
states.pop();
}
else if (state().isListItem) {
if (mode == XML)
write("</item>");
if (mode == HTML)
write("</li>");
states.pop();
}
}
private State state() {
return states.peek();
}
private DataWriter write(String data) {
try {
this.writer.write(data.getBytes(RestServices.UTF8));
} catch (Exception e) {
throw new RuntimeException(e);
}
return this;
}
private void assrt(boolean value, String msg) {
if (!value)
throw new IllegalStateException(this.getClass().getName() + " " + msg);
}
}