/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package org.visage.tools.comp;
import org.visage.tools.tree.VisageScript;
import com.sun.tools.mjavac.code.Symbol;
import com.sun.tools.mjavac.util.Context;
import com.sun.tools.mjavac.util.Options;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.HashSet;
import java.util.Set;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.helpers.AttributesImpl;
/**
* This class writes dependency graph in GXL (Graph eXchange Language) format.
* Please refer to http://www.gupro.de/GXL/ for details about this file format.
* For each compilation unit, a separate .gxl file is created in the dump dir
* configured by -XDdumpdepgraph option.
*
* These GXL files can be viewed graph visualization tools such as "Graphviz"
* (http://www.graphviz.org). To view a .gxl file with Graphviz, we have to use
* "gxl2dot" tool to convert it to .dot file.
*
* @author A. Sundararajan
*/
public class DependencyGraphWriter {
protected static final Context.Key<DependencyGraphWriter> dependencyGraphWriterKey =
new Context.Key<DependencyGraphWriter>();
public static final String GXL_NS = "http://www.gupro.de/GXL/gxl-1.0.dtd";
// GXL element names
public static final String GXL = "gxl";
public static final String GRAPH = "graph";
public static final String NODE = "node";
public static final String EDGE = "edge";
public static final String ATTR = "attr";
public static final String STRING = "string";
public static final String BOOL = "bool";
// GXL attribute names
public static final String ID = "id";
public static final String FROM = "from";
public static final String TO = "to";
public static final String EDGEMODE = "edgemode";
public static final String NAME = "name";
public static final String DIRECTED = "directed";
// attribute types used
public static final String ATTR_ID = "ID";
public static final String ATTR_IDREF = "IDREF";
public static final String ATTR_CDATA = "CDATA";
public static final String ATTR_NMTOKEN = "NMTOKEN";
// DOT "attr" names
public static final String COMMENT = "comment";
public static final String COLOR = "color";
// Values for edge "attr" s
public static final String INTEROBJECT_EDGE_COLOR = "red";
public static final String INTRAOBJECT_EDGE_COLOR = "blue";
public static final String INTEROBJECT = "interobj";
public static final String INTRAOBJECT = "intraobj";
// command line option to enable this feature
public static final String DUMPDEPGRAPH_OPTION = "dumpdepgraph";
public static DependencyGraphWriter instance(Context context) {
DependencyGraphWriter instance = context.get(dependencyGraphWriterKey);
if (instance == null) {
/*
* If you want to dump dependency graph into file, specify the
* directory using -XDdumpdepgraph command line option
*/
Options options = Options.instance(context);
String dir = options.get(DUMPDEPGRAPH_OPTION);
if (dir != null) {
instance = new DependencyGraphWriter(context, dir);
}
}
return instance;
}
public void start(VisageScript tree) {
this.allSyms = new HashSet<Symbol>();
this.attrs = new AttributesImpl();
this.fileName = tree.getSourceFile().getName();
initContentHandler();
startDocument();
startElement(GXL);
attrs.clear();
attrs.addAttribute("", ID, ID, ATTR_ID, fileName);
attrs.addAttribute("", EDGEMODE, EDGEMODE, ATTR_CDATA, DIRECTED);
startElement(GRAPH, attrs);
}
public void writeDependency(Symbol binder, Symbol bindee) {
writeDependency(binder, bindee, true);
}
public void writeInterObjectDependency(Symbol binder, Symbol bindee) {
writeDependency(binder, bindee, false);
}
public void end() {
// write node for each symbol seen
for (Symbol sym : allSyms) {
attrs.clear();
attrs.addAttribute("", ID, ID, ATTR_ID, id(sym));
startElement(NODE, attrs);
attrs.clear();
attrs.addAttribute("", NAME, NAME, ATTR_NMTOKEN, COMMENT);
// output data type of the symbol as a comment
startElement(ATTR, attrs);
startElement(STRING);
emitData("type: " + sym.type.toString());
endElement(STRING);
endElement(ATTR);
endElement(NODE);
}
// end the graph
endElement(GRAPH);
endElement(GXL);
endDocument();
// clear all state
allSyms.clear();
allSyms = null;
attrs.clear();
attrs = null;
fileName = null;
handler = null;
}
// -- Internals only below this point
// directory where GXL documents are dumped
private String dumpDir;
// all dependency symbols for current class
private Set<Symbol> allSyms;
// repeatedly cleared and used attributes object
private AttributesImpl attrs;
// current GXL output file name
private String fileName;
// SAX sink to output SAX events
private ContentHandler handler;
private DependencyGraphWriter(Context context, String dumpDir) {
context.put(dependencyGraphWriterKey, this);
this.dumpDir = dumpDir;
}
private void initContentHandler() {
try {
new File(dumpDir).mkdirs();
FileOutputStream fis = new FileOutputStream(new File(dumpDir, fileName + ".gxl"));
BufferedOutputStream bos = new BufferedOutputStream(fis);
StreamResult streamResult = new StreamResult(bos);
SAXTransformerFactory fac = (SAXTransformerFactory) TransformerFactory.newInstance();
TransformerHandler thandler = fac.newTransformerHandler();
Transformer transformer = thandler.getTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
thandler.setResult(streamResult);
this.handler = thandler;
} catch (Exception exp) {
throw wrapException(exp);
}
}
private void writeDependency(Symbol binder, Symbol bindee, boolean intraObject) {
// put these symbols in set of all symbols
allSyms.add(binder);
allSyms.add(bindee);
// write an edge from binder to bindee node
attrs.clear();
attrs.addAttribute("", FROM, FROM, ATTR_IDREF, id(binder));
attrs.addAttribute("", TO, TO, ATTR_IDREF, id(bindee));
startElement(EDGE, attrs);
attrs.clear();
attrs.addAttribute("", NAME, NAME, ATTR_NMTOKEN, COMMENT);
startElement(ATTR, attrs);
startElement(BOOL);
emitData(intraObject? INTRAOBJECT : INTEROBJECT);
endElement(BOOL);
endElement(ATTR);
attrs.clear();
attrs.addAttribute("", NAME, NAME, ATTR_NMTOKEN, COLOR);
startElement(ATTR, attrs);
startElement(STRING);
emitData(intraObject? INTRAOBJECT_EDGE_COLOR : INTEROBJECT_EDGE_COLOR);
endElement(STRING);
endElement(ATTR);
endElement(EDGE);
}
private String id(Symbol sym) {
StringBuilder buf = new StringBuilder();
buf.append(sym.owner.getQualifiedName());
if (sym.isStatic()) {
buf.append(VisageDefs.scriptClassSuffix);
}
buf.append('.');
buf.append(sym.name);
return buf.toString();
}
private void startDocument() {
try {
handler.startDocument();
} catch (Exception exp) {
throw wrapException(exp);
}
}
private void endDocument() {
try {
handler.endDocument();
} catch (Exception exp) {
throw wrapException(exp);
}
}
private void startElement(String element) {
attrs.clear();
startElement(element, attrs);
}
private void startElement(String element, Attributes attrs) {
try {
handler.startElement(GXL_NS, element, element, attrs);
} catch (Exception exp) {
throw wrapException(exp);
}
}
private void endElement(String element) {
try {
handler.endElement(GXL_NS, element, element);
} catch (Exception exp) {
throw wrapException(exp);
}
}
private void emitData(String data) {
if (data == null) {
return;
}
char[] chars = data.toCharArray();
try {
handler.characters(chars, 0, chars.length);
} catch (Exception exp) {
throw wrapException(exp);
}
}
private RuntimeException wrapException(Exception exp) {
if (exp instanceof RuntimeException) {
return (RuntimeException) exp;
} else {
return new RuntimeException(exp);
}
}
}