/** * * @author greg (at) myrobotlab.org * * This file is part of MyRobotLab (http://myrobotlab.org). * * MyRobotLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version (subject to the "Classpath" exception * as provided in the LICENSE.txt file that accompanied this code). * * MyRobotLab is distributed in the hope that it will be useful or fun, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * All libraries in thirdParty bundle are subject to their own license * requirements - please refer to http://myrobotlab.org/libraries for * details. * * Enjoy ! * * References : * http://libjgraphx-java.sourcearchive.com/documentation/1.7.0.7/classcom_1_1mxgraph_1_1util_1_1mxConstants.html * */ package org.myrobotlab.control; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTabbedPane; import org.myrobotlab.control.GUIServiceGraphVertex.Type; import org.myrobotlab.control.widget.Style; import org.myrobotlab.framework.MRLListener; import org.myrobotlab.service.GUIService; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.interfaces.ServiceInterface; import org.w3c.dom.Document; import com.mxgraph.io.mxCellCodec; import com.mxgraph.io.mxCodec; import com.mxgraph.io.mxCodecRegistry; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxGeometry; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.util.mxConstants; import com.mxgraph.util.mxPoint; import com.mxgraph.util.mxRectangle; import com.mxgraph.util.mxUtils; import com.mxgraph.view.mxEdgeStyle; import com.mxgraph.view.mxGraph; public class GUIServiceGUI extends ServiceGUI { class ButtonListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { JButton b = (JButton) e.getSource(); if (b == rebuildButton) { rebuildGraph(); } else if (b == hideRoutesButton) { if (b.getText().equals("show routes")) { b.setText("hide routes"); showRoutes = true; } else { b.setText("show routes"); showRoutes = false; } rebuildGraph(); } else if (b == showRouteLabelsButton) { if (b.getText().equals("show route labels")) { b.setText("hide route labels"); showRouteLabels = true; } else { b.setText("show route labels"); showRouteLabels = false; } rebuildGraph(); } else if (b == accessURLButton) { if (b.getText().equals("show access URLs")) { b.setText("hide access URLs"); showAccessURLs = true; } else { b.setText("show access URLs"); showAccessURLs = false; } rebuildGraph(); } else if (b == dumpButton) { Runtime.dump(); } } } static final long serialVersionUID = 1L; final int PORT_DIAMETER = 20; final int PORT_RADIUS = PORT_DIAMETER / 2; // addListener structure begin ------------- public JLabel srcServiceName = new JLabel(" "); public JLabel srcMethodName = new JLabel(" "); public JLabel parameterList = new JLabel(" "); public JLabel dstMethodName = new JLabel(); public JLabel dstServiceName = new JLabel(); public JLabel period0 = new JLabel(" "); public JLabel period1 = new JLabel(" "); public JLabel arrow0 = new JLabel(" "); // public JLabel arrow1 = new JLabel(" ");o // addListener structure end ------------- ButtonListener buttonListener = new ButtonListener(); boolean showRoutes = false; // DEPRICATE - ITS NOT NORMALIZED !!!! boolean showRouteLabels = false; boolean showAccessURLs = false; public HashMap<String, mxCell> serviceCells = new HashMap<String, mxCell>(); public mxGraph graph = null; mxCell currentlySelectedCell = null; mxGraphComponent graphComponent = null; JButton rebuildButton = new JButton("rebuild"); JButton hideRoutesButton = new JButton("show routes"); JButton accessURLButton = new JButton("show access URLs"); JButton showRouteLabelsButton = new JButton("show route labels"); JButton dumpButton = new JButton("dump"); public static String formatMethodString(String out, String in) { // test if outmethod = in String methodString = out; // if (methodString != in) { methodString += "->" + in; // } // TODO FYI - depricate MRLListener use MethodEntry // These parameter types could always be considered "inbound" ? or // returnType // TODO - view either full named paths or shortnames methodString += "("; /* * if (paramTypes != null) { for (int j = 0; j < paramTypes.length; ++j) { * // methodString += paramTypes[j].getCanonicalName(); Class c = * paramTypes[j]; String t[] = c.getCanonicalName().split("\\."); * methodString += t[t.length - 1]; * * if (j < paramTypes.length - 1) { methodString += ","; } } } */ methodString += ")"; return methodString; } public GUIServiceGUI(final String boundServiceName, final GUIService myService, final JTabbedPane tabs) { super(boundServiceName, myService, tabs); } // FIXME - should it hook to the Runtime ??? @Override public void attachGUI() { } public void buildGraph() { log.debug("buildGraph"); // -------------------------BEGIN PURE JGRAPH // ---------------------------- if (myService.getGraphXML() == null || myService.getGraphXML().length() == 0) { if (graph == null) { graph = getNewMXGraph(); } // new graph ! graph.getModel().beginUpdate(); try { buildLocalServiceGraph(); if (showRoutes) { buildLocalServiceRoutes(); } } finally { graph.getModel().endUpdate(); } } else { // we have serialized version of graph... // de-serialize it // register mxCodecRegistry.addPackage("org.myrobotlab.control"); mxCodecRegistry.register(new mxCellCodec(new org.myrobotlab.control.GUIServiceGraphVertex())); mxCodecRegistry.register(new mxCellCodec(Type.INPORT)); // load Document document = mxUtils.parseXml(myService.getGraphXML()); mxCodec codec2 = new mxCodec(document); graph = getNewMXGraph(); codec2.decode(document.getDocumentElement(), graph.getModel()); Object parent = graph.getDefaultParent(); Object services[] = graph.getChildVertices(parent); // log.debug("serviceCount " + services.length); for (int i = 0; i < services.length; ++i) { // serviceCells Object s = services[i]; log.debug("service {}", s); mxCell m = (mxCell) services[i]; GUIServiceGraphVertex v = (GUIServiceGraphVertex) m.getValue(); log.debug(v.name); serviceCells.put(v.name, m); // serviceCells.put(arg0, s.); } } // TODO - get # of services to set size? graph.setMinimumGraphSize(new mxRectangle(0, 0, 600, 400)); // Sets the default edge style // list of styles - // http://libjgraphx-java.sourcearchive.com/documentation/1.7.0.7/classcom_1_1mxgraph_1_1util_1_1mxConstants.html Map<String, Object> style = graph.getStylesheet().getDefaultEdgeStyle(); style.put(mxConstants.STYLE_EDGE, mxEdgeStyle.EntityRelation);// .ElbowConnector style.put(mxConstants.STYLE_STROKECOLOR, "black");// .ElbowConnector style.put(mxConstants.STYLE_EDITABLE, "0");// .ElbowConnector style.put(mxConstants.STYLE_MOVABLE, "0");// .ElbowConnector // creating JComponent if (graphComponent == null) { graphComponent = new mxGraphComponent(graph); // graphPanel.add(graphComponent); display.add(graphComponent, BorderLayout.CENTER); // graphComponent.addKeyListener(this); // graphComponent.getGraphControl().addMouseListener(this); graphComponent.getGraphControl().addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged(MouseEvent e) { // TODO: this doesn't do anything. // Object cell = graphComponent.getCellAt(e.getX(), e.getY()); // too chatty log.debug("dragged cell " + cell + " " + // e.getX() + "," + e.getY()); } @Override public void mouseMoved(MouseEvent e) { // TODO: this doesn't do anything. // Object cell = graphComponent.getCellAt(e.getX(), e.getY()); // too chatty log.debug("dragged - mouseMoved - cell " + cell // + " " + e.getX() + "," + e.getY()); } }); graphComponent.getGraphControl().addMouseListener(new MouseAdapter() { /* * protected void mouseLocationChanged(MouseEvent e) { * log.debug(e.getX() + ", " + e.getY()); } * * public void mouseDragged(MouseEvent e) { // http://forum.jgraph * .com/questions/1343/mouse-coordinates-at-drop-event Object cell = * graphComponent.getCellAt(e.getX(), e.getY()); log.debug(e.getX() + * "," + e.getY()); } */ @Override public void mouseReleased(MouseEvent e) { Object cell = graphComponent.getCellAt(e.getX(), e.getY()); // too chatty log.debug("cell " + e.getX() + "," + e.getY()); currentlySelectedCell = (mxCell) cell; if (cell != null) { mxCell m = (mxCell) cell; // too chatty log.debug("cell=" + graph.getLabel(cell) + // ", " + m.getId() + ", " // + graph.getLabel(m.getParent())); if (m.isVertex()) { // TODO - edges get filtered through here too - need // to process - (String) type GUIServiceGraphVertex v = (GUIServiceGraphVertex) m.getValue(); if (v.displayName.equals("out")) { new GUIServiceOutMethodDialog(myService, "out method", v); } else if (v.displayName.equals("in")) { new GUIServiceInMethodDialog(myService, "in method", v); } } else if (m.isEdge()) { log.error("isEdge"); } } } }); graphComponent.setToolTips(true); } // -------------------------END PURE // JGRAPH-------------------------------------- } public void buildLocalServiceGraph() { log.debug("buildLocalServiceGraph-begin"); Map<String, ServiceInterface> services = Runtime.getRegistry(); log.debug("GUIServiceGUI service count " + Runtime.getRegistry().size()); TreeMap<String, ServiceInterface> sortedMap = new TreeMap<String, ServiceInterface>(services); Iterator<String> it = sortedMap.keySet().iterator(); int x = 20; int y = 20; Object parent = graph.getDefaultParent(); serviceCells.clear(); while (it.hasNext()) { String serviceName = it.next(); ServiceInterface sw = services.get(serviceName); String displayName; String toolTip; String canonicalName; canonicalName = sw.getSimpleName(); displayName = serviceName + "\n\n\n\n\n.";// + // sw.get().getSimpleName(); toolTip = sw.getDescription(); String blockColor = null; if (sw.getInstanceId() == null) { blockColor = mxUtils.getHexColorString(Style.background); } else { blockColor = mxUtils.getHexColorString(Style.remoteBackground); } if (showAccessURLs) { displayName = sw.getInstanceId() + "\n" + displayName; } mxCell v1 = (mxCell) graph.insertVertex(parent, null, new GUIServiceGraphVertex(serviceName, canonicalName, displayName, toolTip, GUIServiceGraphVertex.Type.SERVICE), x, y, 100, 50, "shape=image;image=/resource/" + canonicalName + ".png"); // "ROUNDED;fillColor=" + blockColor); // graphComponent.getGraphControl().scrollRectToVisible(new // Rectangle(0, 0, 900, 800), true); serviceCells.put(serviceName, v1); v1.setConnectable(false); mxGeometry geo = graph.getModel().getGeometry(v1); // The size of the rectangle when the minus sign is clicked geo.setAlternateBounds(new mxRectangle(20, 20, 100, 50)); mxGeometry geo1 = new mxGeometry(0, 0.5, PORT_DIAMETER, PORT_DIAMETER); // Because the origin is at upper left corner, need to translate to // position the center of port correctly geo1.setOffset(new mxPoint(-PORT_RADIUS, -PORT_RADIUS)); geo1.setRelative(true); mxCell inport = new mxCell(new GUIServiceGraphVertex(serviceName, canonicalName, "in", toolTip, GUIServiceGraphVertex.Type.INPORT), geo1, "shape=ellipse;perimter=ellipsePerimeter;fillColor=" + blockColor); inport.setVertex(true); mxGeometry geo2 = new mxGeometry(1.0, 0.5, PORT_DIAMETER, PORT_DIAMETER); geo2.setOffset(new mxPoint(-PORT_RADIUS, -PORT_RADIUS)); geo2.setRelative(true); mxCell outport = new mxCell(new GUIServiceGraphVertex(serviceName, canonicalName, "out", toolTip, GUIServiceGraphVertex.Type.OUTPORT), geo2, "shape=ellipse;perimter=ellipsePerimeter;fillColor=" + blockColor); outport.setVertex(true); graph.addCell(inport, v1); graph.addCell(outport, v1); x += 150; if (x > 800) { y += 150; x = 20; } } log.debug("buildLocalServiceGraph-end"); } // FIXME - return a "copy" of registry ???? // versus sunchronize on it? public synchronized void buildLocalServiceRoutes() { Iterator<String> it = Runtime.getRegistry().keySet().iterator(); Object parent = graph.getDefaultParent(); // FIXME either getServiceWrapper & getNotifyList need to return copies // - or they need // to be implemented with type safe collections - // "copies are probably preferred" while (it.hasNext()) { String serviceName = it.next(); ServiceInterface s = Runtime.getService(serviceName); if (s != null) { Iterator<String> ri = s.getNotifyListKeySet().iterator(); while (ri.hasNext()) { ArrayList<MRLListener> nl = s.getNotifyList(ri.next()); for (int i = 0; i < nl.size(); ++i) { MRLListener listener = nl.get(i); // ROUTING LABELS if (showRouteLabels) { graph.insertEdge(parent, null, formatMethodString(listener.topicMethod, listener.callbackMethod), serviceCells.get(s.getName()), serviceCells.get(listener.callbackName)); } else { graph.insertEdge(parent, null, "", serviceCells.get(s.getName()), serviceCells.get(listener.callbackName)); } } } } else { log.error("can not add graphic routes, since " + serviceName + "'s type is unknown"); } } } public void clearGraph() { graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); buildGraph(); } @Override public void detachGUI() { } public mxGraph getNewMXGraph() { mxGraph g = new mxGraph() { // Implements a tooltip that shows the actual // source and target of an edge @Override public String getToolTipForCell(Object cell) { if (model.isEdge(cell)) { return convertValueToString(model.getTerminal(cell, true)) + " -> " + convertValueToString(model.getTerminal(cell, false)); } mxCell m = (mxCell) cell; GUIServiceGraphVertex sw = (GUIServiceGraphVertex) m.getValue(); if (sw != null) { return sw.toolTip; } else { return "<html>port node<br>click to drag and drop static routes</html>"; } } // Removes the folding icon and disables any folding @Override public boolean isCellFoldable(Object cell, boolean collapse) { // return true; return false; } // Ports are not used as terminals for edges, they are // only used to compute the graphical connection point @Override public boolean isPort(Object cell) { mxGeometry geo = getCellGeometry(cell); return (geo != null) ? geo.isRelative() : false; } }; return g; } @Override public void init() { display.setLayout(new BorderLayout()); JPanel top = new JPanel(); JPanel newRoute = new JPanel(new FlowLayout()); newRoute.setBorder(BorderFactory.createTitledBorder("new message route")); newRoute.add(srcServiceName); newRoute.add(period0); newRoute.add(srcMethodName); newRoute.add(arrow0); newRoute.add(dstServiceName); newRoute.add(period1); newRoute.add(dstMethodName); buildGraph(); // begin graph view buttons JPanel filters = new JPanel(); filters.add(rebuildButton); filters.add(hideRoutesButton); filters.add(showRouteLabelsButton); filters.add(accessURLButton); filters.add(dumpButton); top.add(newRoute); top.add(filters); display.add(top, BorderLayout.PAGE_START); accessURLButton.addActionListener(buttonListener); rebuildButton.addActionListener(buttonListener); hideRoutesButton.addActionListener(buttonListener); showRouteLabelsButton.addActionListener(buttonListener); dumpButton.addActionListener(buttonListener); } public void rebuildGraph() { graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); buildGraph(); } }