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