001/*
002 * Copyright (c) 2016-2017 Chris K Wensel <chris@wensel.net>. All Rights Reserved.
003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
004 *
005 * Project and contact information: http://www.cascading.org/
006 *
007 * This file is part of the Cascading project.
008 *
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022/*
023 * See package LICENSE.txt for additional license information.
024 */
025
026package cascading.util.jgrapht;
027
028import java.io.PrintWriter;
029import java.io.Writer;
030import java.util.Map;
031
032import org.jgrapht.DirectedGraph;
033import org.jgrapht.Graph;
034
035public class DOTExporter<V, E>
036  {
037  private VertexNameProvider<V> vertexIDProvider;
038  private VertexNameProvider<V> vertexLabelProvider;
039  private EdgeNameProvider<E> edgeLabelProvider;
040  private ComponentAttributeProvider<V> vertexAttributeProvider;
041  private ComponentAttributeProvider<E> edgeAttributeProvider;
042
043  /**
044   * Constructs a new DOTExporter object with an integer name provider for the
045   * vertex IDs and null providers for the vertex and edge labels.
046   */
047  public DOTExporter()
048    {
049    this( new IntegerNameProvider<V>(), null, null );
050    }
051
052  /**
053   * Constructs a new DOTExporter object with the given ID and label
054   * providers.
055   *
056   * @param vertexIDProvider    for generating vertex IDs. Must not be null.
057   * @param vertexLabelProvider for generating vertex labels. If null, vertex
058   *                            labels will not be written to the file.
059   * @param edgeLabelProvider   for generating edge labels. If null, edge labels
060   *                            will not be written to the file.
061   */
062  public DOTExporter(
063    VertexNameProvider<V> vertexIDProvider,
064    VertexNameProvider<V> vertexLabelProvider,
065    EdgeNameProvider<E> edgeLabelProvider )
066    {
067    this(
068      vertexIDProvider,
069      vertexLabelProvider,
070      edgeLabelProvider,
071      null,
072      null );
073    }
074
075  /**
076   * Constructs a new DOTExporter object with the given ID, label, and
077   * attribute providers. Note that if a label provider conflicts with a
078   * label-supplying attribute provider, the label provider is given
079   * precedence.
080   *
081   * @param vertexIDProvider        for generating vertex IDs. Must not be null.
082   * @param vertexLabelProvider     for generating vertex labels. If null, vertex
083   *                                labels will not be written to the file (unless an attribute provider is
084   *                                supplied which also supplies labels).
085   * @param edgeLabelProvider       for generating edge labels. If null, edge labels
086   *                                will not be written to the file.
087   * @param vertexAttributeProvider for generating vertex attributes. If null,
088   *                                vertex attributes will not be written to the file.
089   * @param edgeAttributeProvider   for generating edge attributes. If null,
090   *                                edge attributes will not be written to the file.
091   */
092  public DOTExporter(
093    VertexNameProvider<V> vertexIDProvider,
094    VertexNameProvider<V> vertexLabelProvider,
095    EdgeNameProvider<E> edgeLabelProvider,
096    ComponentAttributeProvider<V> vertexAttributeProvider,
097    ComponentAttributeProvider<E> edgeAttributeProvider )
098    {
099    this.vertexIDProvider = vertexIDProvider;
100    this.vertexLabelProvider = vertexLabelProvider;
101    this.edgeLabelProvider = edgeLabelProvider;
102    this.vertexAttributeProvider = vertexAttributeProvider;
103    this.edgeAttributeProvider = edgeAttributeProvider;
104    }
105
106  /**
107   * Exports a graph into a plain text file in DOT format.
108   *
109   * @param writer the writer to which the graph to be exported
110   * @param g      the graph to be exported
111   */
112  public void export( Writer writer, Graph<V, E> g )
113    {
114    PrintWriter out = new PrintWriter( writer );
115    String indent = "  ";
116    String connector;
117
118    if( g instanceof DirectedGraph<?, ?> )
119      {
120      out.println( "digraph G {" );
121      connector = " -> ";
122      }
123    else
124      {
125      out.println( "graph G {" );
126      connector = " -- ";
127      }
128
129    for( V v : g.vertexSet() )
130      {
131      out.print( indent + getVertexID( v ) );
132
133      String labelName = null;
134      if( vertexLabelProvider != null )
135        {
136        labelName = vertexLabelProvider.getVertexName( v );
137        }
138      Map<String, String> attributes = null;
139      if( vertexAttributeProvider != null )
140        {
141        attributes = vertexAttributeProvider.getComponentAttributes( v );
142        }
143      renderAttributes( out, labelName, attributes );
144
145      out.println( ";" );
146      }
147
148    for( E e : g.edgeSet() )
149      {
150      String source = getVertexID( g.getEdgeSource( e ) );
151      String target = getVertexID( g.getEdgeTarget( e ) );
152
153      out.print( indent + source + connector + target );
154
155      String labelName = null;
156      if( edgeLabelProvider != null )
157        {
158        labelName = edgeLabelProvider.getEdgeName( e );
159        }
160      Map<String, String> attributes = null;
161      if( edgeAttributeProvider != null )
162        {
163        attributes = edgeAttributeProvider.getComponentAttributes( e );
164        }
165      renderAttributes( out, labelName, attributes );
166
167      out.println( ";" );
168      }
169
170    out.println( "}" );
171
172    out.flush();
173    }
174
175  private void renderAttributes(
176    PrintWriter out,
177    String labelName,
178    Map<String, String> attributes )
179    {
180    if( ( labelName == null ) && ( attributes == null ) )
181      {
182      return;
183      }
184    out.print( " [ " );
185    if( ( labelName == null ) && ( attributes != null ) )
186      {
187      labelName = attributes.get( "label" );
188      }
189    if( labelName != null )
190      {
191      out.print( "label=\"" + labelName + "\" " );
192      }
193    if( attributes != null )
194      {
195      for( Map.Entry<String, String> entry : attributes.entrySet() )
196        {
197        String name = entry.getKey();
198        if( name.equals( "label" ) )
199          {
200          // already handled by special case above
201          continue;
202          }
203        out.print( name + "=\"" + entry.getValue() + "\" " );
204        }
205      }
206    out.print( "]" );
207    }
208
209  /**
210   * Return a valid vertex ID (with respect to the .dot language definition as
211   * described in http://www.graphviz.org/doc/info/lang.html Quoted from above
212   * mentioned source: An ID is valid if it meets one of the following
213   * criteria:
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}
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  }