    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
    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  }