/* * 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.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 mImports; private HashSet mClassNames; private String mFilename; // TODO - remove this eventually private ClassInfo mRootClass; public InfoBuilder(String filename) { mImports = new ArrayList(); 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(); 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 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 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 annotations) { for (AnnotationInstanceInfo i : annotations) { System.out.println(i); } } private static void printTypeVariables(TypeInfo type) { printTypeVariableList(type.typeArguments()); } private static void printTypeVariableList(ArrayList 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 it = (Iterator) 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. * null 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 buildFields(ParseTree tree, ClassInfo containingClass) { ArrayList fields = new ArrayList(); Modifiers modifiers = new Modifiers(this); CommentAndPosition commentAndPosition = parseCommentAndPosition(tree); String name = null; Object constantValue = null; TypeInfo type = null; boolean hasValue = false; @SuppressWarnings("unchecked") Iterator it = (Iterator) 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 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 buildTypeVariables(ParseTree tree) { ArrayList typeVariables = new ArrayList(); ArrayList superBounds = new ArrayList(); ArrayList extendsBounds = new ArrayList(); 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 typeParameters = null; ArrayList parameters = new ArrayList(); ArrayList thrownExceptions = new ArrayList(); TypeInfo returnType = null; boolean isAnnotationElement = false; boolean isVarArg = false; String kind = "method"; // annotationElement, method, or constructor AnnotationValueInfo elementValue = null; ArrayList pendingResolutions = new ArrayList(); @SuppressWarnings("unchecked") Iterator it = (Iterator) 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, 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 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 it = (Iterator) 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())); } } } 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 it = (Iterator) 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 it = (Iterator) 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 it = (Iterator) 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 it = (Iterator) 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 it = (Iterator) 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 values = new ArrayList(); 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 packagesToCheck = new ArrayList(); 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 classesToCheck = new ArrayList(); 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 = ("".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 InfoBuilder mBuilder; private ArrayList mAnnotations; public Modifiers(InfoBuilder builder) { mAnnotations = new ArrayList(); 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 ("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 ArrayList getAnnotations() { return mAnnotations; } }; /** * Singleton class to store all of the global data amongst every InfoBuilder. */ public static class Caches { private static HashMap mPackages = new HashMap(); private static HashMap mClasses = new HashMap(); private static HashSet mInfosToResolve = new HashSet(); 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 resolveList = mInfosToResolve; mInfosToResolve = new HashSet(); 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 getImports() { return mImports; } public HashSet getClassNames() { return mClassNames; } public ClassInfo getRootClass() { return mRootClass; } }