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 021package cascading.util; 022 023import java.io.PrintWriter; 024import java.io.Writer; 025import java.util.Arrays; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.Iterator; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032 033import cascading.flow.FlowElement; 034import cascading.flow.planner.Scope; 035import cascading.flow.planner.graph.ElementGraph; 036import cascading.flow.planner.graph.Extent; 037import cascading.flow.planner.process.ProcessGraph; 038import cascading.flow.planner.process.ProcessModel; 039import cascading.flow.planner.process.ProcessModels; 040import cascading.tap.Tap; 041import cascading.util.jgrapht.ComponentAttributeProvider; 042import cascading.util.jgrapht.EdgeNameProvider; 043import cascading.util.jgrapht.VertexNameProvider; 044 045/** 046 * This class is a derivative of the JGraphT DOTExporter, with numerous enhancements but with 047 * retained compatibility. 048 */ 049public class DOTProcessGraphWriter 050 { 051 public static final String INDENT = " "; 052 public static final String CONNECTOR = " -> "; 053 054 private VertexNameProvider<Pair<ElementGraph, FlowElement>> vertexIDProvider; 055 private VertexNameProvider<FlowElement> vertexLabelProvider; 056 private EdgeNameProvider<Scope> edgeLabelProvider; 057 private ComponentAttributeProvider<FlowElement> vertexAttributeProvider; 058 private ComponentAttributeProvider<Scope> edgeAttributeProvider; 059 private VertexNameProvider<ProcessModel> clusterIDProvider; 060 private VertexNameProvider<ProcessModel> clusterLabelProvider; 061 062 public DOTProcessGraphWriter( VertexNameProvider<Pair<ElementGraph, FlowElement>> vertexIDProvider, VertexNameProvider<FlowElement> vertexLabelProvider, 063 EdgeNameProvider<Scope> edgeLabelProvider, 064 ComponentAttributeProvider<FlowElement> vertexAttributeProvider, ComponentAttributeProvider<Scope> edgeAttributeProvider, 065 VertexNameProvider<ProcessModel> clusterIDProvider, VertexNameProvider<ProcessModel> clusterLabelProvider ) 066 { 067 this.vertexIDProvider = vertexIDProvider; 068 this.vertexLabelProvider = vertexLabelProvider; 069 this.edgeLabelProvider = edgeLabelProvider; 070 this.vertexAttributeProvider = vertexAttributeProvider; 071 this.edgeAttributeProvider = edgeAttributeProvider; 072 this.clusterIDProvider = clusterIDProvider; 073 this.clusterLabelProvider = clusterLabelProvider; 074 } 075 076 public void writeGraph( Writer writer, ElementGraph parentGraph, ProcessGraph<? extends ProcessModel> processGraph ) 077 { 078 PrintWriter out = new PrintWriter( writer ); 079 080 out.println( "digraph G {" ); 081 082 Set<FlowElement> spanElements = getSpanElements( processGraph ); 083 Set<FlowElement> identityElements = getIdentityElements( processGraph ); 084 Set<FlowElement> duplicatedElements = processGraph.getDuplicatedElements( parentGraph ); 085 086 writeVertexSet( null, parentGraph, parentGraph, out, spanElements, true, duplicatedElements, identityElements ); 087 writeEdgeSet( processGraph, parentGraph, parentGraph, out, spanElements, true, identityElements ); 088 089 Iterator<? extends ProcessModel> topologicalIterator = processGraph.getOrdinalTopologicalIterator(); 090 091 while( topologicalIterator.hasNext() ) 092 { 093 ProcessModel processModel = topologicalIterator.next(); 094 095 out.println(); 096 out.print( "subgraph cluster_" ); 097 out.print( clusterIDProvider.getVertexName( processModel ) ); 098 out.println( " {" ); 099 100 out.print( INDENT ); 101 out.print( "label = \"" ); 102 out.print( clusterLabelProvider.getVertexName( processModel ) ); 103 out.println( "\";" ); 104 out.println(); 105 106 writeVertexSet( processModel, parentGraph, processModel.getElementGraph(), out, spanElements, false, duplicatedElements, identityElements ); 107 writeEdgeSet( processGraph, parentGraph, processModel.getElementGraph(), out, spanElements, false, identityElements ); 108 109 out.println( "}" ); 110 } 111 112 out.println( "}" ); 113 114 out.flush(); 115 } 116 117 protected Set<FlowElement> getIdentityElements( ProcessGraph<? extends ProcessModel> processGraph ) 118 { 119 Set<FlowElement> identityElements = new HashSet<>(); 120 121 // force identity nodes to be visualized 122 for( ElementGraph elementGraph : processGraph.getIdentityElementGraphs() ) 123 { 124 if( !Util.contains( Tap.class, elementGraph.vertexSet() ) ) 125 identityElements.addAll( elementGraph.vertexSet() ); 126 } 127 128 identityElements.remove( Extent.head ); 129 identityElements.remove( Extent.tail ); 130 131 return identityElements; 132 } 133 134 protected Set<FlowElement> getSpanElements( ProcessGraph<? extends ProcessModel> processGraph ) 135 { 136 Set<FlowElement> spanElements = new HashSet<>(); 137 138 spanElements.add( Extent.head ); 139 spanElements.add( Extent.tail ); 140 spanElements.addAll( processGraph.getAllSourceElements() ); 141 spanElements.addAll( processGraph.getAllSinkElements() ); 142 143 // forces tap to be within a node 144 spanElements.removeAll( processGraph.getSourceTaps() ); 145 spanElements.removeAll( processGraph.getSinkTaps() ); 146 147 return spanElements; 148 } 149 150 /** 151 * if renderSpans == true, write edges if either side crosses a node boundary. 152 * if renderSpans == false, only write edged that are contained in a node 153 */ 154 protected void writeEdgeSet( ProcessGraph<? extends ProcessModel> processGraph, ElementGraph parentGraph, ElementGraph currentGraph, PrintWriter out, Set<FlowElement> spansClusters, boolean renderSpans, Set<FlowElement> identityElements ) 155 { 156 out.println(); 157 158 for( Scope scope : currentGraph.edgeSet() ) 159 { 160 FlowElement edgeSource = currentGraph.getEdgeSource( scope ); 161 FlowElement edgeTarget = currentGraph.getEdgeTarget( scope ); 162 163 boolean sourceSpans = spansClusters.contains( edgeSource ); 164 boolean targetSpans = spansClusters.contains( edgeTarget ); 165 boolean spans = sourceSpans || targetSpans; 166 167 boolean sourceIdentity = identityElements.contains( edgeSource ); 168 boolean targetIdentity = identityElements.contains( edgeTarget ); 169 170 if( sourceIdentity && targetIdentity ) 171 spans = false; 172 173 if( spans != renderSpans ) 174 continue; 175 176 List<ElementGraph> sourceGraphs = Arrays.asList( currentGraph ); 177 List<ElementGraph> targetGraphs = Arrays.asList( currentGraph ); 178 179 if( sourceIdentity && targetIdentity ) 180 { 181 sourceGraphs = Arrays.asList( parentGraph ); 182 targetGraphs = Arrays.asList( parentGraph ); 183 } 184 else if( sourceSpans && targetSpans ) 185 { 186 sourceGraphs = Arrays.asList( currentGraph ); 187 targetGraphs = Arrays.asList( currentGraph ); 188 } 189 else if( sourceSpans ) 190 { 191 sourceGraphs = Arrays.asList( parentGraph ); 192 targetGraphs = processGraph.getElementGraphs( edgeTarget ); 193 } 194 else if( targetSpans ) 195 { 196 sourceGraphs = processGraph.getElementGraphs( edgeSource ); 197 targetGraphs = Arrays.asList( parentGraph ); 198 } 199 200 for( ElementGraph sourceGraph : sourceGraphs ) 201 { 202 for( ElementGraph targetGraph : targetGraphs ) 203 writeEdge( out, scope, edgeSource, edgeTarget, sourceGraph, targetGraph ); 204 } 205 } 206 } 207 208 private void writeEdge( PrintWriter out, Scope scope, FlowElement edgeSource, FlowElement edgeTarget, ElementGraph sourceGraph, ElementGraph targetGraph ) 209 { 210 String source = getVertexID( sourceGraph, edgeSource ); 211 String target = getVertexID( targetGraph, edgeTarget ); 212 213 out.print( INDENT + source + CONNECTOR + target ); 214 215 String labelName = null; 216 217 if( edgeLabelProvider != null ) 218 labelName = edgeLabelProvider.getEdgeName( scope ); 219 220 Map<String, String> attributes = null; 221 222 if( edgeAttributeProvider != null ) 223 attributes = edgeAttributeProvider.getComponentAttributes( scope ); 224 225 renderAttributes( out, labelName, attributes ); 226 227 out.println( ";" ); 228 } 229 230 protected void writeVertexSet( ProcessModel processModel, ElementGraph parentGraph, ElementGraph currentGraph, PrintWriter out, Set<FlowElement> spansClusters, boolean onlySpans, Set<FlowElement> duplicatedElements, Set<FlowElement> identityElements ) 231 { 232 boolean isIdentityGraph = false; 233 234 if( processModel != null ) 235 isIdentityGraph = ProcessModels.isIdentity( processModel, Tap.class ); 236 237 for( FlowElement flowElement : currentGraph.vertexSet() ) 238 { 239 boolean spans = spansClusters.contains( flowElement ); 240 boolean isIdentity = identityElements.contains( flowElement ); 241 242 if( isIdentity && isIdentityGraph ) 243 continue; 244 245 if( isIdentity ) 246 spans = false; 247 248 if( spans != onlySpans ) 249 continue; 250 251 out.print( INDENT + getVertexID( isIdentity ? parentGraph : currentGraph, flowElement ) ); 252 253 String labelName = null; 254 255 if( vertexLabelProvider != null ) 256 labelName = vertexLabelProvider.getVertexName( flowElement ); 257 258 Map<String, String> attributes = new HashMap<>(); 259 260 if( duplicatedElements.contains( flowElement ) ) 261 attributes.put( "color", getHSBColorFor( flowElement ) ); 262 263 if( vertexAttributeProvider != null ) 264 attributes.putAll( vertexAttributeProvider.getComponentAttributes( flowElement ) ); 265 266 renderAttributes( out, labelName, attributes ); 267 268 out.println( ";" ); 269 } 270 } 271 272 private void renderAttributes( PrintWriter out, String labelName, Map<String, String> attributes ) 273 { 274 if( labelName == null && attributes == null ) 275 return; 276 277 out.print( " [ " ); 278 279 if( labelName == null ) 280 labelName = attributes.get( "label" ); 281 282 if( labelName != null ) 283 out.print( "label=\"" + labelName + "\" " ); 284 285 if( attributes != null ) 286 { 287 for( Map.Entry<String, String> entry : attributes.entrySet() ) 288 { 289 String name = entry.getKey(); 290 291 if( name.equals( "label" ) ) 292 continue; 293 294 out.print( name + "=\"" + entry.getValue() + "\" " ); 295 } 296 } 297 298 out.print( "]" ); 299 } 300 301 private String getVertexID( ElementGraph elementGraph, FlowElement flowElement ) 302 { 303 return vertexIDProvider.getVertexName( new Pair<>( elementGraph, flowElement ) ); 304 } 305 306 Map<FlowElement, String> colors = new HashMap<>(); 307 float hue = 0.3f; 308 309 // a rudimentary attempt to progress the colors so they can be differentiated 310 private String getHSBColorFor( FlowElement flowElement ) 311 { 312 if( colors.containsKey( flowElement ) ) 313 return colors.get( flowElement ); 314 315 String result = String.format( "%f,%f,%f", 1.0 - hue % 1.0, 1.0, 0.9 ); 316 317 colors.put( flowElement, result ); 318 319 hue += 0.075 + ( 0.025 * Math.floor( hue ) ); 320 321 return result; 322 } 323 }