/*
* This file or a portion of this file is licensed under the terms of
* the Globus Toolkit Public License, found in file GTPL, or at
* http://www.globus.org/toolkit/download/license.html. This notice must
* appear in redistributions of this file, with or without modification.
*
* Redistributions of this Software, with or without modification, must
* reproduce the GTPL in: (1) the Software, or (2) the Documentation or
* some other similar material which is provided with the Software (if
* any).
*
* Copyright 1999-2004 University of Chicago and The University of
* Southern California. All rights reserved.
*/
package org.griphyn.vdl.util;
import java.util.TreeMap;
import java.util.Map;
import java.util.Iterator;
import java.io.*;
import edu.isi.pegasus.common.util.Separator;
import org.griphyn.vdl.dax.*;
import org.griphyn.vdl.classes.LFN;
/**
* Convert a dag structure into GraphViz dot format.
*
* @author Jens-S. Vöckler
* @author Yong Zhao
* @version $Revision$
*/
public class DAX2DOT
{
/**
* Separator for strings.
*/
public static final String SEPARATOR = "/";
/**
* Linefeed element for labels.
*/
public static final String LINEFEED = "\\n";
/**
* height in inches?
*/
private double m_height;
/**
* width in inches?
*/
private double m_width;
/**
* predicate to show the derivation (DV) name.
*/
private boolean m_showDV;
/**
* predicate to show the transformation (TR) name.
*/
private boolean m_showTR;
/**
* Maintains namespace to color mappings.
*/
private Map m_color;
/**
* Maintains the color cycle.
*/
private int m_index;
/**
* Map of default colors to cycle through for
* coloration of job nodes by TR namespace.
*/
private static final String c_color[] = {
"#FFAAFF", "#FFFFAA", "#FFAAAA"
};
/**
* Constructor
*/
public DAX2DOT()
{
m_height = 10;
m_width = 8;
m_showDV = false;
m_showTR = true;
m_color = new TreeMap();
m_index = 0;
}
/**
* Convenience constructor sets the size of the graph.
*
* @param h is the height in inches
* @param w is the width in inches
*/
public DAX2DOT(double h, double w)
{
m_height = h;
m_width = w;
m_showDV = false;
m_showTR = true;
m_color = new TreeMap();
m_index = 0;
}
/**
* Sets the size of the graph.
*
* @param h is the height in inches
* @param w is the width in inches
* @see #getHeight()
* @see #getWidth()
*/
public void setSize(double h, double w)
{
m_height = h;
m_width = w;
}
/**
* Determines the height of the graph.
* @return height in inches
* @see #setSize( double, double )
* @see #getWidth()
*/
public double getHeight()
{
return m_height;
}
/**
* Determines the width of the graph.
* @return width in inches
* @see #setSize( double, double )
* @see #getHeight()
*/
public double getWidth()
{
return m_width;
}
/**
* Determines, if DV identifiers are show.
*
* @return true, if the DV identifier is shown
* @see #setShowDV( boolean )
*/
public boolean getShowDV()
{
return m_showDV;
}
/**
* Sets the showing of derivation names.
*
* @param showDV is true to show derivation identifiers.
* @see #getShowDV()
*/
public void setShowDV( boolean showDV )
{
m_showDV = showDV;
}
/**
* Determines, if TR identifiers are show.
*
* @return true, if the TR identifier is shown
* @see #setShowTR( boolean )
*/
public boolean getShowTR()
{
return m_showTR;
}
/**
* Sets the showing of derivation names.
*
* @param showTR is true to show derivation identifiers.
* @see #getShowTR()
*/
public void setShowTR( boolean showTR )
{
m_showTR = showTR;
}
/**
* Generates GraphViz .dot format from the specified ADAG
* @param adag is the ADAG instance
* @return a string representing .dot format
*/
public String toDOT( ADAG adag )
throws IOException
{
// do not show files in the graph by default
StringWriter sw = new StringWriter();
toDOT(adag, sw, false);
return sw.toString();
}
/**
* Generates GraphViz .dot format from the specified ADAG
* @param adag is the ADAG instance
* @param showFiles if set to true, then display files in the graph
* @return a string representing .dot format
* @see #toDOT( ADAG, Writer, boolean )
* @see #toDOT( ADAG, Writer, boolean, String, String )
*/
public String toDOT(ADAG adag, boolean showFiles)
throws IOException
{
StringWriter sw = new StringWriter();
toDOT(adag, sw, showFiles);
return sw.toString();
}
/**
* Generates GraphViz .dot format from the specified ADAG
* @param adag is the ADAG instance
* @param writer is the target to output the dot specification
* @param showFiles if set to true, then display files in the graph
* @see #toDOT( ADAG, Writer, boolean, String, String )
*/
public void toDOT(ADAG adag, Writer writer, boolean showFiles)
throws IOException
{
toDOT(adag, writer, showFiles, null, null);
}
/**
* Prepares and prints the job node of the graph. The job's unique
* identifier assigned in the DAX is taken as the job's identifier,
* but the TR, ID, and DV are used as a label.
*
* @param w is the open file writer to print to
* @param j is a Job element.
* @param url is the job URL, which may be <code>null</code>.
* @return the identifier for the job to connect the graph.
*/
private String showJob( Writer w, Job j, String url )
throws IOException
{
StringBuffer label = new StringBuffer(48);
String id = j.getID();
String tr = Separator.combine(j.getNamespace(), j.getName(), j.getVersion());
label.append(id);
if ( m_showTR && tr != null )
label.append(LINEFEED).append("TR ").append(tr);
if ( m_showDV ) {
String dv = Separator.combine(j.getDVNamespace(), j.getDVName(), j.getDVVersion());
if ( dv != null ) label.append(LINEFEED).append("DV ").append(dv);
}
//
// Doug's wish: color by namespace
//
String color = null;
String ns = j.getNamespace(); // may be null!
if ( ns != null ) {
if ( m_color.containsKey(ns) ) {
// existing namespace, recycle color
color = (String) m_color.get(ns);
} else {
// insert new color for new namespace
color = c_color[m_index];
m_index = (m_index + 1) % c_color.length;
m_color.put( ns, color );
}
}
// write output for job node
w.write( " \"" );
w.write(id);
w.write( "\" [label=\"" );
w.write( label.toString() );
if ( url != null ) {
w.write( "\" URL=\"" );
w.write( url );
w.write( tr );
}
if ( color != null ) {
w.write( "\" color=\"" );
w.write( color );
}
w.write( "\"]\n" );
return id;
}
/**
* Prepares and prints the file node of the graph. The file's LFN
* will be its unique identifier, and its label.
*
* @param w is the open file writer to print to
* @param f is a Filename element.
* @param url is the file URL, which may be <code>null</code>.
* @return the identifier for the file to connect the graph.
*/
private String showFile( Writer w, Filename f, String url )
throws IOException
{
String lfn = f.getFilename();
// write output for filename node
w.write( " \"" );
w.write(lfn);
w.write( "\" [color=\"#88" );
w.write( ((f.getLink() & LFN.INPUT) > 0 ? "FF" : "AA" ) );
w.write( ((f.getLink() & LFN.OUTPUT) > 0 ? "FF" : "AA" ) );
if ( url != null ) {
w.write( "\" URL=\"" );
w.write( url );
w.write( lfn );
}
w.write( "\"]\n" );
return lfn;
}
/**
* Generates GraphViz .dot format from the specified ADAG, also generates
* the client side HTML map for nodes.
* @param adag is the ADAG instance
* @param writer is the target to output the dot specification
* @param showFiles if set to true, then display files in the graph
* @param jobURL is the base URL for jobs
* @param fileURL is the base URL for files
*/
public void toDOT(ADAG adag, Writer writer, boolean showFiles,
String jobURL, String fileURL )
throws IOException
{
this.m_index = 0;
writer.write("digraph DAG {\n");
writer.write(" size=\"" + m_width + "," + m_height +"\"\n");
writer.write(" ratio = fill\n");
if ( showFiles ) {
writer.write(" node[shape=parallelogram]\n");
for (Iterator i=adag.iterateFilename(); i.hasNext();) {
Filename fn = (Filename) i.next();
String lfn = showFile( writer, fn, fileURL );
}
writer.write(" node [shape=ellipse, color=orange, style=filled]\n");
for (Iterator i=adag.iterateJob(); i.hasNext();) {
Job job = (Job) i.next();
String jid = showJob(writer,job,jobURL);
for (Iterator j=job.iterateUses(); j.hasNext();) {
Filename fn = (Filename)j.next();
String lfn = fn.getFilename();
// this covers in, out, and io (two arrows)
if ( (fn.getLink() & LFN.INPUT) > 0 )
writer.write(" \"" + lfn + "\" -> \"" + jid + "\"\n");
if ( (fn.getLink() & LFN.OUTPUT) > 0 )
writer.write(" \"" + jid + "\" -> \"" + lfn + "\"\n");
}
}
} else {
writer.write(" node [shape=ellipse, color=orange, style=filled]\n");
for (Iterator i=adag.iterateJob(); i.hasNext();) {
Job job = (Job) i.next();
String jid = showJob(writer,job,jobURL);
}
for (Iterator c=adag.iterateChild(); c.hasNext();) {
Child chld = (Child) c.next();
String ch = chld.getChild();
Job cjob = adag.getJob(ch);
String cid = cjob.getID();
for (Iterator p=chld.iterateParent(); p.hasNext();) {
String pr = (String) p.next();
Job pjob = adag.getJob(pr);
String pid = pjob.getID();
writer.write(" \"" + pid + "\" -> \"" + cid + "\"\n");
}
}
}
writer.write("}\n");
writer.flush();
}
/**
* Simple test
*/
public static void main(String[] args)
throws IOException
{
ADAG adag = new ADAG();
Job A = new Job("ns1","trA",null,"ID000001");
Job B = new Job("ns2","trB",null,"ID000002");
Job C = new Job("ns3","trC",null,"ID000003");
Job D = new Job(null,"trD",null,"ID000004");
A.setDV("ns2","dvA",null);
B.setDV("ns2","dvB",null);
C.setDV("ns3","dvC",null);
D.setDV("ns3","dvD",null);
A.addUses( new Filename("f.1",LFN.INPUT) );
adag.addFilename("f.1",true,"true",false,LFN.XFER_MANDATORY);
A.addUses( new Filename("f.2",LFN.OUTPUT) );
adag.addFilename("f.2",false,"true",false,LFN.XFER_MANDATORY);
B.addUses( new Filename("f.2",LFN.INPUT) );
adag.addFilename("f.2",true,"true",false,LFN.XFER_MANDATORY);
B.addUses( new Filename("f.3",LFN.OUTPUT) );
adag.addFilename("f.3",false,"true",false,LFN.XFER_MANDATORY);
C.addUses( new Filename("f.2",LFN.INPUT) );
adag.addFilename("f.2",true,"true",false,LFN.XFER_MANDATORY);
C.addUses( new Filename("f.4",LFN.OUTPUT) );
adag.addFilename("f.4",false,"true",false,LFN.XFER_MANDATORY);
D.addUses( new Filename("f.3",LFN.INPUT) );
adag.addFilename("f.3",true,"true",false,LFN.XFER_MANDATORY);
D.addUses( new Filename("f.4",LFN.INPUT) );
adag.addFilename("f.4",true,"true",false,LFN.XFER_MANDATORY);
D.addUses( new Filename("f.5",LFN.OUTPUT) );
adag.addFilename("f.5",false,"true",false,LFN.XFER_MANDATORY);
adag.addJob(A);
adag.addJob(B);
adag.addJob(C);
adag.addJob(D);
adag.addChild("ID000003","ID000001");
adag.addChild("ID000003","ID000002");
adag.addChild("ID000004","ID000003");
DAX2DOT d2d = new DAX2DOT(5, 5);
d2d.setShowDV(true);
String dot = d2d.toDOT(adag,true);
System.out.println(dot);
}
}