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