blob: 2775a57ed041337b08efffc2ee4eb819a500b545 [file] [log] [blame]
package aQute.bnd.make.calltree;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import aQute.lib.osgi.*;
/**
* Create an XML call tree of a set of classes. The structure of the XML is:
*
* <pre>
* calltree ::= &lt;using&gt; &lt;usedby&gt;
* using ::= &lt;method&gt; *
* usedby ::= &lt;method&gt; *
* method ::= &lt;ref&gt;
* </pre>
*
* The <code>using</code> element contains methods in the set of classes and
* their references. The <code>usedby</code> element contains the used methods
* and their references to the set of classes. The <code>ref</code> element
* contains the class, the method name, the descriptor, and a pretty print
* version of the method.
*
* The XML does not contain an XML processor instruction to make it easier to
* include in other XML. The encoding is always UTF-8.
*
* This class can be used as a resource, just add it to a JAR and the data is
* generated when the resource is written (saving time when the JAR is up to
* date and does not have to be generated). However, the actual write method is
* a static method and can be called as well:
* {@link #writeCalltree(PrintWriter, Collection)}.
*/
public class CalltreeResource extends WriteResource {
Collection<Clazz> classes;
/**
* Create a resource for inclusion that will print a call tree.
*
* @param values the classes for which the call tree is generated.
*/
public CalltreeResource(Collection<Clazz> values) {
this.classes = values;
System.out.println(values);
}
/**
* We set the last modified to 0 so this resource does not force
* a new JAR if all other resources are up to date.
*/
public long lastModified() {
return 0;
}
/**
* The write method is called to write the resource. We just call the static
* method.
*/
public void write(OutputStream out) throws Exception {
OutputStreamWriter osw = new OutputStreamWriter(out, Constants.DEFAULT_CHARSET);
PrintWriter pw = new PrintWriter(osw);
try {
writeCalltree(pw, classes);
} finally {
pw.flush();
}
}
/**
* Print the call tree in XML.
*
* @param out The output writer
* @param classes The set of classes
* @throws IOException Any errors
*/
public static void writeCalltree(PrintWriter out, Collection<Clazz> classes)
throws Exception {
final Map<Clazz.MethodDef, Set<Clazz.MethodDef>> using = new TreeMap<Clazz.MethodDef, Set<Clazz.MethodDef>>();
final Map<Clazz.MethodDef, Set<Clazz.MethodDef>> usedby = new TreeMap<Clazz.MethodDef, Set<Clazz.MethodDef>>();
ClassDataCollector cd = new ClassDataCollector() {
Clazz.MethodDef source;
// Before a method is parsed
public void method(Clazz.MethodDef source) {
this.source = source;
xref(using, source, null);
xref(usedby, source, null);
}
// For any reference in the previous method.
public void reference(Clazz.MethodDef reference) {
xref(using, source, reference);
xref(usedby, reference, source);
}
};
for (Clazz clazz : classes) {
clazz.parseClassFileWithCollector(cd);
}
out.println("<calltree>");
xref(out, "using", using);
xref(out, "usedby", usedby);
out.println("</calltree>");
}
/*
* Add a new reference
*/
private static void xref(
Map<Clazz.MethodDef, Set<Clazz.MethodDef>> references,
Clazz.MethodDef source, Clazz.MethodDef reference) {
Set<Clazz.MethodDef> set = references.get(source);
if (set == null)
references.put(source, set=new TreeSet<Clazz.MethodDef>());
if ( reference != null)
set.add(reference);
}
/*
* Print out either using or usedby sets
*/
private static void xref(PrintWriter out, String group,
Map<Clazz.MethodDef, Set<Clazz.MethodDef>> references) {
out.println(" <" + group + ">");
for (Map.Entry<Clazz.MethodDef, Set<Clazz.MethodDef>> entry : references
.entrySet()) {
Clazz.MethodDef source = entry.getKey();
Set<Clazz.MethodDef> refs = entry.getValue();
method(out, "method", source, ">");
for (Clazz.MethodDef ref : refs) {
method(out, "ref", ref, "/>");
}
out.println(" </method>");
}
out.println(" </" + group + ">");
}
/*
* Print out a method.
*/
private static void method(PrintWriter out, String element,
Clazz.MethodDef source, String closeElement) {
out.println(" <" + element + " class='" + source.clazz + "'"
+ getAccess(source.access) +
( source.isConstructor() ? "" : " name='" + source.name + "'") + " descriptor='" + source.descriptor + "' pretty='"
+ source.getPretty() + "'" + closeElement);
}
private static String getAccess(int access) {
StringBuilder sb = new StringBuilder();
if ( Modifier.isPublic(access) )
sb.append(" public='true'");
if ( Modifier.isStatic(access) )
sb.append(" static='true'");
if ( Modifier.isProtected(access) )
sb.append(" protected='true'");
if ( Modifier.isInterface(access) )
sb.append(" interface='true'");
return sb.toString();
}
}