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.HashMap;
    8   import java.util.List;
    9   import java.util.Map;
    10
    11  import net.sourceforge.pmd.lang.ast.Node;
    12  import net.sourceforge.pmd.lang.java.ast.ASTName;
    13
    14  public class ClassScope extends AbstractScope {
    15
    16      protected Map<ClassNameDeclaration, List<NameOccurrence>> classNames = new HashMap<ClassNameDeclaration, List<NameOccurrence>>();
    17      protected Map<MethodNameDeclaration, List<NameOccurrence>> methodNames = new HashMap<MethodNameDeclaration, List<NameOccurrence>>();
    18      protected Map<VariableNameDeclaration, List<NameOccurrence>> variableNames = new HashMap<VariableNameDeclaration, List<NameOccurrence>>();
    19
    20      // FIXME - this breaks given sufficiently nested code
    21      private static ThreadLocal<Integer> anonymousInnerClassCounter = new ThreadLocal<Integer>() {
    22          protected Integer initialValue() { return Integer.valueOf(1); }
    23      };
    24
    25      private String className;
    26
    27      public ClassScope(String className) {
    28          this.className = className;
    29          anonymousInnerClassCounter.set(Integer.valueOf(1));
    30      }
    31
    32      /**
    33       * This is only for anonymous inner classes
    34       * <p/>
    35       * FIXME - should have name like Foo$1, not Anonymous$1
    36       * to get this working right, the parent scope needs
    37       * to be passed in when instantiating a ClassScope
    38       */
    39      public ClassScope() {
    40          //this.className = getParent().getEnclosingClassScope().getClassName() + "$" + String.valueOf(anonymousInnerClassCounter);
    41          int v = anonymousInnerClassCounter.get().intValue();
    42          this.className = "Anonymous$" + v;
    43          anonymousInnerClassCounter.set(v + 1);
    44      }
    45
    46      public void addDeclaration(VariableNameDeclaration variableDecl) {
    47          if (variableNames.containsKey(variableDecl)) {
    48              throw new RuntimeException(variableDecl + " is already in the symbol table");
    49          }
    50          variableNames.put(variableDecl, new ArrayList<NameOccurrence>());
    51      }
    52
    53      public NameDeclaration addVariableNameOccurrence(NameOccurrence occurrence) {
    54          NameDeclaration decl = findVariableHere(occurrence);
    55          if (decl != null && occurrence.isMethodOrConstructorInvocation()) {
    56              List<NameOccurrence> nameOccurrences = methodNames.get(decl);
    57              if (nameOccurrences == null) {
    58                  // TODO may be a class name: Foo.this.super();
    59              } else {
    60                  nameOccurrences.add(occurrence);
    61                  Node n = occurrence.getLocation();
    62                  if (n instanceof ASTName) {
    63                      ((ASTName) n).setNameDeclaration(decl);
    64                  } // TODO what to do with PrimarySuffix case?
    65              }
    66
    67          } else if (decl != null && !occurrence.isThisOrSuper()) {
    68              List<NameOccurrence> nameOccurrences = variableNames.get(decl);
    69              if (nameOccurrences == null) {
    70                  // TODO may be a class name
    71
    72                  // search inner classes
    73                  for (ClassNameDeclaration innerClass : classNames.keySet()) {
    74                      Scope innerClassScope = innerClass.getScope();
    75                      if (innerClassScope.contains(occurrence)) {
    76                          innerClassScope.addVariableNameOccurrence(occurrence);
    77                      }
    78                  }
    79              } else {
    80                  nameOccurrences.add(occurrence);
    81                  Node n = occurrence.getLocation();
    82                  if (n instanceof ASTName) {
    83                      ((ASTName) n).setNameDeclaration(decl);
    84                  } // TODO what to do with PrimarySuffix case?
    85              }
    86          }
    87          return decl;
    88      }
    89
    90      public Map<VariableNameDeclaration, List<NameOccurrence>> getVariableDeclarations() {
    91          VariableUsageFinderFunction f = new VariableUsageFinderFunction(variableNames);
    92          Applier.apply(f, variableNames.keySet().iterator());
    93          return f.getUsed();
    94      }
    95
    96      public Map<MethodNameDeclaration, List<NameOccurrence>> getMethodDeclarations() {
    97          return methodNames;
    98      }
    99
    100     public Map<ClassNameDeclaration, List<NameOccurrence>> getClassDeclarations() {
    101         return classNames;
    102     }
    103
    104     public ClassScope getEnclosingClassScope() {
    105         return this;
    106     }
    107
    108     public String getClassName() {
    109         return this.className;
    110     }
    111
    112     public void addDeclaration(MethodNameDeclaration decl) {
    113         methodNames.put(decl, new ArrayList<NameOccurrence>());
    114     }
    115
    116     public void addDeclaration(ClassNameDeclaration decl) {
    117         classNames.put(decl, new ArrayList<NameOccurrence>());
    118     }
    119
    120     protected NameDeclaration findVariableHere(NameOccurrence occurrence) {
    121         if (occurrence.isThisOrSuper() || occurrence.getImage().equals(className)) {
    122             if (variableNames.isEmpty() && methodNames.isEmpty()) {
    123                 // this could happen if you do this:
    124                 // public class Foo {
    125                 //  private String x = super.toString();
    126                 // }
    127                 return null;
    128             }
    129             // return any name declaration, since all we really want is to get the scope
    130             // for example, if there's a
    131             // public class Foo {
    132             //  private static final int X = 2;
    133             //  private int y = Foo.X;
    134             // }
    135             // we'll look up Foo just to get a handle to the class scope
    136             // and then we'll look up X.
    137             if (!variableNames.isEmpty()) {
    138                 return variableNames.keySet().iterator().next();
    139             }
    140             return methodNames.keySet().iterator().next();
    141         }
    142
    143         if (occurrence.isMethodOrConstructorInvocation()) {
    144             for (MethodNameDeclaration mnd: methodNames.keySet()) {
    145                 if (mnd.getImage().equals(occurrence.getImage())) {
    146                     int args = occurrence.getArgumentCount();
    147                     if (args == mnd.getParameterCount() || (mnd.isVarargs() && args >= mnd.getParameterCount() - 1)) {
    148                         // FIXME if several methods have the same name
    149                         // and parameter count, only one will get caught here
    150                         // we need to make some attempt at type lookup and discrimination
    151                         // or, failing that, mark this as a usage of all those methods
    152                         return mnd;
    153                     }
    154                 }
    155             }
    156             return null;
    157         }
    158
    159         List<String> images = new ArrayList<String>();
    160         images.add(occurrence.getImage());
    161         if (occurrence.getImage().startsWith(className)) {
    162             images.add(clipClassName(occurrence.getImage()));
    163         }
    164         ImageFinderFunction finder = new ImageFinderFunction(images);
    165         Applier.apply(finder, variableNames.keySet().iterator());
    166         NameDeclaration result = finder.getDecl();
    167
    168         // search inner classes
    169         if (result == null && !classNames.isEmpty()) {
    170             for (ClassNameDeclaration innerClass : classNames.keySet()) {
    171                 Applier.apply(finder, innerClass.getScope().getVariableDeclarations().keySet().iterator());
    172                 result = finder.getDecl();
    173                 if (result != null) {
    174                     break;
    175                 }
    176             }
    177         }
    178         return result;
    179     }
    180
    181     public String toString() {
    182         StringBuilder res = new StringBuilder("ClassScope (").append(className).append("): ");
    183         if (!classNames.isEmpty()) {
    184             res.append("Inner Classes ").append(glomNames(classNames.keySet())).append("; ");
    185         }
    186         if (!methodNames.isEmpty()) {
    187             for (MethodNameDeclaration mnd: methodNames.keySet()) {
    188                 res.append(mnd.toString());
    189                 int usages = methodNames.get(mnd).size();
    190                 res.append("(begins at line ").append(mnd.getNode().getBeginLine()).append(", ").append(usages).append(" usages)");
    191                 res.append(", ");
    192             }
    193         }
    194         if (!variableNames.isEmpty()) {
    195             res.append("Variables ").append(glomNames(variableNames.keySet()));
    196         }
    197         return res.toString();
    198     }
    199
    200     private String clipClassName(String s) {
    201         return s.substring(s.indexOf('.') + 1);
    202     }
    203 }