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