monotone-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Monotone-devel] Java code to convert monotone log output to a GXL graph


From: Joel Crisp
Subject: [Monotone-devel] Java code to convert monotone log output to a GXL graph format
Date: Sat, 26 Mar 2005 17:47:00 +0000
User-agent: Mozilla Thunderbird 1.0 (Windows/20041206)

Hi Guys

Whilst monotone-viz is a lovely app, it doesn't appear to be available for windows. After trying and failing to get it to compile under cygwin I knocked up this little java class which reads the output from "monotone log" and converts it to a GXL graph.

If you have Graphviz you can then run gxl2dot to get the dot format which Graphviz can layout.

On the advice of some peeps on the IRC channel I've attached the file to this message as it is only a single small class.

NOTE: You need the GXL java API from http://www.gupro.de/GXL/index.html, JDK 1.5 and (optionally) the Graphviz toolkit from ATT

Hope this is useful

Joel
//package uk.co.srs.monotree;

import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Map;
import java.util.HashMap;
import net.sourceforge.gxl.GXLDocument;
import net.sourceforge.gxl.GXLGraph;
import net.sourceforge.gxl.GXLNode;
import net.sourceforge.gxl.GXLEdge;
import net.sourceforge.gxl.GXLString;
import net.sourceforge.gxl.GXLSet;
import net.sourceforge.gxl.GXLAttr;
import net.sourceforge.gxl.GXLInt;
import net.sourceforge.gxl.GXLTup;

/**
 * Simple filter to convert a monotone log output into a GXL graph
 *
 * This file is licensed under the artistic license - do with it what you will
 *
 * NOTE: This file requires the GXL Java API from 
http://www.gupro.de/GXL/index.html
 * NOTE: Optionally you can get Graphviz from ATT and use the gxl2dot program 
to generate a dot file (input to graphviz)
 * NOTE: Required JDK 1.5 as it uses generics
 *
 * @author Joel Crisp
 */
public class Log2Gxl {
    /**
     * Main class. Invoke via monotone --db=<database>.db log <id> | java 
-classpath gxl.jar:. uk.co.srs.monotree.Log2Gxl | gxl2dot >log.dot
     *
     * @param argv command line arguments - ignored for now
     */
    public static void main(String argv[]) throws 
IOException,IllegalStateException { 
        new Log2Gxl().Start(argv);
    }

    /**
     * Read a line from the std input stream and verify it starts with the 
specified prefix
     * 
     * @param prefix the prefix to check as a simple string
     * @return the line
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the prefix doesn't match the input line
     */
    private String readLine(String prefix) throws 
IOException,IllegalStateException {
        String line=source.readLine();
        if(line==null) throw new IOException(source.getLineNumber()+": 
Unexpected end of input");
        if(!line.startsWith(prefix)) throw new 
IllegalStateException(source.getLineNumber()+": Expected ["+prefix+"], got 
["+line+"]");
        return line;
    }

    /**
     * Parse the header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected
     */
    private void parseHeader() throws IOException,IllegalStateException {
        parseRevision();
        parseAncestors();
        parseAuthors();
        String line=lookahead();
        if(line.startsWith("Date:")) parseDates();
        parseBranches();
        line=lookahead();
        if(line.startsWith("Tag:")) parseTags();

    }

    /**
     * Parse the author(s) from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected
     */    
    private void parseAuthors() throws IOException,IllegalStateException {
        parseAuthor();
        String line=lookahead();
        if(line.startsWith("Author:")) parseAuthors();  
    }
    
    /**
     * Parse the tags(s) from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected
     */    
    private void parseTags() throws IOException,IllegalStateException {
        parseTag();
        String line=lookahead();
        if(line.startsWith("Tag:")) parseTags();        
    }

    /**
     * Parse the date(s) from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected
     */    
    private void parseDates() throws IOException,IllegalStateException {
        parseDate();
        String line=lookahead();
        if(line.startsWith("Date:")) parseDates();      
    }

    /**
     * Parse the branch(s) from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected
     */    
    private void parseBranches() throws IOException,IllegalStateException {
        parseBranch();
        String line=lookahead();
        if(line.startsWith("Branch:")) parseBranches();
    }

    /**
     * Parse a date line from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected
     */    
    private void parseDate() throws IOException,IllegalStateException {
        String line=readLine("Date:");
        currentNode.setAttr("Date",new 
GXLString(line.substring("Date:".length()+1)));
    }

    /**
     * Parse a revision line from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected
     */    
    private void parseRevision() throws IOException,IllegalStateException {
        String line=readLine("Revision:");
        
        revision=line.substring("Revision:".length()+1);            
        currentNode=nodes.get(revision);
        if(currentNode==null) {
            currentNode=createDefaultNode(revision);
        }
    }

    /**
     * Read a line marking the current place in the stream first and restoring 
it after reading the line
     * This method only supports lines up to 16384 characters in length. For 
monotone logs this should not be a problem
     * @throws IOException if there is a read error on the input stream 
     * @return the line which was read or null if the end of the input stream 
is reached
     */    
    private String lookahead() throws IOException {
        source.mark(16384);
        String line=source.readLine();
        source.reset();      
        return line;
    }
    
    /**
     * Parse the ancestors(s) from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected
     */    
    private void parseAncestors() throws IOException,IllegalStateException {
        parseAncestor();
        String line=lookahead();
        if(line.startsWith("Ancestor:")) parseAncestors();
    }

    /**
     * Create a GXL node with a few default attributes
     *
     * @param id the string identifier for the node (should be the revision 
hash id)
     * @return a new GXLNode with the specified id and some useful default 
attributes
     */
    private GXLNode createDefaultNode(String id) {
        GXLNode node=new GXLNode(id);
        // GXL Schema support. Commented out until I understand it a bit better
        // node.setType("MonotoneRevisionGraphSchema.gxl#RevisionNode");

        // These attributes are used by JGraphPad and DOT - node label
        node.setAttr("Label",new GXLString(id));

        // These attributes are used by JGraphPad - node border color
        node.setAttr("BorderColor",new GXLTup());
        GXLTup borderColor=(GXLTup)node.getAttr("BorderColor").getValue();
        borderColor.add(new GXLInt(0));
        borderColor.add(new GXLInt(0));
        borderColor.add(new GXLInt(0));

        // These attributes are used by JGraphPad - initial node location and 
size
        node.setAttr("Bounds",new GXLTup());
        GXLTup bounds=(GXLTup)node.getAttr("Bounds").getValue();
        bounds.add(new GXLInt(20));
        bounds.add(new GXLInt(20+nodes.size()*60));
        bounds.add(new GXLInt(20));
        bounds.add(new GXLInt(20));

        graph.add(node);
        nodes.put(id,node);
        return node;
    }

    /**
     * Parse an ancestor line from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void parseAncestor() throws IOException,IllegalStateException {
        String line=readLine("Ancestor:");
        String ancestor=line.substring("Ancestor:".length()+1);
        if(ancestor.length()!=0) {
            GXLNode ancestorNode=nodes.get(ancestor);
            if(ancestorNode==null) {
                ancestorNode=createDefaultNode(ancestor);
            }
            GXLEdge edge=new GXLEdge(ancestorNode,currentNode);
            // GXL Schema support. Commented out until I understand it a bit 
better
            //edge.setType("MonotoneRevisionGraphSchema.gxl#AncestorEdge");
            edge.setDirected(true);
            graph.add(edge);
        }
    }

    /**
     * Parse an author line from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void parseAuthor() throws IOException,IllegalStateException {
        String line=readLine("Author:");
        String author=line.substring("Author:".length()+1);
        if(author.length()!=0) {
            currentNode.setAttr("Author",new GXLString(author));
        }
    }

    /**
     * Parse a branch line from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void parseBranch() throws IOException,IllegalStateException {
        String line=readLine("Branch:");
        String branch=line.substring("Branch:".length()+1);
        if(branch.length()!=0) {
            GXLAttr branches=currentNode.getAttr("Branches");
            if(branches==null) {
                currentNode.setAttr("Branches",new GXLSet());
                branches=currentNode.getAttr("Branches");
            }
            GXLSet value=(GXLSet)branches.getValue();
            value.add(new GXLString(branch));
        } 
    }

    /**
     * Parse a tag line from a header from a log entry
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void parseTag() throws IOException,IllegalStateException {
        String line=readLine("Tag:");
        String tag=line.substring("Tag:".length()+1);
        if(tag.length()!=0) {
            GXLAttr tages=currentNode.getAttr("Tags");
            if(tages==null) {
                currentNode.setAttr("Tags",new GXLSet());
                tages=currentNode.getAttr("Tags");
            }
            GXLSet value=(GXLSet)tages.getValue();
            value.add(new GXLString(tag));
        } 
    }

    /**
     * Parse the deleted files section of a log entry from a monotone log output
     * Optionally pass the list of files to GXL (Don't do this is you're using 
dot as dxl2dot chokes on it)
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void parseDeletedFiles() throws IOException,IllegalStateException {
        String files=readFileBlock("Deleted files:");
        if(includeFiles) currentNode.setAttr("Deleted files",new 
GXLString(files));
    }

    /**
     * Parse the added files section of a log entry from a monotone log output
     * Optionally pass the list of files to GXL (Don't do this is you're using 
dot as dxl2dot chokes on it)
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void parseAddedFiles() throws IOException,IllegalStateException {
        String files=readFileBlock("Added files:");
        if(includeFiles) currentNode.setAttr("Added files",new 
GXLString(files));
    }

    /**
     * Parse a block of file names from one of the files sections of a log 
entry from a monotone log output
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     */
    private String readFileBlock(String header) throws IOException {
        String line=readLine(header);
        StringBuffer files=new StringBuffer();
        boolean parsing=true;
        while(parsing) {
            line=lookahead();
            if(!line.startsWith("        ")) { 
                parsing=false;
            }
            else {
                line=source.readLine();
                files.append(" ");
                files.append(line.substring(10));
            }
        }
        return files.toString();
    }

    /**
     * Parse the modified files section of a log entry from a monotone log 
output
     * Optionally pass the list of files to GXL (Don't do this is you're using 
dot as dxl2dot chokes on it)
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void parseModifiedFiles() throws IOException,IllegalStateException {
        String files=readFileBlock("Modified files:");
        if(includeFiles) currentNode.setAttr("Modified files",new 
GXLString(files));
    }

    /**
     * Parse the change log section of a log entry from a monotone log output
     * Currently this is discarded
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void parseChangeLog() throws IOException,IllegalStateException {
        String line=source.readLine();
        if(line.length()>0) throw new IOException(source.getLineNumber()+": 
Unexpected data ["+line+"]");
        line=readLine("ChangeLog:");
        boolean parsing=true;
        while(parsing) {
            line=lookahead();
            if(line==null || line.startsWith("----")) {
                parsing=false;
            }
            else {
                line=source.readLine();
                // TODO: record changelog 
            }
        }
    }

    /**
     * Parse the various files section of a log entry from a monotone log output
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void parseFiles() throws IOException,IllegalStateException {
        String line=source.readLine();
        if(line.length()>0) throw new IOException(source.getLineNumber()+": 
Unexpected data ["+line+"]");
        line=lookahead();
        if(line.startsWith("Deleted files:")) parseDeletedFiles();
        line=lookahead();
        if(line.startsWith("Added files:")) parseAddedFiles();
        line=lookahead(); 
        if(line.startsWith("Modified files:")) parseModifiedFiles();
    }

    /**
     * Log output line source which is a wrapper on System.in
     */
    private LineNumberReader source;

    /**
     * The GXL Graph which is being built
     */
    private GXLGraph graph;

    /**
     * A cache of nodes which we have already seen
     */
    private Map<String,GXLNode> nodes;
    
    /**
     * The current revision ID for which the log is being parsed
     */
    private String revision=null;

    /**
     * The GXLNode corresponding to revision above
     */
    private GXLNode currentNode;

    /**
     * This flag determines if the revision file lists are passed to the GXL 
node as attributes
     * Set this to false if using DOT as gxl2dot chokes on it
     */
    private boolean includeFiles=false;

    /**
     * Start parsing the output of a monotone log command and output the 
corresponding GXL graph
     *
     * @param argv the command line arguments - ignored for now
     * @throws IOException if there is a read error on the input stream or the 
input stream runs dry
     * @throws IllegalStateException if the header lines aren't as expected     
     */
    private void Start(String argv[]) throws IOException,IllegalStateException {
        nodes=new HashMap<String,GXLNode>();

        GXLDocument gxlDocument = new GXLDocument();
        graph = new GXLGraph("graph1");
        graph.setAttribute("edgemode","directed");
        gxlDocument.getDocumentElement().add(graph);
        source=new LineNumberReader(new InputStreamReader(System.in));
        boolean parsing=true;
        while(parsing) {
            String line=source.readLine();
            if(line==null) { 
                parsing=false;
                break;
            }
            
if(!line.equals("-----------------------------------------------------------------"))
 {
                throw new IOException(source.getLineNumber()+": Input 
["+line+"] doesn't look like output of monotone log");
            }
            parseHeader();
            parseFiles();
            parseChangeLog();
        }
        
        // Write the graph to std out
        gxlDocument.write(System.out);
    }
}

reply via email to

[Prev in Thread] Current Thread [Next in Thread]