/**
* <copyright>
* Copyright (c) 2010-2014 Henshin developers. 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
* </copyright>
*/
package org.eclipse.emf.henshin.model.exporters;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.henshin.HenshinModelExporter;
import org.eclipse.emf.henshin.HenshinModelPlugin;
import org.eclipse.emf.henshin.model.Attribute;
import org.eclipse.emf.henshin.model.AttributeCondition;
import org.eclipse.emf.henshin.model.Edge;
import org.eclipse.emf.henshin.model.Graph;
import org.eclipse.emf.henshin.model.Mapping;
import org.eclipse.emf.henshin.model.MappingList;
import org.eclipse.emf.henshin.model.Module;
import org.eclipse.emf.henshin.model.NestedCondition;
import org.eclipse.emf.henshin.model.Node;
import org.eclipse.emf.henshin.model.Parameter;
import org.eclipse.emf.henshin.model.Rule;
import org.eclipse.emf.henshin.model.Unit;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Henshin model exporter for AGG.
* @author Christian Krause
*/
public class HenshinAGGExporter implements HenshinModelExporter {
/**
* ID of this model exporter.
*/
public static final String EXPORTER_ID = "org.eclipse.emf.henshin.henshin2agg";
// Ecore package:
private static final EcorePackage ECORE = EcorePackage.eINSTANCE;
// Colors for node types:
private static int[][] COLORS = {
{ 0, 0, 0 }, // black
{ 255, 0, 0 }, // red
{ 0, 0, 255 }, // blue
{ 128, 0, 128 }, // purple
{ 128, 128, 0 }, // yellow
{ 128, 128, 128 } // grey
// add more colors...
};
// XML document:
private Document document;
// Element ID:
private int elementID = 0;
// Color index:
private int color = 0;
// Node type IDs:
private Map<EClass,String> nodeTypeIDs, nodeIDs;
// Edge type IDs:
private Map<EReference,String> edgeTypeIDs;
// Attribute type IDs:
private Map<EAttribute,String> attrTypeIDs;
// Graph node IDs:
private Map<Node,String> graphNodeIDs;
// Graph edge IDs:
private Map<Edge,String> graphEdgeIDs;
// Warnings:
private List<String> warnings;
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.HenshinModelExporter#doExport(org.eclipse.emf.henshin.model.Module, org.eclipse.emf.common.util.URI)
*/
@Override
public IStatus doExport(Module module, URI uri) {
// Reset first:
reset();
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.newDocument();
// Root element:
Element root = newElement("Document", document, false);
root.setAttribute("version", "1.0");
// Signature:
Comment comment = document.createComment("Generated by Henshin on " + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()));
root.appendChild(comment);
// Graph transformation system:
Element systemElem = newElement("GraphTransformationSystem", root, true);
String name = module.getName();
if (name==null || name.trim().length()==0) {
if (module.eResource()!=null) {
name = module.eResource().getURI().trimFileExtension().lastSegment();
} else {
name = "GraGra";
}
}
systemElem.setAttribute("name", name);
Element javaTag = newTaggedValue("AttrHandler", "Java Expr", systemElem);
newTaggedValue("Package", "java.lang", javaTag);
newTaggedValue("Package", "java.util", javaTag);
newTaggedValue("Package", "com.objectspace.jgl", javaTag);
newTaggedValue("CSP", "true", systemElem);
newTaggedValue("injective", "true", systemElem);
newTaggedValue("dangling", "true", systemElem);
newTaggedValue("NACs", "true", systemElem);
newTaggedValue("PACs", "true", systemElem);
newTaggedValue("TypeGraphLevel", "ENABLED_MAX", systemElem);
// Types:
Element typesElem = newElement("Types", systemElem, false);
// Type graph:
Element typeGraphElem = newElement("Graph", typesElem, true);
typeGraphElem.setAttribute("kind", "TG");
typeGraphElem.setAttribute("name", "TypeGraph");
typesElem.removeChild(typeGraphElem);
// Collect list of EClasses:
List<EClass> eclasses = new ArrayList<EClass>();
for (EPackage epackage : module.getImports()) {
for (EClassifier eclassifier : epackage.getEClassifiers()) {
if (eclassifier instanceof EClass) {
eclasses.add((EClass) eclassifier);
}
}
}
// Sort EClasses using Topological Sort.
eclasses = sortEClasses(eclasses);
// Nodes and attribute types:
for (EClass eclass : eclasses) {
// Node type:
Element nodeTypeElem = newElement("NodeType", typesElem, true);
nodeTypeElem.setAttribute("abstract", String.valueOf(eclass.isAbstract()));
nodeTypeElem.setAttribute("name", eclass.getName() + "%:RECT:" + newColor() + ":[NODE]:");
nodeTypeIDs.put(eclass, nodeTypeElem.getAttribute("ID"));
// Node in type graph:
Element nodeElem = newElement("Node", typeGraphElem, true);
nodeElem.setAttribute("type", nodeTypeIDs.get(eclass));
nodeIDs.put(eclass, nodeElem.getAttribute("ID"));
// Inheritance:
if(eclass.getESuperTypes().size()==1){
// handle the case of one super type
EClass parentEClass=eclass.getESuperTypes().get(0);
Element parentElem = newElement("Parent", nodeTypeElem, false);
String parentNodeTypeID = nodeTypeIDs.get(parentEClass);
parentElem.setAttribute("pID", parentNodeTypeID);
// parent element XML node does not have an own ID in AGG
}
else if (eclass.getESuperTypes().size()>1){
warnings.add(" - multiple inheritance for " + eclass.getName() +
" not supported");
}
// Attributes:
for (EAttribute attribute : eclass.getEAttributes()) {
if (isSupportedPrimitiveType(attribute.getEType())) {
Element attrElem = newElement("AttrType", nodeTypeElem, true);
attrElem.setAttribute("attrname", attribute.getName());
attrElem.setAttribute("typename", getPrimitiveType(attribute.getEType()));
attrElem.setAttribute("visible", "true");
attrTypeIDs.put(attribute, attrElem.getAttribute("ID"));
} else {
String attributesTypeName = "???";
if(attribute.getEAttributeType() != null){
attributesTypeName = attribute.getEAttributeType().getName();
}
String message = " - Attribute " + eclass.getName() + "." + attribute.getName() +
" of type " + attributesTypeName + " not supported";
warnings.add(message);
}
}
}
// Check whether the reference names are unique:
boolean hasUniqureRefNames = hasUniqueEReferenceNames(module);
// Edge types:
for (EClass eclass : eclasses) {
// Edges:
for (EReference reference : eclass.getEReferences()) {
// Edge type:
Element edgeTypeElem = newElement("EdgeType", typesElem, true);
edgeTypeElem.setAttribute("abstract", "false");
String refName = hasUniqureRefNames ? reference.getName() : getUniqueReferenceName(reference);
edgeTypeElem.setAttribute("name", refName + "%:SOLID_LINE:java.awt.Color[r=0,g=0,b=0]:[EDGE]:");
edgeTypeIDs.put(reference, edgeTypeElem.getAttribute("ID"));
// Edge in type graph:
Element edgeElem = newElement("Edge", typeGraphElem, true);
edgeElem.setAttribute("type", edgeTypeIDs.get(reference));
edgeElem.setAttribute("source", nodeIDs.get(eclass));
edgeElem.setAttribute("target", nodeIDs.get(reference.getEReferenceType()));
if(reference.isContainment()){
edgeElem.setAttribute("sourcemax", "1");
edgeElem.setAttribute("sourcemin", "1");
} else {
edgeElem.setAttribute("sourcemin", "0");
}
edgeElem.setAttribute("targetmin", reference.getLowerBound()+"");
if (reference.getUpperBound()>=0) {
edgeElem.setAttribute("targetmax", reference.getUpperBound()+"");
}
}
}
// Now append the type graph:
typesElem.appendChild(typeGraphElem);
// Rules:
for (Unit unit : module.getUnits()) {
if (!(unit instanceof Rule)) continue;
Rule rule = (Rule) unit;
Element ruleElem = newElement("Rule", systemElem, true);
ruleElem.setAttribute("name", rule.getName());
ruleElem.setAttribute("formula", "true");
// Parameters:
for (Parameter param : rule.getParameters()) {
if (isSupportedPrimitiveType(param.getType())) {
Element paramElem = newElement("Parameter", ruleElem, false);
paramElem.setAttribute("name", param.getName());
paramElem.setAttribute("type", getPrimitiveType(param.getType()));
} else {
String type = param.getType()!=null ? param.getType().getName() : "null";
warnings.add(" - Parameter " + rule.getName() + "." + param.getName() + " of type " + type + " not supported");
}
}
// LHS, RHS and morphism:
convertGraph(rule.getLhs(), ruleElem, "LHS", "Left");
convertGraph(rule.getRhs(), ruleElem, "RHS", "Right");
convertMorphism(rule.getName(), rule.getMappings(), rule.getLhs(), rule.getRhs(), ruleElem);
// Application conditions:
Element applCondElem = newElement("ApplCondition", ruleElem, false);
// Attribute conditions:
if (!rule.getAttributeConditions().isEmpty()) {
Element attrCondElem = newElement("AttrCondition", applCondElem, false);
for (AttributeCondition cond : rule.getAttributeConditions()) {
Element condElem = newElement("Condition", attrCondElem, false);
Element valueElem = newElement("Value", condElem, false);
Element javaElem = newElement("java", valueElem, false);
javaElem.setAttribute("class", "java.beans.XMLDecoder");
javaElem.setAttribute("version", "1.6.0_03");
Element stringElem = newElement("string", javaElem, false);
stringElem.setTextContent(cond.getConditionText());
}
}
// PACs and NACs:
for (NestedCondition nested : rule.getLhs().getNestedConditions()) {
if (nested.isNAC()) {
Element nacElem = newElement("NAC", applCondElem, false);
convertGraph(nested.getConclusion(), nacElem, "NAC", "Graph");
convertMorphism(nested.getConclusion().getName(), nested.getMappings(), rule.getLhs(), nested.getConclusion(), nacElem);
}
else if (nested.isPAC()) {
Element pacElem = newElement("PAC", applCondElem, false);
convertGraph(nested.getConclusion(), pacElem, "PAC", "Graph");
convertMorphism(nested.getConclusion().getName(), nested.getMappings(), rule.getLhs(), nested.getConclusion(), pacElem);
}
}
}
// Save the XML file:
TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer trans = transFactory.newTransformer();
trans.setOutputProperty(OutputKeys.INDENT, "yes");
File file = new File(uri.toFileString());
StreamResult result = new StreamResult(file);
DOMSource source = new DOMSource(document);
trans.transform(source, result);
}
catch (Throwable t) {
reset();
return new Status(IStatus.ERROR, HenshinModelPlugin.PLUGIN_ID, "Error exporting to AGG", t);
}
if (!warnings.isEmpty()) {
String message = "Warning:\n";
for (String warning : warnings) {
message = message + "\n" + warning;
}
reset();
return new Status(IStatus.WARNING, HenshinModelPlugin.PLUGIN_ID, message);
}
reset();
return Status.OK_STATUS;
}
/*
* Topological sorting of EClasses. In the sorted list superclasses
* precede subclasses. Only single-inheritance is supported.
*/
private static List<EClass> sortEClasses(List<EClass> eclasses) {
// Parent->Children map:
Map<EClass,List<EClass>> childrenMap = new LinkedHashMap<EClass,List<EClass>>();
// Root classes:
List<EClass> roots = new ArrayList<EClass>();
// Initialize helper structures:
for (EClass eclass : eclasses) {
if (eclass.getESuperTypes().size()==1) {
EClass parent = eclass.getESuperTypes().get(0);
List<EClass> children = childrenMap.get(parent);
if (children==null) {
children = new ArrayList<EClass>();
childrenMap.put(parent, children);
}
children.add(eclass);
} else {
roots.add(eclass);
}
}
// Construct sorted list by traversing starting from the roots:
List<EClass> sorted = new ArrayList<EClass>();
for (EClass root : roots) {
Deque<EClass> stack = new ArrayDeque<EClass>();
stack.push(root);
while (!stack.isEmpty()) {
EClass eclass = stack.pop();
sorted.add(eclass);
List<EClass> children = childrenMap.get(eclass);
if (children!=null) {
for (EClass child : children) {
stack.push(child);
}
}
}
}
return sorted;
}
/*
* Translate a graph into XML.
*/
private void convertGraph(Graph graph, Element parent, String kind, String name) {
Element graphElem = newElement("Graph", parent, true);
graphElem.setAttribute("kind", kind);
graphElem.setAttribute("name", name);
// Nodes:
for (Node node : graph.getNodes()) {
Element nodeElem = newElement("Node", graphElem, true);
nodeElem.setAttribute("type", nodeTypeIDs.get(node.getType()));
graphNodeIDs.put(node, nodeElem.getAttribute("ID"));
// Attributes:
for (Attribute attribute : node.getAttributes()) {
if (isSupportedPrimitiveType(attribute.getType().getEType())) {
EDataType t = attribute.getType().getEAttributeType();
Element attrElem = newElement("Attribute", nodeElem, false);
attrElem.setAttribute("type", attrTypeIDs.get(attribute.getType()));
String constValue = getConstant(attribute);
Element valueElem = null;
if (constValue!=null) {
attrElem.setAttribute("constant", "true");
if (t==ECORE.getEInt()) { // integers
valueElem = newElement("Value", attrElem, false);
Element intElem = newElement("int", valueElem, false);
intElem.setTextContent(attribute.getValue());
}
if (t==ECORE.getEDouble()) { // integers
valueElem = newElement("Value", attrElem, false);
Element doubleElem = newElement("double", valueElem, false);
doubleElem.setTextContent(attribute.getValue());
}
else if (t==ECORE.getEBoolean()) { // booleans
valueElem = newElement("Value", attrElem, false);
Element booleanElem = newElement("boolean", valueElem, false);
booleanElem.setTextContent(attribute.getValue());
}
}
if (valueElem==null) { // string and variables
attrElem.setAttribute("variable", "true");
valueElem = newElement("Value", attrElem, false);
Element stringElem = newElement("string", valueElem, false);
stringElem.setTextContent(constValue!=null ? constValue : attribute.getValue());
}
}
}
}
// Edges:
for (Edge edge : graph.getEdges()) {
Element edgeElem = newElement("Edge", graphElem, true);
graphEdgeIDs.put(edge, edgeElem.getAttribute("ID"));
edgeElem.setAttribute("type", edgeTypeIDs.get(edge.getType()));
edgeElem.setAttribute("source", graphNodeIDs.get(edge.getSource()));
edgeElem.setAttribute("target", graphNodeIDs.get(edge.getTarget()));
}
}
/*
* Convert a morphism.
*/
private void convertMorphism(String name, MappingList mappings, Graph source, Graph target, Element parent) {
Element morphismElem = newElement("Morphism", parent, false);
morphismElem.setAttribute("name", name);
// Node mappings:
for (Mapping mapping : mappings) {
if (mapping.getImage().getGraph()==target) {
Element mappingElem = newElement("Mapping", morphismElem, false);
mappingElem.setAttribute("orig", graphNodeIDs.get(mapping.getOrigin())); // "orig" (not "origin")
mappingElem.setAttribute("image", graphNodeIDs.get(mapping.getImage()));
}
}
// Edge mappings:
for (Edge edge : source.getEdges()) {
Edge image = mappings.getImage(edge, target);
if (image!=null) {
Element mappingElem = newElement("Mapping", morphismElem, false);
mappingElem.setAttribute("orig", graphEdgeIDs.get(edge)); // "orig" (not "origin")
mappingElem.setAttribute("image", graphEdgeIDs.get(image));
}
}
}
/*
* Get the name of a reference.
*/
private String getUniqueReferenceName(EReference reference) {
String srcName = ((EClass) reference.eContainer()).getName();
srcName = Character.toLowerCase(srcName.charAt(0)) + srcName.substring(1);
String refName = reference.getName();
refName = Character.toUpperCase(refName.charAt(0)) + refName.substring(1);
return srcName + refName;
}
/*
* Create a new document element.
*/
private Element newElement(String type, org.w3c.dom.Node parent, boolean generateID) {
Element elem = document.createElement(type);
if (generateID) {
elem.setAttribute("ID", "I" + (elementID++));
elem.setIdAttribute("ID", true);
}
parent.appendChild(elem);
return elem;
}
/*
* Create a new color (string representation for AGG).
*/
private String newColor() {
int[] rgb = COLORS[color];
color = (color+1) % COLORS.length;
return "java.awt.Color[r=" + rgb[0] + ",g=" + rgb[1] + ",b=" + rgb[2] + "]";
}
/*
* Create a new tagged value.
*/
private Element newTaggedValue(String tag, String tagValue, org.w3c.dom.Node parent) {
Element elem = document.createElement("TaggedValue");
elem.setAttribute("Tag", tag);
elem.setAttribute("TagValue", tagValue);
parent.appendChild(elem);
return elem;
}
/*
* Reset internal data.
*/
private void reset() {
nodeTypeIDs = new HashMap<EClass, String>();
nodeIDs = new HashMap<EClass, String>();
edgeTypeIDs = new HashMap<EReference, String>();
attrTypeIDs = new HashMap<EAttribute, String>();
graphNodeIDs = new HashMap<Node, String>();
graphEdgeIDs = new HashMap<Edge, String>();
warnings = new ArrayList<String>();
elementID = 0;
color = 0;
}
/*
* Check whether a type is a supported primitive type.
*/
private boolean isSupportedPrimitiveType(EClassifier type) {
return (type==ECORE.getEInt() || type==ECORE.getEDouble() ||
type==ECORE.getEBoolean() || type==ECORE.getEString());
}
/*
* Get the string representation of a primitive type.
*/
private String getPrimitiveType(EClassifier type) {
if (isSupportedPrimitiveType(type)) {
if (type==ECORE.getEString()) {
return "String";
}
return type.getInstanceClassName();
}
return "null";
}
/*
* Check whether an attribute has an constant value.
* Returns the string representation of the constant
* if yes. Otherwise null.
*/
private String getConstant(Attribute attribute) {
String val = String.valueOf(attribute.getValue()).trim();
EDataType type = attribute.getType().getEAttributeType();
if (type==ECORE.getEInt()) {
try {
int intVal = Integer.parseInt(val);
return String.valueOf(intVal);
} catch (Throwable t) {
return null;
}
}
if (type==ECORE.getEDouble()) {
try {
double doubleVal = Double.parseDouble(val);
return String.valueOf(doubleVal);
} catch (Throwable t) {
return null;
}
}
if (type==ECORE.getEBoolean()) {
try {
boolean boolVal = Boolean.parseBoolean(val);
return String.valueOf(boolVal);
} catch (Throwable t) {
return null;
}
}
if (type==ECORE.getEString()) {
if ((val.startsWith("\"") && val.endsWith("\"")) ||
(val.startsWith("'") && val.endsWith("'"))) {
return val.substring(1, val.length()-1);
} else {
return null;
}
}
return null;
}
/*
* Check whether all used EReferences in a module have a unique name.
*/
private static boolean hasUniqueEReferenceNames(Module module) {
Set<String> refNames = new HashSet<String>();
for (EPackage epackage : module.getImports()) {
for (EClassifier classifier : epackage.getEClassifiers()) {
if (classifier instanceof EClass) {
for (EReference ref : ((EClass) classifier).getEReferences()) {
if (refNames.contains(ref.getName())) return false;
refNames.add(ref.getName());
}
}
}
}
return true;
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.HenshinModelExporter#getExporterName()
*/
@Override
public String getExporterName() {
return "AGG";
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.HenshinModelExporter#getExportFileExtensions()
*/
@Override
public String[] getExportFileExtensions() {
return new String[] { "ggx" };
}
}