package org.rascalmpl.library.lang.json.io;
import java.io.IOException;
import org.rascalmpl.interpreter.utils.RuntimeExceptionFactory;
import org.rascalmpl.library.lang.json.Factory;
import org.rascalmpl.value.IBool;
import org.rascalmpl.value.IConstructor;
import org.rascalmpl.value.IDateTime;
import org.rascalmpl.value.IExternalValue;
import org.rascalmpl.value.IInteger;
import org.rascalmpl.value.IList;
import org.rascalmpl.value.IMap;
import org.rascalmpl.value.INode;
import org.rascalmpl.value.IRational;
import org.rascalmpl.value.IReal;
import org.rascalmpl.value.ISet;
import org.rascalmpl.value.ISourceLocation;
import org.rascalmpl.value.IString;
import org.rascalmpl.value.ITuple;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.IWithKeywordParameters;
import org.rascalmpl.value.impl.ConstructorWithKeywordParametersFacade;
import org.rascalmpl.value.impl.NodeWithKeywordParametersFacade;
import org.rascalmpl.value.visitors.IValueVisitor;
import com.google.gson.stream.JsonWriter;
public class JSONWritingValueVisitor implements IValueVisitor<Void, IOException> {
private final JsonWriter out;
private final boolean compact;
public JSONWritingValueVisitor(JsonWriter out, boolean compact) {
this.out = out;
this.compact = compact;
}
public static void write(JsonWriter out, IValue value, boolean compact)
throws IOException {
value.accept(new JSONWritingValueVisitor(out, compact));
}
public static void write(JsonWriter out, IValue value)
throws IOException {
write(out, value, false);
}
@Override
public Void visitReal(IReal value) throws IOException {
// {real: n}
if (compact) {
out.value(value.doubleValue());
} else {
out.beginArray()
.value("real")
.value(value.doubleValue())
.endArray();
}
return null;
}
@Override
public Void visitInteger(IInteger value) throws IOException {
// {int: n}
if (compact) {
out.value(value.intValue());
} else {
out.beginArray()
.value("int")
.value(((IInteger) value).intValue())
.endArray();
}
return null;
}
@Override
public Void visitRational(IRational value) throws IOException {
// {rat: [n, d] }
if (compact) {
throw RuntimeExceptionFactory.illegalTypeArgument(value.toString(),
null, null, "cannot serialize rational types");
}
else
out.beginArray()
.value("rat")
.beginArray()
.value(((IRational) value).numerator().longValue())
.value(((IRational) value).denominator().longValue())
.endArray()
.endArray();
return null;
}
@Override
public Void visitList(IList value) throws IOException {
// {list: [ ... ] }
if (compact) {
out.beginArray();
for (IValue v : (IList) value) {
write(out, v, compact);
}
out.endArray();
}
else
{
out.beginArray()
.value("list")
.beginArray();
for (IValue v : (IList) value) {
write(out, v);
}
out.endArray()
.endArray();
}
return null;
}
@Override
public Void visitMap(IMap value) throws IOException {
// {map: [ [k, v], [k, v] ] }
if (compact) {
out.beginObject();
for (IValue k : (IMap) value) {
if (k.getType().isString()) {
String s = ((IString) k).getValue();
out.name(s);
write(out, ((IMap) value).get(k), compact);
} else
throw new IOException("Not possible to translate: " + value);
}
out.endObject();
} else {
out.beginArray()
.value("map")
.beginArray();
for (IValue k : (IMap) value) {
out.beginArray();
write(out, k);
write(out, ((IMap) value).get(k));
out.endArray();
}
out.endArray()
.endArray();
}
return null;
}
@Override
public Void visitSet(ISet value) throws IOException {
// {set: [.... ]}
if (compact) {
throw RuntimeExceptionFactory.illegalTypeArgument(value.toString(),
null, null, "cannot serialize set types");
} else {
out.beginArray();
out.value("set");
out.beginArray();
for (IValue v : (ISet) value) {
write(out, v);
}
out.endArray();
out.endArray();
}
return null;
}
@Override
public Void visitSourceLocation(ISourceLocation value) throws IOException {
if (compact) {
throw RuntimeExceptionFactory.illegalTypeArgument(value.toString(),
null, null, "cannot serialize location types");
} else {
// {loc: {...} }
out.beginArray();
out.value("loc");
out.beginObject();
ISourceLocation loc = (ISourceLocation) value;
out.name("scheme");
out.value(loc.getScheme());
if (loc.hasAuthority()) {
out.name("authority");
out.value(loc.getAuthority());
}
if (loc.hasPath()) {
out.name("path");
out.value(loc.getPath());
}
if (loc.hasFragment()) {
out.name("fragment");
out.value(loc.getFragment());
}
if (loc.hasQuery()) {
out.name("query");
out.value(loc.getQuery());
}
if (loc.hasOffsetLength()) {
out.name("offset");
out.value(loc.getOffset());
out.name("length");
out.value(loc.getLength());
}
if (loc.hasLineColumn()) {
out.name("beginLine");
out.value(loc.getBeginLine());
out.name("endLine");
out.value(loc.getEndLine());
out.name("beginColumn");
out.value(loc.getBeginColumn());
out.name("endColumn");
out.value(loc.getEndColumn());
}
out.endObject();
out.endArray();
}
return null;
}
@Override
public Void visitString(IString value) throws IOException {
if (compact) {
out.value(((IString) value).getValue());
} else {
out.beginArray()
.value("str")
.value(((IString) value).getValue())
.endArray();
}
return null;
}
@Override
public Void visitNode(INode value) throws IOException {
// ["node", ["name", arity, [...]] ]
if (compact) {
throw RuntimeExceptionFactory.illegalTypeArgument(value.toString(),
null, null, "cannot serialize node types");
} else {
out.beginArray();
out.value("node");
out.beginArray();
INode n = (INode) value;
out.value(n.getName());
out.value(n.arity());
out.beginArray();
for (IValue v : n.getChildren()) {
write(out, v);
}
out.endArray();
// if (!value.asAnnotatable().hasAnnotations()) {
// temp hack
if (value instanceof NodeWithKeywordParametersFacade) {
IWithKeywordParameters<? extends INode> kw = value.asWithKeywordParameters();
if (kw.hasParameters()) {
out.beginObject();
for (String k : kw.getParameterNames()) {
out.name(k);
write(out, kw.getParameter(k));
}
out.endObject();
}
}
// }
out.endArray();
out.endArray();
}
return null;
}
@Override
public Void visitConstructor(IConstructor value) throws IOException {
if (compact) {
throw RuntimeExceptionFactory.illegalTypeArgument(value.toString(),
null, null, "cannot serialize constructor types");
}
else {
if (value.getType().getAbstractDataType() == Factory.JSON) {
return writePlainJSON(value);
}
// ["cons", ["name", arity, [...], { }]]
out.beginArray();
out.value("cons");
out.beginArray();
out.value(value.getName());
out.value(value.arity());
out.beginArray();
for (IValue v : value.getChildren()) {
write(out, v);
}
out.endArray();
//if (!value.asAnnotatable().hasAnnotations()) {
// temp hack
if (value instanceof ConstructorWithKeywordParametersFacade) {
IWithKeywordParameters<? extends INode> kw = value.asWithKeywordParameters();
if (kw.hasParameters()) {
out.beginObject();
for (String k : kw.getParameterNames()) {
out.name(k);
write(out, kw.getParameter(k));
}
out.endObject();
}
}
// }
out.endArray();
out.endArray();
}
return null;
}
private Void writePlainJSON(IConstructor value) throws IndexOutOfBoundsException, IOException {
switch (value.getName()) {
case "null":
out.nullValue();
break;
case "object":
IMap props = (IMap) value.get(0);
out.beginObject();
for (IValue k: props) {
out.name(((IString)k).getValue());
writePlainJSON((IConstructor) props.get(k));
}
out.endObject();
break;
case "array":
IList vals = (IList) value.get(0);
out.beginArray();
for (IValue v: vals) {
writePlainJSON((IConstructor) v);
}
out.endArray();
break;
case "number":
out.value(((IReal)value.get(0)).doubleValue());
break;
case "string":
out.value(((IString)value.get(0)).getValue());
break;
case "boolean":
out.value(((IBool)value.get(0)).getValue());
break;
case "ivalue":
out.beginObject();
out.name("#value");
value.get(0).accept(this);
out.endObject();
break;
default:
throw new IOException("invalid JSON constructor " + value);
}
return null;
}
@Override
public Void visitTuple(ITuple value) throws IOException {
// {tuple: [ ... ]}
if (compact) {
throw RuntimeExceptionFactory.illegalTypeArgument(value.toString(),
null, null, "cannot serialize tuple types");
} else {
out.beginArray()
.value("tuple");
out.beginArray();
ITuple t = (ITuple) value;
for (int i = 0; i < t.arity(); i++) {
write(out, t.get(i));
}
out.endArray();
out.endArray();
}
return null;
}
@Override
public Void visitBoolean(IBool value) throws IOException {
// {bool: ..}
if (compact) {
out.value(((IBool) value).getValue());
} else {
out.beginArray()
.value("bool")
.value(((IBool) value).getValue())
.endArray();
}
return null;
}
@Override
public Void visitExternal(IExternalValue value) throws IOException {
throw RuntimeExceptionFactory.illegalTypeArgument(value.toString(),
null, null, "cannot serialize external types");
}
@Override
public Void visitDateTime(IDateTime value) throws IOException {
// {datetime: { }}
if (compact) {
throw RuntimeExceptionFactory.illegalTypeArgument(value.toString(),
null, null, "cannot serialize datetime types");
} else {
IDateTime dt = (IDateTime) value;
out.beginArray();
out.value("datetime");
out.beginObject();
if (dt.isDate() || dt.isDateTime()) {
out.name("year");
out.value(dt.getYear());
out.name("monthOfYear");
out.value(dt.getMonthOfYear());
out.name("dayOfMonth");
out.value(dt.getDayOfMonth());
}
if (dt.isTime() || dt.isDateTime()) {
out.name("hourOfDay");
out.value(dt.getHourOfDay());
out.name("minuteOfHour");
out.value(dt.getMinuteOfHour());
out.name("secondOfMinute");
out.value(dt.getSecondOfMinute());
out.name("millisecondsOfSecond");
out.value(dt.getMillisecondsOfSecond());
out.name("timezoneOffsetHours");
out.value(dt.getTimezoneOffsetHours());
out.name("timezoneOffsetMinutes");
out.value(dt.getTimezoneOffsetMinutes());
}
out.endObject();
out.endArray();
}
return null;
}
@Override
public Void visitRelation(ISet o) throws IOException {
visitSet(o);
return null;
}
@Override
public Void visitListRelation(IList o) throws IOException {
visitList(o);
return null;
}
}