diff --git a/src/main/java/kieker/analysis/graph/GraphTester.java b/src/main/java/kieker/analysis/graph/GraphTester.java index 1e3afc6c58848665d268c506d9e1b1ba2c517ee8..de9a11b636a076d5183e82320fa70be956e3124a 100644 --- a/src/main/java/kieker/analysis/graph/GraphTester.java +++ b/src/main/java/kieker/analysis/graph/GraphTester.java @@ -1,9 +1,14 @@ package kieker.analysis.graph; +import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import kieker.analysis.graph.export.graphml.GraphMLExporter; import kieker.analysis.graph.impl.GraphImpl; +import kieker.analysis.util.dot.DotWriter; public class GraphTester { @@ -100,6 +105,28 @@ public class GraphTester { // DotExporter dotExporter = new DotExporter(); // dotExporter.export(graph, System.out); + // Could be useful for testing + System.out.println("DotWriter Test"); + + DotWriter dotWriter = new DotWriter(new PrintWriter(System.out)); + try { + dotWriter.start("G"); + dotWriter.addGraphAttribute("rotate", "90"); + Map<String, String> defaultNodeAttributes = new HashMap<>(); + defaultNodeAttributes.put("style", "filled"); + dotWriter.addDefaultNodeAttributes(defaultNodeAttributes); + Map<String, String> nodeAttributes = new HashMap<>(); + nodeAttributes.put("label", "LABEL Title"); + dotWriter.addNode("102", nodeAttributes); + dotWriter.addNode("#id", nodeAttributes); + dotWriter.addEdge("102", "#id", new HashMap<>()); + dotWriter.finish(); + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } } diff --git a/src/main/java/kieker/analysis/util/DotBuilder.java b/src/main/java/kieker/analysis/util/DotBuilder.java index ca284f0c8ee82f706e2e7ea834d14d84f3c24c31..1b82af13b2e71e556e2795d9a80b40a833af4078 100644 --- a/src/main/java/kieker/analysis/util/DotBuilder.java +++ b/src/main/java/kieker/analysis/util/DotBuilder.java @@ -1,146 +1,97 @@ -package kieker.analysis.util; - -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import com.google.common.base.Joiner; - -/** - * Simple class for building and representing dot graph files. - * - * @author Sören Henning - * - */ -public class DotBuilder { - - private final String start; - private final String end; - private final StringBuilder body = new StringBuilder(); - - private Map<String, String> defaultNodeProperties; - private Map<String, String> defaultEdgeProperties; - private Map<String, String> defaultProperties; - - private static final String START_BRACKET = "{"; - private static final String END_BRACKET = "}"; - private static final String DEFAULT_GRAPH_NAME = "G"; - private static final String DEFAULT_GRAPH_TYPE = "digraph"; - - public DotBuilder() { - this(DEFAULT_GRAPH_NAME); - } - - public DotBuilder(final String graphName) { - this(graphName, DEFAULT_GRAPH_TYPE); - } - - // TODO graphType has to be one of "graph", "digraph" or "subgraph" so maybe use an enum - public DotBuilder(final String graphName, final String graphType) { - start = graphType + " " + graphName + " " + START_BRACKET + "\n"; - end = END_BRACKET; - } - - // TODO Deprecated - public DotBuilder(final String name, final Map<String, String> defaultNodeProperties, final Map<String, String> defaultEdgeProperties) { - this(name); - this.defaultNodeProperties = defaultNodeProperties; - this.defaultEdgeProperties = defaultEdgeProperties; - } - - // TODO Deprecated - public DotBuilder(final String name, final Map<String, String> defaultNodeProperties, final Map<String, String> defaultEdgeProperties, - final Map<String, String> defaultProperties) { - this(name, defaultNodeProperties, defaultEdgeProperties); - this.defaultProperties = defaultProperties; - } - - public void setDefaultNodeProperties(final Map<String, String> defaultNodeProperties) { - this.defaultNodeProperties = defaultNodeProperties; - } - - public void setDefaultEdgeProperties(final Map<String, String> defaultEdgeProperties) { - this.defaultEdgeProperties = defaultEdgeProperties; - } - - public void setDefaultProperties(final Map<String, String> defaultProperties) { - this.defaultProperties = defaultProperties; - } - - public String get() { - return start + body.toString() + end; - } - - @Override - public String toString() { - return get(); - } - - public void addNode(final String id) { - addNode(id, new HashMap<>()); - } - - public void addNode(final String id, final String label) { - Map<String, String> properties = new HashMap<String, String>(); - properties.put("label", label); - addNode(id, properties); - } - - public void addNode(final String id, final Map<String, String> properties) { - addElement('"' + id + '"', extendNodeProperties(properties)); - } - - public void addEdge(final String source, final String target) { - addEdge(source, target, new HashMap<>()); - } - - public void addEdge(final String source, final String target, final String label) { - Map<String, String> properties = new HashMap<String, String>(); - properties.put("label", label); - addEdge(source, target, properties); - } - - public void addEdge(final String source, final String target, final Map<String, String> properties) { - addElement('"' + source + '"' + " -> " + '"' + target + '"', extendEdgeProperties(properties)); - } - - public void addSubgraph(final String subgraph) { - body.append(subgraph + '\n'); - } - - private void addElement(final String element, final Map<String, String> properties) { - body.append(element); - if (properties != null && !properties.isEmpty()) { - body.append(" ["); - Joiner.on("\", ").withKeyValueSeparator("=\"").appendTo(body, properties); - body.append("\"]"); - } - body.append('\n'); - } - - private Map<String, String> extendNodeProperties(final Map<String, String> properties) { - return extendElementProperties(properties, defaultNodeProperties); - } - - private Map<String, String> extendEdgeProperties(final Map<String, String> properties) { - return extendElementProperties(properties, defaultEdgeProperties); - } - - private Map<String, String> extendElementProperties(final Map<String, String> properties, final Map<String, String> defaultElementProperties) { - - if (defaultElementProperties != null) { - for (Entry<String, String> property : defaultElementProperties.entrySet()) { - properties.putIfAbsent(property.getKey(), property.getValue()); - } - } - - if (defaultProperties != null) { - for (Entry<String, String> property : defaultProperties.entrySet()) { - properties.putIfAbsent(property.getKey(), property.getValue()); - } - } - - return properties; - } - -} +package kieker.analysis.util; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.common.base.Joiner; + +/** + * Simple class for building and representing dot graph files. + * + * @author Sören Henning + * + */ +public class DotBuilder { + + private final String start; + private final String end; + private final StringBuilder body = new StringBuilder(); + + private Map<String, String> graphProperties; + private Map<String, String> defaultNodeProperties; + private Map<String, String> defaultEdgeProperties; + + private Map<String, Map<String, String>> nodes; + private Map<String, Map<String, String>> edges; + private List<DotBuilder> subgraphs; + + private static final String START_BRACKET = "{"; + private static final String END_BRACKET = "}"; + private static final String DEFAULT_GRAPH_NAME = "G"; + private static final String DEFAULT_GRAPH_TYPE = "digraph"; + + public DotBuilder() { + this(DEFAULT_GRAPH_NAME); + } + + public DotBuilder(final String graphName) { + this(graphName, DEFAULT_GRAPH_TYPE); + } + + // TODO graphType has to be one of "graph", "digraph" or "subgraph" so maybe use an enum + public DotBuilder(final String graphName, final String graphType) { + start = graphType + " " + graphName + " " + START_BRACKET + "\n"; + end = END_BRACKET; + } + + // TODO maybe rename + public void setDefaultNodeProperties(final Map<String, String> defaultNodeProperties) { + this.defaultNodeProperties = defaultNodeProperties; + } + + // TODO maybe rename + public void setDefaultEdgeProperties(final Map<String, String> defaultEdgeProperties) { + this.defaultEdgeProperties = defaultEdgeProperties; + } + + public String get() { + return start + body.toString() + end; + } + + @Override + public String toString() { + return get(); + } + + public void addNode(final String id) { + addNode(id, new HashMap<>()); + } + + public void addNode(final String id, final Map<String, String> properties) { + addElement('"' + id + '"', properties); + } + + public void addEdge(final String source, final String target) { + addEdge(source, target, new HashMap<>()); + } + + public void addEdge(final String source, final String target, final Map<String, String> properties) { + addElement('"' + source + '"' + " -> " + '"' + target + '"', properties); + } + + public void addSubgraph(final String subgraph) { + body.append(subgraph + '\n'); + } + + private void addElement(final String element, final Map<String, String> properties) { + body.append(element); + if (properties != null && !properties.isEmpty()) { + body.append(" ["); + Joiner.on("\", ").withKeyValueSeparator("=\"").appendTo(body, properties); + body.append("\"]"); + } + body.append('\n'); + } + +} diff --git a/src/main/java/kieker/analysis/util/DotBuilderSupport.java b/src/main/java/kieker/analysis/util/DotBuilderSupport.java new file mode 100644 index 0000000000000000000000000000000000000000..1c8a7a0e097e871c454538a094d1ed9c6f9fb8f5 --- /dev/null +++ b/src/main/java/kieker/analysis/util/DotBuilderSupport.java @@ -0,0 +1,149 @@ +package kieker.analysis.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.Joiner; + +/** + * Simple class for building and representing dot graph files. + * + * @deprecated use {@link dot.DotWriter} instead. + * + * @author Sören Henning + * + */ +@Deprecated +public class DotBuilderSupport { + + private final String start; + private final String end; + private final StringBuilder body = new StringBuilder(); + + private Map<String, String> defaultNodeProperties; + private Map<String, String> defaultEdgeProperties; + private Map<String, String> defaultProperties; + + private static final String START_BRACKET = "{"; + private static final String END_BRACKET = "}"; + private static final String DEFAULT_GRAPH_NAME = "G"; + private static final String DEFAULT_GRAPH_TYPE = "digraph"; + + public DotBuilderSupport() { + this(DEFAULT_GRAPH_NAME); + } + + public DotBuilderSupport(final String graphName) { + this(graphName, DEFAULT_GRAPH_TYPE); + } + + // TODO graphType has to be one of "graph", "digraph" or "subgraph" so maybe use an enum + public DotBuilderSupport(final String graphName, final String graphType) { + start = graphType + " " + graphName + " " + START_BRACKET + "\n"; + end = END_BRACKET; + } + + // TODO Deprecated + public DotBuilderSupport(final String name, final Map<String, String> defaultNodeProperties, final Map<String, String> defaultEdgeProperties) { + this(name); + this.defaultNodeProperties = defaultNodeProperties; + this.defaultEdgeProperties = defaultEdgeProperties; + } + + // TODO Deprecated + public DotBuilderSupport(final String name, final Map<String, String> defaultNodeProperties, final Map<String, String> defaultEdgeProperties, + final Map<String, String> defaultProperties) { + this(name, defaultNodeProperties, defaultEdgeProperties); + this.defaultProperties = defaultProperties; + } + + public void setDefaultNodeProperties(final Map<String, String> defaultNodeProperties) { + this.defaultNodeProperties = defaultNodeProperties; + } + + public void setDefaultEdgeProperties(final Map<String, String> defaultEdgeProperties) { + this.defaultEdgeProperties = defaultEdgeProperties; + } + + public void setDefaultProperties(final Map<String, String> defaultProperties) { + this.defaultProperties = defaultProperties; + } + + public String get() { + return start + body.toString() + end; + } + + @Override + public String toString() { + return get(); + } + + public void addNode(final String id) { + addNode(id, new HashMap<>()); + } + + public void addNode(final String id, final String label) { + Map<String, String> properties = new HashMap<String, String>(); + properties.put("label", label); + addNode(id, properties); + } + + public void addNode(final String id, final Map<String, String> properties) { + addElement('"' + id + '"', extendNodeProperties(properties)); + } + + public void addEdge(final String source, final String target) { + addEdge(source, target, new HashMap<>()); + } + + public void addEdge(final String source, final String target, final String label) { + Map<String, String> properties = new HashMap<String, String>(); + properties.put("label", label); + addEdge(source, target, properties); + } + + public void addEdge(final String source, final String target, final Map<String, String> properties) { + addElement('"' + source + '"' + " -> " + '"' + target + '"', extendEdgeProperties(properties)); + } + + public void addSubgraph(final String subgraph) { + body.append(subgraph + '\n'); + } + + private void addElement(final String element, final Map<String, String> properties) { + body.append(element); + if (properties != null && !properties.isEmpty()) { + body.append(" ["); + Joiner.on("\", ").withKeyValueSeparator("=\"").appendTo(body, properties); + body.append("\"]"); + } + body.append('\n'); + } + + private Map<String, String> extendNodeProperties(final Map<String, String> properties) { + return extendElementProperties(properties, defaultNodeProperties); + } + + private Map<String, String> extendEdgeProperties(final Map<String, String> properties) { + return extendElementProperties(properties, defaultEdgeProperties); + } + + private Map<String, String> extendElementProperties(final Map<String, String> properties, final Map<String, String> defaultElementProperties) { + + if (defaultElementProperties != null) { + for (Entry<String, String> property : defaultElementProperties.entrySet()) { + properties.putIfAbsent(property.getKey(), property.getValue()); + } + } + + if (defaultProperties != null) { + for (Entry<String, String> property : defaultProperties.entrySet()) { + properties.putIfAbsent(property.getKey(), property.getValue()); + } + } + + return properties; + } + +} diff --git a/src/main/java/kieker/analysis/util/IndentWriter.java b/src/main/java/kieker/analysis/util/IndentWriter.java index be9dd60ce1092a491fb4828e0bd291451abc5492..0ba2edb28a1c6e7388e37135c91ec43be5c687c5 100644 --- a/src/main/java/kieker/analysis/util/IndentWriter.java +++ b/src/main/java/kieker/analysis/util/IndentWriter.java @@ -40,7 +40,7 @@ public class IndentWriter extends Writer { } private String getIndentChars() { - return new String(new char[indentLength]).replace('\0', indentChar); + return new String(new char[indented * indentLength]).replace('\0', indentChar); } @Override diff --git a/src/main/java/kieker/analysis/util/dot/DotGraph.java b/src/main/java/kieker/analysis/util/dot/DotGraph.java new file mode 100644 index 0000000000000000000000000000000000000000..132e340a938a33b577fd96e6ec8a1d08901041bb --- /dev/null +++ b/src/main/java/kieker/analysis/util/dot/DotGraph.java @@ -0,0 +1,31 @@ +package kieker.analysis.util.dot; + +public final class DotGraph { + + public static final String START_GRAPH_BRACKET = "{"; + + public static final String END_GRAPH_BRACKET = "}"; + + public static final String START_ATTRS_BRACKET = "["; + + public static final String END_ATTRS_BRACKET = "]"; + + public static final String ATTR_CONNECTOR = "="; + + public static final String DIRECTED_START_TOKEN = "digraph"; + + public static final String UNDIRECTED_START_TOKEN = "graph"; + + public static final String SUB_START_TOKEN = "subgraph"; + + public static final String NODE = "node"; + + public static final String EDGE = "edge"; + + public static final String CLUSTER_PREFIX = "cluster_"; + + public static final String DIRECTED_EDGE_CONNECTOR = "->"; + + public static final String UNDIRECTED_EDGE_CONNECTOR = "--"; + +} diff --git a/src/main/java/kieker/analysis/util/dot/DotGraphType.java b/src/main/java/kieker/analysis/util/dot/DotGraphType.java new file mode 100644 index 0000000000000000000000000000000000000000..97d1a709f7345eae635d9386c8810d51fdd14d50 --- /dev/null +++ b/src/main/java/kieker/analysis/util/dot/DotGraphType.java @@ -0,0 +1,7 @@ +package kieker.analysis.util.dot; + +public enum DotGraphType { + + DIRECTED, UNDIRECTED + +} diff --git a/src/main/java/kieker/analysis/util/dot/DotWriter.java b/src/main/java/kieker/analysis/util/dot/DotWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..2d3e3da75f63af629db1a5a99bec58b89462e655 --- /dev/null +++ b/src/main/java/kieker/analysis/util/dot/DotWriter.java @@ -0,0 +1,157 @@ +package kieker.analysis.util.dot; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; +import java.util.stream.Collectors; + +import kieker.analysis.util.IndentWriter; + +/** + * Class to build and write a DOT Graph to a writer. + * + * @author Sören Henning + * + */ +public class DotWriter { + + private final IndentWriter writer; + private DotWriterState state = DotWriterState.CREATED; + private int openSubgraphs = 0; + + private DotGraphType graphType = DotGraphType.DIRECTED; + + public DotWriter(final Writer writer) { + this.writer = new IndentWriter(writer); + } + + public void start(final String name) throws IOException { + start(DotGraphType.DIRECTED, name); + } + + public void start(final DotGraphType graphType, final String name) throws IOException { + checkState(DotWriterState.CREATED); + + this.graphType = graphType; + + String openToken; + if (graphType == DotGraphType.UNDIRECTED) { + openToken = DotGraph.UNDIRECTED_START_TOKEN; + } else { + openToken = DotGraph.DIRECTED_START_TOKEN; + } + writer.writeln(openToken + ' ' + name + ' ' + DotGraph.START_GRAPH_BRACKET); + writer.indent(); + state = DotWriterState.STARTED; + } + + public void finish() throws IOException { + checkState(DotWriterState.STARTED); + + if (openSubgraphs > 0) { + throw new IllegalStateException("There are unclosed subgraphs."); + } + + writer.unindent(); + writer.writeln(DotGraph.END_GRAPH_BRACKET); + state = DotWriterState.FINISHED; + + writer.close(); + } + + public void addDefaultNodeAttributes(final Map<String, String> attributes) throws IOException { + checkState(DotWriterState.STARTED); + + writer.writeln(DotGraph.NODE + ' ' + assembleAttributes(attributes)); + } + + public void addDefaultEdgeAttributes(final Map<String, String> attributes) throws IOException { + checkState(DotWriterState.STARTED); + + writer.writeln(DotGraph.EDGE + ' ' + assembleAttributes(attributes)); + } + + public void addGraphAttribute(final String key, final String value) throws IOException { + checkState(DotWriterState.STARTED); + + writer.writeln(assembleAttribute(key, value)); + } + + public void addNode(final String id, final Map<String, String> attributes) throws IOException { + checkState(DotWriterState.STARTED); + + writer.writeln('"' + id + '"' + ' ' + assembleAttributes(attributes)); + } + + public void addEdge(final String sourceId, final String targetId, final Map<String, String> attributes) throws IOException { + checkState(DotWriterState.STARTED); + + String edgeConnector; + if (graphType == DotGraphType.UNDIRECTED) { + edgeConnector = DotGraph.UNDIRECTED_EDGE_CONNECTOR; + } else { + edgeConnector = DotGraph.DIRECTED_EDGE_CONNECTOR; + } + writer.writeln('"' + sourceId + '"' + ' ' + edgeConnector + ' ' + '"' + targetId + '"' + ' ' + assembleAttributes(attributes)); + } + + public void addSubgraphStart(final String name) throws IOException { + checkState(DotWriterState.STARTED); + + writer.writeln(DotGraph.SUB_START_TOKEN + ' ' + name + ' ' + DotGraph.START_GRAPH_BRACKET); + writer.indent(); + openSubgraphs++; + } + + public void addSubgraphStop() throws IOException { + checkState(DotWriterState.STARTED); + + if (openSubgraphs == 0) { + throw new IllegalStateException("There is no subgraph to close."); + } + + writer.unindent(); + writer.writeln(DotGraph.END_GRAPH_BRACKET); + openSubgraphs--; + } + + public void addClusterStart(final String name) throws IOException { + addSubgraphStart(DotGraph.CLUSTER_PREFIX + name); + } + + public void addClusterStop() throws IOException { + addSubgraphStop(); + } + + private void checkState(final DotWriterState expectedState) { + if (state != expectedState) { + switch (expectedState) { + case CREATED: + throw new IllegalStateException("The writing has already been started."); + case STARTED: + throw new IllegalStateException("The writing has never started or already been finished."); + case FINISHED: + throw new IllegalStateException("The writing has not been finished."); + default: + throw new IllegalStateException(); + } + } + } + + private String assembleAttributes(final Map<String, String> attributes) { + return DotGraph.START_ATTRS_BRACKET + + attributes.entrySet().stream() + .map(e -> assembleAttribute(e.getKey(), e.getValue())) + .collect(Collectors.joining(",")) + + DotGraph.END_ATTRS_BRACKET; + } + + private String assembleAttribute(final String key, final String value) { + return key + DotGraph.ATTR_CONNECTOR + '"' + value + '"'; + } + + private enum DotWriterState { + CREATED, STARTED, FINISHED + } + +}