1 /*
2  * Copyright (C) 2011 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.doclava;
18 
19 import com.google.doclava.parser.JavaLexer;
20 import com.google.doclava.parser.JavaParser;
21 
22 import org.antlr.runtime.ANTLRFileStream;
23 import org.antlr.runtime.CommonToken;
24 import org.antlr.runtime.CommonTokenStream;
25 import org.antlr.runtime.RecognitionException;
26 import org.antlr.runtime.debug.ParseTreeBuilder;
27 import org.antlr.runtime.tree.ParseTree;
28 import org.antlr.runtime.tree.Tree;
29 
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Iterator;
36 
37 /**
38  * InfoBuilder parses an individual file and builds Doclava
39  * objects out of the data within the file. This data is
40  * stored within a global cache for later use.
41  */
42 public class InfoBuilder {
43     private PackageInfo mPackage;
44     private ArrayList<String> mImports;
45     private HashSet<String> mClassNames;
46     private String mFilename; // TODO - remove this eventually
47     private ClassInfo mRootClass;
48 
InfoBuilder(String filename)49     public InfoBuilder(String filename) {
50         mImports = new ArrayList<String>();
51         mImports.add("java.lang.*"); // should allow us to resolve this properly, eventually
52                                      // alternatively, we could add everything from java.lang.*
53                                      // but that would probably be too brittle
54         mClassNames = new HashSet<String>();
55         mFilename = filename;
56     }
57 
58     @Override
toString()59     public String toString() {
60         return mFilename;
61     }
62 
parseFile()63     public void parseFile() {
64         JavaLexer lex;
65         try {
66             lex = new JavaLexer(new ANTLRFileStream(mFilename, "UTF8"));
67 
68             CommonTokenStream tokens = new CommonTokenStream(lex);
69 
70             // create the ParseTreeBuilder to build a parse tree
71             // much easier to parse than ASTs
72             ParseTreeBuilder builder = new ParseTreeBuilder("compilationUnit");
73             JavaParser g = new JavaParser(tokens, builder);
74 
75             g.compilationUnit();
76             ParseTree tree = builder.getTree();
77 
78             lex = null;
79             tokens = null;
80             builder = null;
81             g = null;
82 
83             parseFile(tree);
84 
85         } catch (IOException e1) {
86             e1.printStackTrace();
87         } catch (RecognitionException e) {
88             e.printStackTrace();
89         }
90     }
91 
resolve()92     public static void resolve() {
93         Caches.resolve();
94     }
95 
96     // All of the print functions exist for debugging alone.
printStuff()97     public void printStuff() {
98         System.out.println(mPackage.name() + "\n");
99 
100         printList(mImports);
101 
102         Caches.printResolutions();
103     }
104 
printList(ArrayList<String> list)105     private void printList(ArrayList<String> list) {
106         for (String value : list) {
107             System.out.println(value);
108         }
109 
110         System.out.println();
111     }
112 
printClassInfo(ClassInfo cl)113     public static void printClassInfo(ClassInfo cl) {
114         System.out.print("Class: " + cl.toString());
115 
116         printTypeVariables(cl.type());
117 
118         System.out.println();
119 
120         System.out.println(cl.comment().mText);
121 
122         if (!cl.annotations().isEmpty()) {
123             System.out.println("\nAnnotations:");
124             printAnnotations(cl.annotations());
125         }
126 
127         if (cl.superclass() != null) {
128             System.out.print("Superclass: " + cl.superclass().qualifiedName());
129             printTypeVariables(cl.superclassType());
130             System.out.println();
131         }
132 
133         if (!cl.realInterfaces().isEmpty()) {
134             System.out.println("\nInterfaces Implemented:");
135             Iterator<TypeInfo> it = cl.realInterfaceTypes().iterator();
136             for (ClassInfo cls : cl.realInterfaces()) {
137                 TypeInfo outerType = it.next();
138                 if (cls == null) {
139                     System.out.print(outerType.simpleTypeName());
140                 } else {
141                     System.out.print(cls.qualifiedName());
142                 }
143 
144                 printTypeVariables(outerType);
145 
146                 System.out.println();
147             }
148 
149             System.out.println();
150         }
151 
152         if (!cl.allSelfFields().isEmpty()) {
153             System.out.println("\nFields:");
154             for (FieldInfo f : cl.allSelfFields()) {
155                 if (f != cl.allSelfFields().get(0)) {
156                     System.out.println();
157                 }
158                 System.out.println(f.comment().mText);
159 
160                 printAnnotations(f.annotations());
161                 printTypeName(f.type());
162 
163                 System.out.print(" " + f.name());
164 
165                 if (f.constantValue() != null) {
166                     System.out.println(": " + f.constantValue());
167                 } else if (f.hasValue()) {
168                     System.out.println(": has some value");
169                 } else {
170                     System.out.println();
171                 }
172             }
173 
174             System.out.println();
175         }
176 
177         if (cl.enumConstants() != null && !cl.enumConstants().isEmpty()) {
178             System.out.println("\nEnum Constants:");
179             for (FieldInfo f : cl.enumConstants()) {
180                 if (f != cl.enumConstants().get(0)) {
181                     System.out.println();
182                 }
183                 System.out.println(f.comment().mText);
184                 printAnnotations(f.annotations());
185                 System.out.print(f.type().simpleTypeName() + " " + f.name());
186 
187                 if (f.constantValue() != null) {
188                     System.out.println(": " + f.constantValue());
189                 } else {
190                     System.out.println();
191                 }
192             }
193 
194             System.out.println();
195         }
196 
197         if (!cl.allConstructors().isEmpty()) {
198             System.out.println("\nConstructors:");
199             for (MethodInfo m : cl.allConstructors()) {
200                 if (m != cl.allConstructors().get(0)) {
201                     System.out.println();
202                 }
203 
204                 System.out.println(m.comment().mText);
205 
206                 printAnnotations(m.annotations());
207                 if (m.getTypeParameters() != null) {
208                     printTypeVariableList(m.getTypeParameters());
209                     System.out.print(" ");
210                 }
211 
212                 System.out.println(m.name() + m.flatSignature());
213             }
214 
215             System.out.println();
216         }
217 
218         if (!cl.allSelfMethods().isEmpty()) {
219             System.out.println("\nMethods:");
220             for (MethodInfo m : cl.allSelfMethods()) {
221                 if (m != cl.allSelfMethods().get(0)) {
222                     System.out.println();
223                 }
224 
225                 System.out.println(m.comment().mText);
226                 printAnnotations(m.annotations());
227                 if (m.getTypeParameters() != null) {
228                     printTypeVariableList(m.getTypeParameters());
229                     System.out.print(" ");
230                 }
231 
232                 printTypeName(m.returnType());
233 
234                 System.out.print(" " + m.name() + m.flatSignature());
235 
236                 if (m.thrownExceptions() != null && !m.thrownExceptions().isEmpty()) {
237                     System.out.print(" throws ");
238                     for (ClassInfo c : m.thrownExceptions()) {
239                         if (c != m.thrownExceptions().get(0)) {
240                             System.out.print(", ");
241                         }
242 
243                         System.out.print(c.name());
244                     }
245                 }
246 
247                 System.out.println();
248             }
249 
250             System.out.println();
251         }
252 
253         if (!cl.annotationElements().isEmpty()) {
254             System.out.println("\nAnnotation Elements:");
255 
256             for (MethodInfo m : cl.annotationElements()) {
257                 if (m != cl.annotationElements().get(0)) {
258                     System.out.println();
259                 }
260 
261                 System.out.println(m.comment().mText);
262                 printAnnotations(m.annotations());
263                 printTypeName(m.returnType());
264 
265                 System.out.print(" " + m.name() + m.flatSignature());
266 
267                 if (m.defaultAnnotationElementValue() != null) {
268                     System.out.print(" default " +
269                             m.defaultAnnotationElementValue().valueString());
270                 }
271 
272                 System.out.println();
273             }
274 
275             System.out.println();
276         }
277 
278         if (cl.innerClasses() != null && !cl.innerClasses().isEmpty()) {
279             System.out.println("\nInner Classes:");
280             for (ClassInfo c : cl.innerClasses()) {
281                 printClassInfo(c);
282             }
283         }
284     }
285 
printTypeName(TypeInfo type)286     private static void printTypeName(TypeInfo type) {
287         System.out.print(type.simpleTypeName());
288 
289         if (type.extendsBounds() != null && !type.extendsBounds().isEmpty()) {
290             System.out.print(" extends ");
291             for (TypeInfo t : type.extendsBounds()) {
292                 if (t != type.extendsBounds().get(0)) {
293                     System.out.print(" & ");
294                 }
295                 printTypeName(t);
296             }
297         }
298 
299         if (type.superBounds() != null && !type.superBounds().isEmpty()) {
300             System.out.print(" super ");
301             for (TypeInfo t : type.superBounds()) {
302                 if (t != type.superBounds().get(0)) {
303                     System.out.print(" & ");
304                 }
305                 printTypeName(t);
306             }
307         }
308 
309         printTypeVariables(type);
310 
311         if (type.dimension() != null) {
312             System.out.print(type.dimension());
313         }
314     }
315 
printAnnotations(ArrayList<AnnotationInstanceInfo> annotations)316     private static void printAnnotations(ArrayList<AnnotationInstanceInfo> annotations) {
317         for (AnnotationInstanceInfo i : annotations) {
318             System.out.println(i);
319         }
320     }
321 
printTypeVariables(TypeInfo type)322     private static void printTypeVariables(TypeInfo type) {
323         printTypeVariableList(type.typeArguments());
324     }
325 
printTypeVariableList(ArrayList<TypeInfo> typeList)326     private static void printTypeVariableList(ArrayList<TypeInfo> typeList) {
327         if (typeList != null && !typeList.isEmpty()) {
328             System.out.print("<");
329             for (TypeInfo type : typeList) {
330                 if (type != typeList.get(0)) {
331                     System.out.print(", ");
332                 }
333                 printTypeName(type);
334             }
335             System.out.print(">");
336         }
337     }
338 
339     /**
340      * Parses the file represented by the ParseTree.
341      * @param tree A ParseTree of the file to parse.
342      */
parseFile(ParseTree tree)343     private void parseFile(ParseTree tree) {
344         if (tree.payload != null) {
345             String payload = tree.payload.toString();
346 
347             // first pass at ignore method blocks
348             if ("block".equals(payload) ||
349                     "blockStatement".equals(payload) ||
350                     "explicitConstructorInvocation".equals(payload)) {
351                 tree = null;
352                 return;
353             }
354 
355             // parse package of file
356             if ("packageDeclaration".equals(payload)) {
357                 mPackage = buildPackage(tree);
358                 return;
359             // parse imports
360             } else if ("importDeclaration".equals(payload)) {
361                 mImports.add(buildImport(tree));
362                 return;
363             // classes
364             } else if ("normalClassDeclaration".equals(payload)) {
365                 buildClass(tree, null);
366                 return;
367             // enums
368             }  else if ("enumDeclaration".equals(payload)) {
369                 buildEnum(tree, null);
370                 return;
371             // interfaces
372             } else if ("normalInterfaceDeclaration".equals(payload)) {
373                 buildInterface(tree, null);
374                 return;
375             // annotations
376             } else if ("annotationTypeDeclaration".equals(payload)) {
377                 buildAnnotationDeclaration(tree, null);
378                 return;
379             }
380         }
381 
382         // if we're not at the end, recurse down the tree
383         for (int i = 0; i < tree.getChildCount(); i++) {
384             parseFile((ParseTree) tree.getChild(i));
385         }
386     }
387 
388     /**
389      * Parses a packageDeclaration in the tree. This function should only be called once per file.
390      * @param tree The tree to parse. packageDeclaration should be the root value.
391      * @return a PackageInfo representing the package in which this file exists.
392      */
buildPackage(ParseTree tree)393     private PackageInfo buildPackage(ParseTree tree) {
394         for (int i = 0; i < tree.getChildCount(); i++) {
395             ParseTree child = (ParseTree) tree.getChild(i);
396 
397             if (child.payload != null && "qualifiedName".equals(child.payload.toString())) {
398                 String packageName = buildQualifiedName(child);
399 
400                 // return package because we might be creating packages for other classes
401                 return Caches.obtainPackage(packageName);
402             }
403         }
404 
405         return null;
406     }
407 
408     /**
409      * Parses a qualifiedName, returning it as a String.
410      * @param tree The tree to parse. qualifiedName should be the root value.
411      * @return
412      */
buildQualifiedName(ParseTree tree)413     private static String buildQualifiedName(ParseTree tree) {
414         StringBuilder packageName = new StringBuilder();
415 
416         for (int j = 0; j < tree.getChildCount(); j++) {
417             packageName.append(tree.getChild(j).toString());
418         }
419 
420         return packageName.toString();
421     }
422 
423     /**
424      * Builds a string representing an import declaration.
425      * @param tree The tree to parse. importDeclaration should be the root value.
426      * @return a String version of the import.
427      */
buildImport(ParseTree tree)428     private String buildImport(ParseTree tree) {
429         StringBuilder theImport = new StringBuilder();
430         for (int i = 1; i < tree.getChildCount(); i++) {
431             String part = tree.getChild(i).toString();
432 
433             if ((i == 1 && "static".equals(part))
434                     || (i == tree.getChildCount()-1 && ";".equals(part))) {
435                 continue;
436             }
437 
438             theImport.append(part);
439         }
440 
441         return theImport.toString();
442     }
443 
444     /**
445      * Builds a ClassInfo for a normalClassDeclaration.
446      * @param tree The tree to parse. normalClassDeclaration should be the root value.
447      * @param containingClass The class that contains the class that will be built.
448      * This value should be null if this class is a root class in the file.
449      * @return A ClassInfo that contains all of the information about the class.
450      */
buildClass(ParseTree tree, ClassInfo containingClass)451     private ClassInfo buildClass(ParseTree tree, ClassInfo containingClass) {
452         CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
453         Modifiers modifiers = new Modifiers(this);
454         ClassInfo cls = null;
455 
456         @SuppressWarnings("unchecked")
457         Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
458         ParseTree child = it.next();
459 
460         // parse modifiers
461         modifiers.parseModifiers(child);
462 
463         it.next();
464         child = it.next();
465 
466         // parse class name
467         cls = buildClassName(child, containingClass, modifiers,
468                 commentAndPosition.getCommentText(),
469                 commentAndPosition.getPosition(),
470                 ClassType.ORDINARY);
471 
472         child = it.next();
473 
474         // handle generics
475         if ("typeParameters".equals(child.toString())) {
476             cls.type().setTypeArguments(buildTypeVariables(child));
477             child = it.next();
478 
479         }
480 
481         // handle extends
482         if ("extends".equals(child.toString())) {
483             child = it.next();
484 
485             TypeInfo type = buildType(child);
486             cls.setSuperclassType(type);
487 
488             // if ClassInfo is null, we need to add a resolution
489             if (type.asClassInfo() == null) {
490                 addFutureResolution(cls, "superclassQualifiedName", type.simpleTypeName(), this);
491             }
492 
493             cls.setSuperClass(type.asClassInfo());
494 
495             child = it.next();
496         }
497 
498         // TODO - do I have to make java.lang.Object the superclass if there is none otherwise?
499 
500         // handle implements
501         if ("implements".equals(child.toString())) {
502             child = it.next();
503 
504             parseInterfaces(child, cls);
505 
506             child = it.next();
507         }
508 
509         // finally, parse the body
510         buildClassBody(child, cls);
511 
512         return cls;
513     }
514 
515     /**
516      * Parses the list of interfaces that the class implements.
517      * Should only be called if the implements keyword is found.
518      * @param tree The tree to parse. typeList should be the root element.
519      * @param cls The class that implements these interfaces.
520      */
parseInterfaces(ParseTree tree, ClassInfo cls)521     private void parseInterfaces(ParseTree tree, ClassInfo cls) {
522         for (Object o : tree.getChildren()) {
523             if ("type".equals(o.toString())) {
524                 TypeInfo type = buildType((ParseTree) o);
525                 cls.addInterfaceType(type);
526 
527                 // if ClassInfo is null, we need to add a resolution
528                 if (type.asClassInfo() == null) {
529                     addFutureResolution(cls, "interfaceQualifiedName", type.simpleTypeName(), this);
530                 }
531 
532                 cls.addInterface(type.asClassInfo());
533             }
534         }
535     }
536 
537     /**
538      * ClassType exists solely to tell buildClassName which type of ClassInfo is being built.
539      */
540     private enum ClassType {
541         ENUM, INTERFACE, ANNOTATION, ORDINARY
542     }
543 
544     /**
545      * Parses the class name from the declaration. Also initializes the class.
546      * @param tree Position of the tree where the name of the class resides.
547      * @param containingClass Class that this class is contained within.
548      * <tt>null</tt> if this class is the root class.
549      * @param modifiers Contains all the modifiers of this class.
550      * @param commentText Javadoc comment of this class.
551      * @param position Position of the class.
552      * @param classType Type of class being instantiated.
553      * @return the ClassInfo being initialized.
554      */
buildClassName(ParseTree tree, ClassInfo containingClass, Modifiers modifiers, String commentText, SourcePositionInfo position, ClassType classType)555     private ClassInfo buildClassName(ParseTree tree, ClassInfo containingClass, Modifiers modifiers,
556             String commentText, SourcePositionInfo position, ClassType classType) {
557         String qualifiedClassName = null;
558         boolean isOrdinaryClass = true;
559         boolean isException = false;
560         boolean isError = false;
561         boolean isIncluded = false;
562         boolean isPrimitive = false;
563         boolean isEnum = false;
564         boolean isInterface = false;
565         boolean isAnnotation = false;
566 
567         // set appropriate flags based on ClassType
568         switch (classType) {
569             case ENUM:
570                 isEnum = true;
571                 break;
572             case INTERFACE:
573                 isInterface = true;
574                 break;
575             case ANNOTATION:
576                 isAnnotation = true;
577                 break;
578         }
579 
580         String qualifiedTypeName = null;
581         ClassInfo cls = null;
582 
583         // changes the name based upon whether this is the root class or an inner class
584         if (containingClass == null) {
585             qualifiedClassName = mPackage.name() + "." + tree.toString();
586         } else {
587             qualifiedClassName = containingClass.qualifiedName() + "." + tree.toString();
588         }
589 
590         qualifiedTypeName = new String(qualifiedClassName);
591 
592         // add the name to mClassNames so that we can use it to resolve usages of this class
593         mClassNames.add(qualifiedClassName);
594 
595         // get the class from the cache and initialize it
596         cls = Caches.obtainClass(qualifiedClassName);
597         cls.initialize(commentText, position,
598                 modifiers.isPublic(), modifiers.isProtected(),
599                 modifiers.isPackagePrivate(), modifiers.isPrivate(),
600                 modifiers.isStatic(), isInterface, modifiers.isAbstract(),
601                 isOrdinaryClass, isException, isError, isEnum, isAnnotation,
602                 modifiers.isFinal(), isIncluded, qualifiedTypeName, isPrimitive,
603                 modifiers.getAnnotations());
604 
605         cls.setContainingClass(containingClass);
606         cls.setContainingPackage(mPackage);
607 
608         if (containingClass == null) {
609             mRootClass = cls;
610         }
611 
612         // create an set a TypeInfo for this class
613         TypeInfo type = new TypeInfo(false, null, cls.name(), qualifiedTypeName, cls);
614         cls.setTypeInfo(type);
615 
616         return cls;
617     }
618 
619     /**
620      * Parses the body of a class.
621      * @param tree The tree to parse. classBody should be the root value.
622      * @param cls
623      */
buildClassBody(ParseTree tree, ClassInfo cls)624     private void buildClassBody(ParseTree tree, ClassInfo cls) {
625         for (Object o : tree.getChildren()) {
626             ParseTree child = (ParseTree) o;
627 
628             // skip all of the cruft that isn't a declaration
629             if (!"classBodyDeclaration".equals(child.toString())) {
630                 continue;
631             }
632 
633             // get to an actual definition
634             ParseTree member = (ParseTree) child.getChild(0).getChild(0);
635 
636             // ignores static initializers
637             if (member == null) {
638                 continue;
639             }
640 
641             // field
642             if ("fieldDeclaration".equals(member.toString())) {
643                 for (FieldInfo f : buildFields(member, cls)) {
644                     cls.addField(f);
645                 }
646             // method and constructor
647             } else if ("methodDeclaration".equals(member.toString())) {
648                 MethodInfo method = buildMethod(member, cls, false);
649 
650                 if (method.kind().equals("constructor")) {
651                     cls.addConstructor(method);
652                 } else {
653                     cls.addMethod(method);
654                 }
655             // classes and enums
656             } else if ("classDeclaration".equals(member.toString())) {
657                 Object tmp = member.getChild(0);
658 
659                 if ("normalClassDeclaration".equals(tmp.toString())) {
660                     cls.addInnerClass(buildClass((ParseTree) tmp, cls));
661                 } else if ("enumDeclaration".equals(tmp.toString())) {
662                     cls.addInnerClass(buildEnum((ParseTree) tmp, cls));
663                 }
664             // interfaces and annotations
665             } else if ("interfaceDeclaration".equals(member.toString())) {
666                 Object tmp = member.getChild(0);
667 
668                 if ("normalInterfaceDeclaration".equals(tmp.toString())) {
669                     cls.addInnerClass(buildInterface((ParseTree) tmp, cls));
670                 } else if ("annotationTypeDeclaration".equals(tmp.toString())) {
671                     cls.addInnerClass(buildAnnotationDeclaration((ParseTree) tmp, cls));
672                 }
673             }
674         }
675     }
676 
677     /**
678      * Builds one or more FieldInfos for the field declared in this class.
679      * @param tree The tree to parse. fieldDeclaration should be the root value.
680      * @param containingClass The ClassInfo in which this field is contained.
681      * @return A list of FieldInfos for this field declaration.
682      */
buildFields(ParseTree tree, ClassInfo containingClass)683     private ArrayList<FieldInfo> buildFields(ParseTree tree, ClassInfo containingClass) {
684         ArrayList<FieldInfo> fields = new ArrayList<FieldInfo>();
685         Modifiers modifiers = new Modifiers(this);
686         CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
687         String name = null;
688         Object constantValue = null;
689         TypeInfo type = null;
690         boolean hasValue = false;
691 
692         @SuppressWarnings("unchecked")
693         Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
694         ParseTree child = it.next();
695 
696         // modifiers
697         modifiers.parseModifiers(child);
698         child = it.next();
699 
700         // parse the type of this field
701         type = buildType(child);
702 
703         child = it.next();
704 
705         // parse the variable declarators
706         boolean firstType = true;
707         while (!";".equals(child.toString())) {
708             if ("variableDeclarator".equals(child.toString())) {
709                 TypeInfo newType;
710                 if (firstType) {
711                     firstType = false;
712                     newType = type;
713                 } else {
714                     newType = new TypeInfo(type.isPrimitive(), type.dimension(),
715                             type.simpleTypeName(), type.qualifiedTypeName(), type.asClassInfo());
716                     newType.setBounds(type.superBounds(), type.extendsBounds());
717                     newType.setIsWildcard(type.isWildcard());
718                     newType.setIsTypeVariable(type.isTypeVariable());
719                     newType.setTypeArguments(type.typeArguments());
720                 }
721                 name = child.getChild(0).toString();
722 
723                 // if we have a value for the field and/or dimensions
724                 if (child.getChildCount() > 1) {
725                     int j = 1;
726                     ParseTree tmp = (ParseTree) child.getChild(j++);
727 
728                     // if we have dimensions in the wrong place
729                     if ("[".equals(tmp.toString())) {
730                         StringBuilder builder = new StringBuilder();
731 
732                         do {
733                             builder.append(tmp.toString());
734                             tmp = (ParseTree) child.getChild(j++);
735                         } while (j < child.getChildCount() && !"=".equals(tmp.toString()));
736 
737                         newType.setDimension(builder.toString());
738                     }
739 
740                     // get value if it exists
741                     if (j < child.getChildCount()) {
742                         // get to variableInitializer
743                         do {
744                             tmp = (ParseTree) child.getChild(j++);
745                         } while (!"variableInitializer".equals(tmp.toString()));
746 
747                         // get the constantValue
748                         constantValue = parseExpression(tmp);
749                     }
750 
751                     hasValue = true;
752                 }
753 
754                 FieldInfo field = new FieldInfo(name, containingClass, containingClass,
755                         modifiers.isPublic(), modifiers.isProtected(),
756                         modifiers.isPackagePrivate(), modifiers.isPrivate(),
757                         modifiers.isFinal(), modifiers.isStatic(), modifiers.isTransient(),
758                         modifiers.isVolatile(), modifiers.isSynthetic(),
759                         newType, commentAndPosition.getCommentText(), constantValue,
760                         commentAndPosition.getPosition(), modifiers.getAnnotations());
761                 field.setHasValue(hasValue);
762                 fields.add(field);
763             }
764 
765             child = it.next();
766         }
767 
768         return fields;
769     }
770 
771     /**
772      * Parses an expression in the ParseTree to get a constant value.
773      * @param tree the place in the tree to get the constant value.
774      * @return the constant value.
775      */
parseExpression(ParseTree tree)776     private static Object parseExpression(ParseTree tree) {
777         Object constantValue = null;
778         StringBuilder builder = new StringBuilder();
779 
780         while (!"primary".equals(tree.toString())) {
781             if (tree.getChildCount() > 1) {
782                 if ("unaryExpression".equals(tree.toString()) ||
783                         "unaryExpressionNotPlusMinus".equals(tree.toString())) {
784                     if ("selector".equals(tree.getChild(1).toString())) {
785                         return constantValue;
786                     }
787 
788                     builder.append(tree.getChild(0));
789                     tree = (ParseTree) tree.getChild(1);
790                 } else if ("arrayInitializer".equals(tree.toString())) {
791                     // TODO - do we wanna parse arrays or just skip it
792                     return constantValue;
793                 } else {
794                     return constantValue;
795                 }
796             } else if ("castExpression".equals(tree.toString())) {
797                 tree = (ParseTree) tree.getChild(tree.getChildCount()-1);
798             } else {
799                 tree = (ParseTree) tree.getChild(0);
800             }
801         }
802 
803         if ("literal".equals(tree.getChild(0).toString())) {
804             constantValue = builder.append(tree.getChild(0).getChild(0).toString()).toString();
805         } else if (tree.getChildCount() > 1) {
806             for (Object o : tree.getChildren()) {
807                 builder.append(o.toString());
808             }
809 
810             constantValue = builder.toString();
811         }
812 
813         return constantValue;
814     }
815 
816     /**
817      * Builds  TypeInfo. Requires that tree points to "type" in the ParseTree.
818      * @param tree The tree to parse. type should be the root value.
819      * @return A TypeInfo for this type.
820      */
buildType(ParseTree tree)821     private TypeInfo buildType(ParseTree tree) {
822         boolean isPrimitive = false;
823         String dimension = null;
824         String simpleTypeName = null;
825         String qualifiedTypeName = null;
826         ClassInfo cl = null;
827         boolean addResolution = false;
828         ArrayList<TypeInfo> typeArguments = null;
829 
830         // parse primitive types - very easy
831         if ("primitiveType".equals(tree.getChild(0).toString())) {
832             isPrimitive = true;
833 
834             simpleTypeName = tree.getChild(0).getChild(0).toString();
835             qualifiedTypeName = simpleTypeName;
836         // any non-primitives
837         } else {
838             StringBuilder builder = new StringBuilder();
839 
840             // get the full name of the type
841             for (Object namePart : ((ParseTree) tree.getChild(0)).getChildren()) {
842                 // if we get to typeArguments, aka generics, parse that and bale out
843                 // of building the name
844                 if ("typeArguments".equals(namePart.toString())) {
845                     typeArguments = buildTypeVariables((ParseTree) namePart);
846                     break;
847                 }
848 
849                 builder.append(namePart.toString());
850             }
851 
852             // get simple and qualified name
853             simpleTypeName = builder.toString();
854             StringBuilder qualifiedTypeNameBuilder = new StringBuilder();
855             boolean isGeneric = resolveQualifiedName(simpleTypeName,
856                     qualifiedTypeNameBuilder, this);
857             qualifiedTypeName = qualifiedTypeNameBuilder.toString();
858 
859             // if we couldn't figure out the qualified name
860             // tell us we need to resolve this
861             // can't add the resolution until the TypeInfo has been created
862             if ("".equals(qualifiedTypeName)) {
863                 addResolution = true;
864             // otherwise, if the name is not a generic, get the class that this Type refers to
865             } else if (!isGeneric) {
866                 cl = Caches.obtainClass(qualifiedTypeName);
867             }
868         }
869 
870         // get the dimensions of this type
871         dimension = getDimensions(tree);
872 
873         TypeInfo type = new TypeInfo(isPrimitive, dimension, simpleTypeName, qualifiedTypeName, cl);
874         type.setTypeArguments(typeArguments);
875 
876         if (addResolution) {
877             addFutureResolution(type, "class", simpleTypeName, this);
878         }
879 
880         return type;
881     }
882 
883     /**
884      * Processes the type variables of a class that contains generics.
885      * @param tree Root of the type parameters.
886      * @param cls Class in which these type variables are contained.
887      */
buildTypeVariables(ParseTree tree)888     private ArrayList<TypeInfo> buildTypeVariables(ParseTree tree) {
889         ArrayList<TypeInfo> typeVariables = new ArrayList<TypeInfo>();
890         ArrayList<TypeInfo> superBounds = new ArrayList<TypeInfo>();
891         ArrayList<TypeInfo> extendsBounds = new ArrayList<TypeInfo>();
892 
893         for (Object o : tree.getChildren()) {
894             // if we're not dealing with a type, skip
895             // basically gets rid of commas and lessthan and greater than signs
896             if (!o.toString().equals("typeParameter") &&
897                     !o.toString().equals("typeArgument")) {
898                 continue;
899             }
900 
901             ParseTree typeParameter = (ParseTree) o;
902 
903             TypeInfo type;
904             // if we have a typeArgument and it is not a wildcard
905             if ("typeArgument".equals(typeParameter.toString()) &&
906                     !"?".equals(typeParameter.getChild(0).toString())) {
907                 type = buildType((ParseTree) typeParameter.getChild(0));
908             } else {
909                 // otherwise, we have a wildcard or parameter
910                 // which can be more vague because of generics
911                 String name = typeParameter.getChild(0).toString();
912 
913                 type = new TypeInfo(false, null, name, name, null);
914                 if ("?".equals(name)) {
915                     type.setIsWildcard(true);
916                 } else {
917                     // add generic
918                     mClassNames.add(name);
919                 }
920             }
921 
922             // if we have an extends or super on our type variable
923             if (typeParameter.getChildCount() > 1) {
924                 ParseTree value = (ParseTree) typeParameter.getChild(1);
925 
926                 if ("extends".equals(value.toString())) {
927                     value = (ParseTree) typeParameter.getChild(2);
928 
929                     // wildcard extends
930                     if ("type".equals(value.toString())) {
931                         extendsBounds.add(buildType(value));
932                     // all other extends
933                     } else {
934                         // will have to handle stuff with typeBound - multiple types
935                         for (Object obj : value.getChildren()) {
936                             if ("type".equals(obj.toString())) {
937                                 extendsBounds.add(buildType((ParseTree) obj));
938                             }
939                         }
940                     }
941                 } else if ("super".equals(value.toString())) {
942                     superBounds.add(buildType((ParseTree) typeParameter.getChild(2)));
943                 }
944             }
945 
946             type.setIsTypeVariable(true);
947             type.setBounds(superBounds, extendsBounds);
948             typeVariables.add(type);
949         }
950 
951         return typeVariables;
952     }
953 
954     /**
955      * Builds a MethodInfo for methods, constructors and annotation elements.
956      * @param tree The tree to parse. methodDeclaration, interfaceMethodDeclaration
957      * or annotationMethodDeclaration should be the root value.
958      * @param containingClass the class in which this method exists.
959      * @param isAnnotation true if the class is an annotation element
960      * @return the MethodInfo
961      */
buildMethod(ParseTree tree, ClassInfo containingClass, boolean isAnnotation)962     private MethodInfo buildMethod(ParseTree tree, ClassInfo containingClass,
963             boolean isAnnotation) {
964         Modifiers modifiers = new Modifiers(this);
965         CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
966 
967         String name = null;
968         StringBuilder flatSignature = new StringBuilder().append('(');
969         ArrayList<TypeInfo> typeParameters = null;
970         ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
971         ArrayList<ClassInfo> thrownExceptions = new ArrayList<ClassInfo>();
972         TypeInfo returnType = null;
973         boolean isAnnotationElement = false;
974         boolean isVarArg = false;
975         String kind = "method"; // annotationElement, method, or constructor
976         AnnotationValueInfo elementValue = null;
977         ArrayList<Resolution> pendingResolutions = new ArrayList<Resolution>();
978 
979         @SuppressWarnings("unchecked")
980         Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
981         ParseTree child = it.next();
982 
983         modifiers.parseModifiers(child);
984 
985         child = it.next();
986 
987         // generics stuff
988         if ("typeParameters".equals(child.toString())) {
989             typeParameters = buildTypeVariables(child);
990             child = it.next();
991         }
992 
993         // handle returnType if we're not in a constructor
994         if ("type".equals(child.toString())) {
995             returnType = buildType(child);
996             child = it.next();
997         } else if ("void".equals(child.toString())) {
998             returnType = new TypeInfo(true, null, "void", "void", null);
999             child = it.next();
1000         }
1001 
1002         // this is the method name
1003         name = child.toString();
1004 
1005         if (name.equals(containingClass.name())) {
1006             kind = "constructor";
1007         }
1008 
1009         // probably don't need this check any longer since I unrolled the loop
1010 //        if (isConstructorOrMethodName(child)) {
1011 //            // this is the method name
1012 //            name = child.toString();
1013 //
1014 //            if (name.equals(containingClass.name())) {
1015 //                kind = "constructor";
1016 //            }
1017 //        }
1018 
1019         child = it.next();
1020 
1021         // method parameters
1022         if ("formalParameters".equals(child.toString())) {
1023             isVarArg = buildMethodParameters(child, parameters, flatSignature);
1024         } else {
1025             child = it.next();
1026         }
1027 
1028         child = it.next();
1029         flatSignature.append(')');
1030 
1031         // handle exception throwing
1032         if ("throws".equals(child.toString())) {
1033             child = it.next();
1034 
1035             for (Object o : child.getChildren()) {
1036                 if (",".equals(o.toString())) {
1037                     continue;
1038                 }
1039 
1040                 // get the name of the exception, resolve it and add it to the list
1041                 // unless we can't, in which case, add a resolution
1042                 String exceptionName = buildQualifiedName(((ParseTree) o));
1043                 StringBuilder exceptionQualifiedName = new StringBuilder();
1044                 boolean isGeneric = resolveQualifiedName(exceptionName,
1045                         exceptionQualifiedName, this);
1046 
1047                 if ("".equals(exceptionQualifiedName.toString())) {
1048                     pendingResolutions.add(new Resolution("thrownException", exceptionName, null));
1049                 } else if (!isGeneric) {
1050                     thrownExceptions.add(Caches.obtainClass(exceptionQualifiedName.toString()));
1051                 }
1052             }
1053         // handle default values for annotation elements
1054         } else if ("default".equals(child.toString())) {
1055             child = it.next();
1056 
1057             elementValue = buildElementValue(child, this);
1058             child = it.next();
1059         }
1060 
1061         if (isAnnotation) {
1062             kind = "annotationElement";
1063         }
1064 
1065         // Here we set signature, overridden method to null because
1066         // MethodInfo figures these values out later on
1067         MethodInfo method =  new MethodInfo(commentAndPosition.getCommentText(), typeParameters,
1068                 name, null, containingClass, containingClass, modifiers.isPublic(),
1069                 modifiers.isProtected(), modifiers.isPackagePrivate(),
1070                 modifiers.isPrivate(), modifiers.isFinal(),
1071                 modifiers.isStatic(), modifiers.isSynthetic(),
1072                 modifiers.isAbstract(), modifiers.isSynchronized(),
1073                 false, modifiers.isDefault(), isAnnotationElement, kind, flatSignature.toString(),
1074                 null, returnType, parameters, thrownExceptions,
1075                 commentAndPosition.getPosition(), modifiers.getAnnotations());
1076 
1077         method.setVarargs(isVarArg);
1078         method.init(elementValue);
1079 
1080         for (Resolution r : pendingResolutions) {
1081             addFutureResolution(method, r.getVariable(), r.getValue(), this);
1082         }
1083 
1084         return method;
1085     }
1086 
1087     /**
1088      * Build the method parameters.
1089      * @param tree The tree to parse. formalParamaters should be the root value.
1090      * @param parameters List to put the method ParamaterInfos into.
1091      * @param flatSignature Pass in a StringBuilder with "(" in it to build the
1092      * flatSignature of the MethodInfo
1093      * @return true if the Method has a VarArgs parameter. false otherwise.
1094      */
buildMethodParameters(ParseTree tree, ArrayList<ParameterInfo> parameters, StringBuilder flatSignature)1095     private boolean buildMethodParameters(ParseTree tree,
1096                                     ArrayList<ParameterInfo> parameters,
1097                                     StringBuilder flatSignature) {
1098         boolean isVarArg = false;
1099         for (Object obj : tree.getChildren()) {
1100             ParseTree child = (ParseTree) obj;
1101 
1102             if ("formalParameterDecls".equals(child.toString())) {
1103                 for (Object formalParam : child.getChildren()) {
1104                     ParseTree param = (ParseTree) formalParam;
1105                     TypeInfo type = null;
1106 
1107                     if (param.getChildCount() == 0) {
1108                         continue;
1109                     }
1110 
1111                     @SuppressWarnings("unchecked")
1112                     Iterator<ParseTree> it = (Iterator<ParseTree>) param.getChildren().iterator();
1113 
1114                     ParseTree paramPart = it.next();
1115 
1116                     if ("variableModifiers".equals(paramPart.toString())) {
1117                         // TODO - handle variable modifiers - final, etc
1118                     }
1119 
1120                     paramPart = it.next();
1121 
1122                     type = buildType(paramPart);
1123 
1124                     buildSignatureForType(flatSignature, type);
1125 
1126                     if (param != child.getChildren().get(child.getChildCount()-1)) {
1127                         flatSignature.append(", ");
1128                     }
1129 
1130                     paramPart = it.next();
1131 
1132                     if ("...".equals(paramPart.toString())) {
1133                         isVarArg = true;
1134                         // thank you varargs for only being the last parameter
1135                         // you make life so much nicer
1136                         flatSignature.append("...");
1137                         paramPart = it.next();
1138                     }
1139 
1140                     String name = paramPart.toString();
1141 
1142                     CommentAndPosition commentAndPosition = new CommentAndPosition();
1143                     commentAndPosition.setPosition(paramPart);
1144 
1145                     parameters.add(new ParameterInfo(name, type.qualifiedTypeName(), type,
1146                             isVarArg, commentAndPosition.getPosition(),
1147                             Collections.<AnnotationInstanceInfo>emptyList()));
1148                 }
1149             }
1150         }
1151 
1152         return isVarArg;
1153     }
1154 
1155     /**
1156      * Builds a StringBuilder representing the Type, including type arguments.
1157      * @param builder StringBuilder in which the Type will be placed.
1158      * @param type the TypeInfo to turn into a String.
1159      */
buildSignatureForType(StringBuilder builder, TypeInfo type)1160     private void buildSignatureForType(StringBuilder builder, TypeInfo type) {
1161         // simple name
1162         builder.append(type.simpleTypeName());
1163 
1164         // generics
1165         if (type.typeArguments() != null && !type.typeArguments().isEmpty()) {
1166             builder.append('<');
1167             for (TypeInfo inner : type.typeArguments()) {
1168                 if (inner != type.typeArguments().get(0)) {
1169                     builder.append(", ");
1170                 }
1171 
1172                 // recurse
1173                 buildSignatureForType(builder, inner);
1174             }
1175             builder.append('>');
1176         }
1177     }
1178 
1179     /**
1180      * Builds a ClassInfo for an enum.
1181      * @param tree The tree to parse. enumDeclaration should be the root value.
1182      * @param containingClass ClassInfo that contains the enum declaration.
1183      * null if the enum is a root class.
1184      * @return the enum as a ClassInfo
1185      */
buildEnum(ParseTree tree, ClassInfo containingClass)1186     private ClassInfo buildEnum(ParseTree tree, ClassInfo containingClass) {
1187         CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
1188         Modifiers modifiers = new Modifiers(this);
1189         ClassInfo cls = null;
1190 
1191         @SuppressWarnings("unchecked")
1192         Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
1193 
1194         ParseTree child = it.next();
1195 
1196         modifiers.parseModifiers(child);
1197 
1198         child = it.next();
1199         child = it.next();
1200 
1201         cls = buildClassName(child, containingClass, modifiers,
1202                 commentAndPosition.getCommentText(),
1203                 commentAndPosition.getPosition(), ClassType.ENUM);
1204 
1205         child = it.next();
1206 
1207         // handle implements
1208         if ("implements".equals(child.toString())) {
1209             child = it.next();
1210 
1211             parseInterfaces(child, cls);
1212 
1213             child = it.next();
1214         }
1215 
1216         buildEnumBody(child, cls);
1217 
1218         return cls;
1219     }
1220 
1221     /**
1222      * Parses the body of an enum.
1223      * @param tree The tree to parse. enumBody should be the root value.
1224      * @param containingClass ClassInfo to which this enum body pertains.
1225      */
buildEnumBody(ParseTree tree, ClassInfo containingClass)1226     private void buildEnumBody(ParseTree tree, ClassInfo containingClass) {
1227         for (Object o : tree.getChildren()) {
1228             ParseTree child = (ParseTree) o;
1229 
1230             if ("enumConstants".equals(child.toString())) {
1231                 for (Object o2 : child.getChildren()) {
1232                     ParseTree tmp = (ParseTree) o2;
1233 
1234                     if ("enumConstant".equals(tmp.toString())) {
1235                         containingClass.addEnumConstant(buildEnumConstant(tmp, containingClass));
1236                     }
1237                 }
1238             } else if ("enumBodyDeclarations".equals(child.toString())) {
1239                 buildClassBody(child, containingClass);
1240             }
1241         }
1242         return;
1243     }
1244 
1245     /**
1246      * Builds an enum constant.
1247      * @param tree The tree to parse. enumConstant should be the root value.
1248      * @param containingClass ClassInfo to which this enum constant pertains.
1249      * @return
1250      */
buildEnumConstant(ParseTree tree, ClassInfo containingClass)1251     private FieldInfo buildEnumConstant(ParseTree tree, ClassInfo containingClass) {
1252         @SuppressWarnings("unchecked")
1253         Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
1254         ParseTree child = it.next();
1255 
1256         Modifiers modifiers = new Modifiers(this);
1257         if ("annotations".equals(child.toString())) {
1258             modifiers.parseModifiers(child);
1259             child = it.next();
1260         }
1261 
1262         String name = child.toString();
1263         CommentAndPosition commentAndPosition = new CommentAndPosition();
1264         commentAndPosition.setCommentText(child);
1265         commentAndPosition.setPosition(child);
1266         Object constantValue = null;
1267 
1268         // get constantValue if it exists
1269         if (it.hasNext()) {
1270             child = it.next();
1271 
1272             // if we have an expressionList
1273             if (child.getChildCount() == 3) {
1274                 StringBuilder builder = new StringBuilder();
1275                 child = (ParseTree) child.getChild(1); // get the middle child
1276 
1277                 for (Object o : child.getChildren()) {
1278                     if ("expression".equals(o.toString())) {
1279                         builder.append(parseExpression((ParseTree) o));
1280 
1281                         if (o != child.getChild(child.getChildCount()-1)) {
1282                             builder.append(", ");
1283                         }
1284                     }
1285                 }
1286 
1287                 constantValue = builder.toString();
1288             }
1289         }
1290 
1291         return new FieldInfo(name, containingClass, containingClass, containingClass.isPublic(),
1292         containingClass.isProtected(), containingClass.isPackagePrivate(),
1293         containingClass.isPrivate(), containingClass.isFinal(),
1294         containingClass.isStatic(), false, false, false,
1295         containingClass.type(), commentAndPosition.getCommentText(),
1296         constantValue, commentAndPosition.getPosition(),
1297         modifiers.getAnnotations());
1298     }
1299 
1300     /**
1301      * Builds a ClassInfo for an interface.
1302      * @param tree The tree to parse. normalInterfaceDeclaration should be the root value.
1303      * @param containingClass ClassInfo that contains the interface declaration.
1304      * null if the interface is a root class.
1305      * @return a ClassInfo representing the interface.
1306      */
buildInterface(ParseTree tree, ClassInfo containingClass)1307     private ClassInfo buildInterface(ParseTree tree, ClassInfo containingClass) {
1308         @SuppressWarnings("unchecked")
1309         Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
1310         ParseTree child = it.next();
1311 
1312         // parse modifiers and get comment and position
1313         Modifiers modifiers = new Modifiers(this);
1314         modifiers.parseModifiers(child);
1315         CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
1316 
1317         it.next();
1318         child = it.next();
1319 
1320         // get class name
1321         ClassInfo iface = buildClassName(child, containingClass, modifiers,
1322                 commentAndPosition.getCommentText(),
1323                 commentAndPosition.getPosition(), ClassType.INTERFACE);
1324 
1325         child = it.next();
1326 
1327         // parse generics if they exist
1328         if ("typeParameters".equals(child.toString())) {
1329             iface.type().setTypeArguments(buildTypeVariables(child));
1330             child = it.next();
1331         }
1332 
1333         // parse interfaces implemented by this interface
1334         if ("extends".equals(child.toString())) {
1335             child = it.next();
1336 
1337             parseInterfaces(child, iface);
1338 
1339             child = it.next();
1340         }
1341 
1342         // finally, build the body of the interface
1343         buildInterfaceBody(child, iface);
1344 
1345         return iface;
1346     }
1347 
1348     /**
1349      * Parses the body of the interface, adding it to iface.
1350      * @param tree The tree to parse. interfaceBody should be the root value.
1351      * @param iface ClassInfo that will contain all of the interface body.
1352      */
buildInterfaceBody(ParseTree tree, ClassInfo iface)1353     private void buildInterfaceBody(ParseTree tree, ClassInfo iface) {
1354         for (Object o : tree.getChildren()) {
1355             if (!o.toString().equals("interfaceBodyDeclaration")) {
1356                 continue;
1357             }
1358 
1359             ParseTree child = (ParseTree) ((ParseTree) o).getChild(0);
1360 
1361             if (";".equals(child.toString())) {
1362                 continue;
1363             }
1364 
1365             // field
1366             if ("interfaceFieldDeclaration".equals(child.toString())) {
1367                 for (FieldInfo f : buildFields(child, iface)) {
1368                     iface.addField(f);
1369                 }
1370             // method
1371             } else if ("interfaceMethodDeclaration".equals(child.toString())) {
1372                 iface.addMethod(buildMethod(child, iface, false));
1373             // inner class
1374             } else if ("normalClassDeclaration".equals(child.getChild(0).toString())) {
1375                 iface.addInnerClass(buildClass((ParseTree) child.getChild(0), iface));
1376             // inner enum
1377             } else if ("enumDeclaration".equals(child.getChild(0).toString())) {
1378                 iface.addInnerClass(buildEnum((ParseTree) child.getChild(0), iface));
1379             // inner interface
1380             } else if ("normalInterfaceDeclaration".equals(child.getChild(0).toString())) {
1381                 iface.addInnerClass(buildInterface((ParseTree) child.getChild(0), iface));
1382             // inner annotation
1383             } else if ("annotationTypeDeclaration".equals(child.getChild(0).toString())) {
1384                 iface.addInnerClass(buildAnnotationDeclaration(
1385                         (ParseTree) child.getChild(0), iface));
1386             }
1387         }
1388     }
1389 
1390     /**
1391      * Builds a ClassInfo of an annotation declaration.
1392      * @param tree The tree to parse. annotationTypeDeclaration should be the root value.
1393      * @param containingClass The class that contains this annotation.
1394      * null if this is a root annotation.
1395      * @return the ClassInfo of the annotation declaration.
1396      */
buildAnnotationDeclaration(ParseTree tree, ClassInfo containingClass)1397     private ClassInfo buildAnnotationDeclaration(ParseTree tree, ClassInfo containingClass) {
1398         @SuppressWarnings("unchecked")
1399         Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
1400         ParseTree child = it.next();
1401 
1402         // get comment and position
1403         CommentAndPosition commentAndPosition = parseCommentAndPosition(tree);
1404 
1405         // modifiers
1406         Modifiers modifiers = new Modifiers(this);
1407         modifiers.parseModifiers(child);
1408 
1409         // three calls to next to skip over @, interface and then
1410         // make child = the name of this annotation
1411         it.next();
1412         it.next();
1413         child = it.next();
1414 
1415         // build class name and initialize the class
1416         ClassInfo annotation = buildClassName(child, containingClass, modifiers,
1417                 commentAndPosition.getCommentText(),
1418                 commentAndPosition.getPosition(), ClassType.INTERFACE);
1419 
1420         child = it.next();
1421 
1422         // build annotation body
1423         buildAnnotationBody(child, annotation);
1424 
1425         return annotation;
1426     }
1427 
1428     /**
1429      * Parses the body of the annotation declaration.
1430      * @param tree The tree to parse. annotationTypeBody should be the root value.
1431      * @param annotation the Classinfo in which the annotation elements should be added.
1432      */
buildAnnotationBody(ParseTree tree, ClassInfo annotation)1433     private void buildAnnotationBody(ParseTree tree, ClassInfo annotation) {
1434         for (Object o : tree.getChildren()) {
1435             if (!"annotationTypeElementDeclaration".equals(o.toString())) {
1436                 continue;
1437             }
1438 
1439             ParseTree child = (ParseTree) ((ParseTree) o).getChild(0);
1440 
1441             // annotation fields
1442             if ("interfaceFieldDeclaration".equals(child.toString())) {
1443                 for (FieldInfo f : buildFields(child, annotation)) {
1444                     annotation.addField(f);
1445                 }
1446             // annotation methods
1447             } else if ("annotationMethodDeclaration".equals(child.toString())) {
1448                 annotation.addAnnotationElement(buildMethod(child, annotation, true));
1449             // inner class
1450             } else if ("normalClassDeclaration".equals(child.toString())) {
1451                 annotation.addInnerClass(buildClass((ParseTree) child, annotation));
1452             // enum
1453             } else if ("enumDeclaration".equals(child.toString())) {
1454                 annotation.addInnerClass(buildEnum((ParseTree) child, annotation));
1455             // inner interface
1456             } else if ("normalInterfaceDeclaration".equals(child.toString())) {
1457                 annotation.addInnerClass(buildInterface((ParseTree) child, annotation));
1458             // inner annotation
1459             } else if ("annotationTypeDeclaration".equals(child.toString())) {
1460                 annotation.addInnerClass(buildAnnotationDeclaration(
1461                         (ParseTree) child, annotation));
1462             }
1463         }
1464     }
1465 
1466     /**
1467      * Build an annotation instance.
1468      * @param tree The tree to parse. annotation should be the root value.
1469      * @param builder InfoBuilder of this file.
1470      * @return The AnnotationInstanceInfo being parsed.
1471      */
buildAnnotationInstance(ParseTree tree, InfoBuilder builder)1472     private static AnnotationInstanceInfo buildAnnotationInstance(ParseTree tree,
1473             InfoBuilder builder) {
1474         @SuppressWarnings("unchecked")
1475         Iterator<ParseTree> it = (Iterator<ParseTree>) tree.getChildren().iterator();
1476 
1477         AnnotationInstanceInfo annotationInstance = new AnnotationInstanceInfo();
1478 
1479         it.next();
1480 
1481         // parse the name, get its full version, and then get the ClassInfo of it, if possible.
1482         String name = InfoBuilder.buildQualifiedName(it.next());
1483         StringBuilder qualifiedNameBuilder = new StringBuilder();
1484         resolveQualifiedName(name, qualifiedNameBuilder, builder);
1485 
1486         if ("".equals(qualifiedNameBuilder.toString())) {
1487             addFutureResolution(annotationInstance, "annotationTypeName", name, builder);
1488             annotationInstance.setSimpleAnnotationName(name); // TODO - remove once we've completed the parser
1489         } else { // can't have generics here so we won't do a test
1490             annotationInstance.setClass(Caches.obtainClass(qualifiedNameBuilder.toString()));
1491         }
1492 
1493         // at this point, the annotation is either finished or we have more work to do
1494         if (!it.hasNext()) {
1495             return annotationInstance;
1496         }
1497 
1498         it.next();
1499         ParseTree child = it.next();
1500 
1501         // parse elementValue pairs
1502         if ("elementValuePairs".equals(child.toString())) {
1503             for (Object o : child.getChildren()) {
1504                 if (!"elementValuePair".equals(o.toString())) {
1505                     continue;
1506                 }
1507 
1508                 ParseTree inner = (ParseTree) o;
1509                 MethodInfo element = null;
1510                 String methodName = inner.getChild(0).toString();
1511 
1512                 // try and look up the MethodInfo for this annotation, if possible
1513                 if (annotationInstance.type() != null) {
1514                     for (MethodInfo m : annotationInstance.type().annotationElements()) {
1515                         if (methodName.equals(m.name()) ||
1516                                 annotationInstance.type().annotationElements().size() == 1) {
1517                             element = m;
1518                             break;
1519                         }
1520                     }
1521                 }
1522 
1523                 // go to elementValue
1524                 AnnotationValueInfo info = buildElementValue(
1525                         (ParseTree) inner.getChild(2), builder);
1526 
1527                 if (element == null) {
1528                     addFutureResolution(info, "element", methodName, builder);
1529                     info.setAnnotationInstanceName(name);
1530                 } else {
1531                     info.setElement(element);
1532                 }
1533 
1534                 annotationInstance.addElementValue(info);
1535             }
1536         // parse element value
1537         } else if ("elementValue".equals(child.toString())) {
1538             annotationInstance.addElementValue(buildElementValue(child, builder));
1539         }
1540 
1541         return annotationInstance;
1542     }
1543 
1544     /**
1545      * Builds the value of the annotation element.
1546      * @param tree The tree to parse. elementValue should be the root value.
1547      * @param builder InfoBuilder of this file.
1548      * @return AnnotationValueInfo representing the elementValue.
1549      */
buildElementValue(ParseTree tree, InfoBuilder builder)1550     private static AnnotationValueInfo buildElementValue(ParseTree tree, InfoBuilder builder) {
1551         AnnotationValueInfo elementValue = new AnnotationValueInfo();
1552         Object value = null;
1553 
1554         // parse some stuff
1555         String str = tree.getChild(0).toString();
1556         if ("conditionalExpression".equals(str)) {
1557             value = parseExpression((ParseTree) tree.getChild(0));
1558         } else if ("annotation".equals(str)) {
1559             value = InfoBuilder.buildAnnotationInstance((ParseTree) tree.getChild(0), builder);
1560         } else if ("elementValueArrayInitializer".equals(str)) {
1561             ParseTree child = (ParseTree) tree.getChild(0);
1562             ArrayList<AnnotationValueInfo> values = new ArrayList<AnnotationValueInfo>();
1563             for (Object o : child.getChildren()) {
1564                 if ("elementValue".equals(o.toString())) {
1565                     values.add(buildElementValue((ParseTree) o, builder));
1566                 }
1567             }
1568 
1569             value = values;
1570         }
1571 
1572         elementValue.init(value);
1573 
1574         return elementValue;
1575     }
1576 
1577     /**
1578      * Get the dimensions of the type, as a String.
1579      * @param tree The tree to parse. type should be the root value.
1580      * @return A String of the dimensions of the type.
1581      */
getDimensions(ParseTree tree)1582     private String getDimensions(ParseTree tree) {
1583         // we only have dimensions if the count is not 1
1584         if (tree.getChildCount() == 1) {
1585             return null;
1586         }
1587 
1588         StringBuilder builder = new StringBuilder();
1589 
1590         for (int i = 1; i < tree.getChildCount(); i++) {
1591             builder.append(((ParseTree) tree.getChild(i)).toString());
1592         }
1593 
1594         return builder.toString();
1595     }
1596 
1597     /**
1598      * When we have data that we can't yet parse, save it for later.
1599      * @param resolvable Resolvable to which the data refers.
1600      * @param variable Variable in the document to which the data refers;
1601      * @param value Value for the variable
1602      * @param builder The InfoBuilder of this file
1603      */
addFutureResolution(Resolvable resolvable, String variable, String value, InfoBuilder builder)1604     private static void addFutureResolution(Resolvable resolvable, String variable,
1605             String value, InfoBuilder builder) {
1606         resolvable.addResolution(new Resolution(variable, value, builder));
1607 
1608         Caches.addResolvableToCache(resolvable);
1609     }
1610 
1611     /**
1612      * Turns a short name of a class into the qualified name of a class.
1613      * StringBuilder will contain an empty string if not found.
1614      * @param name the abbreviated name of the class
1615      * @param qualifiedClassName the qualified name that will be set if found.
1616      * Unchanged if not found.
1617      * @param builder InfoBuilder with all of the file specific information necessary
1618      * to properly resolve the name.
1619      * @return a boolean is returned that will be true if the type is a generic. false otherwise.
1620      */
resolveQualifiedName(String name, StringBuilder qualifiedClassName, InfoBuilder builder)1621     public static boolean resolveQualifiedName(String name,
1622                                                 StringBuilder qualifiedClassName,
1623                                                 InfoBuilder builder) {
1624         // steps to figure out a class's real name
1625         // check class(es) in this file
1626 
1627         // trying something out. let's see how this works
1628         if (name.indexOf('.') != -1) {
1629             qualifiedClassName.append(name);
1630             return false;
1631         }
1632 
1633         // TODO - search since we're now a HashSet
1634         for (String className : builder.getClassNames()) {
1635             int beginIndex = className.lastIndexOf(".") + 1;
1636 
1637             if (className.substring(beginIndex).equals(name)) {
1638                 qualifiedClassName.append(className);
1639                 return qualifiedClassName.toString().equals(name);
1640             }
1641         }
1642 
1643         // check package
1644         ClassInfo potentialClass = builder.getPackage().getClass(name);
1645 
1646         if (potentialClass != null) {
1647             qualifiedClassName.append(potentialClass.qualifiedName());
1648             return qualifiedClassName.toString().equals(name);
1649         }
1650 
1651         potentialClass = null;
1652 
1653         String potentialName = null;
1654         // check superclass and interfaces for type
1655         if (builder.getRootClass() != null) {
1656             potentialName = resolveQualifiedNameInInheritedClass(name, builder.getRootClass(),
1657                     builder.getRootClass().containingPackage().name());
1658         }
1659 
1660         if (potentialName != null) {
1661             qualifiedClassName.append(potentialName);
1662             return false;
1663         }
1664 
1665 
1666         // check class imports - ie, java.lang.String;
1667         ArrayList<String> packagesToCheck = new ArrayList<String>();
1668         for (String imp : builder.getImports()) {
1669             // +1 to get rid of off by 1 error
1670             String endOfName = imp.substring(imp.lastIndexOf('.') + 1);
1671             if (endOfName.equals(name) || (name.indexOf('.') != -1 &&
1672                                            endOfName.equals(
1673                                                    name.substring(0, name.lastIndexOf('.'))))) {
1674                 qualifiedClassName.append(imp);
1675                 return qualifiedClassName.toString().equals(name);
1676             } else if (endOfName.equals("*")) {
1677                 // add package to check
1678                 packagesToCheck.add(imp.substring(0, imp.lastIndexOf('.')));
1679             } else {
1680                 // check inner classes
1681                 ClassInfo cl = Caches.obtainClass(imp);
1682                 String possibleName = resolveQualifiedInnerName(cl.qualifiedName() + "." + name,
1683                         cl);
1684                 if (possibleName != null) {
1685                     qualifiedClassName.append(possibleName);
1686                     return false;
1687                 }
1688             }
1689         }
1690 
1691         // check package imports - ie, java.lang.*;
1692         for (String packageName : packagesToCheck) {
1693             PackageInfo pkg = Caches.obtainPackage(packageName);
1694 
1695             ClassInfo cls = pkg.getClass(name);
1696 
1697             if (cls != null && name.equals(cls.name())) {
1698                 qualifiedClassName.append(cls.qualifiedName());
1699                 return qualifiedClassName.toString().equals(name);
1700             }
1701         }
1702         //     including import's inner classes...
1703         // check package of imports...
1704 
1705         // TODO - remove
1706         // FROM THE JAVADOC VERSION OF THIS FUNCTION
1707         // Find the specified class or interface within the context of this class doc.
1708         // Search order: 1) qualified name, 2) nested in this class or interface,
1709         // 3) in this package, 4) in the class imports, 5) in the package imports.
1710         // Return the ClassDoc if found, null if not found.
1711 
1712         return false;
1713     }
1714 
resolveQualifiedNameInInheritedClass(String name, ClassInfo cl, String originalPackage)1715     private static String resolveQualifiedNameInInheritedClass(String name, ClassInfo cl,
1716             String originalPackage) {
1717         ArrayList<ClassInfo> classesToCheck = new ArrayList<ClassInfo>();
1718         if (cl != null) {
1719             // if we're in a new package only, check it
1720             if (cl.containingPackage() != null &&
1721                     !originalPackage.equals(cl.containingPackage().name())) {
1722                 // check for new class
1723                 ClassInfo cls = cl.containingPackage().getClass(name);
1724 
1725                 if (cls != null && name.equals(cls.name())) {
1726                     return cls.name();
1727                 }
1728             }
1729 
1730             if (cl.realSuperclass() != null) {
1731                 classesToCheck.add(cl.realSuperclass());
1732             }
1733 
1734             if (cl.realInterfaces() != null) {
1735                 for (ClassInfo iface : cl.realInterfaces()) {
1736                     classesToCheck.add(iface);
1737                 }
1738             }
1739 
1740             for (ClassInfo cls : classesToCheck) {
1741                 String potential = resolveQualifiedNameInInheritedClass(name, cls, originalPackage);
1742 
1743                 if (potential != null) {
1744                     return potential;
1745                 }
1746             }
1747         }
1748         return null;
1749     }
1750 
resolveQualifiedInnerName(String possibleQualifiedName, ClassInfo cl)1751     private static String resolveQualifiedInnerName(String possibleQualifiedName, ClassInfo cl) {
1752         if (cl.innerClasses() == null) {
1753             return null;
1754         }
1755 
1756         for (ClassInfo inner : cl.innerClasses()) {
1757             if (possibleQualifiedName.equals(inner.qualifiedName())) {
1758                 return possibleQualifiedName;
1759             }
1760 
1761             String name = resolveQualifiedInnerName(possibleQualifiedName + "." + inner.name(),
1762                     inner);
1763 
1764             if (name != null) {
1765                 return name;
1766             }
1767         }
1768 
1769         return null;
1770     }
1771 
1772     /**
1773      * Parses the tree, looking for the comment and position.
1774      * @param tree The tree to parse.
1775      * @return a CommentAndPosition object containing the comment and position of the element.
1776      */
parseCommentAndPosition(ParseTree tree)1777     private CommentAndPosition parseCommentAndPosition(ParseTree tree) {
1778         Tree child = tree.getChild(0).getChild(0);
1779 
1780         // three options (modifiers with annotations, modifiers w/o annotations, no modifiers)
1781         // if there are no modifiers, use tree.getChild(1)
1782         // otherwise, dive as deep as possible into modifiers to get to the comment and position.
1783         child = ("<epsilon>".equals(child.toString())) ? tree.getChild(1) : child;
1784 
1785         while (child.getChildCount() > 0) {
1786             child = child.getChild(0);
1787         }
1788 
1789         CommentAndPosition cAndP = new CommentAndPosition();
1790         cAndP.setCommentText((ParseTree) child);
1791         cAndP.setPosition((ParseTree) child);
1792         return cAndP;
1793     }
1794 
1795     /**
1796      * Private class to facilitate passing the comment and position out of a function.
1797      */
1798     private class CommentAndPosition {
getCommentText()1799         public String getCommentText() {
1800             return mCommentText;
1801         }
1802 
1803         /**
1804          * Parses the tree to get the commentText and set that value.
1805          * @param tree The tree to parse. Should be pointing to the node containing the comment.
1806          */
setCommentText(ParseTree tree)1807         public void setCommentText(ParseTree tree) {
1808             if (tree.hiddenTokens != null && !tree.hiddenTokens.isEmpty()) {
1809                 mCommentText = ((CommonToken) tree.hiddenTokens.get(0)).getText();
1810 
1811                 if (mCommentText != null) {
1812                     return;
1813                 }
1814             }
1815 
1816             mCommentText = "";
1817         }
1818 
getPosition()1819         public SourcePositionInfo getPosition() {
1820             return mPosition;
1821         }
1822 
1823         /**
1824          * Parses the tree to get the SourcePositionInfo of the node.
1825          * @param tree The tree to parse. Should be pointing to the node containing the position.
1826          */
setPosition(ParseTree tree)1827         public void setPosition(ParseTree tree) {
1828           CommonToken token = (CommonToken) tree.payload;
1829 
1830           int line = token.getLine();
1831           int column = token.getCharPositionInLine();
1832           String fileName = ((ANTLRFileStream) token.getInputStream()).getSourceName();
1833 
1834           mPosition = new SourcePositionInfo(fileName, line, column);
1835         }
1836 
1837         private String mCommentText;
1838         private SourcePositionInfo mPosition;
1839     }
1840 
1841     /**
1842      * Private class to handle all the possible modifiers to a class/interface/field/anything else.
1843      */
1844     private class Modifiers {
1845         private boolean mIsPublic = false;
1846         private boolean mIsProtected = false;
1847         private boolean mIsPackagePrivate = true;
1848         private boolean mIsPrivate = false;
1849         private boolean mIsStatic = false;
1850         private boolean mIsAbstract = false;
1851         private boolean mIsFinal = false;
1852         private boolean mIsTransient = false;
1853         private boolean mIsVolatile = false;
1854         private boolean mIsSynthetic = false;
1855         private boolean mIsSynchronized = false;
1856         private boolean mIsStrictfp = false;
1857         private boolean mIsDefault = false;
1858         private InfoBuilder mBuilder;
1859         private ArrayList<AnnotationInstanceInfo> mAnnotations;
1860 
Modifiers(InfoBuilder builder)1861         public Modifiers(InfoBuilder builder) {
1862             mAnnotations = new ArrayList<AnnotationInstanceInfo>();
1863             mBuilder = builder;
1864         }
1865 
1866         /**
1867          * Parses all of the modifiers of any declaration, including annotations.
1868          * @param tree
1869          */
parseModifiers(ParseTree tree)1870         public void parseModifiers(ParseTree tree) {
1871             for (Object child : tree.getChildren()) {
1872                 String modifier = child.toString();
1873 
1874                 if ("public".equals(modifier)) {
1875                     mIsPublic = true;
1876                     mIsPackagePrivate = false;
1877                 } else if ("protected".equals(modifier)) {
1878                     mIsProtected = true;
1879                     mIsPackagePrivate = false;
1880                 } else if ("private".equals(modifier)) {
1881                     mIsPrivate = true;
1882                     mIsPackagePrivate = false;
1883                 } else if ("static".equals(modifier)) {
1884                     mIsStatic = true;
1885                 } else if ("abstract".equals(modifier)) {
1886                     mIsAbstract = true;
1887                 } else if ("final".equals(modifier)) {
1888                     mIsFinal = true;
1889                 } else if ("transient".equals(modifier)) {
1890                     mIsTransient = true;
1891                 } else if ("volatile".equals(modifier)) {
1892                     mIsVolatile = true;
1893                 } else if ("synthetic".equals(modifier)) {
1894                     mIsSynthetic = true;
1895                 } else if ("synchronized".equals(modifier)) {
1896                     mIsSynchronized = true;
1897                 }  else if ("strictfp".equals(modifier)) {
1898                     mIsStrictfp = true;
1899                 }  else if ("default".equals(modifier)) {
1900                     mIsDefault = true;
1901                 } else if ("annotation".equals(modifier)) {
1902                     mAnnotations.add(buildAnnotationInstance((ParseTree) child, mBuilder));
1903                 }
1904             }
1905         }
1906 
isPublic()1907         public boolean isPublic() {
1908             return mIsPublic;
1909         }
1910 
isProtected()1911         public boolean isProtected() {
1912             return mIsProtected;
1913         }
1914 
isPackagePrivate()1915         public boolean isPackagePrivate() {
1916             return mIsPackagePrivate;
1917         }
1918 
isPrivate()1919         public boolean isPrivate() {
1920             return mIsPrivate;
1921         }
1922 
isStatic()1923         public boolean isStatic() {
1924             return mIsStatic;
1925         }
1926 
isAbstract()1927         public boolean isAbstract() {
1928             return mIsAbstract;
1929         }
1930 
isFinal()1931         public boolean isFinal() {
1932             return mIsFinal;
1933         }
1934 
isTransient()1935         public boolean isTransient() {
1936             return mIsTransient;
1937         }
1938 
isVolatile()1939         public boolean isVolatile() {
1940             return mIsVolatile;
1941         }
1942 
isSynthetic()1943         public boolean isSynthetic() {
1944             return mIsSynthetic;
1945         }
1946 
isSynchronized()1947         public boolean isSynchronized() {
1948             return mIsSynchronized;
1949         }
1950 
1951         @SuppressWarnings("unused")
isStrictfp()1952         public boolean isStrictfp() {
1953             return mIsStrictfp;
1954         }
1955 
isDefault()1956         public boolean isDefault() {
1957             return mIsDefault;
1958         }
1959 
getAnnotations()1960         public ArrayList<AnnotationInstanceInfo> getAnnotations() {
1961             return mAnnotations;
1962         }
1963     };
1964 
1965 
1966     /**
1967      * Singleton class to store all of the global data amongst every InfoBuilder.
1968      */
1969     public static class Caches {
1970         private static HashMap<String, PackageInfo> mPackages
1971                                         = new HashMap<String, PackageInfo>();
1972         private static HashMap<String, ClassInfo> mClasses
1973                                         = new HashMap<String, ClassInfo>();
1974         private static HashSet<Resolvable> mInfosToResolve
1975                                         = new HashSet<Resolvable>();
1976 
obtainPackage(String packageName)1977         public static PackageInfo obtainPackage(String packageName) {
1978             PackageInfo pkg = mPackages.get(packageName);
1979 
1980             if (pkg == null) {
1981                 pkg = new PackageInfo(packageName);
1982                 mPackages.put(packageName, pkg);
1983             }
1984 
1985             return pkg;
1986         }
1987 
1988         /**
1989          * Gets the ClassInfo from the master list or creates a new one if it does not exist.
1990          * @param qualifiedClassName Qualified name of the ClassInfo to obtain.
1991          * @return the ClassInfo
1992          */
obtainClass(String qualifiedClassName)1993         public static ClassInfo obtainClass(String qualifiedClassName) {
1994             ClassInfo cls = mClasses.get(qualifiedClassName);
1995 
1996             if (cls == null) {
1997                 cls = new ClassInfo(qualifiedClassName);
1998                 mClasses.put(cls.qualifiedName(), cls);
1999             }
2000 
2001             return cls;
2002         }
2003 
2004         /**
2005          * Gets the ClassInfo from the master list or returns null if it does not exist.
2006          * @param qualifiedClassName Qualified name of the ClassInfo to obtain.
2007          * @return the ClassInfo or null, if the ClassInfo does not exist.
2008          */
getClass(String qualifiedClassName)2009         public static ClassInfo getClass(String qualifiedClassName) {
2010             return mClasses.get(qualifiedClassName);
2011         }
2012 
addResolvableToCache(Resolvable resolvable)2013         public static void addResolvableToCache(Resolvable resolvable) {
2014             mInfosToResolve.add(resolvable);
2015         }
2016 
printResolutions()2017         public static void printResolutions() {
2018             if (mInfosToResolve.isEmpty()) {
2019                 System.out.println("We've resolved everything.");
2020                 return;
2021             }
2022 
2023             for (Resolvable r : mInfosToResolve) {
2024                 r.printResolutions();
2025                 System.out.println();
2026             }
2027         }
2028 
resolve()2029         public static void resolve() {
2030             HashSet<Resolvable> resolveList = mInfosToResolve;
2031             mInfosToResolve = new HashSet<Resolvable>();
2032 
2033             for (Resolvable r : resolveList) {
2034                 // if we could not resolve everything in this class
2035                 if (!r.resolveResolutions()) {
2036                     mInfosToResolve.add(r);
2037                 }
2038 
2039                 System.out.println();
2040             }
2041         }
2042     }
2043 
getPackage()2044     public PackageInfo getPackage() {
2045         return mPackage;
2046     }
2047 
getImports()2048     public ArrayList<String> getImports() {
2049         return mImports;
2050     }
2051 
getClassNames()2052     public HashSet<String> getClassNames() {
2053         return mClassNames;
2054     }
2055 
getRootClass()2056     public ClassInfo getRootClass() {
2057         return mRootClass;
2058     }
2059 }
2060