001/* 002 * Copyright (c) 2016-2017 Chris K Wensel <chris@wensel.net>. All Rights Reserved. 003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved. 004 * 005 * Project and contact information: http://www.cascading.org/ 006 * 007 * This file is part of the Cascading project. 008 * 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 */ 021 022/* 023 * See package LICENSE.txt for additional license information. 024 */ 025 026package cascading.util.jgrapht; 027 028import java.io.PrintWriter; 029import java.io.Writer; 030import java.util.Map; 031 032import org.jgrapht.DirectedGraph; 033import org.jgrapht.Graph; 034 035public class DOTExporter<V, E> 036 { 037 private VertexNameProvider<V> vertexIDProvider; 038 private VertexNameProvider<V> vertexLabelProvider; 039 private EdgeNameProvider<E> edgeLabelProvider; 040 private ComponentAttributeProvider<V> vertexAttributeProvider; 041 private ComponentAttributeProvider<E> edgeAttributeProvider; 042 043 /** 044 * Constructs a new DOTExporter object with an integer name provider for the 045 * vertex IDs and null providers for the vertex and edge labels. 046 */ 047 public DOTExporter() 048 { 049 this( new IntegerNameProvider<V>(), null, null ); 050 } 051 052 /** 053 * Constructs a new DOTExporter object with the given ID and label 054 * providers. 055 * 056 * @param vertexIDProvider for generating vertex IDs. Must not be null. 057 * @param vertexLabelProvider for generating vertex labels. If null, vertex 058 * labels will not be written to the file. 059 * @param edgeLabelProvider for generating edge labels. If null, edge labels 060 * will not be written to the file. 061 */ 062 public DOTExporter( 063 VertexNameProvider<V> vertexIDProvider, 064 VertexNameProvider<V> vertexLabelProvider, 065 EdgeNameProvider<E> edgeLabelProvider ) 066 { 067 this( 068 vertexIDProvider, 069 vertexLabelProvider, 070 edgeLabelProvider, 071 null, 072 null ); 073 } 074 075 /** 076 * Constructs a new DOTExporter object with the given ID, label, and 077 * attribute providers. Note that if a label provider conflicts with a 078 * label-supplying attribute provider, the label provider is given 079 * precedence. 080 * 081 * @param vertexIDProvider for generating vertex IDs. Must not be null. 082 * @param vertexLabelProvider for generating vertex labels. If null, vertex 083 * labels will not be written to the file (unless an attribute provider is 084 * supplied which also supplies labels). 085 * @param edgeLabelProvider for generating edge labels. If null, edge labels 086 * will not be written to the file. 087 * @param vertexAttributeProvider for generating vertex attributes. If null, 088 * vertex attributes will not be written to the file. 089 * @param edgeAttributeProvider for generating edge attributes. If null, 090 * edge attributes will not be written to the file. 091 */ 092 public DOTExporter( 093 VertexNameProvider<V> vertexIDProvider, 094 VertexNameProvider<V> vertexLabelProvider, 095 EdgeNameProvider<E> edgeLabelProvider, 096 ComponentAttributeProvider<V> vertexAttributeProvider, 097 ComponentAttributeProvider<E> edgeAttributeProvider ) 098 { 099 this.vertexIDProvider = vertexIDProvider; 100 this.vertexLabelProvider = vertexLabelProvider; 101 this.edgeLabelProvider = edgeLabelProvider; 102 this.vertexAttributeProvider = vertexAttributeProvider; 103 this.edgeAttributeProvider = edgeAttributeProvider; 104 } 105 106 /** 107 * Exports a graph into a plain text file in DOT format. 108 * 109 * @param writer the writer to which the graph to be exported 110 * @param g the graph to be exported 111 */ 112 public void export( Writer writer, Graph<V, E> g ) 113 { 114 PrintWriter out = new PrintWriter( writer ); 115 String indent = " "; 116 String connector; 117 118 if( g instanceof DirectedGraph<?, ?> ) 119 { 120 out.println( "digraph G {" ); 121 connector = " -> "; 122 } 123 else 124 { 125 out.println( "graph G {" ); 126 connector = " -- "; 127 } 128 129 for( V v : g.vertexSet() ) 130 { 131 out.print( indent + getVertexID( v ) ); 132 133 String labelName = null; 134 if( vertexLabelProvider != null ) 135 { 136 labelName = vertexLabelProvider.getVertexName( v ); 137 } 138 Map<String, String> attributes = null; 139 if( vertexAttributeProvider != null ) 140 { 141 attributes = vertexAttributeProvider.getComponentAttributes( v ); 142 } 143 renderAttributes( out, labelName, attributes ); 144 145 out.println( ";" ); 146 } 147 148 for( E e : g.edgeSet() ) 149 { 150 String source = getVertexID( g.getEdgeSource( e ) ); 151 String target = getVertexID( g.getEdgeTarget( e ) ); 152 153 out.print( indent + source + connector + target ); 154 155 String labelName = null; 156 if( edgeLabelProvider != null ) 157 { 158 labelName = edgeLabelProvider.getEdgeName( e ); 159 } 160 Map<String, String> attributes = null; 161 if( edgeAttributeProvider != null ) 162 { 163 attributes = edgeAttributeProvider.getComponentAttributes( e ); 164 } 165 renderAttributes( out, labelName, attributes ); 166 167 out.println( ";" ); 168 } 169 170 out.println( "}" ); 171 172 out.flush(); 173 } 174 175 private void renderAttributes( 176 PrintWriter out, 177 String labelName, 178 Map<String, String> attributes ) 179 { 180 if( ( labelName == null ) && ( attributes == null ) ) 181 { 182 return; 183 } 184 out.print( " [ " ); 185 if( ( labelName == null ) && ( attributes != null ) ) 186 { 187 labelName = attributes.get( "label" ); 188 } 189 if( labelName != null ) 190 { 191 out.print( "label=\"" + labelName + "\" " ); 192 } 193 if( attributes != null ) 194 { 195 for( Map.Entry<String, String> entry : attributes.entrySet() ) 196 { 197 String name = entry.getKey(); 198 if( name.equals( "label" ) ) 199 { 200 // already handled by special case above 201 continue; 202 } 203 out.print( name + "=\"" + entry.getValue() + "\" " ); 204 } 205 } 206 out.print( "]" ); 207 } 208 209 /** 210 * Return a valid vertex ID (with respect to the .dot language definition as 211 * described in http://www.graphviz.org/doc/info/lang.html Quoted from above 212 * mentioned source: An ID is valid if it meets one of the following 213 * criteria: 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} 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 }