package br.uff.ic.dyevc.gui.graph; //~--- non-JDK imports -------------------------------------------------------- import br.uff.ic.dyevc.application.IConstants; import br.uff.ic.dyevc.beans.ApplicationSettingsBean; import br.uff.ic.dyevc.exception.DyeVCException; import br.uff.ic.dyevc.graph.GraphBuilder; import br.uff.ic.dyevc.graph.transform.common.VertexStrokeHighlightTransformer; import br.uff.ic.dyevc.graph.transform.topology.TopologyEdgeLabelTransformer; import br.uff.ic.dyevc.graph.transform.topology.TopologyEdgePaintTransformer; import br.uff.ic.dyevc.graph.transform.topology.TopologyEdgeStrokeTransformer; import br.uff.ic.dyevc.graph.transform.topology.TopologyPickWithIconListener; import br.uff.ic.dyevc.graph.transform.topology.TopologyVertexIconShapeTransformer; import br.uff.ic.dyevc.graph.transform.topology.TopologyVertexIconTransformer; import br.uff.ic.dyevc.graph.transform.topology.TopologyVertexPaintTransformer; import br.uff.ic.dyevc.graph.transform.topology.TopologyVertexTooltipTransformer; import br.uff.ic.dyevc.model.CommitInfo; import br.uff.ic.dyevc.model.CommitRelationship; import br.uff.ic.dyevc.model.topology.CloneRelationship; import br.uff.ic.dyevc.model.topology.PullRelationship; import br.uff.ic.dyevc.model.topology.PushRelationship; import br.uff.ic.dyevc.model.topology.RepositoryInfo; import br.uff.ic.dyevc.persistence.TopologyDAO; import br.uff.ic.dyevc.utils.PreferencesManager; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.visualization.control.CrossoverScalingControl; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ScalingControl; import edu.uci.ics.jung.visualization.decorators.AbstractEdgeShapeTransformer; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.GraphZoomScrollPane; import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer; import edu.uci.ics.jung.visualization.renderers.Renderer; import edu.uci.ics.jung.visualization.VisualizationViewer; import org.apache.commons.collections15.Predicate; import org.apache.commons.collections15.Transformer; import org.apache.commons.lang.time.StopWatch; import org.slf4j.LoggerFactory; //~--- JDK imports ------------------------------------------------------------ import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.GridLayout; import java.awt.Toolkit; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.BorderFactory; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.ToolTipManager; /** * Displays the topology for the specified system * * @author cristiano */ public class TopologyWindow extends javax.swing.JFrame { private static final long serialVersionUID = 1689885032823010309L; private ApplicationSettingsBean settings; private final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse<CommitInfo, CommitRelationship>(); private final ScalingControl scaler = new CrossoverScalingControl(); private String instructions = "<html><p>Each vertex in the graph represents a known clone of this system in the topology.</p>" + "<p>Each vertex label shows the hostname and the clone name of the vertex, separated by a dash.</p>" + "<p>Each vertex type/color has a different meaning: </p>" + "<ul>" + "<li>Blue computer: the vertex that represents your clone;</li>" + "<li>Black computers: ordinary clones;</li>" + "<li>Server: central repositories (do not pull or push " + "to any other clone) or clones where DyeVC is not running;</li>" + "<li>Vertices with a green checkmark: picked vertices.</li>" + "</ul>" + "<p>Each edge in the graph represents a relationship between two repositories. Different" + "strokes represent different relationships:</p>" + "<ul>" + "<li>Continuous edges: the source clone pushes to the destination vertex; </li>" + "<li>Dotted edges: the destination clone pulls from the source vertex. </li>" + "</ul>" + "<p>Edge colors depict the synchronism between two clones:</p>" + "<ul>" + "<li>Yellow edges represent a picked edge;</li>" + "<li>Green edges represent that source clone is synchronized with destination clone;</li>" + "<li>Red edges represent that source clone is not synchronized with destination clone.</li>" + "</ul>" + "<p>First number in edge labels tells how many tracked commits from the source clone are missing in " + "the destination clone.</p>" + "<p>Second number in edge labels tells how many non-tracked commits from the source clone are missing in " + "the destination clone.</p>" + "<br><p>Place the mouse over a vertex to view detailed information " + "regarding it.</p>" + "</html>"; private String systemName; private String callerId; private DirectedSparseMultigraph graph; private VisualizationViewer vv; private Layout layout; private JComboBox mouseModesCombo; private JButton plus; private JButton minus; private JButton btnHelp; private JCheckBox chkShowPush; private JCheckBox chkShowPull; private DirectionDisplayPredicate<RepositoryInfo, CloneRelationship> showRelationPredicate; /** * Creates a topology window without specifying the caller Id * * @param systemName The system that will have the topology plotted */ public TopologyWindow(String systemName) { this(systemName, null); } /** * Creates a topology window specifying the caller Id * * * @param systemName The system that will have the topology plotted * @param callerId The caller Id (node from which the plotting was asked */ public TopologyWindow(String systemName, String callerId) { // SplashScreen splash = SplashScreen.getInstance(); try { // splash.setStatus("Initializing Graph component"); this.systemName = systemName; this.callerId = callerId; settings = PreferencesManager.getInstance().loadPreferences(); // SplashScreen.getInstance().setVisible(true); StopWatch watch = new StopWatch(); if (settings.isPerformanceMode()) { watch.start(); } initGraphComponent(); if (settings.isPerformanceMode()) { watch.stop(); LoggerFactory.getLogger(TopologyWindow.class).info("Time taken to process topology graph for system <" + systemName + ">: " + watch.toString()); } // SplashScreen.getInstance().setStatus("Initializing Window components"); if (settings.isPerformanceMode()) { watch.reset(); watch.start(); } initComponents(); LoggerFactory.getLogger(TopologyWindow.class).info("Time taken to plot topology graph for system <" + systemName + ">: " + watch.toString()); // SplashScreen.getInstance().setVisible(false); } catch (DyeVCException ex) { // splash.dispose(); JOptionPane.showMessageDialog(null, "Application received the following exception trying to show topology:\n" + ex + "\n\nOpen console window to see error details.", "Error found!", JOptionPane.ERROR_MESSAGE); WindowEvent wev = new WindowEvent(this, WindowEvent.WINDOW_CLOSING); Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev); setVisible(false); dispose(); } catch (RuntimeException ex) { ex.printStackTrace(System.err); // splash.dispose(); JOptionPane.showMessageDialog(null, "Application received the following exception trying to show topology:\n" + ex + "\n\nOpen console window to see error details.", "Error found!", JOptionPane.ERROR_MESSAGE); WindowEvent wev = new WindowEvent(this, WindowEvent.WINDOW_CLOSING); Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev); setVisible(false); dispose(); } } // <editor-fold defaultstate="collapsed" desc="initComponents"> /** * Method description * */ private void initComponents() { if (callerId == null) { setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); } else { setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); } addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent we) { resetComponents(); } }); setAutoRequestFocus(true); setTitle("Topology for system " + systemName); java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); setBounds((screenSize.width - 1024) / 2, (screenSize.height - 768) / 2, 1024, 768); mouseModesCombo = graphMouse.getModeComboBox(); mouseModesCombo.addItemListener(graphMouse.getModeListener()); graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); plus = new JButton("+"); plus.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1.1f, vv.getCenter()); vv.repaint(); } }); minus = new JButton("-"); minus.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { scaler.scale(vv, 1 / 1.1f, vv.getCenter()); vv.repaint(); } }); btnHelp = new JButton("Help"); btnHelp.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(null, instructions, "Help", JOptionPane.PLAIN_MESSAGE); } }); chkShowPush = new JCheckBox("Push"); chkShowPush.setSelected(true); chkShowPush.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { showRelationPredicate.showPushRelations(chkShowPush.isSelected()); vv.repaint(); } }); chkShowPull = new JCheckBox("Pull"); chkShowPull.setSelected(true); chkShowPull.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { showRelationPredicate.showPullRelations(chkShowPull.isSelected()); vv.repaint(); } }); Container content = getContentPane(); GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv); content.add(gzsp); JPanel controls = new JPanel(); JPanel zoomControls = new JPanel(new GridLayout(2, 1)); zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom")); zoomControls.add(plus); zoomControls.add(minus); controls.add(zoomControls); JPanel pnlMouseMoude = new JPanel(new GridLayout(1, 1)); pnlMouseMoude.setBorder(BorderFactory.createTitledBorder("Mouse Mode")); pnlMouseMoude.add(mouseModesCombo); controls.add(pnlMouseMoude); JPanel pnlShowEdge = new JPanel(new GridLayout(1, 3)); pnlShowEdge.setBorder(BorderFactory.createTitledBorder("Filter relations")); pnlShowEdge.add(chkShowPush); pnlShowEdge.add(chkShowPull); controls.add(pnlShowEdge); controls.add(btnHelp); content.add(controls, BorderLayout.SOUTH); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc="initGraphComponent"> /** * Method description * * * @throws DyeVCException */ private void initGraphComponent() throws DyeVCException { // create the commit history graph with all commits from repository graph = GraphBuilder.createTopologyGraph(systemName); // Choosing layout layout = new FRLayout<RepositoryInfo, CloneRelationship>(graph); Dimension preferredSize = new Dimension(800, 600); vv = new VisualizationViewer(layout, preferredSize); // Scales the graph to show more nodes scaler.scale(vv, 0.9F, vv.getCenter()); vv.scaleToLayout(scaler); vv.setBackground(IConstants.BACKGROUND_COLOR); // Adds interaction via mouse and defaults mode to Transforming vv.setGraphMouse(graphMouse); vv.addKeyListener(graphMouse.getModeKeyListener()); graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); PickedState<RepositoryInfo> ps = vv.getPickedVertexState(); // <editor-fold defaultstate="collapsed" desc="vertex transformers"> // VertexToolTip vv.setVertexToolTipTransformer(new TopologyVertexTooltipTransformer()); ToolTipManager.sharedInstance().setDismissDelay(15000); // VertexFillPaint vv.getRenderContext().setVertexFillPaintTransformer(new TopologyVertexPaintTransformer(ps, callerId)); // VertexLabel vv.getRenderContext().setVertexLabelTransformer(new Transformer<RepositoryInfo, String>() { @Override public String transform(RepositoryInfo c) { return c.getCloneName() + " [" + c.getId() + "]"; } }); vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.AUTO); // VertexStroke vv.getRenderContext().setVertexStrokeTransformer(new VertexStrokeHighlightTransformer<RepositoryInfo, CloneRelationship>(graph, vv.getPickedVertexState())); // VertexShape // vv.getRenderContext().setVertexShapeTransformer(new ClusterVertexShapeTransformer()); vv.getRenderContext().setVertexShapeTransformer(new TopologyVertexIconShapeTransformer(callerId)); // VertexIcon TopologyVertexIconTransformer vertexIconTransformer = new TopologyVertexIconTransformer(callerId); vv.getRenderContext().setVertexIconTransformer(vertexIconTransformer); // Adds a listener to decorate the vertex with a checkmark icon when its picked ps.addItemListener(new TopologyPickWithIconListener(vertexIconTransformer)); // </editor-fold> // <editor-fold defaultstate="collapsed" desc="edge transformers"> // EdgeLabel Transformer<Object, String> edgeLabel = new TopologyEdgeLabelTransformer(); vv.getRenderContext().setEdgeLabelTransformer(edgeLabel); EdgeLabelRenderer edgeLabelRenderer = vv.getRenderContext().getEdgeLabelRenderer(); edgeLabelRenderer.setRotateEdgeLabels(true); // EdgeDrawPaint TopologyEdgePaintTransformer ept = new TopologyEdgePaintTransformer(vv.getPickedEdgeState()); vv.getRenderContext().setEdgeDrawPaintTransformer(ept); // Arrows vv.getRenderContext().setArrowFillPaintTransformer(ept); vv.getRenderContext().setArrowDrawPaintTransformer(ept); // EdgeStroke vv.getRenderContext().setEdgeStrokeTransformer(new TopologyEdgeStrokeTransformer<CloneRelationship>()); // EdgeShape - sets it as quadcurve and defines a greater offset between parallel edges vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.QuadCurve()); ((AbstractEdgeShapeTransformer)vv.getRenderContext().getEdgeShapeTransformer()).setControlOffsetIncrement(30); // </editor-fold> showRelationPredicate = new DirectionDisplayPredicate<RepositoryInfo, CloneRelationship>(true, true); vv.getRenderContext().setEdgeIncludePredicate(showRelationPredicate); } // </editor-fold> /** * Method description * */ private void resetGraph() { layout.setGraph(graph); layout.initialize(); vv.repaint(); } /** * Method description * */ private void resetComponents() { graph = null; vv = null; layout = null; } /** * runs the graph with a default system name * * @param args */ public static void main(String[] args) { try { String sysName = "dyevc"; TopologyDAO dao = new TopologyDAO(); dao.readTopologyForSystem(sysName); new TopologyWindow(sysName).setVisible(true); } catch (DyeVCException ex) { Logger.getLogger(TopologyWindow.class.getName()).log(Level.SEVERE, null, ex); } } /** * Predicate to filter push and / or pull edges * * @param <V> The type of vertices * @param <E> The type of edges * * @author Cristiano Cesario */ private final static class DirectionDisplayPredicate<V, E> implements Predicate<Context<Graph<V, E>, E>> { /** * If true, than show push edges */ protected boolean showPush; /** * If true, than show pull edges */ protected boolean showPull; /** * Builds the predicate with initial values specified for <code>showPush</code> and <code>showPull</code> * * @param showPush The initial value for showPush * @param showPull Theh initial value for showPull */ public DirectionDisplayPredicate(boolean showPush, boolean showPull) { this.showPush = showPush; this.showPull = showPull; } /** * Specifies whether push relations should be shown or not * * @param b If true, than show push relations */ public void showPushRelations(boolean b) { showPush = b; } /** * Specifies whether pull relations should be shown or not * * @param b If true, than show pull relations */ public void showPullRelations(boolean b) { showPull = b; } /** * Evaluate the predicate for each edge, showing the edge or not, according to its type * * @param context The context where the predicate will be evaluated * * @return True, if the edge is to be shown and false otherwise */ @Override public boolean evaluate(Context<Graph<V, E>, E> context) { Graph<V, E> graph = context.graph; E e = context.element; if ((e instanceof PushRelationship) && showPush) { return true; } if ((e instanceof PullRelationship) && showPull) { return true; } return false; } } }