/******************************************************************************* * Copyright (c) 2009-2013 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * * Tijs van der Storm - storm@cwi.nl - CWI *******************************************************************************/ package org.rascalmpl.library.lang.yaml; import static org.rascalmpl.library.lang.yaml.YAMLTypeFactory.Node; import static org.rascalmpl.library.lang.yaml.YAMLTypeFactory.Node_mapping; import static org.rascalmpl.library.lang.yaml.YAMLTypeFactory.Node_reference; import static org.rascalmpl.library.lang.yaml.YAMLTypeFactory.Node_scalar; import static org.rascalmpl.library.lang.yaml.YAMLTypeFactory.Node_sequence; import java.net.URI; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.rascalmpl.interpreter.IEvaluatorContext; import org.rascalmpl.interpreter.TypeReifier; import org.rascalmpl.interpreter.asserts.ImplementationError; import org.rascalmpl.interpreter.utils.RuntimeExceptionFactory; import org.rascalmpl.value.IBool; import org.rascalmpl.value.IConstructor; import org.rascalmpl.value.IDateTime; 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.IReal; import org.rascalmpl.value.ISourceLocation; import org.rascalmpl.value.IString; import org.rascalmpl.value.IValue; import org.rascalmpl.value.IValueFactory; import org.rascalmpl.value.type.TypeFactory; import org.rascalmpl.value.type.TypeStore; import org.yaml.snakeyaml.Yaml; public class RascalYAML { private static final String ANCHOR_ANNO = "anchor"; private final IValueFactory values; private final TypeReifier reifier; private static final TypeFactory tf = TypeFactory.getInstance(); public RascalYAML(IValueFactory values) { super(); this.values = values; this.reifier = new TypeReifier(values); } public IConstructor loadYAML(IString src, IEvaluatorContext ctx) { Yaml yaml = new Yaml(); Object obj = yaml.load(src.getValue()); if (obj == null) { throw RuntimeExceptionFactory.illegalArgument(src, ctx.getCurrentAST(), ctx.getStackTrace()); } IdentityHashMap<Object, Integer> anchors = new IdentityHashMap<Object, Integer>(); computeAnchors(obj, anchors, 0); return loadRec(obj, anchors, new IdentityHashMap<Object, Boolean>(), ctx); } @SuppressWarnings("unchecked") private int computeAnchors(Object obj, IdentityHashMap<Object, Integer> anchors, int a) { if (anchors.containsKey(obj)) { anchors.put(obj, a); return a + 1; } anchors.put(obj, -1); if (obj instanceof Object[]) { for (Object elt: (Object[])obj) { a = computeAnchors(elt, anchors, a); } } else if (obj instanceof List) { for (Object elt: (List<Object>)obj) { a = computeAnchors(elt, anchors, a); } } else if (obj instanceof Map) { Map<Object, Object> m = (Map<Object,Object>)obj; for (Map.Entry<Object,Object> e: m.entrySet()) { a = computeAnchors(e.getKey(), anchors, a); a = computeAnchors(e.getValue(), anchors, a); } } return a; } @SuppressWarnings("unchecked") private IConstructor loadRec(Object obj, IdentityHashMap<Object, Integer> anchors, IdentityHashMap<Object, Boolean> visited, IEvaluatorContext ctx) { TypeStore store = ctx.getCurrentEnvt().getStore(); IMap empty = values.mapWriter().done(); if (obj instanceof Integer) { return values.constructor(Node_scalar, values.integer((Integer)obj)) .asAnnotatable().setAnnotation("tag", reifier.typeToValue(tf.integerType(), ctx.getCurrentEnvt().getStore(), empty)); } if (obj instanceof Long) { return values.constructor(Node_scalar, values.integer((Long)obj)) .asAnnotatable().setAnnotation("tag", reifier.typeToValue(tf.integerType(), ctx.getCurrentEnvt().getStore(), empty)); } if (obj instanceof Double) { return values.constructor(Node_scalar, values.real((Double)obj)) .asAnnotatable().setAnnotation("tag", reifier.typeToValue(tf.realType(), ctx.getCurrentEnvt().getStore(), empty)); } if (obj instanceof Float) { return values.constructor(Node_scalar, values.real((Float)obj)) .asAnnotatable().setAnnotation("tag", reifier.typeToValue(tf.realType(), ctx.getCurrentEnvt().getStore(), empty)); } if (obj instanceof String) { return values.constructor(Node_scalar, values.string((String)obj)) .asAnnotatable().setAnnotation("tag", reifier.typeToValue(tf.stringType(), ctx.getCurrentEnvt().getStore(), empty)); } if (obj instanceof Boolean) { return values.constructor(Node_scalar, values.bool((Boolean)obj)) .asAnnotatable().setAnnotation("tag", reifier.typeToValue(tf.boolType(), ctx.getCurrentEnvt().getStore(), empty)); } if (obj instanceof Date) { return values.constructor(Node_scalar, values.datetime(((Date)obj).getTime())) .asAnnotatable().setAnnotation("tag", reifier.typeToValue(tf.dateTimeType(), ctx.getCurrentEnvt().getStore(), empty)); } if (obj instanceof URI) { return values.constructor(Node_scalar, values.sourceLocation((URI)obj)) .asAnnotatable().setAnnotation("tag", reifier.typeToValue(tf.sourceLocationType(), ctx.getCurrentEnvt().getStore(), empty)); } // Structural types may be shared. if (visited.containsKey(obj)) { assert anchors.get(obj) != - 1; return values.constructor(Node_reference, values.integer(anchors.get(obj))); } visited.put(obj, true); IConstructor result; if (obj instanceof Object[]) { IListWriter w = values.listWriter(Node); for (Object elt: (Object[])obj) { w.append(loadRec(elt, anchors, visited, ctx)); } result = values.constructor(Node_sequence, w.done()); } else if (obj instanceof List) { IListWriter w = values.listWriter(Node); for (Object elt: (List<Object>)obj) { w.append(loadRec(elt, anchors, visited, ctx)); } result = values.constructor(Node_sequence, w.done()); } else if (obj instanceof Map) { IMapWriter w = values.mapWriter(Node, Node); Map<Object, Object> m = (Map<Object,Object>)obj; for (Map.Entry<Object,Object> e: m.entrySet()) { w.put(loadRec(e.getKey(), anchors, visited, ctx), loadRec(e.getValue(), anchors, visited, ctx)); } result = values.constructor(Node_mapping, w.done()); } else { throw RuntimeExceptionFactory.illegalArgument( values.string(obj.toString() + " (class=" + obj.getClass() + ")"), ctx.getCurrentAST(), ctx.getStackTrace()); } if (anchors.get(obj) != -1) { result = result.asAnnotatable().setAnnotation(ANCHOR_ANNO, values.integer(anchors.get(obj))); } return result; } public IString dumpYAML(IConstructor yaml, IEvaluatorContext ctx) { Map<Integer, Object> visited = new HashMap<Integer, Object>(); Object obj = dumpYAMLrec(yaml, visited, ctx); Yaml y = new Yaml(); return values.string(y.dump(obj)); } private Object dumpYAMLrec(IConstructor yaml, Map<Integer, Object> visited, IEvaluatorContext ctx) { // in valid YAML anchors always occur before any references // this should also hold in our YAML data type if (yaml.getConstructorType() == Node_reference) { int id = ((IInteger)yaml.get(0)).intValue(); if (!visited.containsKey(id)) { throw RuntimeExceptionFactory.indexOutOfBounds(values.integer(id), ctx.getCurrentAST(), ctx.getStackTrace()); } return visited.get(id); } if (yaml.getConstructorType() == Node_scalar) { // we're ignoring the tag annotations right now. IValue value = yaml.get(0); if (value.getType() == tf.integerType()) { // TODO: detect Long, BigInt etc. return new Integer(((IInteger)value).intValue()); } if (value.getType() == tf.realType()) { return new Double(((IReal)value).doubleValue()); } if (value.getType() == tf.stringType()) { return new String((((IString)value).getValue())); } if (value.getType() == tf.boolType()) { return new Boolean((((IBool)value).getValue())); } if (value.getType() == tf.dateTimeType()) { return new Date(((IDateTime)value).getInstant()); } if (value.getType() == tf.sourceLocationType()) { return ((ISourceLocation)value).getURI(); } throw RuntimeExceptionFactory.illegalArgument(yaml, ctx.getCurrentAST(), ctx.getStackTrace()); } if (yaml.getConstructorType() == Node_sequence) { List<Object> l = new ArrayList<Object>(); if (yaml.asAnnotatable().hasAnnotation(ANCHOR_ANNO)) { visited.put(((IInteger)yaml.asAnnotatable().getAnnotation(ANCHOR_ANNO)).intValue(), l); } for (IValue v: (IList)yaml.get(0)) { l.add(dumpYAMLrec((IConstructor)v, visited, ctx)); } return l; } if (yaml.getConstructorType() == Node_mapping) { Map<Object, Object> m = new IdentityHashMap<Object, Object>(); if (yaml.asAnnotatable().hasAnnotation(ANCHOR_ANNO)) { visited.put(((IInteger)yaml.asAnnotatable().getAnnotation(ANCHOR_ANNO)).intValue(), m); } Iterator<Entry<IValue, IValue>> iter = ((IMap)yaml.get(0)).entryIterator(); while (iter.hasNext()) { Entry<IValue, IValue> e = iter.next(); m.put(dumpYAMLrec((IConstructor)e.getKey(), visited, ctx), dumpYAMLrec((IConstructor)e.getValue(), visited, ctx)); } return m; } throw new ImplementationError("Invalid YAML data type: " + yaml); } }