View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.symboltable;
5   
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.HashSet;
9   import java.util.LinkedHashSet;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Set;
13  
14  import net.sourceforge.pmd.lang.ast.Node;
15  import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
16  import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
17  import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral;
18  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
19  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
20  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
21  import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
22  import net.sourceforge.pmd.lang.java.ast.ASTExtendsList;
23  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
24  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
25  import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
26  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
27  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
28  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
29  import net.sourceforge.pmd.lang.java.ast.ASTName;
30  import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
31  import net.sourceforge.pmd.lang.java.ast.ASTType;
32  import net.sourceforge.pmd.lang.java.ast.ASTTypeParameter;
33  import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters;
34  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
35  import net.sourceforge.pmd.lang.java.ast.JavaParserTreeConstants;
36  import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
37  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
38  import net.sourceforge.pmd.lang.symboltable.Scope;
39  
40  /**
41   * This scope represents one Java class. It can have variable declarations,
42   * method declarations and inner class declarations.
43   */
44  public class ClassScope extends AbstractJavaScope {
45  
46      // FIXME - this breaks given sufficiently nested code
47      private static ThreadLocal<Integer> anonymousInnerClassCounter = new ThreadLocal<Integer>() {
48          protected Integer initialValue() {
49              return Integer.valueOf(1);
50          }
51      };
52  
53      private String className;
54  
55      private boolean isEnum;
56  
57      public ClassScope(String className) {
58          this.className = className;
59          anonymousInnerClassCounter.set(Integer.valueOf(1));
60      }
61  
62      /**
63       * This is only for anonymous inner classes
64       * <p/>
65       * FIXME - should have name like Foo$1, not Anonymous$1 to get this working
66       * right, the parent scope needs to be passed in when instantiating a
67       * ClassScope
68       */
69      public ClassScope() {
70          // this.className = getParent().getEnclosingClassScope().getClassName()
71          // + "$" + String.valueOf(anonymousInnerClassCounter);
72          int v = anonymousInnerClassCounter.get().intValue();
73          this.className = "Anonymous$" + v;
74          anonymousInnerClassCounter.set(v + 1);
75      }
76  
77      public void setIsEnum(boolean isEnum) {
78          this.isEnum = isEnum;
79      }
80  
81      public Map<ClassNameDeclaration, List<NameOccurrence>> getClassDeclarations() {
82          return getDeclarations(ClassNameDeclaration.class);
83      }
84  
85      public Map<MethodNameDeclaration, List<NameOccurrence>> getMethodDeclarations() {
86          return getDeclarations(MethodNameDeclaration.class);
87      }
88  
89      public Map<VariableNameDeclaration, List<NameOccurrence>> getVariableDeclarations() {
90          return getDeclarations(VariableNameDeclaration.class);
91      }
92  
93      public Set<NameDeclaration> addNameOccurrence(NameOccurrence occurrence) {
94          JavaNameOccurrence javaOccurrence = (JavaNameOccurrence) occurrence;
95          Set<NameDeclaration> declarations = findVariableHere(javaOccurrence);
96          if (!declarations.isEmpty() && (javaOccurrence.isMethodOrConstructorInvocation() || javaOccurrence.isMethodReference())) {
97              for (NameDeclaration decl : declarations) {
98                  List<NameOccurrence> nameOccurrences = getMethodDeclarations().get(decl);
99                  if (nameOccurrences == null) {
100                     // TODO may be a class name: Foo.this.super();
101                 } else {
102                     nameOccurrences.add(javaOccurrence);
103                     Node n = javaOccurrence.getLocation();
104                     if (n instanceof ASTName) {
105                         ((ASTName) n).setNameDeclaration(decl);
106                     } // TODO what to do with PrimarySuffix case?
107                 }
108             }
109         } else if (!declarations.isEmpty() && !javaOccurrence.isThisOrSuper()) {
110             for (NameDeclaration decl : declarations) {
111                 List<NameOccurrence> nameOccurrences = getVariableDeclarations().get(decl);
112                 if (nameOccurrences == null) {
113                     // TODO may be a class name
114     
115                     // search inner classes
116                     for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
117                         Scope innerClassScope = innerClass.getScope();
118                         if (innerClassScope.contains(javaOccurrence)) {
119                             innerClassScope.addNameOccurrence(javaOccurrence);
120                         }
121                     }
122                 } else {
123                     nameOccurrences.add(javaOccurrence);
124                     Node n = javaOccurrence.getLocation();
125                     if (n instanceof ASTName) {
126                         ((ASTName) n).setNameDeclaration(decl);
127                     } // TODO what to do with PrimarySuffix case?
128                 }
129             }
130         }
131         return declarations;
132     }
133 
134     public String getClassName() {
135         return this.className;
136     }
137 
138     protected Set<NameDeclaration> findVariableHere(JavaNameOccurrence occurrence) {
139         Set<NameDeclaration> result = new HashSet<>();
140         Map<MethodNameDeclaration, List<NameOccurrence>> methodDeclarations = getMethodDeclarations();
141         Map<VariableNameDeclaration, List<NameOccurrence>> variableDeclarations = getVariableDeclarations();
142         if (occurrence.isThisOrSuper() || occurrence.getImage() != null && occurrence.getImage().equals(className)) {
143             if (variableDeclarations.isEmpty() && methodDeclarations.isEmpty()) {
144                 // this could happen if you do this:
145                 // public class Foo {
146                 // private String x = super.toString();
147                 // }
148                 return Collections.emptySet();
149             }
150             // return any name declaration, since all we really want is to get
151             // the scope
152             // for example, if there's a
153             // public class Foo {
154             // private static final int X = 2;
155             // private int y = Foo.X;
156             // }
157             // we'll look up Foo just to get a handle to the class scope
158             // and then we'll look up X.
159             if (!variableDeclarations.isEmpty()) {
160                 result.add(variableDeclarations.keySet().iterator().next());
161                 return result;
162             }
163             result.add(methodDeclarations.keySet().iterator().next());
164             return result;
165         }
166 
167         if (occurrence.isMethodOrConstructorInvocation()) {
168             for (MethodNameDeclaration mnd : methodDeclarations.keySet()) {
169                 if (mnd.getImage().equals(occurrence.getImage())) {
170                     List<TypedNameDeclaration> parameterTypes = determineParameterTypes(mnd);
171                     List<TypedNameDeclaration> argumentTypes = determineArgumentTypes(occurrence, parameterTypes);
172 
173                     if (!mnd.isVarargs()
174                             && occurrence.getArgumentCount() == mnd.getParameterCount()
175                             && (!getEnclosingScope(SourceFileScope.class).hasAuxclasspath() || parameterTypes
176                                     .equals(argumentTypes))) {
177                         result.add(mnd);
178                     } else if (mnd.isVarargs()) {
179                         int varArgIndex = parameterTypes.size() - 1;
180                         TypedNameDeclaration varArgType = parameterTypes.get(varArgIndex);
181 
182                         // first parameter is varArg, calling method might have
183                         // 0 or more arguments
184                         // or the calling method has enough arguments to fill in
185                         // the parameters before the vararg
186                         if ((varArgIndex == 0 || argumentTypes.size() >= varArgIndex)
187                                 && (!getEnclosingScope(SourceFileScope.class).hasAuxclasspath() || parameterTypes
188                                         .subList(0, varArgIndex).equals(argumentTypes.subList(0, varArgIndex)))) {
189 
190                             if (!getEnclosingScope(SourceFileScope.class).hasAuxclasspath()) {
191                                 result.add(mnd);
192                             }
193 
194                             boolean sameType = true;
195                             for (int i = varArgIndex; i < argumentTypes.size(); i++) {
196                                 if (!varArgType.equals(argumentTypes.get(i))) {
197                                     sameType = false;
198                                     break;
199                                 }
200                             }
201                             if (sameType) {
202                                 result.add(mnd);
203                             }
204                         }
205                     }
206                 }
207             }
208             if (isEnum && "valueOf".equals(occurrence.getImage())) {
209                 result.add(createBuiltInMethodDeclaration("valueOf", 1));
210             }
211             return result;
212         }
213         if (occurrence.isMethodReference()) {
214             for (MethodNameDeclaration mnd : methodDeclarations.keySet()) {
215                 if (mnd.getImage().equals(occurrence.getImage())) {
216                     result.add(mnd);
217                 }
218             }
219             return result;
220         }
221 
222         List<String> images = new ArrayList<>();
223         if (occurrence.getImage() != null) {
224             images.add(occurrence.getImage());
225             if (occurrence.getImage().startsWith(className)) {
226                 images.add(clipClassName(occurrence.getImage()));
227             }
228         }
229         ImageFinderFunction finder = new ImageFinderFunction(images);
230         Applier.apply(finder, variableDeclarations.keySet().iterator());
231         if (finder.getDecl() != null) {
232             result.add(finder.getDecl());
233         }
234 
235         // search inner classes
236         Map<ClassNameDeclaration, List<NameOccurrence>> classDeclarations = getClassDeclarations();
237         if (result.isEmpty() && !classDeclarations.isEmpty()) {
238             for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
239                 Applier.apply(finder, innerClass.getScope().getDeclarations().keySet().iterator());
240                 if (finder.getDecl() != null) {
241                     result.add(finder.getDecl());
242                 }
243             }
244         }
245         return result;
246     }
247 
248     /**
249      * Creates a fake method name declaration for built-in methods from Java like
250      * the Enum Method "valueOf".
251      *
252      * @param methodName the method name
253      * @param parameterCount the parameter count of the method
254      * @return a method name declaration
255      */
256     private MethodNameDeclaration createBuiltInMethodDeclaration(final String methodName, final int parameterCount) {
257         ASTMethodDeclaration methodDeclaration = new ASTMethodDeclaration(JavaParserTreeConstants.JJTMETHODDECLARATION);
258         methodDeclaration.setPublic(true);
259         methodDeclaration.setScope(this);
260 
261         ASTMethodDeclarator methodDeclarator = new ASTMethodDeclarator(JavaParserTreeConstants.JJTMETHODDECLARATOR);
262         methodDeclarator.setImage(methodName);
263         methodDeclarator.setScope(this);
264 
265         ASTFormalParameters formalParameters = new ASTFormalParameters(JavaParserTreeConstants.JJTFORMALPARAMETERS);
266         formalParameters.setScope(this);
267 
268         methodDeclaration.jjtAddChild(methodDeclarator, 0);
269         methodDeclarator.jjtSetParent(methodDeclaration);
270         methodDeclarator.jjtAddChild(formalParameters, 0);
271         formalParameters.jjtSetParent(methodDeclarator);
272 
273         for (int i = 0; i < parameterCount; i++) {
274             ASTFormalParameter formalParameter = new ASTFormalParameter(JavaParserTreeConstants.JJTFORMALPARAMETER);
275             formalParameters.jjtAddChild(formalParameter, i);
276             formalParameter.jjtSetParent(formalParameters);
277 
278             ASTType type = new ASTType(JavaParserTreeConstants.JJTTYPE);
279             formalParameter.jjtAddChild(type, 0);
280             type.jjtSetParent(formalParameter);
281             ASTVariableDeclaratorId variableDeclaratorId = new ASTVariableDeclaratorId(JavaParserTreeConstants.JJTVARIABLEDECLARATORID);
282             variableDeclaratorId.setImage("arg" + i);
283             formalParameter.jjtAddChild(variableDeclaratorId, 1);
284             variableDeclaratorId.jjtSetParent(formalParameter);
285         }
286 
287         MethodNameDeclaration mnd = new MethodNameDeclaration(methodDeclarator);
288         return mnd;
289     }
290 
291     /**
292      * Provide a list of types of the parameters of the given method
293      * declaration. The types are simple type images.
294      * 
295      * @param mnd
296      *            the method declaration.
297      * @return List of types
298      */
299     private List<TypedNameDeclaration> determineParameterTypes(MethodNameDeclaration mnd) {
300         List<TypedNameDeclaration> parameterTypes = new ArrayList<>();
301         List<ASTFormalParameter> parameters = mnd.getMethodNameDeclaratorNode().findDescendantsOfType(
302                 ASTFormalParameter.class);
303         for (ASTFormalParameter p : parameters) {
304             String typeImage = p.getTypeNode().getTypeImage();
305             // typeImage might be qualified/unqualified. If it refers to a type,
306             // defined in the same toplevel class,
307             // we should normalize the name here.
308             // It might also refer to a type, that is imported.
309             typeImage = qualifyTypeName(typeImage);
310             Node declaringNode = getEnclosingScope(SourceFileScope.class).getQualifiedTypeNames().get(typeImage);
311             Class<?> resolvedType = this.getEnclosingScope(SourceFileScope.class).resolveType(typeImage);
312             if (resolvedType == null) {
313                 resolvedType = resolveGenericType(p, typeImage);
314             }
315             parameterTypes.add(new SimpleTypedNameDeclaration(typeImage, resolvedType, determineSuper(declaringNode)));
316         }
317         return parameterTypes;
318     }
319 
320     private String qualifyTypeName(String typeImage) {
321         if (typeImage == null) {
322             return null;
323         }
324 
325         Set<String> qualifiedNames = new LinkedHashSet<>();
326         qualifiedNames.addAll(this.getEnclosingScope(SourceFileScope.class).getQualifiedTypeNames().keySet());
327         qualifiedNames.addAll(this.getEnclosingScope(SourceFileScope.class).getExplicitImports());
328 
329         int nameLength = typeImage.length();
330 
331         for (String qualified : qualifiedNames) {
332             int fullLength = qualified.length();
333             if (qualified.endsWith(typeImage)
334                     && (fullLength == nameLength || qualified.substring(0, fullLength - nameLength).endsWith("."))) {
335                 return qualified;
336             }
337         }
338         return typeImage;
339     }
340 
341     /**
342      * Provide a list of types of the arguments of the given method call. The
343      * types are simple type images. If the argument type cannot be determined
344      * (e.g. because it is itself the result of a method call), the parameter
345      * type is used - so it is assumed, it is of the correct type. This might
346      * cause confusion when methods are overloaded.
347      * 
348      * @param occurrence
349      *            the method call
350      * @param parameterTypes
351      *            the parameter types of the called method
352      * @return the list of argument types
353      */
354     private List<TypedNameDeclaration> determineArgumentTypes(JavaNameOccurrence occurrence,
355             List<TypedNameDeclaration> parameterTypes) {
356         List<TypedNameDeclaration> argumentTypes = new ArrayList<>();
357         Map<String, Node> qualifiedTypeNames = getEnclosingScope(SourceFileScope.class).getQualifiedTypeNames();
358         ASTArgumentList arguments = null;
359         Node nextSibling = null;
360         if (occurrence.getLocation() instanceof ASTPrimarySuffix) {
361             nextSibling = getNextSibling(occurrence.getLocation());
362         } else {
363             nextSibling = getNextSibling(occurrence.getLocation().jjtGetParent());
364         }
365         if (nextSibling != null) {
366             arguments = nextSibling.getFirstDescendantOfType(ASTArgumentList.class);
367         }
368 
369         if (arguments != null) {
370             for (int i = 0; i < arguments.jjtGetNumChildren(); i++) {
371                 Node argument = arguments.jjtGetChild(i);
372                 Node child = null;
373                 boolean isMethodCall = false;
374                 if (argument.jjtGetNumChildren() > 0 && argument.jjtGetChild(0).jjtGetNumChildren() > 0
375                         && argument.jjtGetChild(0).jjtGetChild(0).jjtGetNumChildren() > 0) {
376                     child = argument.jjtGetChild(0).jjtGetChild(0).jjtGetChild(0);
377                     isMethodCall = argument.jjtGetChild(0).jjtGetNumChildren() > 1;
378                 }
379                 TypedNameDeclaration type = null;
380                 if (child instanceof ASTName && !isMethodCall) {
381                     ASTName name = (ASTName) child;
382                     Scope s = name.getScope();
383                     while (s != null) {
384                         if (s.contains(new JavaNameOccurrence(name, name.getImage()))) {
385                             break;
386                         }
387                         s = s.getParent();
388                     }
389                     if (s != null) {
390                         Map<VariableNameDeclaration, List<NameOccurrence>> vars = s
391                                 .getDeclarations(VariableNameDeclaration.class);
392                         for (VariableNameDeclaration d : vars.keySet()) {
393                             // in case of simple lambda expression, the type might be unknown
394                             if (d.getImage().equals(name.getImage()) && d.getTypeImage() != null) {
395                                 String typeName = d.getTypeImage();
396                                 typeName = qualifyTypeName(typeName);
397                                 Node declaringNode = qualifiedTypeNames.get(typeName);
398                                 type = new SimpleTypedNameDeclaration(typeName, this.getEnclosingScope(
399                                         SourceFileScope.class).resolveType(typeName), determineSuper(declaringNode));
400                                 break;
401                             }
402                         }
403                     }
404                 } else if (child instanceof ASTLiteral) {
405                     ASTLiteral literal = (ASTLiteral) child;
406                     if (literal.isCharLiteral()) {
407                         type = new SimpleTypedNameDeclaration("char", literal.getType());
408                     } else if (literal.isStringLiteral()) {
409                         type = new SimpleTypedNameDeclaration("String", literal.getType());
410                     } else if (literal.isFloatLiteral()) {
411                         type = new SimpleTypedNameDeclaration("float", literal.getType());
412                     } else if (literal.isDoubleLiteral()) {
413                         type = new SimpleTypedNameDeclaration("double", literal.getType());
414                     } else if (literal.isIntLiteral()) {
415                         type = new SimpleTypedNameDeclaration("int", literal.getType());
416                     } else if (literal.isLongLiteral()) {
417                         type = new SimpleTypedNameDeclaration("long", literal.getType());
418                     } else if (literal.jjtGetNumChildren() == 1 && literal.jjtGetChild(0) instanceof ASTBooleanLiteral) {
419                         type = new SimpleTypedNameDeclaration("boolean", Boolean.TYPE);
420                     }
421                 } else if (child instanceof ASTAllocationExpression
422                         && child.jjtGetChild(0) instanceof ASTClassOrInterfaceType) {
423                     ASTClassOrInterfaceType classInterface = (ASTClassOrInterfaceType) child.jjtGetChild(0);
424                     type = convertToSimpleType(classInterface);
425                 }
426                 if (type == null && !parameterTypes.isEmpty()) {
427                     // replace the unknown type with the correct parameter type
428                     // of the method.
429                     // in case the argument is itself a method call, we can't
430                     // determine the result type of the called
431                     // method. Therefore the parameter type is used.
432                     // This might cause confusion, if method overloading is
433                     // used.
434 
435                     // the method might be vararg, so, there might be more
436                     // arguments than parameterTypes
437                     if (parameterTypes.size() > i) {
438                         type = parameterTypes.get(i);
439                     } else {
440                         type = parameterTypes.get(parameterTypes.size() - 1); // last
441                                                                               // parameter
442                                                                               // is
443                                                                               // the
444                                                                               // vararg
445                                                                               // type
446                     }
447                 }
448                 if (type != null && type.getType() == null) {
449                     Class<?> typeBound = resolveGenericType(argument, type.getTypeImage());
450                     if (typeBound != null) {
451                         type = new SimpleTypedNameDeclaration(type.getTypeImage(), typeBound);
452                     }
453                 }
454                 argumentTypes.add(type);
455             }
456         }
457         return argumentTypes;
458     }
459 
460     private SimpleTypedNameDeclaration determineSuper(Node declaringNode) {
461         SimpleTypedNameDeclaration result = null;
462         if (declaringNode instanceof ASTClassOrInterfaceDeclaration) {
463             ASTClassOrInterfaceDeclaration classDeclaration = (ASTClassOrInterfaceDeclaration) declaringNode;
464             ASTImplementsList implementsList = classDeclaration.getFirstChildOfType(ASTImplementsList.class);
465             if (implementsList != null) {
466                 List<ASTClassOrInterfaceType> types = implementsList.findChildrenOfType(ASTClassOrInterfaceType.class);
467                 SimpleTypedNameDeclaration type = convertToSimpleType(types);
468                 result = type;
469             }
470             ASTExtendsList extendsList = classDeclaration.getFirstChildOfType(ASTExtendsList.class);
471             if (extendsList != null) {
472                 List<ASTClassOrInterfaceType> types = extendsList.findChildrenOfType(ASTClassOrInterfaceType.class);
473                 SimpleTypedNameDeclaration type = convertToSimpleType(types);
474                 if (result == null) {
475                     result = type;
476                 } else {
477                     result.addNext(type);
478                 }
479             }
480         }
481         return result;
482     }
483 
484     private SimpleTypedNameDeclaration convertToSimpleType(List<ASTClassOrInterfaceType> types) {
485         SimpleTypedNameDeclaration result = null;
486         for (ASTClassOrInterfaceType t : types) {
487             SimpleTypedNameDeclaration type = convertToSimpleType(t);
488             if (result == null) {
489                 result = type;
490             } else {
491                 result.addNext(type);
492             }
493         }
494         return result;
495     }
496 
497     private SimpleTypedNameDeclaration convertToSimpleType(ASTClassOrInterfaceType t) {
498         String typeImage = t.getImage();
499         typeImage = qualifyTypeName(typeImage);
500         Node declaringNode = getEnclosingScope(SourceFileScope.class).getQualifiedTypeNames().get(typeImage);
501         return new SimpleTypedNameDeclaration(typeImage, this.getEnclosingScope(SourceFileScope.class).resolveType(
502                 typeImage), determineSuper(declaringNode));
503     }
504 
505     /**
506      * Tries to resolve a given typeImage as a generic Type. If the Generic Type
507      * is found, any defined ClassOrInterfaceType below this type declaration is
508      * used (this is typically a type bound, e.g. {@code <T extends List>}.
509      *
510      * @param argument
511      *            the node, from where to start searching.
512      * @param typeImage
513      *            the type as string
514      * @return the resolved class or <code>null</code> if nothing was found.
515      */
516     private Class<?> resolveGenericType(Node argument, String typeImage) {
517         List<ASTTypeParameter> types = new ArrayList<>();
518         // first search only within the same method
519         ASTClassOrInterfaceBodyDeclaration firstParentOfType =
520                 argument.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
521         if (firstParentOfType != null) {
522             types.addAll(firstParentOfType.findDescendantsOfType(ASTTypeParameter.class));
523         }
524 
525         // then search class level types
526         ASTClassOrInterfaceDeclaration enclosingClassOrEnum = argument
527                 .getFirstParentOfType(ASTClassOrInterfaceDeclaration.class);
528         if (enclosingClassOrEnum == null) {
529             argument.getFirstParentOfType(ASTEnumDeclaration.class);
530         }
531         ASTTypeParameters classLevelTypeParameters = null;
532         if (enclosingClassOrEnum != null) {
533             classLevelTypeParameters = enclosingClassOrEnum.getFirstChildOfType(ASTTypeParameters.class);
534         }
535         if (classLevelTypeParameters != null) {
536             types.addAll(classLevelTypeParameters.findDescendantsOfType(ASTTypeParameter.class));
537         }
538         return resolveGenericType(typeImage, types);
539     }
540 
541     private Class<?> resolveGenericType(String typeImage, List<ASTTypeParameter> types) {
542         for (ASTTypeParameter type : types) {
543             if (typeImage.equals(type.getImage())) {
544                 ASTClassOrInterfaceType bound = type.getFirstDescendantOfType(ASTClassOrInterfaceType.class);
545                 if (bound != null) {
546                     if (bound.getType() != null) {
547                         return bound.getType();
548                     } else {
549                         return this.getEnclosingScope(SourceFileScope.class).resolveType(bound.getImage());
550                     }
551                 } else {
552                     return Object.class; // type parameter found, but no binding.
553                 }
554             }
555         }
556         return null;
557     }
558 
559     private Node getNextSibling(Node current) {
560         Node nextSibling = null;
561         for (int i = 0; i < current.jjtGetParent().jjtGetNumChildren() - 1; i++) {
562             if (current.jjtGetParent().jjtGetChild(i) == current) {
563                 nextSibling = current.jjtGetParent().jjtGetChild(i + 1);
564                 break;
565             }
566         }
567         return nextSibling;
568     }
569 
570     public String toString() {
571         StringBuilder res = new StringBuilder("ClassScope (").append(className).append("): ");
572         Map<ClassNameDeclaration, List<NameOccurrence>> classDeclarations = getClassDeclarations();
573         if (classDeclarations.isEmpty()) {
574             res.append("Inner Classes ").append(glomNames(classDeclarations.keySet())).append("; ");
575         }
576         Map<MethodNameDeclaration, List<NameOccurrence>> methodDeclarations = getMethodDeclarations();
577         if (!methodDeclarations.isEmpty()) {
578             for (MethodNameDeclaration mnd : methodDeclarations.keySet()) {
579                 res.append(mnd.toString());
580                 int usages = methodDeclarations.get(mnd).size();
581                 res.append("(begins at line ").append(mnd.getNode().getBeginLine()).append(", ").append(usages)
582                         .append(" usages)");
583                 res.append(", ");
584             }
585         }
586         Map<VariableNameDeclaration, List<NameOccurrence>> variableDeclarations = getVariableDeclarations();
587         if (!variableDeclarations.isEmpty()) {
588             res.append("Variables ").append(glomNames(variableDeclarations.keySet()));
589         }
590         return res.toString();
591     }
592 
593     private String clipClassName(String s) {
594         return s.substring(s.indexOf('.') + 1);
595     }
596 }