    001/*
    002 * Copyright (c) 2007-2017 Xplenty, 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> candidates = 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        candidates.addAll( elementGraph.vertexSet() );
    126      }
    127
    128    candidates.remove( Extent.head );
    129    candidates.remove( Extent.tail );
    130
    131    // many elements span, but are included into multiple nodes
    132    // the first to include the element owns it, making other node representations mis-representative
    133    // for now, lets force shared elements to span, but this hides the identity nodes outright
    134    Set<FlowElement> identityElements = new HashSet<>();
    135
    136    for( FlowElement identityElement : candidates )
    137      {
    138      if( processGraph.getElementProcesses( identityElement ).size() == 1 )
    139        identityElements.add( identityElement );
    140      }
    141
    142    return identityElements;
    143    }
    144
    145  protected Set<FlowElement> getSpanElements( ProcessGraph<? extends ProcessModel> processGraph )
    146    {
    147    Set<FlowElement> spanElements = new HashSet<>();
    148
    149    spanElements.add( Extent.head );
    150    spanElements.add( Extent.tail );
    151    spanElements.addAll( processGraph.getAllSourceElements() );
    152    spanElements.addAll( processGraph.getAllSinkElements() );
    153
    154    // forces tap to be within a node
    155    spanElements.removeAll( processGraph.getSourceTaps() );
    156    spanElements.removeAll( processGraph.getSinkTaps() );
    157
    158    return spanElements;
    159    }
    160
    161  /**
    162   * if renderSpans == true, write edges if either side crosses a node boundary.
    163   * if renderSpans == false, only write edged that are contained in a node
    164   */
    165  protected void writeEdgeSet( ProcessGraph<? extends ProcessModel> processGraph, ElementGraph parentGraph, ElementGraph currentGraph, PrintWriter out, Set<FlowElement> spansClusters, boolean renderSpans, Set<FlowElement> identityElements )
    166    {
    167    out.println();
    168
    169    for( Scope scope : currentGraph.edgeSet() )
    170      {
    171      FlowElement edgeSource = currentGraph.getEdgeSource( scope );
    172      FlowElement edgeTarget = currentGraph.getEdgeTarget( scope );
    173
    174      boolean sourceSpans = spansClusters.contains( edgeSource );
    175      boolean targetSpans = spansClusters.contains( edgeTarget );
    176      boolean spans = sourceSpans || targetSpans;
    177
    178      boolean sourceIdentity = identityElements.contains( edgeSource );
    179      boolean targetIdentity = identityElements.contains( edgeTarget );
    180
    181      if( sourceIdentity && targetIdentity )
    182        spans = false;
    183
    184      if( spans != renderSpans )
    185        continue;
    186
    187      List<ElementGraph> sourceGraphs = Arrays.asList( currentGraph );
    188      List<ElementGraph> targetGraphs = Arrays.asList( currentGraph );
    189
    190      if( sourceIdentity && targetIdentity )
    191        {
    192        sourceGraphs = Arrays.asList( parentGraph );
    193        targetGraphs = Arrays.asList( parentGraph );
    194        }
    195      else if( sourceSpans && targetSpans )
    196        {
    197        sourceGraphs = Arrays.asList( currentGraph );
    198        targetGraphs = Arrays.asList( currentGraph );
    199        }
    200      else if( sourceSpans )
    201        {
    202        sourceGraphs = Arrays.asList( parentGraph );
    203        targetGraphs = processGraph.getElementGraphs( edgeTarget );
    204        }
    205      else if( targetSpans )
    206        {
    207        sourceGraphs = processGraph.getElementGraphs( edgeSource );
    208        targetGraphs = Arrays.asList( parentGraph );
    209        }
    210
    211      for( ElementGraph sourceGraph : sourceGraphs )
    212        {
    213        for( ElementGraph targetGraph : targetGraphs )
    214          writeEdge( out, scope, edgeSource, edgeTarget, sourceGraph, targetGraph );
    215        }
    216      }
    217    }
    218
    219  private void writeEdge( PrintWriter out, Scope scope, FlowElement edgeSource, FlowElement edgeTarget, ElementGraph sourceGraph, ElementGraph targetGraph )
    220    {
    221    String source = getVertexID( sourceGraph, edgeSource );
    222    String target = getVertexID( targetGraph, edgeTarget );
    223
    224    out.print( INDENT + source + CONNECTOR + target );
    225
    226    String labelName = null;
    227
    228    if( edgeLabelProvider != null )
    229      labelName = edgeLabelProvider.getEdgeName( scope );
    230
    231    Map<String, String> attributes = null;
    232
    233    if( edgeAttributeProvider != null )
    234      attributes = edgeAttributeProvider.getComponentAttributes( scope );
    235
    236    renderAttributes( out, labelName, attributes );
    237
    238    out.println( ";" );
    239    }
    240
    241  protected void writeVertexSet( ProcessModel processModel, ElementGraph parentGraph, ElementGraph currentGraph, PrintWriter out, Set<FlowElement> spansClusters, boolean onlySpans, Set<FlowElement> duplicatedElements, Set<FlowElement> identityElements )
    242    {
    243    boolean isIdentityGraph = false;
    244
    245    if( processModel != null )
    246      isIdentityGraph = ProcessModels.isIdentity( processModel, Tap.class );
    247
    248    for( FlowElement flowElement : currentGraph.vertexSet() )
    249      {
    250      boolean spans = spansClusters.contains( flowElement );
    251      boolean isIdentity = identityElements.contains( flowElement );
    252
    253      if( isIdentity && isIdentityGraph )
    254        continue;
    255
    256      if( isIdentity )
    257        spans = false;
    258
    259      if( spans != onlySpans )
    260        continue;
    261
    262      out.print( INDENT + getVertexID( isIdentity ? parentGraph : currentGraph, flowElement ) );
    263
    264      String labelName = null;
    265
    266      if( vertexLabelProvider != null )
    267        labelName = vertexLabelProvider.getVertexName( flowElement );
    268
    269      Map<String, String> attributes = new HashMap<>();
    270
    271      if( duplicatedElements.contains( flowElement ) )
    272        attributes.put( "color", getHSBColorFor( flowElement ) );
    273
    274      if( vertexAttributeProvider != null )
    275        attributes.putAll( vertexAttributeProvider.getComponentAttributes( flowElement ) );
    276
    277      renderAttributes( out, labelName, attributes );
    278
    279      out.println( ";" );
    280      }
    281    }
    282
    283  private void renderAttributes( PrintWriter out, String labelName, Map<String, String> attributes )
    284    {
    285    if( labelName == null && attributes == null )
    286      return;
    287
    288    out.print( " [ " );
    289
    290    if( labelName == null )
    291      labelName = attributes.get( "label" );
    292
    293    if( labelName != null )
    294      out.print( "label=\"" + labelName + "\" " );
    295
    296    if( attributes != null )
    297      {
    298      for( Map.Entry<String, String> entry : attributes.entrySet() )
    299        {
    300        String name = entry.getKey();
    301
    302        if( name.equals( "label" ) )
    303          continue;
    304
    305        out.print( name + "=\"" + entry.getValue() + "\" " );
    306        }
    307      }
    308
    309    out.print( "]" );
    310    }
    311
    312  private String getVertexID( ElementGraph elementGraph, FlowElement flowElement )
    313    {
    314    return vertexIDProvider.getVertexName( new Pair<>( elementGraph, flowElement ) );
    315    }
    316
    317  Map<FlowElement, String> colors = new HashMap<>();
    318  float hue = 0.3f;
    319
    320  // a rudimentary attempt to progress the colors so they can be differentiated
    321  private String getHSBColorFor( FlowElement flowElement )
    322    {
    323    if( colors.containsKey( flowElement ) )
    324      return colors.get( flowElement );
    325
    326    String result = String.format( "%f,%f,%f", 1.0 - hue % 1.0, 1.0, 0.9 );
    327
    328    colors.put( flowElement, result );
    329
    330    hue += 0.075 + ( 0.025 * Math.floor( hue ) );
    331
    332    return result;
    333    }
    334  }