blob: 895f5b6b1dfe60339d3f176b17ea486a4928ea97 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.doclava;
import com.google.doclava.parser.JavaLexer;
import com.google.doclava.parser.JavaParser;
import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.debug.ParseTreeBuilder;
import org.antlr.runtime.tree.ParseTree;
import org.antlr.runtime.tree.Tree;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
/**
* InfoBuilder parses an individual file and builds Doclava
* objects out of the data within the file. This data is
* stored within a global cache for later use.
*/
public class InfoBuilder {
private PackageInfo mPackage;
private ArrayList<String> mImports;
private HashSet<String> mClassNames;
private String mFilename; // TODO - remove this eventually
private ClassInfo mRootClass;
public InfoBuilder(String filename) {
mImports = new ArrayList<String>();
mImports.add("java.lang.*"); // should allow us to resolve this properly, eventually
// alternatively, we could add everything from java.lang.*
// but that would probably be too brittle
mClassNames = new HashSet<String>();
mFilename = filename;
}
@Override
public String toString() {
return mFilename;
}
public void parseFile() {
JavaLexer lex;
try {
lex = new JavaLexer(new ANTLRFileStream(mFilename, "UTF8"));
CommonTokenStream tokens = new CommonTokenStream(lex);
// create the ParseTreeBuilder to build a parse tree
// much easier to parse than ASTs
ParseTreeBuilder builder = new ParseTreeBuilder("compilationUnit");
JavaParser g = new JavaParser(tokens, builder);
g.compilationUnit();
ParseTree tree = builder.getTree();
lex = null;
tokens = null;
builder = null;
g = null;
parseFile(tree);
} catch (IOException e1) {
e1.printStackTrace();
} catch (RecognitionException e) {
e.printStackTrace();
}
}
public static void resolve() {
Caches.resolve();
}
// All of the print functions exist for debugging alone.
public void printStuff() {
System.out.println(mPackage.name() + "\n");
printList(mImports);
Caches.printResolutions();
}
private void printList(ArrayList<String> list) {
for (String value : list) {
System.out.println(value);
}
System.out.println();
}
public static void printClassInfo(ClassInfo cl) {
System.out.print("Class: " + cl.toString());
printTypeVariables(cl.type());
System.out.println();
System.out.println(cl.comment().mText);
if (!cl.annotations().isEmpty()) {
System.out.println("\nAnnotations:");
printAnnotations(cl.annotations());
}
if (cl.superclass() != null) {
System.out.print("Superclass: " + cl.superclass().qualifiedName());
printTypeVariables(cl.superclassType());
System.out.println();
}
if (!cl.realInterfaces().isEmpty()) {
System.out.println("\nInterfaces Implemented:");
Iterator<TypeInfo> it = cl.realInterfaceTypes().iterator();
for (ClassInfo cls : cl.realInterfaces()) {
TypeInfo outerType = it.next();
if (cls == null) {
System.out.print(outerType.simpleTypeName());
} else {
System.out.print(cls.qualifiedName());
}
printTypeVariables(outerType);
System.out.println();
}
System.out.println();
}
if (!cl.allSelfFields().isEmpty()) {
System.out.println("\nFields:");
for (FieldInfo f : cl.allSelfFields()) {
if (f != cl.allSelfFields().get(0)) {
System.out.println();
}
System.out.println(f.comment().mText);
printAnnotations(f.annotations());
printTypeName(f.type());
System.out.print(" " + f.name());
if (f.constantValue() != null) {
System.out.println(": " + f.constantValue());
} else if (f.hasValue()) {
System.out.println(": has some value");
} else {
System.out.println();
}
}
System.out.println();
}
if (cl.enumConstants() != null && !cl.enumConstants().isEmpty()) {
System.out.println("\nEnum Constants:");
for (FieldInfo f : cl.enumConstants()) {
if (f != cl.enumConstants().get(0)) {
System.out.println();
}
System.out.println(f.comment().mText);
printAnnotations(f.annotations());
System.out.print(f.type().simpleTypeName() + " " + f.name());
if (f.constantValue() != null) {
System.out.println(": " + f.constantValue());
} else {
System.out.println();
}
}
System.out.println();
}
if (!cl.allConstructors().isEmpty()) {
System.out.println("\nConstructors:");
for (MethodInfo m : cl.allConstructors()) {
if (m != cl.allConstructors().get(0)) {
System.out.println();
}
System.out.println(m.comment().mText);
printAnnotations(m.annotations());
if (m.getTypeParameters() != null) {
printTypeVariableList(m.getTypeParameters());
System.out.print(" ");
}
System.out.println(m.name() + m.flatSignature());
}
System.out.println();
}
if (!cl.allSelfMethods().isEmpty()) {
System.out.println("\nMethods:");
for (MethodInfo m : cl.allSelfMethods()) {
if (m != cl.allSelfMethods().get(0)) {
System.out.println();
}
System.out.println(m.comment().mText);
printAnnotations(m.annotations());
if (m.getTypeParameters() != null) {
printTypeVariableList(m.getTypeParameters());
System.out.print(" ");
}
printTypeName(m.returnType());
System.out.print(" " + m.name() + m.flatSignature());
if (m.thrownExceptions() != null && !m.thrownExceptions().isEmpty()) {
System.out.print(" throws ");
for (ClassInfo c : m.thrownExceptions()) {
if (c != m.thrownExceptions().get(0)) {
System.out.print(", ");
}
System.out.print(c.name());
}
}
System.out.println();
}
System.out.println();
}
if (!cl.annotationElements().isEmpty()) {
System.out.println("\nAnnotation Elements:");
for (MethodInfo m : cl.annotationElements()) {
if (m != cl.annotationElements().get(0)) {
System.out.println();
}
System.out.println(m.comment().mText);
printAnnotations(m.annotations());
printTypeName(m.returnType());
System.out.print(" " + m.name() + m.flatSignature());
if (m.defaultAnnotationElementValue() != null) {
System.out.print(" default " +
m.defaultAnnotationElementValue().valueString());
}
System.out.println();
}
System.out.println();
}
if (cl.innerClasses() != null && !cl.innerClasses().isEmpty()) {
System.out.println("\nInner Classes:");
for (ClassInfo c : cl.innerClasses()) {
printClassInfo(c);
}
}
}
private static void printTypeName(TypeInfo type) {
System.out.print(type.simpleTypeName());
if (type.extendsBounds() != null && !type.extendsBounds().isEmpty()) {
System.out.print(" extends ");
for (TypeInfo t : type.extendsBounds()) {
if (t != type.extendsBounds().get(0)) {
System.out.print(" & ");
}
printTypeName(t);
}
}
if (type.superBounds() != null && !type.superBounds().isEmpty()) {
System.out.print(" super ");
for (TypeInfo t : type.superBounds()) {
if (t != type.superBounds().get(0)) {
System.out.print(" & ");
}
printTypeName(t);
}
}
printTypeVariables(type);
if (type.dimension() != null) {
System.out.print(type.dimension());
}
}
private static void printAnnotations(ArrayList<AnnotationInstanceInfo> annotations) {
for (AnnotationInstanceInfo i : annotations) {
System.out.println(i);
}
}
private static void printTypeVariables(TypeInfo type) {
printTypeVariableList(type.typeArguments());
}
private static void printTypeVariableList(ArrayList<TypeInfo> typeList) {
if (typeList != null && !typeList.isEmpty()) {
System.out.print("<");
for (TypeInfo type : typeList) {
if (type != typeList.get(0)) {
System.out.print(", ");
}
printTypeName(type);
}
System.out.print(">");
}
}
/**
* Parses the file represented by the ParseTree.
* @param tree A ParseTree of the file to parse.
*/
private void parseFile(ParseTree tree) {
if (tree.payload != null) {
String payload = tree.payload.toString();
// first pass at ignore method blocks
if ("block".equals(payload) ||
"blockStatement".equals(payload) ||
"explicitConstructorInvocation".equals(payload)) {
tree = null;
return;
}
// parse package of file
if ("packageDeclaration".equals(payload)) {
mPackage = buildPackage(tree);
return;
// parse imports
} else if ("importDeclaration".equals(payload)) {
mImports.add(buildImport(tree));
return;
// classes
} else if ("normalClassDeclaration".equals(payload)) {
buildClass(tree, null);
return;
// enums
} else if ("enumDeclaration".equals(payload)) {
buildEnum(tree, null);
return;
// interfaces
} else if ("normalInterfaceDeclaration".equals(payload)) {
buildInterface(tree, null);
return;
// annotations
} else if ("annotationTypeDeclaration".equals(payload)) {
buildAnnotationDeclaration(tree, null);
return;
}
}
// if we're not at the end, recurse down the tree
for (int i = 0; i < tree.getChildCount(); i++) {
parseFile((ParseTree) tree.getChild(i));
}
}
/**
* Parses a packageDeclaration in the tree. This function should only be called once per file.
* @param tree The tree to parse. packageDeclaration should be the root value.
* @return a PackageInfo representing the package in which this file exists.
*/
private PackageInfo buildPackage(ParseTree tree) {
for (int i = 0; i < tree.getChildCount(); i++) {
ParseTree child = (ParseTree) tree.getChild(i);
if (child.payload != null && "qualifiedName".equals(child.payload.toString())) {
String packageName = buildQualifiedName(child);
// return package because we might be creating packages for other classes
return Caches.obtainPackage(packageName);
}
}
return null;
}
/**
* Parses a qualifiedName, returning it as a String.
* @param tree The tree to parse. qualifiedName should be the root value.
* @return
*/
private static String buildQualifiedName(ParseTree tree) {
StringBuilder packageName = new StringBuilder();
for (int j = 0; j < tree.getChildCount(); j++) {
packageName.append(tree.getChild(j).toString());
}
return packageName.toString();
}
/**
* Builds a string representing an import declaration.
* @param tree The tree to parse. importDeclaration should be the root value.
* @return a String version of the import.
*/
private String buildImport(ParseTree tree) {
StringBuilder theImport = new StringBuilder();
for (int i = 1; i < tree.getChildCount(); i++) {
String part = tree.getChild(i).toString();
if ((i == 1 && "static".equals(part))
|| (i == tree.getChildCount()-1 && ";".equals(part))) {
continue;
}
theImport.append(part);
}
return theImport.toString();
}
/**
* Builds a ClassInfo for a normalClassDeclaration.
* @param tree The tree to parse. normalClassDeclaration should be the root value.
* @param containingClass The class that contains the class that will be built.
* This value should be null if this class is a root class in the file.
* @return A ClassInfo that contains all of the information about the class.
*/
private ClassInfo buildClass(ParseTree tree, ClassInfo containingClass) {
CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
Modifiers modifiers = new Modifiers(this);
ClassInfo cls = null;
@SuppressWarnings("unchecked")
Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
ParseTree child = it.next();
// parse modifiers
modifiers.parseModifiers(child);
it.next();
child = it.next();
// parse class name
cls = buildClassName(child, containingClass, modifiers,
commentAndPosition.getCommentText(),
commentAndPosition.getPosition(),
ClassType.ORDINARY);
child = it.next();
// handle generics
if ("typeParameters".equals(child.toString())) {
cls.type().setTypeArguments(buildTypeVariables(child));
child = it.next();
}
// handle extends
if ("extends".equals(child.toString())) {
child = it.next();
TypeInfo type = buildType(child);
cls.setSuperclassType(type);
// if ClassInfo is null, we need to add a resolution
if (type.asClassInfo() == null) {
addFutureResolution(cls, "superclassQualifiedName", type.simpleTypeName(), this);
}
cls.setSuperClass(type.asClassInfo());
child = it.next();
}
// TODO - do I have to make java.lang.Object the superclass if there is none otherwise?
// handle implements
if ("implements".equals(child.toString())) {
child = it.next();
parseInterfaces(child, cls);
child = it.next();
}
// finally, parse the body
buildClassBody(child, cls);
return cls;
}
/**
* Parses the list of interfaces that the class implements.
* Should only be called if the implements keyword is found.
* @param tree The tree to parse. typeList should be the root element.
* @param cls The class that implements these interfaces.
*/
private void parseInterfaces(ParseTree tree, ClassInfo cls) {
for (Object o : tree.getChildren()) {
if ("type".equals(o.toString())) {
TypeInfo type = buildType((ParseTree) o);
cls.addInterfaceType(type);
// if ClassInfo is null, we need to add a resolution
if (type.asClassInfo() == null) {
addFutureResolution(cls, "interfaceQualifiedName", type.simpleTypeName(), this);
}
cls.addInterface(type.asClassInfo());
}
}
}
/**
* ClassType exists solely to tell buildClassName which type of ClassInfo is being built.
*/
private enum ClassType {
ENUM, INTERFACE, ANNOTATION, ORDINARY
}
/**
* Parses the class name from the declaration. Also initializes the class.
* @param tree Position of the tree where the name of the class resides.
* @param containingClass Class that this class is contained within.
* <tt>null</tt> if this class is the root class.
* @param modifiers Contains all the modifiers of this class.
* @param commentText Javadoc comment of this class.
* @param position Position of the class.
* @param classType Type of class being instantiated.
* @return the ClassInfo being initialized.
*/
private ClassInfo buildClassName(ParseTree tree, ClassInfo containingClass, Modifiers modifiers,
String commentText, SourcePositionInfo position, ClassType classType) {
String qualifiedClassName = null;
boolean isOrdinaryClass = true;
boolean isException = false;
boolean isError = false;
boolean isIncluded = false;
boolean isPrimitive = false;
boolean isEnum = false;
boolean isInterface = false;
boolean isAnnotation = false;
// set appropriate flags based on ClassType
switch (classType) {
case ENUM:
isEnum = true;
break;
case INTERFACE:
isInterface = true;
break;
case ANNOTATION:
isAnnotation = true;
break;
}
String qualifiedTypeName = null;
ClassInfo cls = null;
// changes the name based upon whether this is the root class or an inner class
if (containingClass == null) {
qualifiedClassName = mPackage.name() + "." + tree.toString();
} else {
qualifiedClassName = containingClass.qualifiedName() + "." + tree.toString();
}
qualifiedTypeName = new String(qualifiedClassName);
// add the name to mClassNames so that we can use it to resolve usages of this class
mClassNames.add(qualifiedClassName);
// get the class from the cache and initialize it
cls = Caches.obtainClass(qualifiedClassName);
cls.initialize(commentText, position,
modifiers.isPublic(), modifiers.isProtected(),
modifiers.isPackagePrivate(), modifiers.isPrivate(),
modifiers.isStatic(), isInterface, modifiers.isAbstract(),
isOrdinaryClass, isException, isError, isEnum, isAnnotation,
modifiers.isFinal(), isIncluded, qualifiedTypeName, isPrimitive,
modifiers.getAnnotations());
cls.setContainingClass(containingClass);
cls.setContainingPackage(mPackage);
if (containingClass == null) {
mRootClass = cls;
}
// create an set a TypeInfo for this class
TypeInfo type = new TypeInfo(false, null, cls.name(), qualifiedTypeName, cls);
cls.setTypeInfo(type);
return cls;
}
/**
* Parses the body of a class.
* @param tree The tree to parse. classBody should be the root value.
* @param cls
*/
private void buildClassBody(ParseTree tree, ClassInfo cls) {
for (Object o : tree.getChildren()) {
ParseTree child = (ParseTree) o;
// skip all of the cruft that isn't a declaration
if (!"classBodyDeclaration".equals(child.toString())) {
continue;
}
// get to an actual definition
ParseTree member = (ParseTree) child.getChild(0).getChild(0);
// ignores static initializers
if (member == null) {
continue;
}
// field
if ("fieldDeclaration".equals(member.toString())) {
for (FieldInfo f : buildFields(member, cls)) {
cls.addField(f);
}
// method and constructor
} else if ("methodDeclaration".equals(member.toString())) {
MethodInfo method = buildMethod(member, cls, false);
if (method.kind().equals("constructor")) {
cls.addConstructor(method);
} else {
cls.addMethod(method);
}
// classes and enums
} else if ("classDeclaration".equals(member.toString())) {
Object tmp = member.getChild(0);
if ("normalClassDeclaration".equals(tmp.toString())) {
cls.addInnerClass(buildClass((ParseTree) tmp, cls));
} else if ("enumDeclaration".equals(tmp.toString())) {
cls.addInnerClass(buildEnum((ParseTree) tmp, cls));
}
// interfaces and annotations
} else if ("interfaceDeclaration".equals(member.toString())) {
Object tmp = member.getChild(0);
if ("normalInterfaceDeclaration".equals(tmp.toString())) {
cls.addInnerClass(buildInterface((ParseTree) tmp, cls));
} else if ("annotationTypeDeclaration".equals(tmp.toString())) {
cls.addInnerClass(buildAnnotationDeclaration((ParseTree) tmp, cls));
}
}
}
}
/**
* Builds one or more FieldInfos for the field declared in this class.
* @param tree The tree to parse. fieldDeclaration should be the root value.
* @param containingClass The ClassInfo in which this field is contained.
* @return A list of FieldInfos for this field declaration.
*/
private ArrayList<FieldInfo> buildFields(ParseTree tree, ClassInfo containingClass) {
ArrayList<FieldInfo> fields = new ArrayList<FieldInfo>();
Modifiers modifiers = new Modifiers(this);
CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
String name = null;
Object constantValue = null;
TypeInfo type = null;
boolean hasValue = false;
@SuppressWarnings("unchecked")
Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
ParseTree child = it.next();
// modifiers
modifiers.parseModifiers(child);
child = it.next();
// parse the type of this field
type = buildType(child);
child = it.next();
// parse the variable declarators
boolean firstType = true;
while (!";".equals(child.toString())) {
if ("variableDeclarator".equals(child.toString())) {
TypeInfo newType;
if (firstType) {
firstType = false;
newType = type;
} else {
newType = new TypeInfo(type.isPrimitive(), type.dimension(),
type.simpleTypeName(), type.qualifiedTypeName(), type.asClassInfo());
newType.setBounds(type.superBounds(), type.extendsBounds());
newType.setIsWildcard(type.isWildcard());
newType.setIsTypeVariable(type.isTypeVariable());
newType.setTypeArguments(type.typeArguments());
}
name = child.getChild(0).toString();
// if we have a value for the field and/or dimensions
if (child.getChildCount() > 1) {
int j = 1;
ParseTree tmp = (ParseTree) child.getChild(j++);
// if we have dimensions in the wrong place
if ("[".equals(tmp.toString())) {
StringBuilder builder = new StringBuilder();
do {
builder.append(tmp.toString());
tmp = (ParseTree) child.getChild(j++);
} while (j < child.getChildCount() && !"=".equals(tmp.toString()));
newType.setDimension(builder.toString());
}
// get value if it exists
if (j < child.getChildCount()) {
// get to variableInitializer
do {
tmp = (ParseTree) child.getChild(j++);
} while (!"variableInitializer".equals(tmp.toString()));
// get the constantValue
constantValue = parseExpression(tmp);
}
hasValue = true;
}
FieldInfo field = new FieldInfo(name, containingClass, containingClass,
modifiers.isPublic(), modifiers.isProtected(),
modifiers.isPackagePrivate(), modifiers.isPrivate(),
modifiers.isFinal(), modifiers.isStatic(), modifiers.isTransient(),
modifiers.isVolatile(), modifiers.isSynthetic(),
newType, commentAndPosition.getCommentText(), constantValue,
commentAndPosition.getPosition(), modifiers.getAnnotations());
field.setHasValue(hasValue);
fields.add(field);
}
child = it.next();
}
return fields;
}
/**
* Parses an expression in the ParseTree to get a constant value.
* @param tree the place in the tree to get the constant value.
* @return the constant value.
*/
private static Object parseExpression(ParseTree tree) {
Object constantValue = null;
StringBuilder builder = new StringBuilder();
while (!"primary".equals(tree.toString())) {
if (tree.getChildCount() > 1) {
if ("unaryExpression".equals(tree.toString()) ||
"unaryExpressionNotPlusMinus".equals(tree.toString())) {
if ("selector".equals(tree.getChild(1).toString())) {
return constantValue;
}
builder.append(tree.getChild(0));
tree = (ParseTree) tree.getChild(1);
} else if ("arrayInitializer".equals(tree.toString())) {
// TODO - do we wanna parse arrays or just skip it
return constantValue;
} else {
return constantValue;
}
} else if ("castExpression".equals(tree.toString())) {
tree = (ParseTree) tree.getChild(tree.getChildCount()-1);
} else {
tree = (ParseTree) tree.getChild(0);
}
}
if ("literal".equals(tree.getChild(0).toString())) {
constantValue = builder.append(tree.getChild(0).getChild(0).toString()).toString();
} else if (tree.getChildCount() > 1) {
for (Object o : tree.getChildren()) {
builder.append(o.toString());
}
constantValue = builder.toString();
}
return constantValue;
}
/**
* Builds TypeInfo. Requires that tree points to "type" in the ParseTree.
* @param tree The tree to parse. type should be the root value.
* @return A TypeInfo for this type.
*/
private TypeInfo buildType(ParseTree tree) {
boolean isPrimitive = false;
String dimension = null;
String simpleTypeName = null;
String qualifiedTypeName = null;
ClassInfo cl = null;
boolean addResolution = false;
ArrayList<TypeInfo> typeArguments = null;
// parse primitive types - very easy
if ("primitiveType".equals(tree.getChild(0).toString())) {
isPrimitive = true;
simpleTypeName = tree.getChild(0).getChild(0).toString();
qualifiedTypeName = simpleTypeName;
// any non-primitives
} else {
StringBuilder builder = new StringBuilder();
// get the full name of the type
for (Object namePart : ((ParseTree) tree.getChild(0)).getChildren()) {
// if we get to typeArguments, aka generics, parse that and bale out
// of building the name
if ("typeArguments".equals(namePart.toString())) {
typeArguments = buildTypeVariables((ParseTree) namePart);
break;
}
builder.append(namePart.toString());
}
// get simple and qualified name
simpleTypeName = builder.toString();
StringBuilder qualifiedTypeNameBuilder = new StringBuilder();
boolean isGeneric = resolveQualifiedName(simpleTypeName,
qualifiedTypeNameBuilder, this);
qualifiedTypeName = qualifiedTypeNameBuilder.toString();
// if we couldn't figure out the qualified name
// tell us we need to resolve this
// can't add the resolution until the TypeInfo has been created
if ("".equals(qualifiedTypeName)) {
addResolution = true;
// otherwise, if the name is not a generic, get the class that this Type refers to
} else if (!isGeneric) {
cl = Caches.obtainClass(qualifiedTypeName);
}
}
// get the dimensions of this type
dimension = getDimensions(tree);
TypeInfo type = new TypeInfo(isPrimitive, dimension, simpleTypeName, qualifiedTypeName, cl);
type.setTypeArguments(typeArguments);
if (addResolution) {
addFutureResolution(type, "class", simpleTypeName, this);
}
return type;
}
/**
* Processes the type variables of a class that contains generics.
* @param tree Root of the type parameters.
* @param cls Class in which these type variables are contained.
*/
private ArrayList<TypeInfo> buildTypeVariables(ParseTree tree) {
ArrayList<TypeInfo> typeVariables = new ArrayList<TypeInfo>();
ArrayList<TypeInfo> superBounds = new ArrayList<TypeInfo>();
ArrayList<TypeInfo> extendsBounds = new ArrayList<TypeInfo>();
for (Object o : tree.getChildren()) {
// if we're not dealing with a type, skip
// basically gets rid of commas and lessthan and greater than signs
if (!o.toString().equals("typeParameter") &&
!o.toString().equals("typeArgument")) {
continue;
}
ParseTree typeParameter = (ParseTree) o;
TypeInfo type;
// if we have a typeArgument and it is not a wildcard
if ("typeArgument".equals(typeParameter.toString()) &&
!"?".equals(typeParameter.getChild(0).toString())) {
type = buildType((ParseTree) typeParameter.getChild(0));
} else {
// otherwise, we have a wildcard or parameter
// which can be more vague because of generics
String name = typeParameter.getChild(0).toString();
type = new TypeInfo(false, null, name, name, null);
if ("?".equals(name)) {
type.setIsWildcard(true);
} else {
// add generic
mClassNames.add(name);
}
}
// if we have an extends or super on our type variable
if (typeParameter.getChildCount() > 1) {
ParseTree value = (ParseTree) typeParameter.getChild(1);
if ("extends".equals(value.toString())) {
value = (ParseTree) typeParameter.getChild(2);
// wildcard extends
if ("type".equals(value.toString())) {
extendsBounds.add(buildType(value));
// all other extends
} else {
// will have to handle stuff with typeBound - multiple types
for (Object obj : value.getChildren()) {
if ("type".equals(obj.toString())) {
extendsBounds.add(buildType((ParseTree) obj));
}
}
}
} else if ("super".equals(value.toString())) {
superBounds.add(buildType((ParseTree) typeParameter.getChild(2)));
}
}
type.setIsTypeVariable(true);
type.setBounds(superBounds, extendsBounds);
typeVariables.add(type);
}
return typeVariables;
}
/**
* Builds a MethodInfo for methods, constructors and annotation elements.
* @param tree The tree to parse. methodDeclaration, interfaceMethodDeclaration
* or annotationMethodDeclaration should be the root value.
* @param containingClass the class in which this method exists.
* @param isAnnotation true if the class is an annotation element
* @return the MethodInfo
*/
private MethodInfo buildMethod(ParseTree tree, ClassInfo containingClass,
boolean isAnnotation) {
Modifiers modifiers = new Modifiers(this);
CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
String name = null;
StringBuilder flatSignature = new StringBuilder().append('(');
ArrayList<TypeInfo> typeParameters = null;
ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
ArrayList<ClassInfo> thrownExceptions = new ArrayList<ClassInfo>();
TypeInfo returnType = null;
boolean isAnnotationElement = false;
boolean isVarArg = false;
String kind = "method"; // annotationElement, method, or constructor
AnnotationValueInfo elementValue = null;
ArrayList<Resolution> pendingResolutions = new ArrayList<Resolution>();
@SuppressWarnings("unchecked")
Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
ParseTree child = it.next();
modifiers.parseModifiers(child);
child = it.next();
// generics stuff
if ("typeParameters".equals(child.toString())) {
typeParameters = buildTypeVariables(child);
child = it.next();
}
// handle returnType if we're not in a constructor
if ("type".equals(child.toString())) {
returnType = buildType(child);
child = it.next();
} else if ("void".equals(child.toString())) {
returnType = new TypeInfo(true, null, "void", "void", null);
child = it.next();
}
// this is the method name
name = child.toString();
if (name.equals(containingClass.name())) {
kind = "constructor";
}
// probably don't need this check any longer since I unrolled the loop
// if (isConstructorOrMethodName(child)) {
// // this is the method name
// name = child.toString();
//
// if (name.equals(containingClass.name())) {
// kind = "constructor";
// }
// }
child = it.next();
// method parameters
if ("formalParameters".equals(child.toString())) {
isVarArg = buildMethodParameters(child, parameters, flatSignature);
} else {
child = it.next();
}
child = it.next();
flatSignature.append(')');
// handle exception throwing
if ("throws".equals(child.toString())) {
child = it.next();
for (Object o : child.getChildren()) {
if (",".equals(o.toString())) {
continue;
}
// get the name of the exception, resolve it and add it to the list
// unless we can't, in which case, add a resolution
String exceptionName = buildQualifiedName(((ParseTree) o));
StringBuilder exceptionQualifiedName = new StringBuilder();
boolean isGeneric = resolveQualifiedName(exceptionName,
exceptionQualifiedName, this);
if ("".equals(exceptionQualifiedName.toString())) {
pendingResolutions.add(new Resolution("thrownException", exceptionName, null));
} else if (!isGeneric) {
thrownExceptions.add(Caches.obtainClass(exceptionQualifiedName.toString()));
}
}
// handle default values for annotation elements
} else if ("default".equals(child.toString())) {
child = it.next();
elementValue = buildElementValue(child, this);
child = it.next();
}
if (isAnnotation) {
kind = "annotationElement";
}
// Here we set signature, overridden method to null because
// MethodInfo figures these values out later on
MethodInfo method = new MethodInfo(commentAndPosition.getCommentText(), typeParameters,
name, null, containingClass, containingClass, modifiers.isPublic(),
modifiers.isProtected(), modifiers.isPackagePrivate(),
modifiers.isPrivate(), modifiers.isFinal(),
modifiers.isStatic(), modifiers.isSynthetic(),
modifiers.isAbstract(), modifiers.isSynchronized(),
false, modifiers.isDefault(), isAnnotationElement, kind, flatSignature.toString(),
null, returnType, parameters, thrownExceptions,
commentAndPosition.getPosition(), modifiers.getAnnotations());
method.setVarargs(isVarArg);
method.init(elementValue);
for (Resolution r : pendingResolutions) {
addFutureResolution(method, r.getVariable(), r.getValue(), this);
}
return method;
}
/**
* Build the method parameters.
* @param tree The tree to parse. formalParamaters should be the root value.
* @param parameters List to put the method ParamaterInfos into.
* @param flatSignature Pass in a StringBuilder with "(" in it to build the
* flatSignature of the MethodInfo
* @return true if the Method has a VarArgs parameter. false otherwise.
*/
private boolean buildMethodParameters(ParseTree tree,
ArrayList<ParameterInfo> parameters,
StringBuilder flatSignature) {
boolean isVarArg = false;
for (Object obj : tree.getChildren()) {
ParseTree child = (ParseTree) obj;
if ("formalParameterDecls".equals(child.toString())) {
for (Object formalParam : child.getChildren()) {
ParseTree param = (ParseTree) formalParam;
TypeInfo type = null;
if (param.getChildCount() == 0) {
continue;
}
@SuppressWarnings("unchecked")
Iterator<ParseTree> it = (Iterator<ParseTree>) param.getChildren().iterator();
ParseTree paramPart = it.next();
if ("variableModifiers".equals(paramPart.toString())) {
// TODO - handle variable modifiers - final, etc
}
paramPart = it.next();
type = buildType(paramPart);
buildSignatureForType(flatSignature, type);
if (param != child.getChildren().get(child.getChildCount()-1)) {
flatSignature.append(", ");
}
paramPart = it.next();
if ("...".equals(paramPart.toString())) {
isVarArg = true;
// thank you varargs for only being the last parameter
// you make life so much nicer
flatSignature.append("...");
paramPart = it.next();
}
String name = paramPart.toString();
CommentAndPosition commentAndPosition = new CommentAndPosition();
commentAndPosition.setPosition(paramPart);
parameters.add(new ParameterInfo(name, type.qualifiedTypeName(), type,
isVarArg, commentAndPosition.getPosition(),
Collections.<AnnotationInstanceInfo>emptyList()));
}
}
}
return isVarArg;
}
/**
* Builds a StringBuilder representing the Type, including type arguments.
* @param builder StringBuilder in which the Type will be placed.
* @param type the TypeInfo to turn into a String.
*/
private void buildSignatureForType(StringBuilder builder, TypeInfo type) {
// simple name
builder.append(type.simpleTypeName());
// generics
if (type.typeArguments() != null && !type.typeArguments().isEmpty()) {
builder.append('<');
for (TypeInfo inner : type.typeArguments()) {
if (inner != type.typeArguments().get(0)) {
builder.append(", ");
}
// recurse
buildSignatureForType(builder, inner);
}
builder.append('>');
}
}
/**
* Builds a ClassInfo for an enum.
* @param tree The tree to parse. enumDeclaration should be the root value.
* @param containingClass ClassInfo that contains the enum declaration.
* null if the enum is a root class.
* @return the enum as a ClassInfo
*/
private ClassInfo buildEnum(ParseTree tree, ClassInfo containingClass) {
CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
Modifiers modifiers = new Modifiers(this);
ClassInfo cls = null;
@SuppressWarnings("unchecked")
Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
ParseTree child = it.next();
modifiers.parseModifiers(child);
child = it.next();
child = it.next();
cls = buildClassName(child, containingClass, modifiers,
commentAndPosition.getCommentText(),
commentAndPosition.getPosition(), ClassType.ENUM);
child = it.next();
// handle implements
if ("implements".equals(child.toString())) {
child = it.next();
parseInterfaces(child, cls);
child = it.next();
}
buildEnumBody(child, cls);
return cls;
}
/**
* Parses the body of an enum.
* @param tree The tree to parse. enumBody should be the root value.
* @param containingClass ClassInfo to which this enum body pertains.
*/
private void buildEnumBody(ParseTree tree, ClassInfo containingClass) {
for (Object o : tree.getChildren()) {
ParseTree child = (ParseTree) o;
if ("enumConstants".equals(child.toString())) {
for (Object o2 : child.getChildren()) {
ParseTree tmp = (ParseTree) o2;
if ("enumConstant".equals(tmp.toString())) {
containingClass.addEnumConstant(buildEnumConstant(tmp, containingClass));
}
}
} else if ("enumBodyDeclarations".equals(child.toString())) {
buildClassBody(child, containingClass);
}
}
return;
}
/**
* Builds an enum constant.
* @param tree The tree to parse. enumConstant should be the root value.
* @param containingClass ClassInfo to which this enum constant pertains.
* @return
*/
private FieldInfo buildEnumConstant(ParseTree tree, ClassInfo containingClass) {
@SuppressWarnings("unchecked")
Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
ParseTree child = it.next();
Modifiers modifiers = new Modifiers(this);
if ("annotations".equals(child.toString())) {
modifiers.parseModifiers(child);
child = it.next();
}
String name = child.toString();
CommentAndPosition commentAndPosition = new CommentAndPosition();
commentAndPosition.setCommentText(child);
commentAndPosition.setPosition(child);
Object constantValue = null;
// get constantValue if it exists
if (it.hasNext()) {
child = it.next();
// if we have an expressionList
if (child.getChildCount() == 3) {
StringBuilder builder = new StringBuilder();
child = (ParseTree) child.getChild(1); // get the middle child
for (Object o : child.getChildren()) {
if ("expression".equals(o.toString())) {
builder.append(parseExpression((ParseTree) o));
if (o != child.getChild(child.getChildCount()-1)) {
builder.append(", ");
}
}
}
constantValue = builder.toString();
}
}
return new FieldInfo(name, containingClass, containingClass, containingClass.isPublic(),
containingClass.isProtected(), containingClass.isPackagePrivate(),
containingClass.isPrivate(), containingClass.isFinal(),
containingClass.isStatic(), false, false, false,
containingClass.type(), commentAndPosition.getCommentText(),
constantValue, commentAndPosition.getPosition(),
modifiers.getAnnotations());
}
/**
* Builds a ClassInfo for an interface.
* @param tree The tree to parse. normalInterfaceDeclaration should be the root value.
* @param containingClass ClassInfo that contains the interface declaration.
* null if the interface is a root class.
* @return a ClassInfo representing the interface.
*/
private ClassInfo buildInterface(ParseTree tree, ClassInfo containingClass) {
@SuppressWarnings("unchecked")
Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
ParseTree child = it.next();
// parse modifiers and get comment and position
Modifiers modifiers = new Modifiers(this);
modifiers.parseModifiers(child);
CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
it.next();
child = it.next();
// get class name
ClassInfo iface = buildClassName(child, containingClass, modifiers,
commentAndPosition.getCommentText(),
commentAndPosition.getPosition(), ClassType.INTERFACE);
child = it.next();
// parse generics if they exist
if ("typeParameters".equals(child.toString())) {
iface.type().setTypeArguments(buildTypeVariables(child));
child = it.next();
}
// parse interfaces implemented by this interface
if ("extends".equals(child.toString())) {
child = it.next();
parseInterfaces(child, iface);
child = it.next();
}
// finally, build the body of the interface
buildInterfaceBody(child, iface);
return iface;
}
/**
* Parses the body of the interface, adding it to iface.
* @param tree The tree to parse. interfaceBody should be the root value.
* @param iface ClassInfo that will contain all of the interface body.
*/
private void buildInterfaceBody(ParseTree tree, ClassInfo iface) {
for (Object o : tree.getChildren()) {
if (!o.toString().equals("interfaceBodyDeclaration")) {
continue;
}
ParseTree child = (ParseTree) ((ParseTree) o).getChild(0);
if (";".equals(child.toString())) {
continue;
}
// field
if ("interfaceFieldDeclaration".equals(child.toString())) {
for (FieldInfo f : buildFields(child, iface)) {
iface.addField(f);
}
// method
} else if ("interfaceMethodDeclaration".equals(child.toString())) {
iface.addMethod(buildMethod(child, iface, false));
// inner class
} else if ("normalClassDeclaration".equals(child.getChild(0).toString())) {
iface.addInnerClass(buildClass((ParseTree) child.getChild(0), iface));
// inner enum
} else if ("enumDeclaration".equals(child.getChild(0).toString())) {
iface.addInnerClass(buildEnum((ParseTree) child.getChild(0), iface));
// inner interface
} else if ("normalInterfaceDeclaration".equals(child.getChild(0).toString())) {
iface.addInnerClass(buildInterface((ParseTree) child.getChild(0), iface));
// inner annotation
} else if ("annotationTypeDeclaration".equals(child.getChild(0).toString())) {
iface.addInnerClass(buildAnnotationDeclaration(
(ParseTree) child.getChild(0), iface));
}
}
}
/**
* Builds a ClassInfo of an annotation declaration.
* @param tree The tree to parse. annotationTypeDeclaration should be the root value.
* @param containingClass The class that contains this annotation.
* null if this is a root annotation.
* @return the ClassInfo of the annotation declaration.
*/
private ClassInfo buildAnnotationDeclaration(ParseTree tree, ClassInfo containingClass) {
@SuppressWarnings("unchecked")
Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
ParseTree child = it.next();
// get comment and position
CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
// modifiers
Modifiers modifiers = new Modifiers(this);
modifiers.parseModifiers(child);
// three calls to next to skip over @, interface and then
// make child = the name of this annotation
it.next();
it.next();
child = it.next();
// build class name and initialize the class
ClassInfo annotation = buildClassName(child, containingClass, modifiers,
commentAndPosition.getCommentText(),
commentAndPosition.getPosition(), ClassType.INTERFACE);
child = it.next();
// build annotation body
buildAnnotationBody(child, annotation);
return annotation;
}
/**
* Parses the body of the annotation declaration.
* @param tree The tree to parse. annotationTypeBody should be the root value.
* @param annotation the Classinfo in which the annotation elements should be added.
*/
private void buildAnnotationBody(ParseTree tree, ClassInfo annotation) {
for (Object o : tree.getChildren()) {
if (!"annotationTypeElementDeclaration".equals(o.toString())) {
continue;
}
ParseTree child = (ParseTree) ((ParseTree) o).getChild(0);
// annotation fields
if ("interfaceFieldDeclaration".equals(child.toString())) {
for (FieldInfo f : buildFields(child, annotation)) {
annotation.addField(f);
}
// annotation methods
} else if ("annotationMethodDeclaration".equals(child.toString())) {
annotation.addAnnotationElement(buildMethod(child, annotation, true));
// inner class
} else if ("normalClassDeclaration".equals(child.toString())) {
annotation.addInnerClass(buildClass((ParseTree) child, annotation));
// enum
} else if ("enumDeclaration".equals(child.toString())) {
annotation.addInnerClass(buildEnum((ParseTree) child, annotation));
// inner interface
} else if ("normalInterfaceDeclaration".equals(child.toString())) {
annotation.addInnerClass(buildInterface((ParseTree) child, annotation));
// inner annotation
} else if ("annotationTypeDeclaration".equals(child.toString())) {
annotation.addInnerClass(buildAnnotationDeclaration(
(ParseTree) child, annotation));
}
}
}
/**
* Build an annotation instance.
* @param tree The tree to parse. annotation should be the root value.
* @param builder InfoBuilder of this file.
* @return The AnnotationInstanceInfo being parsed.
*/
private static AnnotationInstanceInfo buildAnnotationInstance(ParseTree tree,
InfoBuilder builder) {
@SuppressWarnings("unchecked")
Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
AnnotationInstanceInfo annotationInstance = new AnnotationInstanceInfo();
it.next();
// parse the name, get its full version, and then get the ClassInfo of it, if possible.
String name = InfoBuilder.buildQualifiedName(it.next());
StringBuilder qualifiedNameBuilder = new StringBuilder();
resolveQualifiedName(name, qualifiedNameBuilder, builder);
if ("".equals(qualifiedNameBuilder.toString())) {
addFutureResolution(annotationInstance, "annotationTypeName", name, builder);
annotationInstance.setSimpleAnnotationName(name); // TODO - remove once we've completed the parser
} else { // can't have generics here so we won't do a test
annotationInstance.setClass(Caches.obtainClass(qualifiedNameBuilder.toString()));
}
// at this point, the annotation is either finished or we have more work to do
if (!it.hasNext()) {
return annotationInstance;
}
it.next();
ParseTree child = it.next();
// parse elementValue pairs
if ("elementValuePairs".equals(child.toString())) {
for (Object o : child.getChildren()) {
if (!"elementValuePair".equals(o.toString())) {
continue;
}
ParseTree inner = (ParseTree) o;
MethodInfo element = null;
String methodName = inner.getChild(0).toString();
// try and look up the MethodInfo for this annotation, if possible
if (annotationInstance.type() != null) {
for (MethodInfo m : annotationInstance.type().annotationElements()) {
if (methodName.equals(m.name()) ||
annotationInstance.type().annotationElements().size() == 1) {
element = m;
break;
}
}
}
// go to elementValue
AnnotationValueInfo info = buildElementValue(
(ParseTree) inner.getChild(2), builder);
if (element == null) {
addFutureResolution(info, "element", methodName, builder);
info.setAnnotationInstanceName(name);
} else {
info.setElement(element);
}
annotationInstance.addElementValue(info);
}
// parse element value
} else if ("elementValue".equals(child.toString())) {
annotationInstance.addElementValue(buildElementValue(child, builder));
}
return annotationInstance;
}
/**
* Builds the value of the annotation element.
* @param tree The tree to parse. elementValue should be the root value.
* @param builder InfoBuilder of this file.
* @return AnnotationValueInfo representing the elementValue.
*/
private static AnnotationValueInfo buildElementValue(ParseTree tree, InfoBuilder builder) {
AnnotationValueInfo elementValue = new AnnotationValueInfo();
Object value = null;
// parse some stuff
String str = tree.getChild(0).toString();
if ("conditionalExpression".equals(str)) {
value = parseExpression((ParseTree) tree.getChild(0));
} else if ("annotation".equals(str)) {
value = InfoBuilder.buildAnnotationInstance((ParseTree) tree.getChild(0), builder);
} else if ("elementValueArrayInitializer".equals(str)) {
ParseTree child = (ParseTree) tree.getChild(0);
ArrayList<AnnotationValueInfo> values = new ArrayList<AnnotationValueInfo>();
for (Object o : child.getChildren()) {
if ("elementValue".equals(o.toString())) {
values.add(buildElementValue((ParseTree) o, builder));
}
}
value = values;
}
elementValue.init(value);
return elementValue;
}
/**
* Get the dimensions of the type, as a String.
* @param tree The tree to parse. type should be the root value.
* @return A String of the dimensions of the type.
*/
private String getDimensions(ParseTree tree) {
// we only have dimensions if the count is not 1
if (tree.getChildCount() == 1) {
return null;
}
StringBuilder builder = new StringBuilder();
for (int i = 1; i < tree.getChildCount(); i++) {
builder.append(((ParseTree) tree.getChild(i)).toString());
}
return builder.toString();
}
/**
* When we have data that we can't yet parse, save it for later.
* @param resolvable Resolvable to which the data refers.
* @param variable Variable in the document to which the data refers;
* @param value Value for the variable
* @param builder The InfoBuilder of this file
*/
private static void addFutureResolution(Resolvable resolvable, String variable,
String value, InfoBuilder builder) {
resolvable.addResolution(new Resolution(variable, value, builder));
Caches.addResolvableToCache(resolvable);
}
/**
* Turns a short name of a class into the qualified name of a class.
* StringBuilder will contain an empty string if not found.
* @param name the abbreviated name of the class
* @param qualifiedClassName the qualified name that will be set if found.
* Unchanged if not found.
* @param builder InfoBuilder with all of the file specific information necessary
* to properly resolve the name.
* @return a boolean is returned that will be true if the type is a generic. false otherwise.
*/
public static boolean resolveQualifiedName(String name,
StringBuilder qualifiedClassName,
InfoBuilder builder) {
// steps to figure out a class's real name
// check class(es) in this file
// trying something out. let's see how this works
if (name.indexOf('.') != -1) {
qualifiedClassName.append(name);
return false;
}
// TODO - search since we're now a HashSet
for (String className : builder.getClassNames()) {
int beginIndex = className.lastIndexOf(".") + 1;
if (className.substring(beginIndex).equals(name)) {
qualifiedClassName.append(className);
return qualifiedClassName.toString().equals(name);
}
}
// check package
ClassInfo potentialClass = builder.getPackage().getClass(name);
if (potentialClass != null) {
qualifiedClassName.append(potentialClass.qualifiedName());
return qualifiedClassName.toString().equals(name);
}
potentialClass = null;
String potentialName = null;
// check superclass and interfaces for type
if (builder.getRootClass() != null) {
potentialName = resolveQualifiedNameInInheritedClass(name, builder.getRootClass(),
builder.getRootClass().containingPackage().name());
}
if (potentialName != null) {
qualifiedClassName.append(potentialName);
return false;
}
// check class imports - ie, java.lang.String;
ArrayList<String> packagesToCheck = new ArrayList<String>();
for (String imp : builder.getImports()) {
// +1 to get rid of off by 1 error
String endOfName = imp.substring(imp.lastIndexOf('.') + 1);
if (endOfName.equals(name) || (name.indexOf('.') != -1 &&
endOfName.equals(
name.substring(0, name.lastIndexOf('.'))))) {
qualifiedClassName.append(imp);
return qualifiedClassName.toString().equals(name);
} else if (endOfName.equals("*")) {
// add package to check
packagesToCheck.add(imp.substring(0, imp.lastIndexOf('.')));
} else {
// check inner classes
ClassInfo cl = Caches.obtainClass(imp);
String possibleName = resolveQualifiedInnerName(cl.qualifiedName() + "." + name,
cl);
if (possibleName != null) {
qualifiedClassName.append(possibleName);
return false;
}
}
}
// check package imports - ie, java.lang.*;
for (String packageName : packagesToCheck) {
PackageInfo pkg = Caches.obtainPackage(packageName);
ClassInfo cls = pkg.getClass(name);
if (cls != null && name.equals(cls.name())) {
qualifiedClassName.append(cls.qualifiedName());
return qualifiedClassName.toString().equals(name);
}
}
// including import's inner classes...
// check package of imports...
// TODO - remove
// FROM THE JAVADOC VERSION OF THIS FUNCTION
// Find the specified class or interface within the context of this class doc.
// Search order: 1) qualified name, 2) nested in this class or interface,
// 3) in this package, 4) in the class imports, 5) in the package imports.
// Return the ClassDoc if found, null if not found.
return false;
}
private static String resolveQualifiedNameInInheritedClass(String name, ClassInfo cl,
String originalPackage) {
ArrayList<ClassInfo> classesToCheck = new ArrayList<ClassInfo>();
if (cl != null) {
// if we're in a new package only, check it
if (cl.containingPackage() != null &&
!originalPackage.equals(cl.containingPackage().name())) {
// check for new class
ClassInfo cls = cl.containingPackage().getClass(name);
if (cls != null && name.equals(cls.name())) {
return cls.name();
}
}
if (cl.realSuperclass() != null) {
classesToCheck.add(cl.realSuperclass());
}
if (cl.realInterfaces() != null) {
for (ClassInfo iface : cl.realInterfaces()) {
classesToCheck.add(iface);
}
}
for (ClassInfo cls : classesToCheck) {
String potential = resolveQualifiedNameInInheritedClass(name, cls, originalPackage);
if (potential != null) {
return potential;
}
}
}
return null;
}
private static String resolveQualifiedInnerName(String possibleQualifiedName, ClassInfo cl) {
if (cl.innerClasses() == null) {
return null;
}
for (ClassInfo inner : cl.innerClasses()) {
if (possibleQualifiedName.equals(inner.qualifiedName())) {
return possibleQualifiedName;
}
String name = resolveQualifiedInnerName(possibleQualifiedName + "." + inner.name(),
inner);
if (name != null) {
return name;
}
}
return null;
}
/**
* Parses the tree, looking for the comment and position.
* @param tree The tree to parse.
* @return a CommentAndPosition object containing the comment and position of the element.
*/
private CommentAndPosition parseCommentAndPosition(ParseTree tree) {
Tree child = tree.getChild(0).getChild(0);
// three options (modifiers with annotations, modifiers w/o annotations, no modifiers)
// if there are no modifiers, use tree.getChild(1)
// otherwise, dive as deep as possible into modifiers to get to the comment and position.
child = ("<epsilon>".equals(child.toString())) ? tree.getChild(1) : child;
while (child.getChildCount() > 0) {
child = child.getChild(0);
}
CommentAndPosition cAndP = new CommentAndPosition();
cAndP.setCommentText((ParseTree) child);
cAndP.setPosition((ParseTree) child);
return cAndP;
}
/**
* Private class to facilitate passing the comment and position out of a function.
*/
private class CommentAndPosition {
public String getCommentText() {
return mCommentText;
}
/**
* Parses the tree to get the commentText and set that value.
* @param tree The tree to parse. Should be pointing to the node containing the comment.
*/
public void setCommentText(ParseTree tree) {
if (tree.hiddenTokens != null && !tree.hiddenTokens.isEmpty()) {
mCommentText = ((CommonToken) tree.hiddenTokens.get(0)).getText();
if (mCommentText != null) {
return;
}
}
mCommentText = "";
}
public SourcePositionInfo getPosition() {
return mPosition;
}
/**
* Parses the tree to get the SourcePositionInfo of the node.
* @param tree The tree to parse. Should be pointing to the node containing the position.
*/
public void setPosition(ParseTree tree) {
CommonToken token = (CommonToken) tree.payload;
int line = token.getLine();
int column = token.getCharPositionInLine();
String fileName = ((ANTLRFileStream) token.getInputStream()).getSourceName();
mPosition = new SourcePositionInfo(fileName, line, column);
}
private String mCommentText;
private SourcePositionInfo mPosition;
}
/**
* Private class to handle all the possible modifiers to a class/interface/field/anything else.
*/
private class Modifiers {
private boolean mIsPublic = false;
private boolean mIsProtected = false;
private boolean mIsPackagePrivate = true;
private boolean mIsPrivate = false;
private boolean mIsStatic = false;
private boolean mIsAbstract = false;
private boolean mIsFinal = false;
private boolean mIsTransient = false;
private boolean mIsVolatile = false;
private boolean mIsSynthetic = false;
private boolean mIsSynchronized = false;
private boolean mIsStrictfp = false;
private boolean mIsDefault = false;
private InfoBuilder mBuilder;
private ArrayList<AnnotationInstanceInfo> mAnnotations;
public Modifiers(InfoBuilder builder) {
mAnnotations = new ArrayList<AnnotationInstanceInfo>();
mBuilder = builder;
}
/**
* Parses all of the modifiers of any declaration, including annotations.
* @param tree
*/
public void parseModifiers(ParseTree tree) {
for (Object child : tree.getChildren()) {
String modifier = child.toString();
if ("public".equals(modifier)) {
mIsPublic = true;
mIsPackagePrivate = false;
} else if ("protected".equals(modifier)) {
mIsProtected = true;
mIsPackagePrivate = false;
} else if ("private".equals(modifier)) {
mIsPrivate = true;
mIsPackagePrivate = false;
} else if ("static".equals(modifier)) {
mIsStatic = true;
} else if ("abstract".equals(modifier)) {
mIsAbstract = true;
} else if ("final".equals(modifier)) {
mIsFinal = true;
} else if ("transient".equals(modifier)) {
mIsTransient = true;
} else if ("volatile".equals(modifier)) {
mIsVolatile = true;
} else if ("synthetic".equals(modifier)) {
mIsSynthetic = true;
} else if ("synchronized".equals(modifier)) {
mIsSynchronized = true;
} else if ("strictfp".equals(modifier)) {
mIsStrictfp = true;
} else if ("default".equals(modifier)) {
mIsDefault = true;
} else if ("annotation".equals(modifier)) {
mAnnotations.add(buildAnnotationInstance((ParseTree) child, mBuilder));
}
}
}
public boolean isPublic() {
return mIsPublic;
}
public boolean isProtected() {
return mIsProtected;
}
public boolean isPackagePrivate() {
return mIsPackagePrivate;
}
public boolean isPrivate() {
return mIsPrivate;
}
public boolean isStatic() {
return mIsStatic;
}
public boolean isAbstract() {
return mIsAbstract;
}
public boolean isFinal() {
return mIsFinal;
}
public boolean isTransient() {
return mIsTransient;
}
public boolean isVolatile() {
return mIsVolatile;
}
public boolean isSynthetic() {
return mIsSynthetic;
}
public boolean isSynchronized() {
return mIsSynchronized;
}
@SuppressWarnings("unused")
public boolean isStrictfp() {
return mIsStrictfp;
}
public boolean isDefault() {
return mIsDefault;
}
public ArrayList<AnnotationInstanceInfo> getAnnotations() {
return mAnnotations;
}
};
/**
* Singleton class to store all of the global data amongst every InfoBuilder.
*/
public static class Caches {
private static HashMap<String, PackageInfo> mPackages
= new HashMap<String, PackageInfo>();
private static HashMap<String, ClassInfo> mClasses
= new HashMap<String, ClassInfo>();
private static HashSet<Resolvable> mInfosToResolve
= new HashSet<Resolvable>();
public static PackageInfo obtainPackage(String packageName) {
PackageInfo pkg = mPackages.get(packageName);
if (pkg == null) {
pkg = new PackageInfo(packageName);
mPackages.put(packageName, pkg);
}
return pkg;
}
/**
* Gets the ClassInfo from the master list or creates a new one if it does not exist.
* @param qualifiedClassName Qualified name of the ClassInfo to obtain.
* @return the ClassInfo
*/
public static ClassInfo obtainClass(String qualifiedClassName) {
ClassInfo cls = mClasses.get(qualifiedClassName);
if (cls == null) {
cls = new ClassInfo(qualifiedClassName);
mClasses.put(cls.qualifiedName(), cls);
}
return cls;
}
/**
* Gets the ClassInfo from the master list or returns null if it does not exist.
* @param qualifiedClassName Qualified name of the ClassInfo to obtain.
* @return the ClassInfo or null, if the ClassInfo does not exist.
*/
public static ClassInfo getClass(String qualifiedClassName) {
return mClasses.get(qualifiedClassName);
}
public static void addResolvableToCache(Resolvable resolvable) {
mInfosToResolve.add(resolvable);
}
public static void printResolutions() {
if (mInfosToResolve.isEmpty()) {
System.out.println("We've resolved everything.");
return;
}
for (Resolvable r : mInfosToResolve) {
r.printResolutions();
System.out.println();
}
}
public static void resolve() {
HashSet<Resolvable> resolveList = mInfosToResolve;
mInfosToResolve = new HashSet<Resolvable>();
for (Resolvable r : resolveList) {
// if we could not resolve everything in this class
if (!r.resolveResolutions()) {
mInfosToResolve.add(r);
}
System.out.println();
}
}
}
public PackageInfo getPackage() {
return mPackage;
}
public ArrayList<String> getImports() {
return mImports;
}
public HashSet<String> getClassNames() {
return mClassNames;
}
public ClassInfo getRootClass() {
return mRootClass;
}
}