package org.archstudio.utils.bna.dot; import java.awt.geom.Point2D; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import org.archstudio.bna.IBNAModel; import org.archstudio.bna.IBNAView; import org.archstudio.bna.IBNAWorld; import org.archstudio.bna.ICoordinate; import org.archstudio.bna.IThing; import org.archstudio.bna.facets.IHasMutableBoundingBox; import org.archstudio.bna.facets.IHasMutableMidpoints; import org.archstudio.bna.logics.AbstractThingLogic; import org.archstudio.bna.ui.IBNAMenuListener2; import org.archstudio.bna.utils.BNAAction; import org.archstudio.bna.utils.BNAUtils; import org.archstudio.bna.utils.BNAUtils2.ThingsAtLocation; import org.archstudio.graphlayout.GraphLayoutConstants; import org.archstudio.sysutils.NativeProcess; import org.archstudio.sysutils.SystemUtils; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import com.google.common.collect.Maps; import com.google.common.collect.Sets; public class ExportImportDot extends AbstractThingLogic implements IBNAMenuListener2 { public ExportImportDot(IBNAWorld world) { super(world); } @Override public void fillMenu(final IBNAView view, ICoordinate location, ThingsAtLocation thingsAtLocation, IMenuManager menuManager) { BNAUtils.checkLock(); if (thingsAtLocation.getViewAtLocation() != null) { menuManager.add(new BNAAction("Layout using Dot") { @Override public void runWithLock() { try { layout(view, Sets.newHashSet(BNAUtils.getSelectedThings(view.getBNAWorld().getBNAModel()))); } catch (Exception e) { e.printStackTrace(); MessageDialog.openError(view.getBNAUI().getComposite().getShell(), "Error", e.getMessage()); } } }); } } protected void layout(IBNAView view, Set<IThing> involvingThings) throws IOException, InvocationTargetException, InterruptedException { final IBNAModel model = view.getBNAWorld().getBNAModel(); if (involvingThings.size() == 0) { involvingThings.addAll(model.getAllThings()); } double scale = 0.1; final Map<String, Node> nodes = Node.scanForNodes(world, involvingThings, scale); final Map<String, Edge> edges = Edge.scanForEdges(world, involvingThings, nodes); StringBuilder sb = new StringBuilder(); sb.append("digraph G {\n"); sb.append("overlap=false;\n"); sb.append("scale=" + Node.SIMPLE_DECIMAL_FORMATTER.format(scale) + ";\n"); sb.append("splines=line;\n"); for (Node node : Edge.filterNodesByEdges(nodes.values(), edges.values())) { sb.append(node.toDotNode()); } for (Edge edge : edges.values()) { sb.append(edge.toDotEdge()); } sb.append("}\n"); // Execute dot. String result = runDot( org.archstudio.graphlayout.core.Activator.getDefault().getPreferenceStore(), sb.toString()); final Map<String, Point> locations = Maps.newHashMap(); BufferedReader br = new BufferedReader(new StringReader(result)); String line; while ((line = br.readLine()) != null) { if (line.startsWith("node")) { String[] fields = line.split("\\s+"); String uid = fields[1]; double x = Float.parseFloat(fields[2]) / scale; double y = Float.parseFloat(fields[3]) / scale; locations.put(uid, new Point(SystemUtils.round(x), SystemUtils.round(y))); } } // Update BNA model. for (Node node : nodes.values()) { String uid = Node.getUniqueId(node.getThing()); Point p = locations.get(uid); if (p != null) { IThing thing = node.getThing(); if (thing instanceof IHasMutableBoundingBox) { Rectangle r = ((IHasMutableBoundingBox) thing).getBoundingBox(); r.x = p.x - r.width / 2; r.y = p.y - r.height / 2; ((IHasMutableBoundingBox) thing).setBoundingBox(r); } } } for (Edge edge : edges.values()) { IThing thing = edge.getThing(); if (thing instanceof IHasMutableMidpoints) { ((IHasMutableMidpoints) thing).setMidpoints(new ArrayList<Point2D>()); } } } protected String runDot(IPreferenceStore prefs, String toolInput) throws IOException { SystemUtils.OperatingSystem os = SystemUtils.guessOperatingSystem(); String toolFilename = "dot"; if (os.equals(SystemUtils.OperatingSystem.OS_WINDOWS)) { toolFilename = toolFilename + ".exe"; } String graphvizPath = prefs.getString(GraphLayoutConstants.PREF_GRAPHVIZ_PATH); if (graphvizPath == null) { throw new IOException("Don't have path for GraphViz dot. Check Graph Layout Preferences."); } String dotPath = graphvizPath; if (!dotPath.endsWith(File.separator)) { dotPath += File.separator; } if (!dotPath.endsWith("bin" + File.separator)) { dotPath += "bin" + File.separator; } dotPath += toolFilename; File dotExecutable = new File(dotPath); if (!dotExecutable.exists()) { throw new IOException("Can't find GraphViz dot. Check Graph Layout Preferences."); } if (!dotExecutable.canRead()) { throw new IOException("No read permission on GraphViz dot."); } // We have to flip Y so we don't get a mathematical (i.e. 0,0 at the lower-left corner) // coordinate system. List<String> commandLineEltList = new ArrayList<String>(); commandLineEltList.add(dotPath); commandLineEltList.add("-Tplain-ext"); commandLineEltList.add("-y"); try { String[] commandline = commandLineEltList.toArray(new String[0]); Process p = Runtime.getRuntime().exec(commandline); NativeProcess np = new NativeProcess(p, toolInput); np.start(); np.waitForCompletion(); String outputData = np.getStdout().trim(); if (outputData.length() == 0) { // we got an error throw new IOException(np.getStderr().trim()); } return outputData; } catch (IOException ioe) { System.err.println(toolInput); throw new IOException("I/O Error Running Graphviz dot", ioe); } } }