DotWriter.java
// $Id$
/* ====================================================================
* Copyright (c) 2002-2003, Christophe Labouisse
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.ggtools.grand.output;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Properties;
import net.ggtools.grand.Configuration;
import net.ggtools.grand.exceptions.GrandException;
import net.ggtools.grand.graph.Graph;
import net.ggtools.grand.graph.GraphProducer;
import net.ggtools.grand.graph.GraphWriter;
import net.ggtools.grand.graph.Node;
import net.ggtools.grand.log.LoggerManager;
import org.apache.commons.logging.Log;
/**
* A class to write dependency graph in dot format.
*
* The rendering can be customized either by properties at object creation or
* at runtime using various setters.
*
* The property names use the following scheme:
* <code>dot.<i>objecttype</i>.attributes</code>.
* Where <i>objecttype</i> can be:
* <ul>
* <li><code>node</code> for "common" nodes,</li>
* <li><code>link</code> for dependency links,</li>
* <li><code>mainnode</code> for nodes with a special importance (i.e.: when
* node.isMainNode() is true),</li>
* <li><code>startnode</code> for the start node,</li>
* <li>and <code>graph</code> for the graph itself.</li>
* </ul>
*
* The property values are sets of valid dot attributes without the surrounding
* bracket.
*
* @to.do The current configuration scheme sucks, create something more generic.
*
* @author Christophe Labouisse
* @see <a href="https://www.graphviz.org/">Graphviz home page</a>
* @see <a href="https://www.graphviz.org/doc/info/attrs.html">Dot attributes</a>
*/
public class DotWriter implements GraphWriter {
/**
* @author Christophe Labouisse
*/
private static final class Output implements DotWriterOutput {
/**
* Escapes a string from special dot chars.
*
* @param str string to escape.
* @return the escaped string.
*/
private static String escapeString(final String str) {
if (str == null) {
return null;
}
return str.replaceAll("(\\\"\\s)", "\\\\\\1");
}
/**
* Field writer.
*/
private final PrintWriter writer;
/**
*
* @param stream OutputStream
*/
private Output(final OutputStream stream) {
writer = new PrintWriter(stream);
}
/**
* Method append.
* @param strValue String
* @return DotWriterOutput
* @see net.ggtools.grand.output.DotWriterOutput#append(java.lang.String)
*/
public DotWriterOutput append(final String strValue) {
writer.print(strValue);
return this;
}
/**
* Method appendEscaped.
* @param strValue String
* @return DotWriterOutput
* @see net.ggtools.grand.output.DotWriterOutput#appendEscaped(java.lang.String)
*/
public DotWriterOutput appendEscaped(final String strValue) {
writer.print(escapeString(strValue));
return this;
}
/**
* Method append.
* @param intValue int
* @return DotWriterOutput
* @see net.ggtools.grand.output.DotWriterOutput#append(int)
*/
public DotWriterOutput append(final int intValue) {
writer.print(intValue);
return this;
}
/**
* Method newLine.
* @return DotWriterOutput
* @see net.ggtools.grand.output.DotWriterOutput#newLine()
*/
public DotWriterOutput newLine() {
writer.println();
return this;
}
/**
* Method close.
*/
private void close() {
writer.close();
}
}
/**
* Field log.
*/
private static final Log LOG = LoggerManager.getLog(DotWriter.class);
/**
* Field DOT_GRAPH_ATTRIBUTES.
* (value is {@value #DOT_GRAPH_ATTRIBUTES})
*/
private static final String DOT_GRAPH_ATTRIBUTES = "dot.graph.attributes";
/**
* Field DOT_LINK_ATTRIBUTES.
* (value is {@value #DOT_LINK_ATTRIBUTES})
*/
private static final String DOT_LINK_ATTRIBUTES = "dot.link.attributes";
/**
* Field DOT_NODE_ATTRIBUTES.
* (value is {@value #DOT_NODE_ATTRIBUTES})
*/
private static final String DOT_NODE_ATTRIBUTES = "dot.node.attributes";
/**
* Field graphAttributes.
*/
private String graphAttributes;
/**
* Field linkAttributes.
*/
private String linkAttributes;
/**
* Field nodeAttributes.
*/
private String nodeAttributes;
/**
* Field config.
*/
private final Configuration config;
/**
* Field graphProducer.
*/
private GraphProducer graphProducer;
/**
* Field showGraphName.
*/
private boolean showGraphName;
/**
* Creates a new DotWriter using default configuration.
* @throws IOException when the default configuration cannot be loaded.
*/
public DotWriter() throws IOException {
this(null);
}
/**
* Creates a new DotWriter with custom properties. No
* overriding will take place if override is null.
*
* @param override
* custom configuration.
* @throws IOException when the configuration cannot be loaded.
*/
public DotWriter(final Properties override) throws IOException {
config = Configuration.getConfiguration(override);
graphAttributes = config.get(DOT_GRAPH_ATTRIBUTES);
linkAttributes = config.get(DOT_LINK_ATTRIBUTES);
nodeAttributes = config.get(DOT_NODE_ATTRIBUTES);
}
/**
* Method write.
* @param output File
* @throws IOException when output cannot be created/written to
* @throws GrandException if an error occurs in getGraph()
* @see net.ggtools.grand.graph.GraphWriter#write(java.io.File)
*/
public final void write(final File output)
throws IOException, GrandException {
LOG.info("Outputting to " + output);
final FileOutputStream oStream = new FileOutputStream(output);
write(oStream);
oStream.flush();
oStream.close();
}
/**
* Method write.
* @param stream OutputStream
* @throws GrandException if an error occurs in getGraph()
* @see net.ggtools.grand.graph.GraphWriter#write(java.io.OutputStream)
*/
public final void write(final OutputStream stream) throws GrandException {
final Output output = new Output(stream);
final Graph graph = graphProducer.getGraph();
output.append("digraph \"").appendEscaped(graph.getName())
.append("\" {").newLine();
output.append("graph [").append(graphAttributes);
if (showGraphName) {
output.append(",label=\"").append(graph.getName()).append("\"");
}
output.append("];").newLine();
output.append("node [").append(nodeAttributes).append("];").newLine();
output.append("edge [").append(linkAttributes).append("];").newLine();
final DotWriterVisitor visitor = new DotWriterVisitor(output, config);
final Node startNode = graph.getStartNode();
if (startNode != null) {
startNode.accept(visitor);
}
for (final Iterator<Node> iter = graph.getNodes(); iter.hasNext();) {
final Node node = iter.next();
if (node.equals(startNode) || node.getName().isEmpty()) {
continue;
}
node.accept(visitor);
}
output.append("}").newLine();
output.close();
}
/**
* Method setProducer.
* @param producer GraphProducer
* @see net.ggtools.grand.graph.GraphConsumer#setProducer(net.ggtools.grand.graph.GraphProducer)
*/
public final void setProducer(final GraphProducer producer) {
graphProducer = producer;
}
/**
* Method setShowGraphName.
* @param show boolean
* @see net.ggtools.grand.graph.GraphWriter#setShowGraphName(boolean)
*/
public final void setShowGraphName(final boolean show) {
showGraphName = show;
}
}