package org.rascalmpl.library.lang.json.io;
import static org.rascalmpl.library.lang.json.Factory.JSON;
import static org.rascalmpl.library.lang.json.Factory.JSON_array;
import static org.rascalmpl.library.lang.json.Factory.JSON_boolean;
import static org.rascalmpl.library.lang.json.Factory.JSON_ivalue;
import static org.rascalmpl.library.lang.json.Factory.JSON_null;
import static org.rascalmpl.library.lang.json.Factory.JSON_number;
import static org.rascalmpl.library.lang.json.Factory.JSON_object;
import static org.rascalmpl.library.lang.json.Factory.JSON_string;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import org.rascalmpl.interpreter.asserts.NotYetImplemented;
import org.rascalmpl.interpreter.types.NonTerminalType;
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.IListWriter;
import org.rascalmpl.value.IMap;
import org.rascalmpl.value.IMapWriter;
import org.rascalmpl.value.INode;
import org.rascalmpl.value.IRational;
import org.rascalmpl.value.IReal;
import org.rascalmpl.value.ISet;
import org.rascalmpl.value.ISetWriter;
import org.rascalmpl.value.ISourceLocation;
import org.rascalmpl.value.IString;
import org.rascalmpl.value.ITuple;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.IValueFactory;
import org.rascalmpl.value.IWithKeywordParameters;
import org.rascalmpl.value.type.ITypeVisitor;
import org.rascalmpl.value.type.Type;
import org.rascalmpl.value.type.TypeFactory;
import org.rascalmpl.value.type.TypeStore;
import org.rascalmpl.value.visitors.IValueVisitor;
import org.rascalmpl.values.uptr.RascalValueFactory;
@SuppressWarnings("rawtypes")
public class JSONReadingTypeVisitor implements
ITypeVisitor<IValue, IOException> {
private static final TypeFactory tf = TypeFactory.getInstance();
private final IValueFactory vf;
private final TypeStore ts;
private final Stack<Object> stack;
public static IValue read(Object obj, IValueFactory vf, TypeStore ts, Type t) throws IOException {
JSONReadingTypeVisitor v = new JSONReadingTypeVisitor(obj, vf, ts);
return v.read(t);
}
private JSONReadingTypeVisitor(Object obj, IValueFactory vf, TypeStore ts) {
this.stack = new Stack<Object>();
this.stack.push(obj);
this.vf = vf;
this.ts = ts;
}
private IValue read(Type type) throws IOException {
while (type.isAliased()) {
type = type.getAliased();
}
if (type == tf.valueType()) {
return visitValue(type);
}
if (type == tf.numberType()) {
return visitNumber(type);
}
if (type == JSON) {
return readPlainJSON();
}
// NB: why not treat isNode same as isNumber:
// because nodes have values, but nums do not.
stack.push(contents()); // skip tag
IValue v = type.accept(this);
stack.pop();
return v;
}
private String tag() {
assert stack.peek() instanceof List && ((List)stack.peek()).size() == 2;
List m = (List)stack.peek();
return (String) m.get(0);
}
private Object contents() {
assert stack.peek() instanceof List && ((List)stack.peek()).size() == 2;
List m = (List)stack.peek();
return m.get(1);
}
@Override
public IValue visitReal(Type type) throws IOException {
return vf.real(((Double)stack.peek()).doubleValue());
}
@Override
public IValue visitInteger(Type type) throws IOException {
return vf.integer(((Double)stack.peek()).longValue());
}
@Override
public IValue visitRational(Type type) throws IOException {
// [ num, denom ]
List l = (List)stack.peek();
long num = ((Double)l.get(0)).longValue();
long den = ((Double)l.get(1)).longValue();
return vf.rational(num, den);
}
private Type elementType(Type t) {
return t.isTop() ? tf.valueType() : t.getElementType();
}
private Type keyType(Type t) {
return t.isTop() ? tf.valueType() : t.getKeyType();
}
private Type valueType(Type t) {
return t.isTop() ? tf.valueType() : t.getValueType();
}
@SuppressWarnings("deprecation")
@Override
public IValue visitList(Type type) throws IOException {
IListWriter w = vf.listWriter(elementType(type));
List l = (List)stack.peek();
for (Object e: l) {
stack.push(e);
w.append(read(elementType(type)));
stack.pop();
}
return w.done();
}
@SuppressWarnings("deprecation")
@Override
public IValue visitMap(Type type) throws IOException {
// [ [k, v], ... ]
IMapWriter w = vf.mapWriter(keyType(type), valueType(type));
List l = (List)stack.peek();
for (Object e: l) {
List pair = (List)e;
stack.push(pair.get(0));
IValue k = read(keyType(type));
stack.pop();
stack.push(pair.get(1));
IValue v = read(valueType(type));
stack.pop();
w.put(k, v);
}
return w.done();
}
@Override
public IValue visitNumber(Type type) throws IOException {
IValue value = null;
String tag = tag();
stack.push(contents());
switch (tag) {
case "int":
value = visitInteger(type);
break;
case "real":
value = visitReal(type);
break;
case "rat":
value = visitRational(type);
break;
default:
throw new IOException("invalid tag for num: " + tag);
}
stack.pop();
return value;
}
@Override
public IValue visitAlias(Type type) throws IOException {
throw new AssertionError("alias normalization should happen in read()");
}
@SuppressWarnings("deprecation")
@Override
public IValue visitSet(Type type) throws IOException {
ISetWriter w = vf.setWriter(elementType(type));
List l = (List)stack.peek();
for (Object e: l) {
stack.push(e);
w.insert(read(elementType(type)));
stack.pop();
}
return w.done();
}
@Override
public IValue visitSourceLocation(Type type) throws IOException {
String scheme = null;
String authority = null;
String path = null;
String fragment = "";
String query = "";
int offset = -1;
int length = -1;
int beginLine = -1;
int endLine = -1;
int beginColumn = -1;
int endColumn = -1;
Map m = (Map)stack.peek();
for (Object k: m.keySet()) {
String name = (String)k;
switch (name) {
case "scheme":
scheme = (String)m.get(name);
break;
case "authority":
authority = (String)m.get(name);
break;
case "path":
path = (String)m.get(name);
break;
case "fragment":
fragment = (String)m.get(name);
break;
case "query":
query = (String)m.get(name);
break;
case "offset":
offset = ((Double)m.get(name)).intValue();
break;
case "length":
length = ((Double)m.get(name)).intValue();
break;
case "beginLine":
beginLine = ((Double)m.get(name)).intValue();
break;
case "endLine":
endLine = ((Double)m.get(name)).intValue();
break;
case "beginColumn":
beginColumn = ((Double)m.get(name)).intValue();
break;
case "endColumn":
endColumn = ((Double)m.get(name)).intValue();
break;
default:
throw new IOException(
"invalid property name in SourceLocation serialization: "
+ name);
}
}
if (path != null && offset != -1 && length != -1 && beginLine != -1 && endLine != -1 && beginColumn != -1 && endColumn != -1) {
return vf.sourceLocation(path, offset, length, beginLine, endLine, beginColumn, endColumn);
}
try {
if (scheme != null && authority != null && query != null && fragment != null) {
return vf.sourceLocation(scheme, authority, path, query, fragment);
}
if (scheme != null) {
return vf.sourceLocation(scheme, authority == null ? "" : authority, path);
}
} catch (URISyntaxException e) {
throw new IOException(e);
}
if (path != null) {
return vf.sourceLocation(path);
}
throw new IOException("Could not parse complete source location");
}
@Override
public IValue visitString(Type type) throws IOException {
return vf.string((String)stack.peek());
}
@Override
public IValue visitNode(Type type) throws IOException {
// ["name", [ ... ] ]
List l = (List)stack.peek();
String name = (String)l.get(0);
int arity = ((Double)l.get(1)).intValue();
List argsList = (List) l.get(2);
IValue[] args = new IValue[arity];
for (int i = 0; i < arity; i++) {
stack.push(argsList.get(i));
args[i] = read(tf.valueType());
stack.pop();
}
Map<String, IValue> kwargs = null;
if (l.size() > 3) {
kwargs = new HashMap<>();
Map kw = (Map)l.get(3);
for (Object k: kw.keySet()) {
String label = (String)k;
stack.push(kw.get(label));
kwargs.put(label, fixupTypeOfNodeValues(read(tf.valueType())));
stack.pop();
}
}
if (kwargs != null) {
return vf.node(name, args, kwargs);
}
return vf.node(name, args);
}
private IValue fixupTypeOfNodeValues(IValue input) {
return input.accept(new IValueVisitor<IValue, RuntimeException>() {
@Override
public IValue visitList(IList o) throws RuntimeException {
List<IValue> elements = new ArrayList<IValue>(o.length());
for (IValue e : o) {
elements.add(e.accept(this));
}
IListWriter writer = vf.listWriter();
writer.appendAll(elements);
return writer.done();
}
@Override
public IValue visitRelation(ISet o) throws RuntimeException {
List<IValue> elements = new ArrayList<IValue>(o.size());
for (IValue e : o) {
elements.add(e.accept(this));
}
ISetWriter writer = vf.setWriter();
writer.insertAll(elements);
return writer.done();
}
@Override
public IValue visitListRelation(IList o) throws RuntimeException {
List<IValue> elements = new ArrayList<IValue>(o.length());
for (IValue e : o) {
elements.add(e.accept(this));
}
IListWriter writer = vf.listWriter();
writer.appendAll(elements);
return writer.done();
}
@Override
public IValue visitSet(ISet o) throws RuntimeException {
List<IValue> elements = new ArrayList<IValue>(o.size());
for (IValue e : o) {
elements.add(e.accept(this));
}
ISetWriter writer = vf.setWriter();
writer.insertAll(elements);
return writer.done();
}
@Override
public IValue visitTuple(ITuple o) throws RuntimeException {
IValue[] elements = new IValue[o.arity()];
Type[] types = new Type[o.arity()];
for (int i = 0; i < elements.length; i++) {
elements[i] = o.get(i).accept(this);
types[i] = elements[i].getType();
}
return vf.tuple(tf.tupleType(types), elements);
}
@Override
public IValue visitNode(INode o) throws RuntimeException {
IValue[] children = new IValue[o.arity()];
for (int i = 0; i < children.length; i++) {
children[i] = o.get(i).accept(this);
}
if (o.mayHaveKeywordParameters()) {
IWithKeywordParameters<? extends INode> okw = o.asWithKeywordParameters();
Map<String, IValue> oldKwParams = okw.getParameters();
Map<String, IValue> kwParams = new HashMap<>(oldKwParams.size());
for (String key : oldKwParams.keySet()) {
kwParams.put(key, oldKwParams.get(key).accept(this));
}
return vf.node(o.getName(), children, kwParams);
}
return vf.node(o.getName(), children);
}
@Override
public IValue visitMap(IMap o) throws RuntimeException {
Iterator<Entry<IValue,IValue>> entries = o.entryIterator();
Map<IValue, IValue> newEntries = new HashMap<>(o.size());
while (entries.hasNext()) {
Entry<IValue, IValue> ent = entries.next();
newEntries.put(ent.getKey().accept(this), ent.getValue().accept(this));
}
IMapWriter writer = vf.mapWriter();
writer.putAll(newEntries);
return writer.done();
}
@Override
public IValue visitConstructor(IConstructor o)
throws RuntimeException {
throw new NotYetImplemented("Constructors are not yet implemented");
}
@Override
public IValue visitString(IString o) throws RuntimeException {
return o;
}
@Override
public IValue visitReal(IReal o) throws RuntimeException {
return o;
}
@Override
public IValue visitRational(IRational o) throws RuntimeException {
return o;
}
@Override
public IValue visitSourceLocation(ISourceLocation o)
throws RuntimeException {
return o;
}
@Override
public IValue visitInteger(IInteger o) throws RuntimeException {
return o;
}
@Override
public IValue visitBoolean(IBool boolValue) throws RuntimeException {
return boolValue;
}
@Override
public IValue visitExternal(IExternalValue externalValue)
throws RuntimeException {
return externalValue;
}
@Override
public IValue visitDateTime(IDateTime o) throws RuntimeException {
return o;
}
});
}
@Override
public IValue visitConstructor(Type type) throws IOException {
throw new NotYetImplemented("constructor types");
}
@Override
public IValue visitAbstractData(Type type) throws IOException {
// [ "name", [ ... ], { ... } }
if (type == JSON) {
return readPlainJSON();
}
List l = (List)stack.peek();
String name = (String)l.get(0);
int arity = ((Double)l.get(1)).intValue();
Set<Type> ctors = ts.lookupConstructor(type, name);
Type ctor = null;
for (Type t: ctors) {
if (t.getArity() == arity) {
ctor = t;
break;
}
}
if (ctor == null) {
throw new IOException("no constructor " + name + "/" + arity+ " in " + type);
}
List argsList = (List) l.get(2);
IValue[] args = new IValue[arity];
for (int i = 0; i < arity; i++) {
stack.push(argsList.get(i));
args[i] = read(ctor.getFieldType(i));
stack.pop();
}
Map<String, IValue> kwargs = null;
Map<String, Type> kwformals = ts.getKeywordParameters(ctor);
if (kwformals.size() > 0 && l.size() > 3) {
kwargs = new HashMap<>();
Map kw = (Map)l.get(3);
for (Object k: kw.keySet()) {
String label = (String)k;
Type kwType = kwformals.get(label);
if (kwType == null) {
// TODO: JV added this, kwType could be null
kwType = tf.valueType();
}
stack.push(kw.get(label));
kwargs.put(label, read(kwType));
stack.pop();
}
}
if (kwargs != null) {
return vf.constructor(ctor, args, kwargs);
}
return vf.constructor(ctor, args);
}
private IValue readPlainJSON() throws IOException {
return convertToIValue(stack.peek());
}
private IValue convertToIValue(Object obj) throws IOException {
if (obj == null) {
return vf.constructor(JSON_null);
}
if (obj instanceof Double) {
return vf.constructor(JSON_number, vf.real(((Double)obj).doubleValue()));
}
if (obj instanceof Boolean) {
return vf.constructor(JSON_boolean, vf.bool(((Boolean)obj).booleanValue()));
}
if (obj instanceof String) {
return vf.constructor(JSON_string, vf.string((String)obj));
}
if (obj instanceof Map) {
Map map = (Map)obj;
if (map.keySet().size() == 1) {
for (Object k: map.keySet()) {
if (k.equals("#value")) {
stack.push(map.get(k));
IValue v = read(tf.valueType());
stack.pop();
return vf.constructor(JSON_ivalue, v);
}
}
}
IMapWriter w = vf.mapWriter();
for (Object k: map.keySet()) {
w.put(vf.string((String) k), convertToIValue(map.get(k)));
}
return vf.constructor(JSON_object, w.done());
}
if (obj instanceof List) {
IListWriter w = vf.listWriter();
for (Object k: (List)obj) {
w.append(convertToIValue(k));
}
return vf.constructor(JSON_array, w.done());
}
if (obj instanceof Double) {
return vf.constructor(JSON_null);
}
throw new AssertionError("unhandled generic JSON object: " + obj);
}
@Override
public IValue visitTuple(Type type) throws IOException {
List l = (List)stack.peek();
if (type.isTop()) {
IValue[] args = new IValue[l.size()];
for (int i = 0; i < l.size(); i++) {
stack.push(l.get(i));
args[i] = read(tf.valueType());
stack.pop();
}
return vf.tuple(args);
}
IValue args[] = new IValue[type.getArity()];
for (int i = 0; i < l.size(); i++) {
stack.push(l.get(i));
args[i] = read(type.getFieldType(i));
stack.pop();
}
return vf.tuple(type, args);
}
@Override
public IValue visitValue(Type type) throws IOException {
String tag = tag();
IValue value = null;
stack.push(contents());
switch (tag) {
case "cons":
value = visitNode(type);
break;
case "node":
value = visitNode(type);
break;
case "int":
value = visitInteger(type);
break;
case "rat":
value = visitRational(type);
break;
case "real":
value = visitReal(type);
break;
case "loc":
value = visitSourceLocation(type);
break;
case "datetime":
value = visitDateTime(type);
break;
case "bool":
value = visitBool(type);
break;
case "list":
value = visitList(type);
break;
case "set":
value = visitSet(type);
break;
case "map":
value = visitMap(type);
break;
case "str":
value = visitString(type);
break;
case "tuple":
value = visitTuple(type);
break;
default:
throw new IOException("invalid tag for value: " + tag);
}
stack.pop();
return value;
}
@Override
public IValue visitDateTime(Type type) throws IOException {
// {datetime: { }}
int year = -1;
int monthOfYear = -1;
int dayOfMonth = -1;
int hourOfDay = -1;
int minuteOfHour = -1;
int secondOfMinute = -1;
int millisecondsOfSecond = -1;
int timezoneOffsetHours = -1;
int timezoneOffsetMinutes = -1;
Map m = (Map)stack.peek();
for (Object k: m.keySet()) {
String fld = (String)k;
int n = ((Double)m.get(fld)).intValue();
switch (fld) {
case "year": year = n; break;
case "monthOfYear": monthOfYear = n; break;
case "dayOfMonth": dayOfMonth = n; break;
case "hourOfDay": hourOfDay = n; break;
case "minuteOfHour": minuteOfHour = n; break;
case "secondOfMinute": secondOfMinute = n; break;
case "millisecondsOfSecond": millisecondsOfSecond = n; break;
case "timezoneOffsetHours": timezoneOffsetHours = n; break;
case "timezoneOffsetMinutes": timezoneOffsetMinutes = n; break;
default: throw new IOException("invalid field for date time: " + fld);
}
}
if (year != -1 && monthOfYear != -1 && dayOfMonth != -1 && hourOfDay != -1
&& minuteOfHour != -1 && secondOfMinute != -1 && millisecondsOfSecond != -1
&& timezoneOffsetHours != -1 && timezoneOffsetMinutes != -1) {
return vf.datetime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisecondsOfSecond, timezoneOffsetHours, timezoneOffsetMinutes);
}
if (year != -1 && monthOfYear != -1 && dayOfMonth != -1 && hourOfDay != -1
&& minuteOfHour != -1 && secondOfMinute != -1 && millisecondsOfSecond != -1) {
return vf.datetime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisecondsOfSecond);
}
if (year != -1 && monthOfYear != -1 && dayOfMonth != -1) {
return vf.date(year, monthOfYear, dayOfMonth);
}
throw new IOException("insufficient fields for making a datetime value");
}
@Override
public IValue visitVoid(Type type) throws IOException {
throw new AssertionError("cannot read values of type void");
}
@Override
public IValue visitBool(Type type) throws IOException {
return vf.bool((Boolean)stack.peek());
}
@Override
public IValue visitParameter(Type type) throws IOException {
throw new AssertionError("parameter types should have been bound");
}
@Override
public IValue visitExternal(Type type) throws IOException {
if (type instanceof NonTerminalType) {
return RascalValueFactory.Tree.accept(this);
}
throw new IOException("cannot deserialize external values");
}
}