/*
* 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.runtime;
import org.visage.runtime.sequence.Sequence;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
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.
*
* 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 final class DependentsGraphWriter {
/**
* Writes given object's dependent network to the file in GXL format.
*
* @param obj The VisageObject whose dependents graph is serialized
* @param fileName The name of the file to which the graph is serialized
*/
public static void write(VisageObject obj, String fileName) {
write(obj, fileName, false);
}
/**
* Writes given object's dependent network to the file in GXL format.
*
* @param obj The VisageObject whose dependents graph is serialized
* @param fileName The name of the file to which the graph is serialized
* @param followFields does the graph includes all transitively reachable VisageObjects?
*/
public static void write(VisageObject obj, String fileName, boolean followFields) {
write(obj, new File(fileName), followFields);
}
/**
* Writes given object's dependent network to the file in GXL format.
*
* @param obj The VisageObject whose dependents graph is serialized
* @param file The file to which the graph is serialized
*/
public static void write(VisageObject obj, File file) {
write(obj, file, false);
}
/**
* Writes given object's dependent network to the file in GXL format.
*
* @param obj The VisageObject whose dependents graph is serialized
* @param file The file to which the graph is serialized
* @param followFields does the graph includes all transitively reachable VisageObjects?
*/
public static void write(VisageObject obj, File file, boolean followFields) {
DependentsGraphWriter depWriter = new DependentsGraphWriter(file, followFields);
depWriter.start(file.getName());
depWriter.writeDependencies(obj);
depWriter.end();
}
// -- Internals only below this point
private static final String GXL_NS = "http://www.gupro.de/GXL/gxl-1.0.dtd";
// GXL element names
private static final String GXL = "gxl";
private static final String GRAPH = "graph";
private static final String NODE = "node";
private static final String EDGE = "edge";
private static final String ATTR = "attr";
private static final String STRING = "string";
private static final String BOOL = "bool";
// GXL attribute names
private static final String ID = "id";
private static final String FROM = "from";
private static final String TO = "to";
private static final String EDGEMODE = "edgemode";
private static final String NAME = "name";
private static final String DIRECTED = "directed";
// attribute types used
private static final String ATTR_ID = "ID";
private static final String ATTR_IDREF = "IDREF";
private static final String ATTR_CDATA = "CDATA";
private static final String ATTR_NMTOKEN = "NMTOKEN";
// DOT "attr" names
private static final String COLOR = "color";
// Values for edge "attr" s
private static final String INTEROBJECT_EDGE_COLOR = "red";
// objects we have seen so far
private Map<VisageObject, VisageObject> allObjects;
// repeatedly cleared and used attributes object
private AttributesImpl attrs;
// SAX sink to output SAX events
private ContentHandler handler;
/*
* Chase variables in every VisageObject or not - by default, the graph
* includes only transitive closure of all dependents of the given object.
* If this flag is true, then graph includes all transitively reachable
* VisageObject type objects as well. (much bigger graph!)
*/
private boolean followFields;
private DependentsGraphWriter(File file, boolean followFields) {
this.allObjects = new IdentityHashMap<VisageObject, VisageObject>();
this.attrs = new AttributesImpl();
this.handler = makeContentHandler(file);
this.followFields = followFields;
}
private static ContentHandler makeContentHandler(File file) {
try {
FileOutputStream fis = new FileOutputStream(file);
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);
return thandler;
} catch (Exception exp) {
throw wrapException(exp);
}
}
private void start(String graphId) {
startDocument();
startElement(GXL);
attrs.clear();
attrs.addAttribute("", ID, ID, ATTR_ID, graphId);
attrs.addAttribute("", EDGEMODE, EDGEMODE, ATTR_CDATA, DIRECTED);
startElement(GRAPH, attrs);
}
private void end() {
// write node for each symbol seen
for (VisageObject obj : allObjects.keySet()) {
attrs.clear();
attrs.addAttribute("", ID, ID, ATTR_ID, id(obj));
startElement(NODE, attrs);
endElement(NODE);
}
// end the graph
endElement(GRAPH);
endElement(GXL);
endDocument();
allObjects.clear();
attrs.clear();
allObjects = null;
attrs = null;
handler = null;
}
private void writeDependencies(VisageObject bindee) {
if (allObjects.containsKey(bindee)) {
return;
}
allObjects.put(bindee, bindee);
List<VisageObject> dependents = DependentsManager.getDependents(bindee);
for (VisageObject binder : dependents) {
// write dependence b/w binder and bindee
writeDependency(binder, bindee);
// write the network of the dependee now
writeDependencies(binder);
}
if (followFields) {
final int count = bindee.count$();
for (int idx = 0; idx < count; idx++) {
Object fieldValue = bindee.get$(idx);
if (fieldValue instanceof VisageObject) {
writeDependencies((VisageObject) fieldValue);
} else if (fieldValue instanceof Sequence) {
Sequence seq = (Sequence)fieldValue;
if (seq.getElementType() == TypeInfo.Object) {
for (Object elem : seq) {
if (elem instanceof VisageObject) {
writeDependencies((VisageObject)elem);
}
}
}
}
}
}
}
private void writeDependency(VisageObject binder, VisageObject 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, COLOR);
startElement(ATTR, attrs);
startElement(STRING);
emitData(INTEROBJECT_EDGE_COLOR);
endElement(STRING);
endElement(ATTR);
endElement(EDGE);
}
private String id(VisageObject obj) {
StringBuilder buf = new StringBuilder();
buf.append(obj.getClass().getName());
buf.append('@');
buf.append(Integer.toHexString(System.identityHashCode(obj)));
return buf.toString();
}
// XML helpers below
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);
}
}
// generic exception wrapper
private static RuntimeException wrapException(Exception exp) {
if (exp instanceof RuntimeException) {
return (RuntimeException) exp;
} else {
return new RuntimeException(exp);
}
}
}