blob: 2775a57ed041337b08efffc2ee4eb819a500b545 [file] [log] [blame]
Stuart McCulloch26e7a5a2011-10-17 10:31:43 +00001package aQute.bnd.make.calltree;
2
3import java.io.*;
4import java.lang.reflect.*;
5import java.util.*;
6
7import aQute.lib.osgi.*;
8
9/**
10 * Create an XML call tree of a set of classes. The structure of the XML is:
11 *
12 * <pre>
13 * calltree ::= &lt;using&gt; &lt;usedby&gt;
14 * using ::= &lt;method&gt; *
15 * usedby ::= &lt;method&gt; *
16 * method ::= &lt;ref&gt;
17 * </pre>
18 *
19 * The <code>using</code> element contains methods in the set of classes and
20 * their references. The <code>usedby</code> element contains the used methods
21 * and their references to the set of classes. The <code>ref</code> element
22 * contains the class, the method name, the descriptor, and a pretty print
23 * version of the method.
24 *
25 * The XML does not contain an XML processor instruction to make it easier to
26 * include in other XML. The encoding is always UTF-8.
27 *
28 * This class can be used as a resource, just add it to a JAR and the data is
29 * generated when the resource is written (saving time when the JAR is up to
30 * date and does not have to be generated). However, the actual write method is
31 * a static method and can be called as well:
32 * {@link #writeCalltree(PrintWriter, Collection)}.
33 */
34public class CalltreeResource extends WriteResource {
35 Collection<Clazz> classes;
36
37 /**
38 * Create a resource for inclusion that will print a call tree.
39 *
40 * @param values the classes for which the call tree is generated.
41 */
42 public CalltreeResource(Collection<Clazz> values) {
43 this.classes = values;
44 System.out.println(values);
45 }
46
47 /**
48 * We set the last modified to 0 so this resource does not force
49 * a new JAR if all other resources are up to date.
50 */
51 public long lastModified() {
52 return 0;
53 }
54
55 /**
56 * The write method is called to write the resource. We just call the static
57 * method.
58 */
59 public void write(OutputStream out) throws Exception {
60 OutputStreamWriter osw = new OutputStreamWriter(out, Constants.DEFAULT_CHARSET);
61 PrintWriter pw = new PrintWriter(osw);
62 try {
63 writeCalltree(pw, classes);
64 } finally {
65 pw.flush();
66 }
67 }
68
69 /**
70 * Print the call tree in XML.
71 *
72 * @param out The output writer
73 * @param classes The set of classes
74 * @throws IOException Any errors
75 */
76 public static void writeCalltree(PrintWriter out, Collection<Clazz> classes)
77 throws Exception {
78
79 final Map<Clazz.MethodDef, Set<Clazz.MethodDef>> using = new TreeMap<Clazz.MethodDef, Set<Clazz.MethodDef>>();
80 final Map<Clazz.MethodDef, Set<Clazz.MethodDef>> usedby = new TreeMap<Clazz.MethodDef, Set<Clazz.MethodDef>>();
81
82 ClassDataCollector cd = new ClassDataCollector() {
83 Clazz.MethodDef source;
84
85 // Before a method is parsed
86 public void method(Clazz.MethodDef source) {
87 this.source = source;
88 xref(using, source, null);
89 xref(usedby, source, null);
90 }
91
92 // For any reference in the previous method.
93 public void reference(Clazz.MethodDef reference) {
94 xref(using, source, reference);
95 xref(usedby, reference, source);
96 }
97 };
98 for (Clazz clazz : classes) {
99 clazz.parseClassFileWithCollector(cd);
100 }
101
102 out.println("<calltree>");
103 xref(out, "using", using);
104 xref(out, "usedby", usedby);
105 out.println("</calltree>");
106 }
107
108 /*
109 * Add a new reference
110 */
111 private static void xref(
112 Map<Clazz.MethodDef, Set<Clazz.MethodDef>> references,
113 Clazz.MethodDef source, Clazz.MethodDef reference) {
114 Set<Clazz.MethodDef> set = references.get(source);
115 if (set == null)
116 references.put(source, set=new TreeSet<Clazz.MethodDef>());
117 if ( reference != null)
118 set.add(reference);
119 }
120
121 /*
122 * Print out either using or usedby sets
123 */
124 private static void xref(PrintWriter out, String group,
125 Map<Clazz.MethodDef, Set<Clazz.MethodDef>> references) {
126 out.println(" <" + group + ">");
127 for (Map.Entry<Clazz.MethodDef, Set<Clazz.MethodDef>> entry : references
128 .entrySet()) {
129 Clazz.MethodDef source = entry.getKey();
130 Set<Clazz.MethodDef> refs = entry.getValue();
131 method(out, "method", source, ">");
132 for (Clazz.MethodDef ref : refs) {
133 method(out, "ref", ref, "/>");
134 }
135 out.println(" </method>");
136 }
137 out.println(" </" + group + ">");
138 }
139
140 /*
141 * Print out a method.
142 */
143 private static void method(PrintWriter out, String element,
144 Clazz.MethodDef source, String closeElement) {
145 out.println(" <" + element + " class='" + source.clazz + "'"
146 + getAccess(source.access) +
147 ( source.isConstructor() ? "" : " name='" + source.name + "'") + " descriptor='" + source.descriptor + "' pretty='"
148 + source.getPretty() + "'" + closeElement);
149 }
150
151 private static String getAccess(int access) {
152 StringBuilder sb = new StringBuilder();
153 if ( Modifier.isPublic(access) )
154 sb.append(" public='true'");
155 if ( Modifier.isStatic(access) )
156 sb.append(" static='true'");
157 if ( Modifier.isProtected(access) )
158 sb.append(" protected='true'");
159 if ( Modifier.isInterface(access) )
160 sb.append(" interface='true'");
161
162 return sb.toString();
163 }
164
165}