View Javadoc

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