001/* 002 * Copyright (c) 2007-2015 Concurrent, Inc. All Rights Reserved. 003 * 004 * Project and contact information: http://www.cascading.org/ 005 * 006 * This file is part of the Cascading project. 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020 021/* 022 * See package LICENSE.txt for additional license information. 023 */ 024 025package cascading.util.jgrapht; 026 027import java.io.PrintWriter; 028import java.io.Writer; 029import java.util.Map; 030 031import org.jgrapht.DirectedGraph; 032import org.jgrapht.Graph; 033 034public class DOTExporter<V, E> 035 { 036 private VertexNameProvider<V> vertexIDProvider; 037 private VertexNameProvider<V> vertexLabelProvider; 038 private EdgeNameProvider<E> edgeLabelProvider; 039 private ComponentAttributeProvider<V> vertexAttributeProvider; 040 private ComponentAttributeProvider<E> edgeAttributeProvider; 041 042 /** 043 * Constructs a new DOTExporter object with an integer name provider for the 044 * vertex IDs and null providers for the vertex and edge labels. 045 */ 046 public DOTExporter() 047 { 048 this( new IntegerNameProvider<V>(), null, null ); 049 } 050 051 /** 052 * Constructs a new DOTExporter object with the given ID and label 053 * providers. 054 * 055 * @param vertexIDProvider for generating vertex IDs. Must not be null. 056 * @param vertexLabelProvider for generating vertex labels. If null, vertex 057 * labels will not be written to the file. 058 * @param edgeLabelProvider for generating edge labels. If null, edge labels 059 * will not be written to the file. 060 */ 061 public DOTExporter( 062 VertexNameProvider<V> vertexIDProvider, 063 VertexNameProvider<V> vertexLabelProvider, 064 EdgeNameProvider<E> edgeLabelProvider ) 065 { 066 this( 067 vertexIDProvider, 068 vertexLabelProvider, 069 edgeLabelProvider, 070 null, 071 null ); 072 } 073 074 /** 075 * Constructs a new DOTExporter object with the given ID, label, and 076 * attribute providers. Note that if a label provider conflicts with a 077 * label-supplying attribute provider, the label provider is given 078 * precedence. 079 * 080 * @param vertexIDProvider for generating vertex IDs. Must not be null. 081 * @param vertexLabelProvider for generating vertex labels. If null, vertex 082 * labels will not be written to the file (unless an attribute provider is 083 * supplied which also supplies labels). 084 * @param edgeLabelProvider for generating edge labels. If null, edge labels 085 * will not be written to the file. 086 * @param vertexAttributeProvider for generating vertex attributes. If null, 087 * vertex attributes will not be written to the file. 088 * @param edgeAttributeProvider for generating edge attributes. If null, 089 * edge attributes will not be written to the file. 090 */ 091 public DOTExporter( 092 VertexNameProvider<V> vertexIDProvider, 093 VertexNameProvider<V> vertexLabelProvider, 094 EdgeNameProvider<E> edgeLabelProvider, 095 ComponentAttributeProvider<V> vertexAttributeProvider, 096 ComponentAttributeProvider<E> edgeAttributeProvider ) 097 { 098 this.vertexIDProvider = vertexIDProvider; 099 this.vertexLabelProvider = vertexLabelProvider; 100 this.edgeLabelProvider = edgeLabelProvider; 101 this.vertexAttributeProvider = vertexAttributeProvider; 102 this.edgeAttributeProvider = edgeAttributeProvider; 103 } 104 105 /** 106 * Exports a graph into a plain text file in DOT format. 107 * 108 * @param writer the writer to which the graph to be exported 109 * @param g the graph to be exported 110 */ 111 public void export( Writer writer, Graph<V, E> g ) 112 { 113 PrintWriter out = new PrintWriter( writer ); 114 String indent = " "; 115 String connector; 116 117 if( g instanceof DirectedGraph<?, ?> ) 118 { 119 out.println( "digraph G {" ); 120 connector = " -> "; 121 } 122 else 123 { 124 out.println( "graph G {" ); 125 connector = " -- "; 126 } 127 128 for( V v : g.vertexSet() ) 129 { 130 out.print( indent + getVertexID( v ) ); 131 132 String labelName = null; 133 if( vertexLabelProvider != null ) 134 { 135 labelName = vertexLabelProvider.getVertexName( v ); 136 } 137 Map<String, String> attributes = null; 138 if( vertexAttributeProvider != null ) 139 { 140 attributes = vertexAttributeProvider.getComponentAttributes( v ); 141 } 142 renderAttributes( out, labelName, attributes ); 143 144 out.println( ";" ); 145 } 146 147 for( E e : g.edgeSet() ) 148 { 149 String source = getVertexID( g.getEdgeSource( e ) ); 150 String target = getVertexID( g.getEdgeTarget( e ) ); 151 152 out.print( indent + source + connector + target ); 153 154 String labelName = null; 155 if( edgeLabelProvider != null ) 156 { 157 labelName = edgeLabelProvider.getEdgeName( e ); 158 } 159 Map<String, String> attributes = null; 160 if( edgeAttributeProvider != null ) 161 { 162 attributes = edgeAttributeProvider.getComponentAttributes( e ); 163 } 164 renderAttributes( out, labelName, attributes ); 165 166 out.println( ";" ); 167 } 168 169 out.println( "}" ); 170 171 out.flush(); 172 } 173 174 private void renderAttributes( 175 PrintWriter out, 176 String labelName, 177 Map<String, String> attributes ) 178 { 179 if( ( labelName == null ) && ( attributes == null ) ) 180 { 181 return; 182 } 183 out.print( " [ " ); 184 if( ( labelName == null ) && ( attributes != null ) ) 185 { 186 labelName = attributes.get( "label" ); 187 } 188 if( labelName != null ) 189 { 190 out.print( "label=\"" + labelName + "\" " ); 191 } 192 if( attributes != null ) 193 { 194 for( Map.Entry<String, String> entry : attributes.entrySet() ) 195 { 196 String name = entry.getKey(); 197 if( name.equals( "label" ) ) 198 { 199 // already handled by special case above 200 continue; 201 } 202 out.print( name + "=\"" + entry.getValue() + "\" " ); 203 } 204 } 205 out.print( "]" ); 206 } 207 208 /** 209 * Return a valid vertex ID (with respect to the .dot language definition as 210 * described in http://www.graphviz.org/doc/info/lang.html Quoted from above 211 * mentioned source: An ID is valid if it meets one of the following 212 * criteria: 213 * <p/> 214 * <ul> 215 * <li>any string of alphabetic characters, underscores or digits, not 216 * beginning with a digit; 217 * <li>a number [-]?(.[0-9]+ | [0-9]+(.[0-9]*)? ); 218 * <li>any double-quoted string ("...") possibly containing escaped quotes 219 * (\"); 220 * <li>an HTML string (<...>). 221 * </ul> 222 * 223 * @throws RuntimeException if the given <code>vertexIDProvider</code> 224 * didn't generate a valid vertex ID. 225 */ 226 private String getVertexID( V v ) 227 { 228 // TODO jvs 28-Jun-2008: possible optimizations here are 229 // (a) only validate once per vertex 230 // (b) compile regex patterns 231 232 // use the associated id provider for an ID of the given vertex 233 String idCandidate = vertexIDProvider.getVertexName( v ); 234 235 // now test that this is a valid ID 236 boolean isAlphaDig = idCandidate.matches( "[a-zA-Z]+([\\w_]*)?" ); 237 boolean isDoubleQuoted = idCandidate.matches( "\".*\"" ); 238 boolean isDotNumber = 239 idCandidate.matches( "[-]?([.][0-9]+|[0-9]+([.][0-9]*)?)" ); 240 boolean isHTML = idCandidate.matches( "<.*>" ); 241 242 if( isAlphaDig || isDotNumber || isDoubleQuoted || isHTML ) 243 { 244 return idCandidate; 245 } 246 247 throw new RuntimeException( 248 "Generated id '" + idCandidate + "'for vertex '" + v 249 + "' is not valid with respect to the .dot language" ); 250 } 251 }