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