package com.thoughtworks.qdox;
import com.thoughtworks.qdox.directorywalker.
DirectoryScanner;
import com.thoughtworks.qdox.directorywalker.
FileVisitor;
import com.thoughtworks.qdox.directorywalker.
SuffixFilter;
import com.thoughtworks.qdox.model.
ClassLibrary;
import com.thoughtworks.qdox.model.
DefaultDocletTagFactory;
import com.thoughtworks.qdox.model.
DocletTagFactory;
import com.thoughtworks.qdox.model.
JavaClass;
import com.thoughtworks.qdox.model.
JavaClassCache;
import com.thoughtworks.qdox.model.
JavaSource;
import com.thoughtworks.qdox.model.
ModelBuilder;
import com.thoughtworks.qdox.model.
JavaPackage;
import com.thoughtworks.qdox.parser.
Lexer;
import com.thoughtworks.qdox.parser.
ParseException;
import com.thoughtworks.qdox.parser.impl.
JFlexLexer;
import com.thoughtworks.qdox.parser.impl.
Parser;
import com.thoughtworks.qdox.parser.structs.
ClassDef;
import com.thoughtworks.qdox.parser.structs.
FieldDef;
import com.thoughtworks.qdox.parser.structs.
MethodDef;
import com.thoughtworks.qdox.parser.structs.
PackageDef;
import com.thoughtworks.qdox.parser.structs.
TypeDef;
import java.io.
File;
import java.io.
FileInputStream;
import java.io.
FileNotFoundException;
import java.io.
FileOutputStream;
import java.io.
IOException;
import java.io.
InputStreamReader;
import java.io.
ObjectInputStream;
import java.io.
ObjectOutputStream;
import java.io.
Reader;
import java.io.
Serializable;
import java.io.
UnsupportedEncodingException;
import java.lang.reflect.
Constructor;
import java.lang.reflect.
Field;
import java.lang.reflect.
Member;
import java.lang.reflect.
Method;
import java.lang.reflect.
Modifier;
import java.net.
URL;
import java.util.
ArrayList;
import java.util.
HashMap;
import java.util.
HashSet;
import java.util.
Iterator;
import java.util.
LinkedList;
import java.util.
List;
import java.util.
Map;
import java.util.
Set;
import java.util.
StringTokenizer;
/**
* Simple facade to QDox allowing a source tree to be parsed and the resulting object model navigated.
*
* <h3>Example</h3>
* <pre><code>
* // -- Create JavaDocBuilder
*
* JavaDocBuilder builder = new JavaDocBuilder();
*
* // -- Add some files
*
* // Reading a single source file.
* builder.addSource(new FileReader("MyFile.java"));
*
* // Reading from another kind of input stream.
* builder.addSource(new StringReader("package test; public class Hello {}"));
*
* // Adding all .java files in a source tree (recursively).
* builder.addSourceTree(new File("mysrcdir"));
*
* // -- Retrieve source files
*
* JavaSource[] source = builder.getSources();
*
* </code></pre>
*
* @author <a href="mailto:joew@thoughtworks.com">Joe Walnes</a>
* @author Aslak Hellesøy
* @author Robert Scholte
*/
public class
JavaDocBuilder implements
Serializable {
private final
JavaClassContext context;
private
Set packages = new
HashSet();
private
List sources = new
ArrayList();
private
DocletTagFactory docletTagFactory;
private
String encoding =
System.
getProperty("file.encoding");
private boolean
debugLexer;
private boolean
debugParser;
private
ErrorHandler errorHandler = new
DefaultErrorHandler();
public static interface
ErrorHandler {
void
handle(
ParseException parseException);
}
public static class
DefaultErrorHandler implements
ErrorHandler,
Serializable {
public void
handle(
ParseException parseException) {
throw
parseException;
}
}
public
JavaDocBuilder() {
this(new
DefaultDocletTagFactory());
}
public
JavaDocBuilder(
DocletTagFactory docletTagFactory) {
this.
docletTagFactory =
docletTagFactory;
ClassLibrary classLibrary = new
ClassLibrary();
classLibrary.
addDefaultLoader();
this.
context = new
JavaClassContext(this);
this.
context.
setClassLibrary(
classLibrary);
}
public
JavaDocBuilder(
ClassLibrary classLibrary) {
this(new
DefaultDocletTagFactory(),
classLibrary);
}
public
JavaDocBuilder(
DocletTagFactory docletTagFactory,
ClassLibrary classLibrary) {
this.
docletTagFactory =
docletTagFactory;
this.
context = new
JavaClassContext(this);
this.
context.
setClassLibrary(
classLibrary);
}
private void
addClasses(
JavaSource source) {
Set resultSet = new
HashSet();
addClassesRecursive(
source,
resultSet);
JavaClass[]
javaClasses = (
JavaClass[])
resultSet.
toArray(new
JavaClass[
resultSet.
size()]);
for (int
classIndex = 0;
classIndex <
javaClasses.length;
classIndex++) {
JavaClass cls =
javaClasses[
classIndex];
addClass(
cls);
}
}
private void
addClass(
JavaClass cls) {
context.
add(
cls);
cls.
setJavaClassContext(
context);
}
public
JavaClass getClassByName(
String name) {
if (
name == null) {
return null;
}
return
context.
getClassByName(
name);
}
protected
JavaClass createSourceClass(
String name) {
File sourceFile =
context.
getClassLibrary().
getSourceFile(
name );
if (
sourceFile != null) {
try
{
JavaSource source =
addSource(
sourceFile );
for (int
index = 0;
index <
source.
getClasses().length;
index++) {
JavaClass clazz =
source.
getClasses()[
index];
if (
name.
equals(
clazz.
getFullyQualifiedName())) {
return
clazz;
}
}
return
source.
getNestedClassByName(
name );
}
catch (
FileNotFoundException e )
{
//nop
}
catch (
IOException e )
{
//nop
}
}
return null;
}
protected
JavaClass createUnknownClass(
String name) {
ModelBuilder unknownBuilder = new
ModelBuilder(
context,
docletTagFactory, new
HashMap());
ClassDef classDef = new
ClassDef();
classDef.
name =
name;
unknownBuilder.
beginClass(
classDef);
unknownBuilder.
endClass();
JavaSource unknownSource =
unknownBuilder.
getSource();
JavaClass result =
unknownSource.
getClasses()[0];
return
result;
}
protected
JavaClass createBinaryClass(
String name) {
// First see if the class exists at all.
Class clazz =
context.
getClass(
name);
if (
clazz == null) {
return null;
} else {
try {
// Create a new builder and mimic the behaviour of the parser.
// We're getting all the information we need via reflection instead.
ModelBuilder binaryBuilder = new
ModelBuilder(
context,
docletTagFactory, new
HashMap());
// Set the package name and class name
String packageName =
getPackageName(
name);
binaryBuilder.
addPackage(new
PackageDef(
packageName));
ClassDef classDef = new
ClassDef();
classDef.
name =
getClassName(
name);
// Set the extended class and interfaces.
Class[]
interfaces =
clazz.
getInterfaces();
if (
clazz.
isInterface()) {
// It's an interface
classDef.
type =
ClassDef.
INTERFACE;
for (int
i = 0;
i <
interfaces.length;
i++) {
Class anInterface =
interfaces[
i];
classDef.
extendz.
add(new
TypeDef(
anInterface.
getName()));
}
} else {
// It's a class
for (int
i = 0;
i <
interfaces.length;
i++) {
Class anInterface =
interfaces[
i];
classDef.
implementz.
add(new
TypeDef(
anInterface.
getName()));
}
Class superclass =
clazz.
getSuperclass();
if (
superclass != null) {
classDef.
extendz.
add(new
TypeDef(
superclass.
getName()));
}
}
addModifiers(
classDef.
modifiers,
clazz.
getModifiers());
binaryBuilder.
beginClass(
classDef);
// add the constructors
//
// This also adds the default constructor if any which is different
// to the source code as that does not create a default constructor
// if no constructor exists.
Constructor[]
constructors =
clazz.
getDeclaredConstructors();
for (int
i = 0;
i <
constructors.length;
i++) {
addMethodOrConstructor(
constructors[
i],
binaryBuilder);
}
// add the methods
Method[]
methods =
clazz.
getDeclaredMethods();
for (int
i = 0;
i <
methods.length;
i++) {
addMethodOrConstructor(
methods[
i],
binaryBuilder);
}
Field[]
fields =
clazz.
getDeclaredFields();
for (int
i = 0;
i <
fields.length;
i++) {
addField(
fields[
i],
binaryBuilder);
}
binaryBuilder.
endClass();
JavaSource binarySource =
binaryBuilder.
getSource();
// There is always only one class in a "binary" source.
JavaClass result =
binarySource.
getClasses()[0];
return
result;
} catch (
NoClassDefFoundError e) {
return null;
}
}
}
private void
addModifiers(
Set set, int
modifier) {
String modifierString =
Modifier.
toString(
modifier);
for (
StringTokenizer stringTokenizer = new
StringTokenizer(
modifierString);
stringTokenizer.
hasMoreTokens();) {
set.
add(
stringTokenizer.
nextToken());
}
}
private void
addField(
Field field,
ModelBuilder binaryBuilder) {
FieldDef fieldDef = new
FieldDef();
Class fieldType =
field.
getType();
fieldDef.
name =
field.
getName();
fieldDef.
type =
getTypeDef(
fieldType);
fieldDef.
dimensions =
getDimension(
fieldType);
addModifiers(
fieldDef.
modifiers,
field.
getModifiers());
binaryBuilder.
addField(
fieldDef);
}
private void
addMethodOrConstructor(
Member member,
ModelBuilder binaryBuilder) {
MethodDef methodDef = new
MethodDef();
// The name of constructors are qualified. Need to strip it.
// This will work for regular methods too, since -1 + 1 = 0
int
lastDot =
member.
getName().
lastIndexOf('.');
methodDef.
name =
member.
getName().
substring(
lastDot + 1);
addModifiers(
methodDef.
modifiers,
member.
getModifiers());
Class[]
exceptions;
Class[]
parameterTypes;
if (
member instanceof
Method) {
methodDef.
constructor = false;
// For some stupid reason, these methods are not defined in Member,
// but in both Method and Construcotr.
exceptions = ((
Method)
member).
getExceptionTypes();
parameterTypes = ((
Method)
member).
getParameterTypes();
Class returnType = ((
Method)
member).
getReturnType();
methodDef.
returnType =
getTypeDef(
returnType);
methodDef.
dimensions =
getDimension(
returnType);
} else {
methodDef.
constructor = true;
exceptions = ((
Constructor)
member).
getExceptionTypes();
parameterTypes = ((
Constructor)
member).
getParameterTypes();
}
for (int
j = 0;
j <
exceptions.length;
j++) {
Class exception =
exceptions[
j];
methodDef.
exceptions.
add(
exception.
getName());
}
binaryBuilder.
addMethod(
methodDef);
for (int
j = 0;
j <
parameterTypes.length;
j++) {
FieldDef param = new
FieldDef();
Class parameterType =
parameterTypes[
j];
param.
name = "p" +
j;
param.
type =
getTypeDef(
parameterType);
param.
dimensions =
getDimension(
parameterType);
binaryBuilder.
addParameter(
param );
}
}
private static final int
getDimension(
Class c) {
return
c.
getName().
lastIndexOf('[') + 1;
}
private static
String getTypeName(
Class c) {
return
c.
getComponentType() != null ?
c.
getComponentType().
getName() :
c.
getName();
}
private static
TypeDef getTypeDef(
Class c) {
return new
TypeDef(
getTypeName(
c));
}
private
String getPackageName(
String fullClassName) {
int
lastDot =
fullClassName.
lastIndexOf('.');
return
lastDot == -1 ? "" :
fullClassName.
substring(0,
lastDot);
}
private
String getClassName(
String fullClassName) {
int
lastDot =
fullClassName.
lastIndexOf('.');
return
lastDot == -1 ?
fullClassName :
fullClassName.
substring(
lastDot + 1);
}
public
JavaSource addSource(
Reader reader) {
return
addSource(
reader, "UNKNOWN SOURCE");
}
public
JavaSource addSource(
Reader reader,
String sourceInfo) {
ModelBuilder builder = new
ModelBuilder(
context,
docletTagFactory, null);
Lexer lexer = new
JFlexLexer(
reader);
Parser parser = new
Parser(
lexer,
builder);
parser.
setDebugLexer(
debugLexer);
parser.
setDebugParser(
debugParser);
try {
parser.
parse();
} catch (
ParseException e) {
e.
setSourceInfo(
sourceInfo);
errorHandler.
handle(
e);
}
finally {
try {
reader.
close();
}
catch (
IOException e) {
}
}
JavaSource source =
builder.
getSource();
sources.
add(
source);
addClasses(
source);
JavaPackage pkg =
context.
getPackageByName(
source.
getPackageName() );
if (!
packages.
contains(
pkg)) {
packages.
add(
pkg);
}
// JavaClass[] classes = source.getClasses();
// for (int i = 0; i < classes.length; i++) {
// if (pkg != null) {
// pkg.addClass(classes[i]);
// }
// }
return
source;
}
public
JavaSource addSource(
File file) throws
IOException,
FileNotFoundException {
return
addSource(
file.
toURL());
}
public
JavaSource addSource(
URL url) throws
IOException,
FileNotFoundException {
JavaSource source =
addSource(new
InputStreamReader(
url.
openStream(),
encoding),
url.
toExternalForm());
source.
setURL(
url);
return
source;
}
public void
setErrorHandler(
ErrorHandler errorHandler) {
this.
errorHandler =
errorHandler;
}
public
JavaSource[]
getSources() {
return (
JavaSource[])
sources.
toArray(new
JavaSource[
sources.
size()]);
}
/**
* Returns all the classes found in all the sources, including inner classes
* and "extra" classes (multiple outer classes defined in the same source file).
*
* @return all the classes found in all the sources.
* @since 1.3
*/
public
JavaClass[]
getClasses() {
Set resultSet = new
HashSet();
JavaSource[]
javaSources =
getSources();
for (int
i = 0;
i <
javaSources.length;
i++) {
JavaSource javaSource =
javaSources[
i];
addClassesRecursive(
javaSource,
resultSet);
}
JavaClass[]
result = (
JavaClass[])
resultSet.
toArray(new
JavaClass[
resultSet.
size()]);
return
result;
}
/**
* Returns all the packages found in all the sources.
*
* @return all the packages found in all the sources.
* @since 1.9
*/
public
JavaPackage[]
getPackages() {
return (
JavaPackage[])
packages.
toArray(new
JavaPackage[
packages.
size()]);
}
private void
addClassesRecursive(
JavaSource javaSource,
Set resultSet) {
JavaClass[]
classes =
javaSource.
getClasses();
for (int
j = 0;
j <
classes.length;
j++) {
JavaClass javaClass =
classes[
j];
addClassesRecursive(
javaClass,
resultSet);
}
}
private void
addClassesRecursive(
JavaClass javaClass,
Set set) {
// Add the class...
set.
add(
javaClass);
// And recursively all of its inner classes
JavaClass[]
innerClasses =
javaClass.
getNestedClasses();
for (int
i = 0;
i <
innerClasses.length;
i++) {
JavaClass innerClass =
innerClasses[
i];
addClassesRecursive(
innerClass,
set);
}
}
/**
* Add all files in a directory (and subdirs, recursively).
*
* If a file cannot be read, a RuntimeException shall be thrown.
*/
public void
addSourceTree(
File file) {
FileVisitor errorHandler = new
FileVisitor() {
public void
visitFile(
File badFile) {
throw new
RuntimeException("Cannot read file : " +
badFile.
getName());
}
};
addSourceTree(
file,
errorHandler);
}
/**
* Add all files in a directory (and subdirs, recursively).
*
* If a file cannot be read, errorHandler will be notified.
*/
public void
addSourceTree(
File file, final
FileVisitor errorHandler) {
DirectoryScanner scanner = new
DirectoryScanner(
file);
scanner.
addFilter(new
SuffixFilter(".java"));
scanner.
scan(new
FileVisitor() {
public void
visitFile(
File currentFile) {
try {
addSource(
currentFile);
} catch (
IOException e) {
errorHandler.
visitFile(
currentFile);
}
}
});
}
public
List search(
Searcher searcher) {
List results = new
LinkedList();
for (
Iterator iterator =
context.
getClassLibrary().
all().
iterator();
iterator.
hasNext();) {
String clsName = (
String)
iterator.
next();
JavaClass cls =
getClassByName(
clsName);
if (
searcher.
eval(
cls)) {
results.
add(
cls);
}
}
return
results;
}
public
ClassLibrary getClassLibrary() {
return
context.
getClassLibrary();
}
public void
save(
File file) throws
IOException {
FileOutputStream fos = new
FileOutputStream(
file);
ObjectOutputStream out = new
ObjectOutputStream(
fos);
try {
out.
writeObject(this);
} finally {
out.
close();
fos.
close();
}
}
/**
* Note that after loading JavaDocBuilder classloaders need to be re-added.
*/
public static
JavaDocBuilder load(
File file) throws
IOException {
FileInputStream fis = new
FileInputStream(
file);
ObjectInputStream in = new
ObjectInputStream(
fis);
JavaDocBuilder builder = null;
try {
builder = (
JavaDocBuilder)
in.
readObject();
} catch (
ClassNotFoundException e) {
throw new
Error("Couldn't load class : " +
e.
getMessage());
} finally {
in.
close();
fis.
close();
}
return
builder;
}
public void
setEncoding(
String encoding) {
this.
encoding =
encoding;
}
/**
* Forces QDox to dump tokens returned from lexer to System.err.
*/
public void
setDebugLexer(boolean
debugLexer) {
this.
debugLexer =
debugLexer;
}
/**
* Forces QDox to dump parser states to System.out.
*/
public void
setDebugParser(boolean
debugParser) {
this.
debugParser =
debugParser;
}
public
JavaPackage getPackageByName(
String name )
{
if(
name != null) {
Iterator iter =
packages.
iterator();
while(
iter.
hasNext()) {
JavaPackage pkg = (
JavaPackage)
iter.
next();
if(
name.
equals(
pkg.
getName() )) {
return
pkg;
}
}
}
return null;
}
}