/*
* 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());
}
}
}
}