Java tutorial
/*************************************************************************** * Copyright 2015 Kieker Project (http://kieker-monitoring.net) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ package kieker.tools; import java.awt.Color; import java.awt.Frame; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.swing.JFrame; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.eclipse.emf.common.util.EList; import org.w3c.dom.Document; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxGeometry; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.util.mxCellRenderer; import com.mxgraph.util.mxConstants; import com.mxgraph.util.mxEvent; import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxEventSource.mxIEventListener; import com.mxgraph.util.mxPoint; import com.mxgraph.util.mxUtils; import com.mxgraph.util.mxXmlUtils; import com.mxgraph.view.mxGraph; import kieker.analysis.AnalysisController; import kieker.analysis.model.analysisMetaModel.MIAnalysisComponent; import kieker.analysis.model.analysisMetaModel.MIFilter; import kieker.analysis.model.analysisMetaModel.MIInputPort; import kieker.analysis.model.analysisMetaModel.MIOutputPort; import kieker.analysis.model.analysisMetaModel.MIPlugin; import kieker.analysis.model.analysisMetaModel.MIPort; import kieker.analysis.model.analysisMetaModel.MIProject; import kieker.analysis.model.analysisMetaModel.MIReader; import kieker.analysis.model.analysisMetaModel.MIRepository; import kieker.analysis.model.analysisMetaModel.MIRepositoryConnector; import kieker.common.logging.Log; import kieker.common.logging.LogFactory; /** * A simple visualization of analysis configurations. * * @author Jan Waller, Nils Christian Ehmke * * @since 1.5 */ public final class KaxViz extends AbstractCommandLineTool { static final Log LOG = LogFactory.getLog(KaxViz.class); // NOPMD package for inner class private String kaxFilename; private String svgFilename; private KaxViz() { super(true); } /** * Starts the visualization of a .kax file. * * @param args * The command line arguments (including the name of the .kax file in question). */ public static final void main(final String[] args) { new KaxViz().start(args); } @Override protected void addAdditionalOptions(final Options options) { final Option inputOption = new Option("i", "input", true, "the analysis project file (.kax) loaded"); final Option outputoption = new Option("svg", true, "name of svg saved on close"); inputOption.setArgName("filename"); outputoption.setArgName("filename"); options.addOption(inputOption); options.addOption(outputoption); } @Override protected boolean readPropertiesFromCommandLine(final CommandLine commandLine) { this.kaxFilename = commandLine.getOptionValue('i'); this.svgFilename = commandLine.getOptionValue("svg"); return this.assertInputFileExists(); } private boolean assertInputFileExists() { if (this.kaxFilename == null) { LOG.error("No input file configured"); return false; } return true; } @Override protected boolean performTask() { try { final KaxVizFrame frame = new KaxVizFrame(this.kaxFilename, AnalysisController.loadFromFile(new File(this.kaxFilename)), this.svgFilename); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setExtendedState(Frame.MAXIMIZED_BOTH); frame.setSize(800, 600); frame.setVisible(true); } catch (final IOException ex) { LOG.error("The given file could not be loaded", ex); return false; } catch (final Exception ex) { // NOPMD NOCS (log all errors) LOG.error("Error", ex); return false; } return true; } private static class KaxVizFrame extends JFrame { private static final long serialVersionUID = 1969467089938687452L; private static final int FILTER_HEIGHT = 80; private static final int FILTER_WIDTH = 200; private static final int FILTER_SPACE = 30; private static final String STYLE_READER_COLOR = mxConstants.STYLE_FILLCOLOR + "=#A3C592;"; private static final String STYLE_FILTER_COLOR = mxConstants.STYLE_FILLCOLOR + "=#C3D9FF;"; private static final String STYLE_REPOSITORY_COLOR = mxConstants.STYLE_FILLCOLOR + "=#EAE385;"; private static final String STYLE_READER = STYLE_READER_COLOR + mxConstants.STYLE_STROKECOLOR + "=#000000;" + mxConstants.STYLE_FONTCOLOR + "=#000000;"; private static final String STYLE_FILTER = STYLE_FILTER_COLOR + mxConstants.STYLE_STROKECOLOR + "=#000000;" + mxConstants.STYLE_FONTCOLOR + "=#000000;"; private static final String STYLE_REPOSITORY = STYLE_REPOSITORY_COLOR + mxConstants.STYLE_ROUNDED + "=1;" + mxConstants.STYLE_STROKECOLOR + "=#000000;" + mxConstants.STYLE_FONTCOLOR + "=#000000;"; private static final String STYLE_PORT = mxConstants.STYLE_NOLABEL + "=1;" + mxConstants.STYLE_STROKECOLOR + "=#000000;" + mxConstants.STYLE_FONTCOLOR + "=#000000;" + mxConstants.STYLE_FONTSTYLE + "=" + mxConstants.FONT_ITALIC + ";"; private static final String STYLE_CONNECTION = mxConstants.STYLE_EDGE + "=orthogonalEdgeStyle;"; private static final String STYLE_CONNECTION_REPOSITORY = mxConstants.STYLE_EDGE + "=orthogonalEdgeStyle;" + mxConstants.STYLE_DASHED + "=1;" + mxConstants.STYLE_FONTCOLOR + "=#000000;" + mxConstants.STYLE_FONTSTYLE + "=" + mxConstants.FONT_ITALIC + ";"; final transient mxGraph graph; // NOPMD NOCS (package visible for inner class) private final transient MIProject mProject; public KaxVizFrame(final String filename, final MIProject mProject, final String outFilename) { super(((mProject.getName() != null) ? (mProject.getName() + " ") : "") + "(" + filename + ((null != outFilename) ? " -> " + outFilename : "") + ")"); // NOCS this.mProject = mProject; // // Menu // final JMenuBar menuBar = new JMenuBar(); // this.setJMenuBar(menuBar); // final JMenu layoutMenu = new JMenu("Layout"); // menuBar.add(layoutMenu); // final JMenuItem autoLayout = new JMenuItem("Auto Layout"); // autoLayout.addActionListener(new ActionListener() { // // public final void actionPerformed(final ActionEvent arg0) { // KaxViz.this.autoGraphLayout(); // } // }); // layoutMenu.add(autoLayout); if (null != outFilename) { this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(final WindowEvent e) { final Document doc = mxCellRenderer.createSvgDocument(KaxVizFrame.this.graph, null, 1d, Color.WHITE, KaxVizFrame.this.graph.getGraphBounds()); try { mxUtils.writeFile(mxXmlUtils.getXml(doc), outFilename); } catch (final IOException ex) { LOG.error("Failed to save Visualization of kax-File.", ex); } } }); } // setup basic graph this.graph = new mxGraph(); // graph.setGridEnabled(true); // true // graph.setGridSize(10); // 10 // graph.setDefaultOverlap(0.5); // 0.5 // graph.setDefaultParent(null); // null // graph.setAlternateEdgeStyle(null); // null // graph.setEnabled(true); // true this.graph.setCellsLocked(false); // true this.graph.setCellsEditable(false); // true this.graph.setCellsResizable(false); // true // graph.setCellsMovable(true); // true // graph.setCellsBendable(true); // true // graph.setCellsSelectable(true); // true this.graph.setCellsDeletable(false); // true this.graph.setCellsCloneable(false); // true this.graph.setCellsDisconnectable(false); // true // graph.setLabelsClipped(false); // false // graph.setEdgeLabelsMovable(true); // true // graph.setVertexLabelsMovable(false); // false this.graph.setDropEnabled(false); // true this.graph.setSplitEnabled(false); // true // graph.setAutoSizeCells(false); // false // graph.setMaximumGraphBounds(null); // null // graph.setMinimumGraphSize(null); // null // graph.setBorder(0); // 0 this.graph.setKeepEdgesInForeground(true); // false this.graph.setCollapseToPreferredSize(false); // true this.graph.setAllowNegativeCoordinates(false); // true // graph.setConstrainChildren(true); // true this.graph.setExtendParents(false); // true this.graph.setExtendParentsOnAdd(false); // true // graph.setResetViewOnRootChange(true); // true // graph.setResetEdgesOnResize(false); // false // graph.setResetEdgesOnMove(true); // false this.graph.setResetEdgesOnConnect(false); // true // graph.setAllowLoops(false); // false // graph.setMultiplicities(null); // null // graph.setDefaultLoopStyle(mxEdgeStyle.Loop); // mxEdgeStyle.Loop // graph.setMultigraph(true); // true // graph.setConnectableEdges(false); // false this.graph.setAllowDanglingEdges(false); // false // graph.setCloneInvalidEdges(false); // false this.graph.setDisconnectOnMove(false); // true // graph.setLabelsVisible(true); // true // graph.setHtmlLabels(false); // false // graph.setSwimlaneNesting(true); // true // graph.setChangesRepaintThreshold(1000); // 1000 // graph.setAutoOrigin(false); // false // graph.setEventsEnabled(true); // true // setup the graphComponent final mxGraphComponent graphComponent = new mxGraphComponent(this.graph); graphComponent.setConnectable(false); // Inhibit edge creation in the graph. graphComponent.setGridVisible(true); // Show the grid graphComponent.setFoldingEnabled(false); // prevent folding of vertexes new com.mxgraph.swing.handler.mxRubberband(graphComponent).isEnabled(); // add rubberband selection this.getContentPane().add(graphComponent); // add the actual graph this.displayGraph(); // repaint fix this.graph.addListener(mxEvent.CELLS_MOVED, new mxIEventListener() { @Override public void invoke(final Object sender, final mxEventObject evt) { KaxVizFrame.this.graph.repaint(); } }); } private void displayGraph() { final Map<MIPlugin, mxCell> mapPlugin2Graph = new HashMap<MIPlugin, mxCell>(); // NOPMD (no concurrent access) final Map<MIPlugin, Map<String, mxCell>> mapPluginInputPorts2Graph = new HashMap<MIPlugin, Map<String, mxCell>>(); // NOPMD (no concurrent access) final Map<MIPlugin, Map<String, mxCell>> mapPluginOutputPorts2Graph = new HashMap<MIPlugin, Map<String, mxCell>>(); // NOPMD (no concurrent access) final Map<MIRepository, mxCell> mapRepository2Graph = new HashMap<MIRepository, mxCell>(); // NOPMD (no concurrent access) // draw the graph this.graph.getModel().beginUpdate(); try { int x = 0; // step 1: add all plugins! for (final MIPlugin plugin : this.mProject.getPlugins()) { if (plugin instanceof MIReader) { final MIReader reader = (MIReader) plugin; final mxCell vertex = this.createReader(reader, x++); mapPlugin2Graph.put(reader, vertex); mapPluginOutputPorts2Graph.put(reader, this.createOutputPorts(reader, vertex, true)); } else if (plugin instanceof MIFilter) { final MIFilter filter = (MIFilter) plugin; final mxCell vertex = this.createFilter(filter, x++); mapPlugin2Graph.put(filter, vertex); mapPluginInputPorts2Graph.put(filter, this.createInputPorts(filter, vertex)); mapPluginOutputPorts2Graph.put(filter, this.createOutputPorts(filter, vertex, false)); } } for (final MIRepository repo : this.mProject.getRepositories()) { final mxCell cell = this.createRepository(repo, x++); mapRepository2Graph.put(repo, cell); } // step 2: connect all plugins! for (final MIPlugin outputPlugin : this.mProject.getPlugins()) { final Map<String, mxCell> mapOutputPorts2Graph = mapPluginOutputPorts2Graph.get(outputPlugin); // NOPMD (no concurrent access) for (final MIOutputPort outputPort : outputPlugin.getOutputPorts()) { for (final MIInputPort inputPort : outputPort.getSubscribers()) { final mxCell outputPluginCell = mapPlugin2Graph.get(outputPlugin); final mxCell outputPortCell = mapOutputPorts2Graph.get(outputPort.getName()); final MIPlugin inputPlugin = inputPort.getParent(); final mxCell inputPluginCell = mapPlugin2Graph.get(inputPlugin); final String inputPortName = inputPort.getName(); final mxCell inputPortCell = mapPluginInputPorts2Graph.get(inputPlugin) .get(inputPortName); this.graph.setCellStyles(mxConstants.STYLE_NOLABEL, "0", new Object[] { inputPortCell, outputPortCell }); final mxCell edge = (mxCell) this.graph.insertEdge(null, null, "", outputPluginCell, inputPluginCell, STYLE_CONNECTION); edge.setSource(outputPortCell); edge.setTarget(inputPortCell); } } for (final MIRepositoryConnector repositoryConnector : outputPlugin.getRepositories()) { if (repositoryConnector.getRepository() != null) { final MIRepository repository = repositoryConnector.getRepository(); final mxCell output = mapPlugin2Graph.get(outputPlugin); final mxCell input = mapRepository2Graph.get(repository); this.graph.insertEdge(null, null, repository.getName(), output, input, STYLE_CONNECTION_REPOSITORY); } } } // step 3: auto layout! // this.autoGraphLayout(); } finally { // finish the drawing this.graph.getModel().endUpdate(); } } // final void autoGraphLayout() { // new mxOrthogonalLayout(this.graph).execute(this.graph.getDefaultParent()); // } private final Map<String, mxCell> createInputPorts(final MIFilter plugin, final mxCell vertex) { final Map<String, mxCell> port2graph = new HashMap<String, mxCell>(); // NOPMD (no concurrent access) final String[] portNames = KaxVizFrame.getAllInputPortNames(plugin); for (int i = 0; i < portNames.length; i++) { final mxGeometry portGeometry = new mxGeometry((i + 1d) / (portNames.length + 1), -0.06, 10, 10); portGeometry.setOffset(new mxPoint(0, 0)); portGeometry.setRelative(true); final mxCell port = new mxCell(portNames[i], portGeometry, STYLE_PORT + "spacingTop=3;verticalLabelPosition=bottom;portConstraint=north;" + STYLE_FILTER_COLOR); port.setVertex(true); this.graph.addCell(port, vertex); port2graph.put(portNames[i], port); } return port2graph; } private static String[] convertPortsToNameArray(final EList<? extends MIPort> eList) { final int len = eList.size(); final String[] result = new String[len]; for (int idx = 0; idx < len; idx++) { result[idx] = eList.get(idx).getName(); } return result; } private static String[] getAllInputPortNames(final MIFilter plugin) { return KaxVizFrame.convertPortsToNameArray(plugin.getInputPorts()); } private static String[] getAllOutputPortNames(final MIPlugin plugin) { return KaxVizFrame.convertPortsToNameArray(plugin.getOutputPorts()); } private final Map<String, mxCell> createOutputPorts(final MIPlugin plugin, final mxCell vertex, final boolean reader) { final Map<String, mxCell> port2graph = new HashMap<String, mxCell>(); // NOPMD (no concurrent access) final String[] portNames = KaxVizFrame.getAllOutputPortNames(plugin); for (int i = 0; i < portNames.length; i++) { final mxGeometry portGeometry = new mxGeometry((i + 1d) / (portNames.length + 1), 1.06, 10, 10); portGeometry.setOffset(new mxPoint(0, -10)); portGeometry.setRelative(true); final String fillcolor = reader ? STYLE_READER_COLOR : STYLE_FILTER_COLOR; // NOPMD NOCS (?:) final mxCell port = new mxCell(portNames[i], portGeometry, STYLE_PORT + "verticalLabelPosition=top;portConstraint=south;" + fillcolor); port.setVertex(true); this.graph.addCell(port, vertex); port2graph.put(portNames[i], port); } return port2graph; } private static String getShortClassName(final MIAnalysisComponent analysisComponent) { final String className = analysisComponent.getClassname(); final int lastPointPos = className.lastIndexOf('.'); return className.substring(lastPointPos + 1); } private final mxCell createReader(final MIReader reader, final int c) { final mxCell vertex = new mxCell( "<<Reader>>\n" + reader.getName() + " : " + KaxVizFrame.getShortClassName(reader), new mxGeometry(FILTER_SPACE, FILTER_SPACE + (c * (FILTER_HEIGHT + FILTER_SPACE)), FILTER_WIDTH, FILTER_HEIGHT), STYLE_READER); vertex.setVertex(true); this.graph.addCell(vertex); return vertex; } private final mxCell createFilter(final MIFilter plugin, final int c) { final mxCell vertex = new mxCell( "<<Filter>>\n" + plugin.getName() + " : " + KaxVizFrame.getShortClassName(plugin), new mxGeometry(FILTER_SPACE, FILTER_SPACE + (c * (FILTER_HEIGHT + FILTER_SPACE)), FILTER_WIDTH, FILTER_HEIGHT), STYLE_FILTER); vertex.setVertex(true); this.graph.addCell(vertex); return vertex; } private final mxCell createRepository(final MIRepository repository, final int c) { final mxCell vertex = new mxCell( "<<Repository>>\n" + repository.getName() + " : " + KaxVizFrame.getShortClassName(repository), new mxGeometry(FILTER_SPACE, FILTER_SPACE + (c * (FILTER_HEIGHT + FILTER_SPACE)), FILTER_WIDTH, FILTER_HEIGHT), STYLE_REPOSITORY); vertex.setVertex(true); this.graph.addCell(vertex); return vertex; } } }