1 package com.thoughtworks.qdox;
2
3 import com.thoughtworks.qdox.directorywalker.DirectoryScanner;
4 import com.thoughtworks.qdox.directorywalker.FileVisitor;
5 import com.thoughtworks.qdox.directorywalker.SuffixFilter;
6 import com.thoughtworks.qdox.model.ClassLibrary;
7 import com.thoughtworks.qdox.model.DefaultDocletTagFactory;
8 import com.thoughtworks.qdox.model.DocletTagFactory;
9 import com.thoughtworks.qdox.model.JavaClass;
10 import com.thoughtworks.qdox.model.JavaClassCache;
11 import com.thoughtworks.qdox.model.JavaSource;
12 import com.thoughtworks.qdox.model.ModelBuilder;
13 import com.thoughtworks.qdox.parser.Lexer;
14 import com.thoughtworks.qdox.parser.ParseException;
15 import com.thoughtworks.qdox.parser.impl.JFlexLexer;
16 import com.thoughtworks.qdox.parser.impl.Parser;
17 import com.thoughtworks.qdox.parser.structs.ClassDef;
18 import com.thoughtworks.qdox.parser.structs.FieldDef;
19 import com.thoughtworks.qdox.parser.structs.MethodDef;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.io.ObjectInputStream;
28 import java.io.ObjectOutputStream;
29 import java.io.Reader;
30 import java.io.Serializable;
31 import java.io.UnsupportedEncodingException;
32 import java.lang.reflect.Constructor;
33 import java.lang.reflect.Field;
34 import java.lang.reflect.Member;
35 import java.lang.reflect.Method;
36 import java.lang.reflect.Modifier;
37 import java.net.URL;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.Iterator;
42 import java.util.LinkedList;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.StringTokenizer;
47
48 /**
49 * Simple facade to QDox allowing a source tree to be parsed and the resulting object model navigated.
50 *
51 * <h3>Example</h3>
52 * <pre><code>
53 * // -- Create JavaDocBuilder
54 *
55 * JavaDocBuilder builder = new JavaDocBuilder();
56 *
57 * // -- Add some files
58 *
59 * // Reading a single source file.
60 * builder.addSource(new FileReader("MyFile.java"));
61 *
62 * // Reading from another kind of input stream.
63 * builder.addSource(new StringReader("package test; public class Hello {}"));
64 *
65 * // Adding all .java files in a source tree (recursively).
66 * builder.addSourceTree(new File("mysrcdir"));
67 *
68 * // -- Retrieve source files
69 *
70 * JavaSource[] source = builder.getSources();
71 *
72 * </code></pre>
73 *
74 * @author <a href="mailto:joew@thoughtworks.com">Joe Walnes</a>
75 * @author Aslak Hellesøy
76 */
77 public class JavaDocBuilder implements Serializable, JavaClassCache {
78
79 private Map classes = new HashMap();
80 private ClassLibrary classLibrary;
81 private List sources = new ArrayList();
82 private DocletTagFactory docletTagFactory;
83 private String encoding = System.getProperty("file.encoding");
84 private boolean debugLexer;
85 private boolean debugParser;
86
87 public JavaDocBuilder() {
88 this(new DefaultDocletTagFactory());
89 }
90
91 public JavaDocBuilder(DocletTagFactory docletTagFactory) {
92 this.docletTagFactory = docletTagFactory;
93 classLibrary = new ClassLibrary(this);
94 classLibrary.addDefaultLoader();
95 }
96
97 private void addClasses(JavaSource source) {
98 Set resultSet = new HashSet();
99 addClassesRecursive(source, resultSet);
100 JavaClass[] javaClasses = (JavaClass[]) resultSet.toArray(new JavaClass[resultSet.size()]);
101 for (int classIndex = 0; classIndex < javaClasses.length; classIndex++) {
102 JavaClass cls = javaClasses[classIndex];
103 addClass(cls);
104 }
105 }
106
107 private void addClass(JavaClass cls) {
108 classes.put(cls.getFullyQualifiedName(), cls);
109 cls.setJavaClassCache(this);
110 }
111
112 public JavaClass getClassByName(String name) {
113 if (name == null) {
114 return null;
115 }
116 JavaClass result = (JavaClass) classes.get(name);
117 if (result == null) {
118 // Try to make a binary class out of it
119 result = createBinaryClass(name);
120 if (result != null) {
121 addClass(result);
122 } else {
123 result = createUnknownClass(name);
124 }
125 }
126 return result;
127 }
128
129 private JavaClass createUnknownClass(String name) {
130 ModelBuilder unknownBuilder = new ModelBuilder(classLibrary, docletTagFactory);
131 ClassDef classDef = new ClassDef();
132 classDef.name = name;
133 unknownBuilder.beginClass(classDef);
134 unknownBuilder.endClass();
135 JavaSource unknownSource = unknownBuilder.getSource();
136 JavaClass result = unknownSource.getClasses()[0];
137 return result;
138 }
139
140 private JavaClass createBinaryClass(String name) {
141 // First see if the class exists at all.
142 Class clazz = classLibrary.getClass(name);
143 if (clazz == null) {
144 return null;
145 } else {
146 // Create a new builder and mimic the behaviour of the parser.
147 // We're getting all the information we need via reflection instead.
148 ModelBuilder binaryBuilder = new ModelBuilder(classLibrary, docletTagFactory);
149
150 // Set the package name and class name
151 String packageName = getPackageName(name);
152 binaryBuilder.addPackage(packageName);
153
154 ClassDef classDef = new ClassDef();
155 classDef.name = getClassName(name);
156
157 // Set the extended class and interfaces.
158 Class[] interfaces = clazz.getInterfaces();
159 if (clazz.isInterface()) {
160 // It's an interface
161 classDef.type = ClassDef.INTERFACE;
162 for (int i = 0; i < interfaces.length; i++) {
163 Class anInterface = interfaces[i];
164 classDef.extendz.add(anInterface.getName());
165 }
166 } else {
167 // It's a class
168 for (int i = 0; i < interfaces.length; i++) {
169 Class anInterface = interfaces[i];
170 classDef.implementz.add(anInterface.getName());
171 }
172 Class superclass = clazz.getSuperclass();
173 if (superclass != null) {
174 classDef.extendz.add(superclass.getName());
175 }
176 }
177
178 addModifiers(classDef.modifiers, clazz.getModifiers());
179
180 binaryBuilder.beginClass(classDef);
181
182 // add the constructors
183 Constructor[] constructors = clazz.getConstructors();
184 for (int i = 0; i < constructors.length; i++) {
185 addMethodOrConstructor(constructors[i], binaryBuilder);
186 }
187
188 // add the methods
189 Method[] methods = clazz.getMethods();
190 for (int i = 0; i < methods.length; i++) {
191 // Ignore methods defined in superclasses
192 if (methods[i].getDeclaringClass() == clazz) {
193 addMethodOrConstructor(methods[i], binaryBuilder);
194 }
195 }
196
197 Field[] fields = clazz.getFields();
198 for (int i = 0; i < fields.length; i++) {
199 if (fields[i].getDeclaringClass() == clazz) {
200 addField(fields[i], binaryBuilder);
201 }
202 }
203
204 binaryBuilder.endClass();
205 JavaSource binarySource = binaryBuilder.getSource();
206 // There is always only one class in a "binary" source.
207 JavaClass result = binarySource.getClasses()[0];
208 return result;
209 }
210 }
211
212 private void addModifiers(Set set, int modifier) {
213 String modifierString = Modifier.toString(modifier);
214 for (StringTokenizer stringTokenizer = new StringTokenizer(modifierString); stringTokenizer.hasMoreTokens();) {
215 set.add(stringTokenizer.nextToken());
216 }
217 }
218
219 private void addField(Field field, ModelBuilder binaryBuilder) {
220 FieldDef fieldDef = new FieldDef();
221 Class fieldType = field.getType();
222 fieldDef.name = field.getName();
223 fieldDef.type = getTypeName(fieldType);
224 fieldDef.dimensions = getDimension(fieldType);
225 binaryBuilder.addField(fieldDef);
226 }
227
228 private void addMethodOrConstructor(Member member, ModelBuilder binaryBuilder) {
229 MethodDef methodDef = new MethodDef();
230 // The name of constructors are qualified. Need to strip it.
231 // This will work for regular methods too, since -1 + 1 = 0
232 int lastDot = member.getName().lastIndexOf('.');
233 methodDef.name = member.getName().substring(lastDot + 1);
234
235 addModifiers(methodDef.modifiers, member.getModifiers());
236 Class[] exceptions;
237 Class[] parameterTypes;
238 if (member instanceof Method) {
239 methodDef.constructor = false;
240
241 // For some stupid reason, these methods are not defined in Member,
242 // but in both Method and Construcotr.
243 exceptions = ((Method) member).getExceptionTypes();
244 parameterTypes = ((Method) member).getParameterTypes();
245
246 Class returnType = ((Method) member).getReturnType();
247 methodDef.returns = getTypeName(returnType);
248 methodDef.dimensions = getDimension(returnType);
249
250 } else {
251 methodDef.constructor = true;
252
253 exceptions = ((Constructor) member).getExceptionTypes();
254 parameterTypes = ((Constructor) member).getParameterTypes();
255 }
256 for (int j = 0; j < exceptions.length; j++) {
257 Class exception = exceptions[j];
258 methodDef.exceptions.add(exception.getName());
259 }
260 for (int j = 0; j < parameterTypes.length; j++) {
261 FieldDef param = new FieldDef();
262 Class parameterType = parameterTypes[j];
263 param.name = "p" + j;
264 param.type = getTypeName(parameterType);
265 param.dimensions = getDimension(parameterType);
266 methodDef.params.add(param);
267 }
268 binaryBuilder.addMethod(methodDef);
269 }
270
271 private static final int getDimension(Class c) {
272 return c.getName().lastIndexOf('[') + 1;
273 }
274
275 private static String getTypeName(Class c) {
276 return c.getComponentType() != null ? c.getComponentType().getName() : c.getName();
277 }
278
279 private String getPackageName(String fullClassName) {
280 int lastDot = fullClassName.lastIndexOf('.');
281 return lastDot == -1 ? "" : fullClassName.substring(0, lastDot);
282 }
283
284 private String getClassName(String fullClassName) {
285 int lastDot = fullClassName.lastIndexOf('.');
286 return lastDot == -1 ? fullClassName : fullClassName.substring(lastDot + 1);
287 }
288
289 public JavaSource addSource(Reader reader) {
290 return addSource(reader, "UNKNOWN SOURCE");
291 }
292
293 public JavaSource addSource(Reader reader, String sourceInfo) {
294 ModelBuilder builder = new ModelBuilder(classLibrary, docletTagFactory);
295 Lexer lexer = new JFlexLexer(reader);
296 Parser parser = new Parser(lexer, builder);
297 parser.setDebugLexer(debugLexer);
298 parser.setDebugParser(debugParser);
299 try {
300 parser.parse();
301 } catch (ParseException e) {
302 e.setSourceInfo(sourceInfo);
303 throw e;
304 }
305 JavaSource source = builder.getSource();
306 sources.add(source);
307 addClasses(source);
308 return source;
309 }
310
311 public JavaSource addSource(File file) throws IOException, FileNotFoundException {
312 return addSource(file.toURL());
313 }
314
315 public JavaSource addSource(URL url) throws IOException, FileNotFoundException {
316 JavaSource source = addSource(new InputStreamReader(url.openStream(),encoding), url.toExternalForm());
317 source.setURL(url);
318 return source;
319 }
320
321 public JavaSource[] getSources() {
322 return (JavaSource[]) sources.toArray(new JavaSource[sources.size()]);
323 }
324
325 /**
326 * Returns all the classes found in all the sources, including inner classes
327 * and "extra" classes (multiple outer classes defined in the same source file).
328 *
329 * @return all the classes found in all the sources.
330 * @since 1.3
331 */
332 public JavaClass[] getClasses() {
333 Set resultSet = new HashSet();
334 JavaSource[] javaSources = getSources();
335 for (int i = 0; i < javaSources.length; i++) {
336 JavaSource javaSource = javaSources[i];
337 addClassesRecursive(javaSource, resultSet);
338 }
339 JavaClass[] result = (JavaClass[]) resultSet.toArray(new JavaClass[resultSet.size()]);
340 return result;
341 }
342
343 private void addClassesRecursive(JavaSource javaSource, Set resultSet) {
344 JavaClass[] classes = javaSource.getClasses();
345 for (int j = 0; j < classes.length; j++) {
346 JavaClass javaClass = classes[j];
347 addClassesRecursive(javaClass, resultSet);
348 }
349 }
350
351 private void addClassesRecursive(JavaClass javaClass, Set set) {
352 // Add the class...
353 set.add(javaClass);
354
355 // And recursively all of its inner classes
356 JavaClass[] innerClasses = javaClass.getNestedClasses();
357 for (int i = 0; i < innerClasses.length; i++) {
358 JavaClass innerClass = innerClasses[i];
359 addClassesRecursive(innerClass, set);
360 }
361 }
362
363 public void addSourceTree(File file) {
364 DirectoryScanner scanner = new DirectoryScanner(file);
365 scanner.addFilter(new SuffixFilter(".java"));
366 scanner.scan(new FileVisitor() {
367 public void visitFile(File currentFile) {
368 try {
369 addSource(currentFile);
370 } catch (UnsupportedEncodingException e) {
371 throw new RuntimeException("Unsupported encoding : " + encoding);
372 } catch (IOException e) {
373 throw new RuntimeException("Cannot read file : " + currentFile.getName());
374 }
375 }
376 });
377 }
378
379 public List search(Searcher searcher) {
380 List results = new LinkedList();
381 for (Iterator iterator = classLibrary.all().iterator(); iterator.hasNext();) {
382 String clsName = (String) iterator.next();
383 JavaClass cls = getClassByName(clsName);
384 if (searcher.eval(cls)) {
385 results.add(cls);
386 }
387 }
388 return results;
389 }
390
391 public ClassLibrary getClassLibrary() {
392 return classLibrary;
393 }
394
395 public void save(File file) throws IOException {
396 FileOutputStream fos = new FileOutputStream(file);
397 ObjectOutputStream out = new ObjectOutputStream(fos);
398 try {
399 out.writeObject(this);
400 } finally {
401 out.close();
402 fos.close();
403 }
404 }
405
406 /**
407 * Note that after loading JavaDocBuilder classloaders need to be re-added.
408 */
409 public static JavaDocBuilder load(File file) throws IOException {
410 FileInputStream fis = new FileInputStream(file);
411 ObjectInputStream in = new ObjectInputStream(fis);
412 JavaDocBuilder builder = null;
413 try {
414 builder = (JavaDocBuilder) in.readObject();
415 } catch (ClassNotFoundException e) {
416 throw new Error("Couldn't load class : " + e.getMessage());
417 } finally {
418 in.close();
419 fis.close();
420 }
421 return builder;
422 }
423
424 public void setEncoding(String encoding) {
425 this.encoding = encoding;
426 }
427
428 /**
429 * Forces QDox to dump tokens returned from lexer to System.err.
430 */
431 public void setDebugLexer(boolean debugLexer) {
432 this.debugLexer = debugLexer;
433 }
434
435 /**
436 * Forces QDox to dump parser states to System.out.
437 */
438 public void setDebugParser(boolean debugParser) {
439 this.debugParser = debugParser;
440 }
441
442 }