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  }