/* * CCVisu is a tool for visual graph clustering * and general force-directed graph layout. * This file is part of CCVisu. * * Copyright (C) 2005-2012 Dirk Beyer * * CCVisu is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * CCVisu is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CCVisu; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Please find the GNU Lesser General Public License in file * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt * * Dirk Beyer (firstname.lastname@uni-passau.de) * University of Passau, Bavaria, Germany */ package org.sosy_lab.ccvisu; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.sosy_lab.ccvisu.Options.InFormat; import org.sosy_lab.ccvisu.Options.OptionsEnum; import org.sosy_lab.ccvisu.Options.OutFormat; import org.sosy_lab.ccvisu.clustering.ClustererMinDistPerc; import org.sosy_lab.ccvisu.clustering.interfaces.Clusterer; import org.sosy_lab.ccvisu.graph.GraphData; import org.sosy_lab.ccvisu.graph.GraphVertex; import org.sosy_lab.ccvisu.graph.NameVisibility; import org.sosy_lab.ccvisu.graph.Position; import org.sosy_lab.ccvisu.layout.Minimizer; import org.sosy_lab.ccvisu.layout.MinimizerBarnesHut; import org.sosy_lab.ccvisu.measuring.ClusterQuality; import org.sosy_lab.ccvisu.measuring.calldep.DependencyCalculator; import org.sosy_lab.ccvisu.readers.ReaderData; import org.sosy_lab.ccvisu.readers.ReaderDataGraph; import org.sosy_lab.ccvisu.readers.ReaderDataGraphAUX; import org.sosy_lab.ccvisu.readers.ReaderDataGraphCVS; import org.sosy_lab.ccvisu.readers.ReaderDataGraphDOX; import org.sosy_lab.ccvisu.readers.ReaderDataGraphFilter; import org.sosy_lab.ccvisu.readers.ReaderDataGraphRSF; import org.sosy_lab.ccvisu.readers.ReaderDataGraphSoftware; import org.sosy_lab.ccvisu.readers.ReaderDataGraphXML; import org.sosy_lab.ccvisu.readers.ReaderDataGroup; import org.sosy_lab.ccvisu.readers.ReaderDataLayoutLAY; import org.sosy_lab.ccvisu.readers.filter.WhitelistRelationFilter; import org.sosy_lab.ccvisu.ui.FrameDisplay; import org.sosy_lab.ccvisu.ui.interfaces.GraphLoadedListener; import org.sosy_lab.ccvisu.writers.Writer; import org.sosy_lab.ccvisu.writers.WriterDataCalldep; import org.sosy_lab.ccvisu.writers.WriterDataClustersRSF; import org.sosy_lab.ccvisu.writers.WriterDataDistHist; import org.sosy_lab.ccvisu.writers.WriterDataGraphGML; import org.sosy_lab.ccvisu.writers.WriterDataGraphGXL; import org.sosy_lab.ccvisu.writers.WriterDataLayoutLAY; import org.sosy_lab.ccvisu.writers.WriterDataLayoutSVG; import org.sosy_lab.ccvisu.writers.WriterDataLayoutVRML; import org.sosy_lab.ccvisu.writers.WriterDataRSF; import org.sosy_lab.ccvisu.writers.WriterDataSTATS; import org.sosy_lab.ccvisu.writers.WriterGui; import org.sosy_lab.ccvisu.writers.WriterNull; public class CCVisuController { /** End of line.*/ public final static String endl = System.getProperty("line.separator"); private BufferedReader createInputReader(String inputFileName) throws IOException { // Setup of input reader. BufferedReader in = null; try { InputStream inStream = null; if (inputFileName.equalsIgnoreCase("stdin")) { inStream = System.in; } else { inStream = new FileInputStream(inputFileName); } if (inputFileName.endsWith(InFormat.ODS.getFileExtension())) { ZipInputStream zipIn = new ZipInputStream(inStream); // Look for file entry 'content.xml' in the ZIP file. ZipEntry entry = zipIn.getNextEntry(); while (entry != null && !entry.getName().equals("content.xml")) { entry = zipIn.getNextEntry(); } if (entry == null) { System.err.println("I/O Error reading 'content.xml' from ODS file."); } inStream = zipIn; } in = new BufferedReader(new InputStreamReader(inStream)); } catch (IOException e) { throw new IOException("Exception while opening file '" + inputFileName + "' for reading.", e); } return in; } private void copyPositionsAndProperties(Options options) { if (options.initialLayout != null) { for (GraphVertex currVertex : options.graph.getVertices()) { GraphVertex oldVertex = options.initialLayout.getVertexByName(currVertex.getName()); if (oldVertex != null) { currVertex.setColor(oldVertex.getColor()); currVertex.setShowName(oldVertex); currVertex.setPosition(new Position(oldVertex.getPosition())); } } } } private void loadOrCalcInitialLayout(Options options) throws IOException { String layoutFileName = options.getOption(OptionsEnum.initLayout).getString(); if (layoutFileName.trim().length() > 0) { BufferedReader initialLayoutStream = null; GraphData layout = options.initialLayout = new GraphData(); // Read initial (pre-computed) layout from file. try { initialLayoutStream = new BufferedReader(new FileReader(layoutFileName)); (new ReaderDataLayoutLAY(initialLayoutStream, options.verbosity)).read(layout); } catch (IOException e) { throw new IOException("Exception while opening file '" + layoutFileName + "' for reading."); } options.infoCollector.addMessage("Successfully loaded layout from file " + layoutFileName + " with " + layout.getVertices().size() + " vertices."); // Close the input file. try { initialLayoutStream.close(); } catch (Exception e) { System.err.println("Exception while closing input file: "); System.err.println(e); } // Reset vertex degrees, // i.e., use the degrees from the graph and ignore the degree from read layout. for (GraphVertex itVertex : layout.getVertices()) { itVertex.setDegree(0); } } applyInitialLayout(options); } private void applyInitialLayout(Options options) { // vertex.fixedInitPos == true means that the minimizer does not change // vertex's position. GraphData initialLayout = options.initialLayout; if (options.getOption(OptionsEnum.fixedInitPos).getBool() && initialLayout != null) { for (GraphVertex currVertex : options.graph.getVertices()) { // If the current vertex exists in the read initial layout, // then mark its position as fixed. if (initialLayout.getVertexByName(currVertex.getName()) != null) { currVertex.setFixedPos(true); } } } if (initialLayout == options.graph) { return; } if ((options.inFormat != InFormat.LAY)) { // Initialize with random positions. options.graph.setNodesToRandomPositions(options.graph.getVertices(), options.getOption(OptionsEnum.dim).getInt()); } // Copy positions and properties from the initial layout // that was read from file. copyPositionsAndProperties(options); } private void readGraph(Options options) throws IOException { String inputFileName = options.getOption(OptionsEnum.inputName).getString(); BufferedReader in = createInputReader(inputFileName); try { ReaderData graphReader = getReader(options, inputFileName, in); // Set filter for selection of user relations for visualization. if (graphReader instanceof ReaderDataGraph) { if (options.filters.size() > 0) { // The user has given relations to select. graphReader = new ReaderDataGraphFilter((ReaderDataGraph) graphReader, options.verbosity, options.filters); } else { if (options.inFormat == InFormat.DOX && options.outFormat != OutFormat.RSF) { // The user has not given any relation name to select, // but wants to actually draw the relation. System.err.println("Warning: Using default DOX-derived relation refFile."); System.err.println("Change this with parameter '-relSelect <rel-name>'"); options.filters.add(new WhitelistRelationFilter("refFile")); graphReader = new ReaderDataGraphFilter((ReaderDataGraph) graphReader, options.verbosity, options.filters); } } } // Read the data using the reader (i.e., fill into existing graph structure). graphReader.read(options.graph); options.infoCollector.addMessage("Successfully loaded graph with " + options.graph.getVertices().size() + " vertices and " + options.graph.getEdges().size() + " edges."); // Handle vertex options. if (options.getOption(OptionsEnum.showAllLabels).getBool()) { for (GraphVertex curVertex : options.graph.getVertices()) { // showAllLabels (annotate each vertex with its name). curVertex.setShowName(NameVisibility.Priority.STARTUP, true); } } } finally { in.close(); } } private ReaderData getReader(Options options, String inputFileName, BufferedReader in) throws IOException { switch (options.inFormat) { case CVS: return new ReaderDataGraphCVS(in, options.verbosity, options.getOption(OptionsEnum.timeWindow).getInt(), options.getOption(OptionsEnum.slidingTW).getBool()); case SVN: return new ReaderDataGraphXML(in, options.verbosity, options.inFormat); case DOX: return new ReaderDataGraphDOX(in, options.verbosity, inputFileName); case ODS: return new ReaderDataGraphXML(in, options.verbosity, options.inFormat); case RSF: return new ReaderDataGraphRSF(in, options.verbosity); case SRC: return new ReaderDataGraphSoftware(in, options.verbosity, options.getOption(OptionsEnum.sourceDirectory).getString(), options.getOption(OptionsEnum.sourceLibraries).getString()); case AUX: // Graph is set by calling client. -- Do nothing. return new ReaderDataGraphAUX(in, options.verbosity); case LAY: return new ReaderDataLayoutLAY(in, options.verbosity); default: throw new IOException("Runtime error: Unexpected input format '" + options.inFormat.toString() + "'."); } } public void process(Options options) throws IOException, InterruptedException { loadGraphAndInitGroups(options); // Run tools createAndRunMinimizer(options); runDepAnalysis(options); String initGroupsFileName = options.getOption(OptionsEnum.initGroups).getString(); runClusteringAndWriteResults(options, initGroupsFileName); writeOutput(options.graph, options.getOption(OptionsEnum.outputName).getString(), options.outFormat, options); } private void loadGraphAndInitGroups(Options options) throws IOException { assert (options.graph != null); // Check for illegal option combinations... if (options.inFormat == InFormat.LAY && options.outFormat == OutFormat.RSF) { throw new IllegalArgumentException( "Usage error: Cannot produce RSF output from LAY input."); } readGraph(options); loadOrCalcInitialLayout(options); // Initialize groups. String initGroupsFileName = options.getOption(OptionsEnum.initGroups).getString(); readInitialGroups(options, initGroupsFileName); // notify about newly loaded graph for (GraphLoadedListener listener : options.graphLoadedListeners) { listener.onGraphLoaded(options.graph); } } /** * Method to use Readers from the GUI. */ public void processVisual(Options options, FrameDisplay display) throws IOException { loadGraphAndInitGroups(options); } private void runDepAnalysis(Options options) { if (options.getOption(OptionsEnum.cdep).getBool()) { DependencyCalculator dc = new DependencyCalculator(options.graph, options); dc.calculateAndSetDegree(); } } private boolean isClusteringRequired(Options options) { int numberOfClusters = options.getOption(OptionsEnum.clusters).getInt(); return (numberOfClusters > 0) || (options.outFormat == OutFormat.CRSF); } private void runClusteringAndWriteResults(Options options, String initGroupsFileName) throws InterruptedException { // Determine if we need to compute a clustering. if (isClusteringRequired(options)) { Clusterer clusterer = new ClustererMinDistPerc(options.graph, options.getOption(OptionsEnum.clusters).getInt(), options.getOption(OptionsEnum.clusterMergeDistancePercent).getFloat(), options.getOption(OptionsEnum.clusterMinDistancePercent).getFloat()); options.graph.clearGroups(); clusterer.runClusteringAndUpdateGroups(options.graph); } // Calculate measures for clustering and append the results to the specified file. writeClusteringMeasures(options, initGroupsFileName, options.getOption(OptionsEnum.writeClusteringMeasures).getString()); } protected void createAndRunMinimizer(Options options) { // Determine if we need to compute a layout. if ((options.inFormat != InFormat.LAY || options.outFormat == OutFormat.LAY) // only use minimizer if layout transformation is requested && options.outFormat != OutFormat.RSF // RSF output does not care about layout && options.outFormat != OutFormat.GML && options.outFormat != OutFormat.GXL ) { // Set minimizer algorithm. // So far there is only one implemented in CCVisu. Minimizer minimizer = new MinimizerBarnesHut(options); try { minimizer.minimizeEnergy(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void writeOutput(GraphData graph, String outputFileName, OutFormat outFormat, Options options) throws IOException { PrintWriter printer = null; try { OutputStream outStream = null; if (outputFileName.equalsIgnoreCase("stdout")) { outStream = System.out; } else { outStream = new FileOutputStream(outputFileName); } printer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outStream))); } catch (IOException e) { throw new IOException("Exception while opening file '" + outputFileName + "' for writing.", e); } try { // Write the data using the writer. Writer writer = getWriter(outFormat, printer, options); writer.write(); printer.flush(); } finally { printer.close(); } } private Writer getWriter(OutFormat outFormat, PrintWriter printer, Options options) throws IOException { GraphData graph = options.graph; switch (outFormat) { case RSF: // Graph in RSF. return new WriterDataRSF(printer, graph); case CRSF: // Clustering in RSF. return new WriterDataClustersRSF(printer, graph); case GML: // Graph in GraphML format. return new WriterDataGraphGML(printer, graph); case GXL: // Graph in GXL format. return new WriterDataGraphGXL(printer, graph); case LAY: // Layout in text format LAY. return new WriterDataLayoutLAY(printer, graph, options); case VRML: // Layout in VRML format. return new WriterDataLayoutVRML(printer, graph, options); case SVG: // Layout in SVG format. return new WriterDataLayoutSVG(printer, graph, options); case STATS: // Statistics in text format LAY. return new WriterDataSTATS(printer, graph); case CALLDEP: // Nodewise information for the calldep algorithm return new WriterDataCalldep(printer, graph, options); case DIST: // Euclidian distances between all node-pairs. return new WriterDataDistHist(printer, graph); case DISP: // Graph in a Window. return new WriterGui(this, options); case NULL: // Give no output return new WriterNull(); default: throw new IOException("Runtime error: Unexpected output format '" + outFormat.toString() + "'."); } } private void writeClusteringMeasures(Options options, String initGroupsFileName, String writeClusterMeasuresTo) { if ((writeClusterMeasuresTo != null) && (writeClusterMeasuresTo.length() > 0)) { try { String clusteringName = initGroupsFileName; if ((clusteringName == null) || (clusteringName.length() == 0)) { clusteringName = options.getOption(OptionsEnum.inputName).getString(); } if ((clusteringName == null) || (clusteringName.length() == 0)) { clusteringName = options.getOption(OptionsEnum.initLayout).getString(); } if ((clusteringName == null) || (clusteringName.length() == 0)) { clusteringName = "UNNAMED"; } FileWriter fileWriter = new FileWriter(writeClusterMeasuresTo, true); fileWriter.append(String.format("%s\t%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\n", clusteringName, options.graph.getNumberOfClusters(), options.graph.getVertices().size(), options.graph.getEdges().size(), ClusterQuality.cutOfClustering(options.graph), ClusterQuality.modularizationQualityOfGraph(options.graph), ClusterQuality.edgeNormalizedCutOfClustering(options.graph), ClusterQuality.edgeNormalizedCutOfClusteringV2(options.graph), ClusterQuality.modularityOfClustering(options.graph))); fileWriter.flush(); } catch (IOException e) { e.printStackTrace(); } } } private void readInitialGroups(Options options, String initGroupsFileName) { if (!initGroupsFileName.equals("")) { try { ReaderData groupReader = new ReaderDataGroup(new BufferedReader(new FileReader(initGroupsFileName)), options.verbosity); groupReader.read(options.graph); } catch (FileNotFoundException e) { System.err.println("Error reading file -- file not found: " + options.getOption(OptionsEnum.initGroups).getString()); } } } }