package org.archstudio.graphlayout.core.graphviz; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.StringTokenizer; import org.archstudio.graphlayout.GraphLayout; import org.archstudio.graphlayout.GraphLayoutException; import org.archstudio.graphlayout.GraphLayoutParameters; import org.archstudio.graphlayout.core.AliasTable; import org.archstudio.graphlayout.core.ILayoutEngine; import org.archstudio.swtutils.constants.Orientation; import org.archstudio.xadl.XadlUtils; import org.archstudio.xadl3.domain_3_0.DomainType; import org.archstudio.xadl3.domain_3_0.Domain_3_0Package; import org.archstudio.xadl3.structure_3_0.Structure_3_0Package; import org.archstudio.xarchadt.IXArchADT; import org.archstudio.xarchadt.ObjRef; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import com.google.common.collect.Iterables; public abstract class AbstractGraphvizLayoutEngine implements ILayoutEngine { public static final String EOL = System.getProperty("line.separator"); @Override public GraphLayout layoutGraph(IXArchADT xarch, IPreferenceStore prefs, ObjRef rootRef, GraphLayoutParameters params) throws GraphLayoutException { StringBuffer sb = new StringBuffer(); AliasTable at = new AliasTable(); createGraph(sb, at, xarch, rootRef, params); String output = runLayoutTool(xarch, prefs, sb.toString()); //System.err.println(sb.toString()); GraphLayout gl = processOutput(at, xarch, rootRef, params, output); return gl; } protected abstract String runLayoutTool(IXArchADT xarch, IPreferenceStore prefs, String toolInput) throws GraphLayoutException; protected void createGraph(StringBuffer sb, AliasTable at, IXArchADT xarch, ObjRef rootRef, GraphLayoutParameters params) throws GraphLayoutException { if (rootRef == null) { throw new IllegalArgumentException("Null root reference in graph layout."); } if (!XadlUtils.isInstanceOf(xarch, rootRef, Structure_3_0Package.Literals.STRUCTURE)) { throw new GraphLayoutException("Invalid root reference in graph layout; must be a Structure"); } sb.append("digraph arch {" + EOL); createGraphParameters(sb, at, xarch, rootRef, params); sb.append(EOL); createBricks(sb, at, xarch, rootRef, params); sb.append(EOL); createLinks(sb, at, xarch, rootRef, params); sb.append(EOL); sb.append("}" + EOL); } protected void createGraphParameters(StringBuffer sb, AliasTable at, IXArchADT xarch, ObjRef rootRef, GraphLayoutParameters params) throws GraphLayoutException { Object o = params.getProperty("nodeSep"); if (o != null && o instanceof Double) { sb.append(" nodesep = "); sb.append(((Double) o).toString()); sb.append(";"); sb.append(EOL); } o = params.getProperty("rankSep"); if (o != null && o instanceof Double) { sb.append(" ranksep = "); sb.append(((Double) o).toString()); sb.append(";"); sb.append(EOL); } } protected void createBricks(StringBuffer sb, AliasTable at, IXArchADT xarch, ObjRef rootRef, GraphLayoutParameters params) throws GraphLayoutException { for (ObjRef componentRef : Iterables.filter(xarch.getAll(rootRef, "component"), ObjRef.class)) { createBrick(sb, at, xarch, componentRef, params, true); } sb.append(EOL); for (ObjRef connectorRef : Iterables.filter(xarch.getAll(rootRef, "connector"), ObjRef.class)) { createBrick(sb, at, xarch, connectorRef, params, false); } } protected void createBrick(StringBuffer sb, AliasTable at, IXArchADT xarch, ObjRef brickRef, GraphLayoutParameters params, boolean isComponent) throws GraphLayoutException { double rw = isComponent ? params.getRelativeComponentWidth() : params.getRelativeConnectorWidth(); double rh = isComponent ? params.getRelativeComponentHeight() : params.getRelativeConnectorHeight(); String brickXArchID = XadlUtils.getID(xarch, brickRef); if (brickXArchID != null) { String brickAlias = at.getAlias(brickXArchID); sb.append(" "); sb.append(brickAlias); sb.append(" "); if (shouldOrientInterfaces(params) && hasOrientedInterface(xarch, brickRef)) { //the brick will be a quartered record sb.append("[shape=record,width="); sb.append(rw); sb.append(",height="); sb.append(rh); sb.append(",label=\"{<__n> | { <__w> | <__c> | <__e> } | <__s>}\""); } else { //The brick will be a box. sb.append("[shape=box,width="); sb.append(rw); sb.append(",height="); sb.append(rh); } sb.append("];"); sb.append(EOL); } } protected void createLinks(StringBuffer sb, AliasTable at, IXArchADT xarch, ObjRef rootRef, GraphLayoutParameters params) throws GraphLayoutException { for (ObjRef linkRef : Iterables.filter(xarch.getAll(rootRef, "link"), ObjRef.class)) { createLink(sb, at, xarch, linkRef, params); } } protected void createLink(StringBuffer sb, AliasTable at, IXArchADT xarch, ObjRef linkRef, GraphLayoutParameters params) throws GraphLayoutException { String linkXArchID = XadlUtils.getID(xarch, linkRef); if (linkXArchID == null) { return; } ObjRef linkEndpoint1Target = (ObjRef) xarch.get(linkRef, "point1"); if (linkEndpoint1Target == null) { return; } ObjRef linkEndpoint1Parent = xarch.getParent(linkEndpoint1Target); if (linkEndpoint1Parent == null) { return; } if (!XadlUtils.isInstanceOf(xarch, linkEndpoint1Parent, Structure_3_0Package.Literals.BRICK)) { return; } String linkEndpoint1ParentXArchID = XadlUtils.getID(xarch, linkEndpoint1Parent); if (linkEndpoint1ParentXArchID == null) { return; } ObjRef linkEndpoint2Target = (ObjRef) xarch.get(linkRef, "point2"); if (linkEndpoint2Target == null) { return; } ObjRef linkEndpoint2Parent = xarch.getParent(linkEndpoint2Target); if (linkEndpoint2Parent == null) { return; } if (!XadlUtils.isInstanceOf(xarch, linkEndpoint2Parent, Structure_3_0Package.Literals.BRICK)) { return; } String linkEndpoint2ParentXArchID = XadlUtils.getID(xarch, linkEndpoint2Parent); if (linkEndpoint2ParentXArchID == null) { return; } //OK, the link is valid. sb.append(" "); sb.append(at.getAlias(linkEndpoint1ParentXArchID)); if (shouldOrientInterfaces(params) && hasOrientedInterface(xarch, linkEndpoint1Parent)) { //The brick is quartered. Orientation linkEndpoint1Orientation = guessInterfaceOrientation(xarch, linkEndpoint1Target); switch (linkEndpoint1Orientation) { case NORTH: sb.append(":__n"); break; case EAST: sb.append(":__e"); break; case SOUTH: sb.append(":__s"); break; case WEST: sb.append(":__w"); break; default: sb.append(":__c"); } } sb.append(" -> "); sb.append(at.getAlias(linkEndpoint2ParentXArchID)); if (shouldOrientInterfaces(params) && hasOrientedInterface(xarch, linkEndpoint2Parent)) { //The brick is quartered. Orientation linkEndpoint2Orientation = guessInterfaceOrientation(xarch, linkEndpoint2Target); switch (linkEndpoint2Orientation) { case NORTH: sb.append(":__n"); break; case EAST: sb.append(":__e"); break; case SOUTH: sb.append(":__s"); break; case WEST: sb.append(":__w"); break; default: sb.append(":__c"); } } sb.append(" [label=\""); sb.append(at.getAlias(linkXArchID)); sb.append("\"];"); sb.append(EOL); } //------------ public GraphLayout processOutput(AliasTable at, IXArchADT xarch, ObjRef rootRef, GraphLayoutParameters params, String output) throws GraphLayoutException { try { GraphLayout gl = new GraphLayout(); BufferedReader br = new BufferedReader(new StringReader(output)); double scale = params.getScale(); while (true) { String line = br.readLine().trim(); if (line.startsWith("stop")) { return gl; } else if (line.startsWith("node")) { StringTokenizer tok = new StringTokenizer(line); @SuppressWarnings("unused") String nodeToken = tok.nextToken(); String eltToken = tok.nextToken(); String xToken = tok.nextToken(); String yToken = tok.nextToken(); String widthToken = tok.nextToken(); String heightToken = tok.nextToken(); GraphLayout.Node node = new GraphLayout.Node(); node.setNodeId(at.getTruename(eltToken)); double xd = Double.parseDouble(xToken); double yd = Double.parseDouble(yToken); double widthd = Double.parseDouble(widthToken); double heightd = Double.parseDouble(heightToken); //We want a bounds rectangle, (xd,yd) is the CENTER //of the box, so we have to offset it by half the width //and half the height to get the UL coordinate. xd -= widthd / 2.0d; yd -= heightd / 2.0d; xd *= scale; int x = (int) Math.round(xd); yd *= scale; int y = (int) Math.round(yd); widthd *= scale; int width = (int) Math.round(widthd); heightd *= scale; int height = (int) Math.round(heightd); Rectangle bounds = new Rectangle(x, y, width, height); node.setBounds(bounds); gl.addNode(node); } else if (line.startsWith("edge")) { StringTokenizer tok = new StringTokenizer(line); @SuppressWarnings("unused") String edgeToken = tok.nextToken(); String endpt1Token = tok.nextToken(); String endpt2Token = tok.nextToken(); String numPointsToken = tok.nextToken(); int numPoints = Integer.parseInt(numPointsToken); String[] xTokens = new String[numPoints]; String[] yTokens = new String[numPoints]; for (int i = 0; i < numPoints; i++) { xTokens[i] = tok.nextToken(); yTokens[i] = tok.nextToken(); } String edgeIdToken = tok.nextToken(); GraphLayout.Edge edge = new GraphLayout.Edge(); edge.setEdgeId(at.getTruename(edgeIdToken)); int colon1Index = endpt1Token.indexOf(":"); String node1Token = null; String port1Token = null; if (colon1Index != -1) { node1Token = endpt1Token.substring(0, colon1Index); port1Token = endpt1Token.substring(colon1Index + 1); } /* * else{ int secondEIndex = endpt1Token.indexOf("e", 1); node1Token = endpt1Token.substring(0, * secondEIndex); port1Token = endpt1Token.substring(secondEIndex); } */ int colon2Index = endpt2Token.indexOf(":"); String node2Token = null; String port2Token = null; if (colon2Index != -1) { node2Token = endpt2Token.substring(0, colon2Index); port2Token = endpt2Token.substring(colon2Index + 1); } /* * else{ int secondEIndex = endpt2Token.indexOf("e", 1); node2Token = endpt2Token.substring(0, * secondEIndex); port2Token = endpt2Token.substring(secondEIndex); } */ edge.setEndpoint1(at.getTruename(node1Token), at.getTruename(port1Token)); edge.setEndpoint2(at.getTruename(node2Token), at.getTruename(port2Token)); for (int i = 0; i < numPoints; i++) { double xd = Double.parseDouble(xTokens[i]); xd *= scale; int x = (int) Math.round(xd); double yd = Double.parseDouble(yTokens[i]); yd *= scale; int y = (int) Math.round(yd); Point p = new Point(x, y); edge.addPoint(p); } gl.addEdge(edge); } } } catch (IOException e) { throw new GraphLayoutException("This shouldn't happen."); } catch (Exception e2) { throw new GraphLayoutException("Exception while parsing Graphviz output", e2); } } //------------ protected static boolean hasOrientedInterface(IXArchADT xarch, ObjRef brickRef) { for (ObjRef interfaceRef : Iterables.filter(xarch.getAll(brickRef, "interface"), ObjRef.class)) { Orientation o = guessInterfaceOrientation(xarch, interfaceRef); if (o != null && !o.equals(Orientation.NONE)) { return true; } } return false; } protected static Orientation guessInterfaceOrientation(IXArchADT xarch, ObjRef interfaceRef) { // Check the domain for (ObjRef extRef : Iterables.filter(xarch.getAll(interfaceRef, "ext"), ObjRef.class)) { if (XadlUtils.isInstanceOf(xarch, extRef, Domain_3_0Package.Literals.DOMAIN_EXTENSION)) { ObjRef domainRef = (ObjRef) xarch.get(extRef, "domain"); if (domainRef != null) { DomainType domainType = (DomainType) xarch.get(domainRef, "type"); if (domainType.equals(DomainType.TOP)) { return Orientation.NORTH; } else if (domainType.equals(DomainType.BOTTOM)) { return Orientation.SOUTH; } } } } //Try to guess based on the name. String name = XadlUtils.getName(xarch, interfaceRef); if (name == null) { return Orientation.NONE; } name = name.toLowerCase(); if (name.indexOf("top") != -1) { return Orientation.NORTH; } if (name.indexOf("bottom") != -1) { return Orientation.SOUTH; } if (name.indexOf("left") != -1) { return Orientation.WEST; } if (name.indexOf("right") != -1) { return Orientation.EAST; } if (name.indexOf("peer") != -1) { return Orientation.EAST; } if (name.indexOf("north") != -1) { return Orientation.NORTH; } if (name.indexOf("east") != -1) { return Orientation.EAST; } if (name.indexOf("west") != -1) { return Orientation.WEST; } if (name.indexOf("south") != -1) { return Orientation.SOUTH; } return Orientation.NONE; } protected static boolean shouldOrientInterfaces(GraphLayoutParameters params) { Object o = params.getProperty("orientInterfaces"); if (o == null) { return false; } if (o instanceof Boolean) { Boolean b = (Boolean) o; return b.booleanValue(); } return false; } }