/* * Copyright 2012 Odysseus Software GmbH * * 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 de.odysseus.ithaka.digraph.io.dot; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import de.odysseus.ithaka.digraph.Digraph; import de.odysseus.ithaka.digraph.DigraphProvider; public class DotExporter { private static class Cluster<V, E, G extends Digraph<? extends V, ? extends E>> { String id; G subgraph; V sample; DotAttribute tail; DotAttribute head; public Cluster(String id, G subgraph) { this.id = id; this.subgraph = subgraph; this.sample = subgraph.vertices().iterator().next(); this.head = new DotAttribute("lhead", id); this.tail = new DotAttribute("ltail", id); } } private final String indent; private final String lineSpeparator; public DotExporter() { this(" ", System.getProperty("line.separator")); } public DotExporter(String indent, String newline) { this.indent = indent; this.lineSpeparator = newline; } private void indent(Writer writer, int level) throws IOException { for (int i = 0; i < level; i++) { writer.write(indent); } } private void writeAttributes(Writer writer, Iterator<DotAttribute> iterator) throws IOException { if (iterator.hasNext()) { boolean first = true; while (iterator.hasNext()) { if (first) { writer.write('['); first = false; } else { writer.write(", "); } iterator.next().write(writer); } writer.write(']'); } } private void writeDefaultAttributes(Writer writer, int level, String name, Iterable<DotAttribute> attributes) throws IOException { if (attributes != null) { indent(writer, level); Iterator<DotAttribute> iterator = attributes.iterator(); if (iterator.hasNext()) { writer.write(name); writeAttributes(writer, iterator); } writer.write(";"); writer.write(lineSpeparator); } } private <V> void writeNode(Writer writer, int level, V vertex, DotProvider<V, ?, ?> provider) throws IOException { indent(writer, level); writer.write(provider.getNodeId(vertex)); Iterable<DotAttribute> attributes = provider.getNodeAttributes(vertex); if (attributes != null) { writeAttributes(writer, attributes.iterator()); } writer.write(";"); writer.write(lineSpeparator); } private <V, E> void writeEdge(Writer writer, int level, V source, V target, E edge, DotProvider<V, E, ?> provider, Cluster<V, E, ?> sourceCluster, Cluster<V, E, ?> targetCluster) throws IOException { indent(writer, level); writer.write(provider.getNodeId(sourceCluster == null ? source : sourceCluster.sample)); writer.write(" -> "); writer.write(provider.getNodeId(targetCluster == null ? target : targetCluster.sample)); Iterable<DotAttribute> attributes = provider.getEdgeAttributes(source, target, edge); if (sourceCluster == null && targetCluster == null) { if (attributes != null) { writeAttributes(writer, attributes.iterator()); } } else { List<DotAttribute> attributeList = new ArrayList<DotAttribute>(); if (sourceCluster != null) { attributeList.add(sourceCluster.tail); } if (targetCluster != null) { attributeList.add(targetCluster.head); } if (attributes != null) { for (DotAttribute attribute : attributes) { attributeList.add(attribute); } } writeAttributes(writer, attributeList.iterator()); } writer.write(";"); writer.write(lineSpeparator); } private <V, E, G extends Digraph<? extends V, ? extends E>> Map<V, Cluster<V,E,G>> createClusters( G digraph, DotProvider<V, E, G> provider, DigraphProvider<? super V, G> subgraphs) throws IOException { Map<V, Cluster<V,E,G>> clusters = new HashMap<V, Cluster<V,E,G>>(); if (subgraphs != null) { for (V vertex : digraph.vertices()) { G subgraph = subgraphs.get(vertex); if (subgraph != null && subgraph.getVertexCount() > 0) { clusters.put(vertex, new Cluster<V, E, G>("cluster_" + provider.getNodeId(vertex), subgraph)); } } } return clusters; } public <V, E, G extends Digraph<? extends V, ? extends E>> void export( DotProvider<V, E, G> provider, G digraph, DigraphProvider<? super V, G> subgraphs, Writer writer) throws IOException { writer.write("digraph G {"); writer.write(lineSpeparator); Map<V, Cluster<V,E,G>> clusters = createClusters(digraph, provider, subgraphs); if (!clusters.isEmpty()) { indent(writer, 1); writer.write("compound=true;"); writer.write(lineSpeparator); } writeDefaultAttributes(writer, 1, "graph", provider.getDefaultGraphAttributes(digraph)); writeDefaultAttributes(writer, 1, "node", provider.getDefaultNodeAttributes(digraph)); writeDefaultAttributes(writer, 1, "edge", provider.getDefaultEdgeAttributes(digraph)); writeNodesAndEdges(writer, 1, provider, digraph, clusters, subgraphs); writer.write("}"); writer.write(lineSpeparator); writer.flush(); } private <V, E, G extends Digraph<? extends V, ? extends E>> void writeNodesAndEdges( Writer writer, int level, DotProvider<V, E, G> provider, G digraph, Map<V, Cluster<V,E,G>> clusters, DigraphProvider<? super V, G> subgraphs) throws IOException { for (V vertex : digraph.vertices()) { if (clusters.containsKey(vertex)) { writeCluster(writer, level, provider, vertex, clusters.get(vertex), subgraphs); } else { writeNode(writer, level, vertex, provider); } } for (V source : digraph.vertices()) { for (V target : digraph.targets(source)) { writeEdge(writer, level, source, target, digraph.get(source, target), provider, clusters.get(source), clusters.get(target)); } } } private <V, E, G extends Digraph<? extends V, ? extends E>> void writeCluster( Writer writer, int level, DotProvider<V, E, G> provider, V subgraphVertex, Cluster<V,E,G> cluster, DigraphProvider<? super V, G> subgraphs) throws IOException { indent(writer, level); writer.write("subgraph "); writer.write(cluster.id); writer.write(" {"); writer.write(lineSpeparator); writeDefaultAttributes(writer, level + 1, "graph", provider.getSubgraphAttributes(cluster.subgraph, subgraphVertex)); Map<V, Cluster<V,E,G>> subclusters = createClusters(cluster.subgraph, provider, subgraphs); writeNodesAndEdges(writer, level + 1, provider, cluster.subgraph, subclusters, subgraphs); indent(writer, level); writer.write("}"); writer.write(lineSpeparator); } }