View Javadoc

1   /*
2    * $Id: JSRVariableScopeCodeVisitor.java,v 1.18 2005/07/06 13:44:32 blackdrag Exp $
3    *
4    * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5    *
6    * Redistribution and use of this software and associated documentation
7    * ("Software"), with or without modification, are permitted provided that the
8    * following conditions are met: 1. Redistributions of source code must retain
9    * copyright statements and notices. Redistributions must also contain a copy
10   * of this document. 2. Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following disclaimer in
12   * the documentation and/or other materials provided with the distribution. 3.
13   * The name "groovy" must not be used to endorse or promote products derived
14   * from this Software without prior written permission of The Codehaus. For
15   * written permission, please contact info@codehaus.org. 4. Products derived
16   * from this Software may not be called "groovy" nor may "groovy" appear in
17   * their names without prior written permission of The Codehaus. "groovy" is a
18   * registered trademark of The Codehaus. 5. Due credit should be given to The
19   * Codehaus - http://groovy.codehaus.org/
20   *
21   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
22   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
25   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31   * DAMAGE.
32   *
33   */
34  
35  package org.codehaus.groovy.classgen;
36  
37  import java.lang.reflect.Modifier;
38  import java.lang.reflect.Field;
39  import java.lang.reflect.Method;
40  import java.util.HashMap;
41  import java.util.HashSet;
42  import java.util.Iterator;
43  import java.util.Set;
44  import java.util.List;
45  import org.codehaus.groovy.ast.ASTNode;
46  import org.codehaus.groovy.ast.ClassNode;
47  import org.codehaus.groovy.ast.CodeVisitorSupport;
48  import org.codehaus.groovy.ast.CompileUnit;
49  import org.codehaus.groovy.ast.ConstructorNode;
50  import org.codehaus.groovy.ast.FieldNode;
51  import org.codehaus.groovy.ast.GroovyClassVisitor;
52  import org.codehaus.groovy.ast.MethodNode;
53  import org.codehaus.groovy.ast.Parameter;
54  import org.codehaus.groovy.ast.PropertyNode;
55  import org.codehaus.groovy.ast.expr.ClosureExpression;
56  import org.codehaus.groovy.ast.expr.DeclarationExpression;
57  import org.codehaus.groovy.ast.expr.Expression;
58  import org.codehaus.groovy.ast.expr.FieldExpression;
59  import org.codehaus.groovy.ast.expr.PropertyExpression;
60  import org.codehaus.groovy.ast.expr.VariableExpression;
61  import org.codehaus.groovy.ast.stmt.BlockStatement;
62  import org.codehaus.groovy.ast.stmt.CatchStatement;
63  import org.codehaus.groovy.ast.stmt.DoWhileStatement;
64  import org.codehaus.groovy.ast.stmt.ForStatement;
65  import org.codehaus.groovy.ast.stmt.Statement;
66  import org.codehaus.groovy.ast.stmt.WhileStatement;
67  import org.codehaus.groovy.control.SourceUnit;
68  import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
69  import org.codehaus.groovy.syntax.SyntaxException;
70  
71  public class JSRVariableScopeCodeVisitor extends CodeVisitorSupport implements GroovyClassVisitor {
72  
73      private static class Var {
74          //TODO: support final and native
75          boolean isStatic=false, isFinal=false, isDynamicTyped=false;
76          String name, type=null;
77          Class typeClass=null;
78          boolean isInStaticContext=false;
79          
80          public boolean equals(Object o){
81              Var v = (Var) o;
82              return v.name.equals(name);
83          }
84          
85          public int hashCode() {
86              return name.hashCode();
87          }
88          
89          public Var(String name) {
90              // a Variable without type and other modifiers
91              // make it dynamic type, non final and non static
92              this.name=name;
93          }
94          
95          public Var(VariableExpression ve, VarScope scope) {
96              name = ve.getVariable();
97              if(ve.isDynamic()) {
98                  isDynamicTyped=true;
99              } else {
100                 type = ve.getType();
101                 typeClass = ve.getTypeClass();
102             }
103             isInStaticContext = scope.isInStaticContext;
104         }
105         
106         public Var(Parameter par, boolean staticContext) {
107             name = par.getName();
108             if (par.isDynamicType()) {
109                 isDynamicTyped=true;
110             } else {
111                 type = par.getType();
112             }
113             isInStaticContext = staticContext;
114         }
115 
116         public Var(FieldNode f) {
117             name = f.getName();
118             if (f.isDynamicType()) {
119                 isDynamicTyped=true;
120             } else {
121                 type = f.getType();
122             }
123             isStatic=f.isStatic();
124             isInStaticContext = isStatic;
125         }
126 
127         public Var(String pName, MethodNode f) {
128             name = pName;
129             if (f.isDynamicReturnType()) {
130                 isDynamicTyped=true;
131             } else {
132                 type = f.getReturnType();
133             }
134             isStatic=f.isStatic();
135             isInStaticContext = isStatic;
136         }
137         
138         public Var(String pName, Method m) {
139             name = pName;
140             typeClass = m.getReturnType();            
141             isStatic=Modifier.isStatic(m.getModifiers());
142             isFinal=Modifier.isFinal(m.getModifiers());
143             isInStaticContext = isStatic;
144         }
145 
146         public Var(PropertyNode f) {
147             //TODO: no static? What about read-/write-only? abstract?
148             isInStaticContext = false;
149             name = f.getName();
150             if (f.isDynamicType()) {
151                 isDynamicTyped=true;
152             } else {
153                 type = f.getType();
154             }
155             isInStaticContext = false;
156         }
157 
158         public Var(Field f) {
159             name = f.getName();
160             typeClass = f.getType();            
161             isStatic=Modifier.isStatic(f.getModifiers());
162             isInStaticContext = isStatic;
163             isFinal=Modifier.isFinal(f.getModifiers());
164             isInStaticContext = isStatic;
165         }
166 
167         public Var(Var v) {
168             isStatic=v.isStatic;
169             isFinal=v.isFinal;
170             isDynamicTyped=v.isDynamicTyped;
171             name=v.name;
172             type=v.type;
173             typeClass=v.typeClass;
174             isInStaticContext=v.isInStaticContext;
175         }        
176     }
177     
178     private static class VarScope {
179         boolean isClass=true;
180         boolean isInStaticContext = false;
181         
182         VarScope parent;
183         HashMap declares = new HashMap();
184         HashMap visibles = new HashMap();
185         
186         public VarScope(boolean isClass, VarScope parent, boolean staticContext) {
187             this.isClass=isClass;
188             this.parent = parent;
189             isInStaticContext = staticContext;
190         }
191         
192         public VarScope(VarScope parent, boolean staticContext) {
193             this(false,parent,staticContext);
194         }
195         
196         public VarScope(VarScope parent) {
197             this(false,parent,parent!=null?parent.isInStaticContext:false);
198         }
199     }
200     
201     private static class JRoseCheck  extends CodeVisitorSupport{
202         boolean closureStarted=false;
203         boolean itUsed=false;
204         
205         public void visitClosureExpression(ClosureExpression expression) {
206             // don't visit subclosures if already in a closure
207             if (closureStarted) return;
208             closureStarted=true;
209             Parameter[] param = expression.getParameters();
210             for (int i=0; i<param.length; i++) {
211                 itUsed = (param[i].getName().equals("it")) && closureStarted || itUsed;
212             }
213             super.visitClosureExpression(expression);
214         }
215         
216         public void visitVariableExpression(VariableExpression expression) {
217             itUsed = (expression.getVariable().equals("it")) && closureStarted || itUsed;
218         }
219         
220     }
221     
222     
223     private VarScope currentScope = null;
224     private CompileUnit unit;
225     private SourceUnit source; 
226     private boolean scriptMode=false;
227     private ClassNode currentClass=null;
228     
229     private boolean jroseRule=false;
230     
231     public JSRVariableScopeCodeVisitor(VarScope scope, SourceUnit source) {
232         //System.out.println("scope check enabled");
233         if ("true".equals(System.getProperty("groovy.jsr.check.rule.jrose"))) {
234             jroseRule=true;
235             //System.out.println("jrose check enabled");
236         }
237         currentScope = scope;
238         this.source = source;
239         if (source.getAST() == null) return;
240         this.unit = source.getAST().getUnit();
241     }
242 
243     public void visitBlockStatement(BlockStatement block) {
244         VarScope scope = currentScope;
245         currentScope = new VarScope(currentScope);
246         super.visitBlockStatement(block);
247         currentScope = scope;
248     }
249 
250     public void visitForLoop(ForStatement forLoop) {
251         VarScope scope = currentScope;
252         // TODO: always define a variable here? What about type?
253         currentScope = new VarScope(currentScope);
254         declare(new Var(forLoop.getVariable()), forLoop);
255         super.visitForLoop(forLoop);
256         currentScope = scope;
257     }
258 
259     public void visitWhileLoop(WhileStatement loop) {
260         //TODO: check while loop variables
261         VarScope scope = currentScope;
262         currentScope = new VarScope(currentScope);
263         super.visitWhileLoop(loop);
264         currentScope = scope;
265     }
266 
267     public void visitDoWhileLoop(DoWhileStatement loop) {
268         //TODO: still existant?
269         VarScope scope = currentScope;
270         currentScope = new VarScope(currentScope);
271         super.visitDoWhileLoop(loop);
272         currentScope = scope;
273     }
274 
275     public void visitDeclarationExpression(DeclarationExpression expression) {
276         // visit right side first to avoid the usage of a 
277         // variable before its declaration
278         expression.getRightExpression().visit(this);
279         // no need to visit left side, just get the variable name
280         VariableExpression vex = expression.getVariableExpression();
281         if (!jroseRule && "it".equals(vex.getVariable())) {
282             // we are not in jrose mode, so don't allow variables 
283             // of the name 'it'
284             addError("'it' is a keyword in this mode.",vex);
285         } else {
286             declare(vex);
287         }
288     }
289     
290     private void addError(String msg, ASTNode expr) {
291         int line = expr.getLineNumber();
292         int col = expr.getColumnNumber();
293         source.getErrorCollector().addErrorAndContinue(
294           new SyntaxErrorMessage(new SyntaxException(msg + '\n', line, col), source)
295         );
296     }
297 
298     private void declare(VariableExpression expr) {
299         declare(new Var(expr,currentScope),expr);
300     }
301     
302     private void declare(Var var, ASTNode expr) {
303         String scopeType = "scope";
304         String variableType = "variable";
305         
306         if (expr.getClass()==FieldNode.class){
307             scopeType = "class"; 
308             variableType = "field";
309         } else if (expr.getClass()==PropertyNode.class){
310             scopeType = "class"; 
311             variableType = "property";
312         }
313         
314         StringBuffer msg = new StringBuffer();
315         msg.append("The current ").append(scopeType);
316         msg.append(" does already contain a ").append(variableType);
317         msg.append(" of the name ").append(var.name);
318         
319         if (currentScope.declares.get(var.name)!=null) {
320             addError(msg.toString(),expr);
321             return;
322         }
323         
324         //TODO: this case is not visited I think
325         if (currentScope.isClass) {
326             currentScope.declares.put(var.name,var);
327         }
328         
329         for (VarScope scope = currentScope.parent; scope!=null; scope = scope.parent) {
330             HashMap declares = scope.declares;
331             if (scope.isClass) break;
332             if (declares.get(var.name)!=null) {
333                 // variable already declared
334                 addError(msg.toString(), expr);
335                 break;
336             }
337         }
338         // declare the variable even if there was an error to allow more checks
339         currentScope.declares.put(var.name,var);
340         var.isInStaticContext = currentScope.isInStaticContext;
341     }
342 
343     /*
344      * public void visitBinaryExpression(BinaryExpression expression) {
345      *  // evaluate right first because for an expression like "def a = a"
346      *  // we need first to know if the a on the rhs is defined, before
347      *  // defining a new a for the lhs
348      * 
349      * Expression right = expression.getRightExpression();
350      * 
351      * right.visit(this);
352      * 
353      * Expression left = expression.getLeftExpression();
354      * 
355      * left.visit(this);
356      *  }
357      */
358     
359     public void visitVariableExpression(VariableExpression expression) {
360         String name = expression.getVariable();
361         Var v = checkVariableNameForDeclaration(name,expression);
362         if (v==null) return;
363         checkVariableContextAccess(v,expression);
364     }
365     
366     
367     public void visitFieldExpression(FieldExpression expression) {
368         String name = expression.getFieldName();
369         //TODO: change that to get the correct scope
370         Var v = checkVariableNameForDeclaration(name,expression);
371         checkVariableContextAccess(v,expression);  
372     }
373     
374     private void checkAbstractDeclaration(MethodNode methodNode) {
375         if (!Modifier.isAbstract(methodNode.getModifiers())) return;
376         if (Modifier.isAbstract(currentClass.getModifiers())) return;
377         addError("Can't have an abstract method in a non abstract class." +
378                  " The class '" + currentClass.getName() +  "' must be declared abstract or the method '" +
379                  methodNode.getName() + "' must not be abstract.",methodNode);
380     }
381     
382     private String getTypeName(String name) {
383         if (!name.endsWith("[]")) return name;
384         
385         String prefix = "";
386         while (name.endsWith("[]")) {
387             name = name.substring(0,name.length()-2);
388             prefix = "[";
389         }
390         
391         if (name.equals("int")) {
392             return prefix + "I";
393         }
394         else if (name.equals("long")) {
395             return prefix + "J";
396         }
397         else if (name.equals("short")) {
398             return prefix + "S";
399         }
400         else if (name.equals("float")) {
401             return prefix + "F";
402         }
403         else if (name.equals("double")) {
404             return prefix + "D";
405         }
406         else if (name.equals("byte")) {
407             return prefix + "B";
408         }
409         else if (name.equals("char")) {
410             return prefix + "C";
411         }
412         else if (name.equals("boolean")) {
413             return prefix + "Z";
414         }
415         // no primitive
416         return prefix+"L"+name+";";
417     }
418     
419     private boolean hasEqualParameterTypes(Parameter[] first, Parameter[] second) {
420         if (first.length!=second.length) return false;
421         for (int i=0; i<first.length; i++) {
422             String ft = getTypeName(first[i].getType());
423             String st = getTypeName(second[i].getType());
424             if (ft.equals(st)) continue;
425             return false;
426         }        
427         return true; 
428     }
429     
430     private void checkImplementsAndExtends(ClassNode node) {
431         ClassNode cn = node.getSuperClassNode();
432         if (cn.isInterface()) addError("you are not allowed to extend the Interface "+cn.getName()+", use implements instead", node);
433         String[] interfaces = node.getInterfaces();
434         for (int i = 0; i < interfaces.length; i++) {
435             cn = node.findClassNode(interfaces[i]);
436             if (!cn.isInterface()) addError ("you are not allowed to implement the Class "+cn.getName()+", use extends instead", node); 
437         }
438     }
439     
440     private void checkClassForOverwritingFinal(ClassNode cn) {
441         ClassNode superCN = cn.getSuperClassNode();
442         if (superCN==null) return;
443         if (!Modifier.isFinal(superCN.getModifiers())) return;
444         StringBuffer msg = new StringBuffer();
445         msg.append("you are not allowed to overwrite the final class ");
446         msg.append(superCN.getName());
447         msg.append(".");
448         addError(msg.toString(),cn);
449         
450     }
451     
452     private void checkMethodsForOverwritingFinal(ClassNode cn) {
453         List l = cn.getMethods();     
454         for (Iterator cnIter = l.iterator(); cnIter.hasNext();) {
455             MethodNode method =(MethodNode) cnIter.next();
456             Parameter[] parameters = method.getParameters();
457             for (ClassNode superCN = cn.getSuperClassNode(); superCN!=null; superCN=superCN.getSuperClassNode()){
458                 List methods = superCN.getMethods(method.getName());
459                 for (Iterator iter = methods.iterator(); iter.hasNext();) {
460                     MethodNode m = (MethodNode) iter.next();
461                     Parameter[] np = m.getParameters();
462                     if (!hasEqualParameterTypes(parameters,np)) continue;
463                     if (!Modifier.isFinal(m.getModifiers())) return;
464                     
465                     StringBuffer msg = new StringBuffer();
466                     msg.append("you are not allowed to overwrite the final method ").append(method.getName());
467                     msg.append("(");
468                     boolean semi = false;
469                     for (int i=0; i<parameters.length;i++) {
470                         if (semi) {
471                             msg.append(",");
472                         } else {
473                             semi = true;
474                         }
475                         msg.append(parameters[i].getType());
476                     }
477                     msg.append(")");
478                     msg.append(" from class ").append(superCN.getName()); 
479                     msg.append(".");
480                     addError(msg.toString(),method);
481                     return;
482                 }
483             }
484         }        
485     }
486     
487     private void checkVariableContextAccess(Var v, Expression expr) {
488         if (v.isInStaticContext || !currentScope.isInStaticContext) return;        
489         
490         String msg =  v.name+
491                       " is declared in a dynamic context, but you tried to"+
492                       " access it from a static context.";
493         addError(msg,expr);
494         
495         // decalre a static variable to be able to continue the check
496         Var v2 = new Var(v);
497         v2.isInStaticContext = true;
498         currentScope.declares.put(v2.name,v2);
499     }
500     
501     private Var checkVariableNameForDeclaration(VariableExpression expression) {
502         if (expression == VariableExpression.THIS_EXPRESSION) return null;
503         String name = expression.getVariable();
504         return checkVariableNameForDeclaration(name,expression);
505     }
506     
507     private Var checkVariableNameForDeclaration(String name, Expression expression) {
508         Var var = new Var(name);
509         
510         // TODO: this line is not working
511         // if (expression==VariableExpression.SUPER_EXPRESSION) return;
512         if ("super".equals(var.name) || "this".equals(var.name)) return null;
513         
514         VarScope scope = currentScope;
515         while (scope != null) {
516             if (scope.declares.get(var.name)!=null) {
517                 var = (Var) scope.declares.get(var.name);
518                 break;
519             }
520             if (scope.visibles.get(var.name)!=null) {
521                 var = (Var) scope.visibles.get(var.name);
522                 break;
523             }
524             // scope.getReferencedVariables().add(name);
525             scope = scope.parent;
526         }
527 
528         VarScope end = scope;
529 
530         if (scope == null) {
531             //TODO add a check to be on the lhs!
532             ClassNode vn = unit.getClass(var.name);
533             // vn==null means there is no class of that name
534             // note: we need to do this check because it's possible in groovy to access
535             //       Classes without the .class known from Java. Example: def type = String;
536             if (vn==null) {
537                 declare(var,expression);
538                 // don't create an error when inside a script body 
539                 if (!scriptMode) addError("The variable " + var.name +
540                                           " is undefined in the current scope", expression);
541             }
542         } else {
543             scope = currentScope;
544             while (scope != end) {
545                 scope.visibles.put(var.name,var);
546                 scope = scope.parent;
547             }
548         }
549         
550         return var;
551     }
552 
553     public void visitClosureExpression(ClosureExpression expression) {
554         VarScope scope = currentScope;
555         currentScope = new VarScope(false,currentScope,scope.isInStaticContext);
556     
557         // TODO: set scope
558         // expression.setVarScope(currentScope);
559 
560         if (expression.isParameterSpecified()) {
561             Parameter[] parameters = expression.getParameters();
562             for (int i = 0; i < parameters.length; i++) {
563                 declare(new Var(parameters[i],scope.isInStaticContext),expression);
564             }
565         } else {
566             Var var = new Var("it");
567             var.isInStaticContext = scope.isInStaticContext;
568             // TODO: when to add "it" and when not?
569             // John's rule is to add it only to the closures using 'it'
570             // and only to the closure itself, not to subclosures
571             if (jroseRule) {
572                 JRoseCheck check = new JRoseCheck();
573                 expression.visit(check);
574                 if (check.itUsed) declare(var,expression);
575             } else {                
576                 currentScope.declares.put("it",var);
577             }
578         }
579 
580         // currentScope = new VarScope(currentScope);
581         super.visitClosureExpression(expression);
582         currentScope = scope;
583     }
584 
585     public void visitClass(ClassNode node) {
586         checkImplementsAndExtends(node);
587         checkClassForOverwritingFinal(node);
588         checkMethodsForOverwritingFinal(node);
589         VarScope scope = currentScope;
590         currentScope = new VarScope(true,currentScope,false);
591         boolean scriptModeBackup = scriptMode;
592         scriptMode = node.isScript();
593         ClassNode classBackup = currentClass;
594         currentClass = node;
595         
596         HashMap declares = currentScope.declares;
597         // first pass, add all possible variable names (properies and fields)
598         // TODO: handle interfaces
599         // TODO: handle static imports
600         try {
601             addVarNames(node);
602             addVarNames(node.getOuterClass(), currentScope.visibles, true);
603             addVarNames(node.getSuperClass(), currentScope.visibles, true);
604         } catch (ClassNotFoundException cnfe) {
605             //TODO: handle this case properly
606             // throw new GroovyRuntimeException("couldn't find super
607             // class",cnfe);
608             cnfe.printStackTrace();
609         }
610        
611         // second pass, check contents
612         node.visitContents(this);
613         
614         currentClass = classBackup;
615         currentScope = scope;
616         scriptMode = scriptModeBackup;
617     }
618 
619     private void addVarNames(Class c, HashMap refs, boolean visitParent)
620             throws ClassNotFoundException 
621     {
622         if (c == null) return;
623         // to prefer compiled code try to get a ClassNode via name first
624         addVarNames(c.getName(), refs, visitParent);
625     }
626     
627     private void addVarNames(ClassNode cn) {
628         //TODO: change test for currentScope.declares
629         //TODO: handle indexed properties
630         if (cn == null) return;
631         List l = cn.getFields();
632         Set fields = new HashSet();        
633         for (Iterator iter = l.iterator(); iter.hasNext();) {
634             FieldNode f = (FieldNode) iter.next();
635             Var var = new Var(f);
636             if (fields.contains(var)) {
637                 declare(var,f);
638             } else {
639                 fields.add(var);
640                 currentScope.declares.put(var.name,var);
641             }            
642         }
643 
644         //TODO: ignore double delcaration of methods for the moment
645         l = cn.getMethods();
646         Set setter = new HashSet();
647         Set getter = new HashSet();
648         for (Iterator iter = l.iterator(); iter.hasNext();) {
649             MethodNode f =(MethodNode) iter.next();
650             String methodName = f.getName();
651             String pName = getPropertyName(methodName);
652             if (pName == null) continue; 
653             Var var = new Var(pName,f);
654             currentScope.declares.put(var.name,var);
655         }
656 
657         l = cn.getProperties();
658         Set props = new HashSet();
659         for (Iterator iter = l.iterator(); iter.hasNext();) {
660             PropertyNode f = (PropertyNode) iter.next();
661             Var var = new Var(f);
662             if (props.contains(var)) {
663                 declare(var,f);
664             } else {
665                 props.add(var);
666                 currentScope.declares.put(var.name,var);
667             } 
668         }
669     }
670 
671     private void addVarNames(ClassNode cn, HashMap refs, boolean visitParent)
672             throws ClassNotFoundException {
673         // note this method is only called for parent classes
674         
675         if (cn == null) return;
676         List l = cn.getFields();
677         for (Iterator iter = l.iterator(); iter.hasNext();) {
678             FieldNode f = (FieldNode) iter.next();
679             if (visitParent && Modifier.isPrivate(f.getModifiers()))
680                 continue;
681             refs.put(f.getName(),new Var(f));
682         }
683         l = cn.getMethods();
684         for (Iterator iter = l.iterator(); iter.hasNext();) {
685             MethodNode f = (MethodNode) iter.next();
686             if (visitParent && Modifier.isPrivate(f.getModifiers()))
687                 continue;
688             String name = getPropertyName(f.getName());
689             if (name == null) continue;
690             refs.put(name, new Var(name,f));
691         }
692 
693         l = cn.getProperties();
694         for (Iterator iter = l.iterator(); iter.hasNext();) {
695             PropertyNode f = (PropertyNode) iter.next();
696             if (visitParent && Modifier.isPrivate(f.getModifiers()))
697                 continue;
698             refs.put(f.getName(),new Var(f));
699         }
700 
701         if (!visitParent) return;
702 
703         addVarNames(cn.getSuperClass(), refs, visitParent);
704         MethodNode enclosingMethod = cn.getEnclosingMethod();
705 
706         if (enclosingMethod == null) return;
707 
708         Parameter[] params = enclosingMethod.getParameters();
709         for (int i = 0; i < params.length; i++) {
710             refs.put(params[i].getName(),new Var(params[i],enclosingMethod.isStatic()));
711         }
712 
713         if (visitParent)
714             addVarNames(enclosingMethod.getDeclaringClass(), refs, visitParent);
715 
716         addVarNames(cn.getOuterClass(), refs, visitParent);
717     }
718 
719     private void addVarNames(String superclassName, HashMap refs, boolean visitParent) 
720       throws ClassNotFoundException 
721     {
722 
723         if (superclassName == null) return;
724 
725         ClassNode cn = unit.getClass(superclassName);
726         if (cn != null) {
727             addVarNames(cn, refs, visitParent);
728             return;
729         }
730 
731         Class c = unit.getClassLoader().loadClass(superclassName);
732         Field[] fields = c.getFields();
733         for (int i = 0; i < fields.length; i++) {
734             Field f = fields[i];
735             if (visitParent && Modifier.isPrivate(f.getModifiers()))
736                 continue;
737             refs.put(f.getName(),new Var(f));
738         }
739 
740         Method[] methods = c.getMethods();
741         for (int i = 0; i < methods.length; i++) {
742             Method m = methods[i];
743             if (visitParent && Modifier.isPrivate(m.getModifiers()))
744                 continue;
745             String name = getPropertyName(m.getName());
746             if (name == null) continue;
747             refs.put(name,new Var(name,m));
748         }
749 
750         if (!visitParent) return;
751 
752         addVarNames(c.getSuperclass(), refs, visitParent);
753 
754         // it's not possible to know the variable names used for an enclosing
755         // method
756 
757         // addVarNames(c.getEnclosingClass(),refs,visitParent);
758     }
759     
760     private String getPropertyName(String name) {
761         if (!(name.startsWith("set") || name.startsWith("get"))) return null;
762         String pname = name.substring(3);
763         if (pname.length() == 0) return null;
764         String s = pname.substring(0, 1).toLowerCase();
765         String rest = pname.substring(1);
766         return s + rest;
767     }    
768 
769     public void visitConstructor(ConstructorNode node) {
770         VarScope scope = currentScope;
771         currentScope = new VarScope(currentScope);
772         
773         // TODO: set scope
774         // node.setVarScope(currentScope);
775         
776         HashMap declares = currentScope.declares;
777         Parameter[] parameters = node.getParameters();
778         for (int i = 0; i < parameters.length; i++) {
779             // a constructor is never static
780             declare(new Var(parameters[i],false),node);
781         }
782         currentScope = new VarScope(currentScope);
783         Statement code = node.getCode();
784         if (code != null) code.visit(this);
785         currentScope = scope;
786     }
787     
788     public void visitMethod(MethodNode node) {
789         checkAbstractDeclaration(node);
790         
791         VarScope scope = currentScope;
792         currentScope = new VarScope(currentScope,node.isStatic());
793         
794         // TODO: set scope
795         // node.setVarScope(currentScope);
796         
797         HashMap declares = currentScope.declares;
798         Parameter[] parameters = node.getParameters();
799         for (int i = 0; i < parameters.length; i++) {
800             declares.put(parameters[i].getName(),new Var(parameters[i],node.isStatic()));
801         }
802 
803         currentScope = new VarScope(currentScope);
804         Statement code = node.getCode();
805         if (code!=null) code.visit(this);
806         currentScope = scope;
807     }
808 
809     public void visitField(FieldNode node) {
810         Expression init = node.getInitialValueExpression();
811         if (init != null) init.visit(this);
812     }
813 
814     public void visitProperty(PropertyNode node) {
815         Statement statement = node.getGetterBlock();
816         if (statement != null) statement.visit(this);
817         
818         statement = node.getSetterBlock();
819         if (statement != null) statement.visit(this);
820         
821         Expression init = node.getInitialValueExpression();
822         if (init != null) init.visit(this);
823     }
824 
825     public void visitPropertyExpression(PropertyExpression expression) {
826 
827     }
828 
829     public void visitCatchStatement(CatchStatement statement) {
830         VarScope scope = currentScope;
831         currentScope = new VarScope(currentScope);
832         declare(new Var(statement.getVariable()), statement);
833         super.visitCatchStatement(statement);
834         currentScope = scope;
835     }
836 }