View Javadoc

1   /*
2    * $Id: JSRVariableScopeCodeVisitor.java,v 1.16 2005/06/13 20:50:50 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         
416         throw new AssertionError(false);
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 checkClassForOverwritingFinal(ClassNode cn) {
431         ClassNode superCN = cn.getSuperClassNode();
432         if (superCN==null) return;
433         if (!Modifier.isFinal(superCN.getModifiers())) return;
434         StringBuffer msg = new StringBuffer();
435         msg.append("you are not allowed to overwrite the final class ");
436         msg.append(superCN.getName());
437         msg.append(".");
438         addError(msg.toString(),cn);
439         
440     }
441     
442     private void checkMethodsForOverwritingFinal(ClassNode cn) {
443         List l = cn.getMethods();     
444         for (Iterator cnIter = l.iterator(); cnIter.hasNext();) {
445             MethodNode method =(MethodNode) cnIter.next();
446             Parameter[] parameters = method.getParameters();
447             for (ClassNode superCN = cn.getSuperClassNode(); superCN!=null; superCN=superCN.getSuperClassNode()){
448                 List methods = superCN.getMethods(method.getName());
449                 for (Iterator iter = methods.iterator(); iter.hasNext();) {
450                     MethodNode m = (MethodNode) iter.next();
451                     Parameter[] np = m.getParameters();
452                     if (!hasEqualParameterTypes(parameters,np)) continue;
453                     if (!Modifier.isFinal(m.getModifiers())) return;
454                     
455                     StringBuffer msg = new StringBuffer();
456                     msg.append("you are not allowed to overwrite the final method ").append(method.getName());
457                     msg.append("(");
458                     boolean semi = false;
459                     for (int i=0; i<parameters.length;i++) {
460                         if (semi) {
461                             msg.append(",");
462                         } else {
463                             semi = true;
464                         }
465                         msg.append(parameters[i].getType());
466                     }
467                     msg.append(")");
468                     msg.append(" from class ").append(superCN.getName()); 
469                     msg.append(".");
470                     addError(msg.toString(),method);
471                     return;
472                 }
473             }
474         }        
475     }
476     
477     private void checkVariableContextAccess(Var v, Expression expr) {
478         if (v.isInStaticContext || !currentScope.isInStaticContext) return;        
479         
480         String msg =  v.name+
481                       " is declared in a dynamic context, but you tried to"+
482                       " access it from a static context.";
483         addError(msg,expr);
484         
485         // decalre a static variable to be able to continue the check
486         Var v2 = new Var(v);
487         v2.isInStaticContext = true;
488         currentScope.declares.put(v2.name,v2);
489     }
490     
491     private Var checkVariableNameForDeclaration(VariableExpression expression) {
492         if (expression == VariableExpression.THIS_EXPRESSION) return null;
493         String name = expression.getVariable();
494         return checkVariableNameForDeclaration(name,expression);
495     }
496     
497     private Var checkVariableNameForDeclaration(String name, Expression expression) {
498         Var var = new Var(name);
499         
500         // TODO: this line is not working
501         // if (expression==VariableExpression.SUPER_EXPRESSION) return;
502         if ("super".equals(var.name) || "this".equals(var.name)) return null;
503         
504         VarScope scope = currentScope;
505         while (scope != null) {
506             if (scope.declares.get(var.name)!=null) {
507                 var = (Var) scope.declares.get(var.name);
508                 break;
509             }
510             if (scope.visibles.get(var.name)!=null) {
511                 var = (Var) scope.visibles.get(var.name);
512                 break;
513             }
514             // scope.getReferencedVariables().add(name);
515             scope = scope.parent;
516         }
517 
518         VarScope end = scope;
519 
520         if (scope == null) {
521             //TODO add a check to be on the lhs!
522             ClassNode vn = unit.getClass(var.name);
523             // vn==null means there is no class of that name
524             // note: we need to do this check because it's possible in groovy to access
525             //       Classes without the .class known from Java. Example: def type = String;
526             if (vn==null) {
527                 declare(var,expression);
528                 // don't create an error when inside a script body 
529                 if (!scriptMode) addError("The variable " + var.name +
530                                           " is undefined in the current scope", expression);
531             }
532         } else {
533             scope = currentScope;
534             while (scope != end) {
535                 scope.visibles.put(var.name,var);
536                 scope = scope.parent;
537             }
538         }
539         
540         return var;
541     }
542 
543     public void visitClosureExpression(ClosureExpression expression) {
544         VarScope scope = currentScope;
545         currentScope = new VarScope(false,currentScope,scope.isInStaticContext);
546     
547         // TODO: set scope
548         // expression.setVarScope(currentScope);
549 
550         if (expression.isParameterSpecified()) {
551             Parameter[] parameters = expression.getParameters();
552             for (int i = 0; i < parameters.length; i++) {
553                 declare(new Var(parameters[i],scope.isInStaticContext),expression);
554             }
555         } else {
556             Var var = new Var("it");
557             var.isInStaticContext = scope.isInStaticContext;
558             // TODO: when to add "it" and when not?
559             // John's rule is to add it only to the closures using 'it'
560             // and only to the closure itself, not to subclosures
561             if (jroseRule) {
562                 JRoseCheck check = new JRoseCheck();
563                 expression.visit(check);
564                 if (check.itUsed) declare(var,expression);
565             } else {                
566                 currentScope.declares.put("it",var);
567             }
568         }
569 
570         // currentScope = new VarScope(currentScope);
571         super.visitClosureExpression(expression);
572         currentScope = scope;
573     }
574 
575     public void visitClass(ClassNode node) {
576         checkClassForOverwritingFinal(node);
577         checkMethodsForOverwritingFinal(node);
578         VarScope scope = currentScope;
579         currentScope = new VarScope(true,currentScope,false);
580         boolean scriptModeBackup = scriptMode;
581         scriptMode = node.isScript();
582         ClassNode classBackup = currentClass;
583         currentClass = node;
584         
585         HashMap declares = currentScope.declares;
586         // first pass, add all possible variable names (properies and fields)
587         // TODO: handle interfaces
588         // TODO: handle static imports
589         try {
590             addVarNames(node);
591             addVarNames(node.getOuterClass(), currentScope.visibles, true);
592             addVarNames(node.getSuperClass(), currentScope.visibles, true);
593         } catch (ClassNotFoundException cnfe) {
594             //TODO: handle this case properly
595             // throw new GroovyRuntimeException("couldn't find super
596             // class",cnfe);
597             cnfe.printStackTrace();
598         }
599        
600         // second pass, check contents
601         node.visitContents(this);
602         
603         currentClass = classBackup;
604         currentScope = scope;
605         scriptMode = scriptModeBackup;
606     }
607 
608     private void addVarNames(Class c, HashMap refs, boolean visitParent)
609             throws ClassNotFoundException 
610     {
611         if (c == null) return;
612         // to prefer compiled code try to get a ClassNode via name first
613         addVarNames(c.getName(), refs, visitParent);
614     }
615     
616     private void addVarNames(ClassNode cn) {
617         //TODO: change test for currentScope.declares
618         //TODO: handle indexed properties
619         if (cn == null) return;
620         List l = cn.getFields();
621         Set fields = new HashSet();        
622         for (Iterator iter = l.iterator(); iter.hasNext();) {
623             FieldNode f = (FieldNode) iter.next();
624             Var var = new Var(f);
625             if (fields.contains(var)) {
626                 declare(var,f);
627             } else {
628                 fields.add(var);
629                 currentScope.declares.put(var.name,var);
630             }            
631         }
632 
633         //TODO: ignore double delcaration of methods for the moment
634         l = cn.getMethods();
635         Set setter = new HashSet();
636         Set getter = new HashSet();
637         for (Iterator iter = l.iterator(); iter.hasNext();) {
638             MethodNode f =(MethodNode) iter.next();
639             String methodName = f.getName();
640             String pName = getPropertyName(methodName);
641             if (pName == null) continue; 
642             Var var = new Var(pName,f);
643             currentScope.declares.put(var.name,var);
644         }
645 
646         l = cn.getProperties();
647         Set props = new HashSet();
648         for (Iterator iter = l.iterator(); iter.hasNext();) {
649             PropertyNode f = (PropertyNode) iter.next();
650             Var var = new Var(f);
651             if (props.contains(var)) {
652                 declare(var,f);
653             } else {
654                 props.add(var);
655                 currentScope.declares.put(var.name,var);
656             } 
657         }
658     }
659 
660     private void addVarNames(ClassNode cn, HashMap refs, boolean visitParent)
661             throws ClassNotFoundException {
662         if (cn == null) return;
663         List l = cn.getFields();
664         for (Iterator iter = l.iterator(); iter.hasNext();) {
665             FieldNode f = (FieldNode) iter.next();
666             if (visitParent && Modifier.isPrivate(f.getModifiers()))
667                 continue;
668             refs.put(f.getName(),new Var(f));
669         }
670         l = cn.getMethods();
671         for (Iterator iter = l.iterator(); iter.hasNext();) {
672             MethodNode f = (MethodNode) iter.next();
673             if (visitParent && Modifier.isPrivate(f.getModifiers()))
674                 continue;
675             String name = getPropertyName(f.getName());
676             if (name == null) continue;
677             refs.put(name, new Var(name,f));
678         }
679 
680         l = cn.getProperties();
681         for (Iterator iter = l.iterator(); iter.hasNext();) {
682             PropertyNode f = (PropertyNode) iter.next();
683             if (visitParent && Modifier.isPrivate(f.getModifiers()))
684                 continue;
685             refs.put(f.getName(),new Var(f));
686         }
687 
688         if (!visitParent) return;
689 
690         addVarNames(cn.getSuperClass(), refs, visitParent);
691         MethodNode enclosingMethod = cn.getEnclosingMethod();
692 
693         if (enclosingMethod == null) return;
694 
695         Parameter[] params = enclosingMethod.getParameters();
696         for (int i = 0; i < params.length; i++) {
697             refs.put(params[i].getName(),new Var(params[i],enclosingMethod.isStatic()));
698         }
699 
700         if (visitParent)
701             addVarNames(enclosingMethod.getDeclaringClass(), refs, visitParent);
702 
703         addVarNames(cn.getOuterClass(), refs, visitParent);
704     }
705 
706     private void addVarNames(String superclassName, HashMap refs, boolean visitParent) 
707       throws ClassNotFoundException 
708     {
709 
710         if (superclassName == null) return;
711 
712         ClassNode cn = unit.getClass(superclassName);
713         if (cn != null) {
714             addVarNames(cn, refs, visitParent);
715             return;
716         }
717 
718         Class c = unit.getClassLoader().loadClass(superclassName);
719         Field[] fields = c.getFields();
720         for (int i = 0; i < fields.length; i++) {
721             Field f = fields[i];
722             if (visitParent && Modifier.isPrivate(f.getModifiers()))
723                 continue;
724             refs.put(f.getName(),new Var(f));
725         }
726 
727         Method[] methods = c.getMethods();
728         for (int i = 0; i < methods.length; i++) {
729             Method m = methods[i];
730             if (visitParent && Modifier.isPrivate(m.getModifiers()))
731                 continue;
732             String name = getPropertyName(m.getName());
733             if (name == null) continue;
734             refs.put(name,new Var(name,m));
735         }
736 
737         if (!visitParent) return;
738 
739         addVarNames(c.getSuperclass(), refs, visitParent);
740 
741         // it's not possible to know the variable names used for an enclosing
742         // method
743 
744         // addVarNames(c.getEnclosingClass(),refs,visitParent);
745     }
746     
747     private String getPropertyName(String name) {
748         if (!(name.startsWith("set") || name.startsWith("get"))) return null;
749         String pname = name.substring(3);
750         if (pname.length() == 0) return null;
751         String s = pname.substring(0, 1).toLowerCase();
752         String rest = pname.substring(1);
753         return s + rest;
754     }    
755 
756     public void visitConstructor(ConstructorNode node) {
757         VarScope scope = currentScope;
758         currentScope = new VarScope(currentScope);
759         
760         // TODO: set scope
761         // node.setVarScope(currentScope);
762         
763         HashMap declares = currentScope.declares;
764         Parameter[] parameters = node.getParameters();
765         for (int i = 0; i < parameters.length; i++) {
766             // a constructor is never static
767             declare(new Var(parameters[i],false),node);
768         }
769         currentScope = new VarScope(currentScope);
770         Statement code = node.getCode();
771         if (code != null) code.visit(this);
772         currentScope = scope;
773     }
774     
775     public void visitMethod(MethodNode node) {
776         checkAbstractDeclaration(node);
777         
778         VarScope scope = currentScope;
779         currentScope = new VarScope(currentScope,node.isStatic());
780         
781         // TODO: set scope
782         // node.setVarScope(currentScope);
783         
784         HashMap declares = currentScope.declares;
785         Parameter[] parameters = node.getParameters();
786         for (int i = 0; i < parameters.length; i++) {
787             declares.put(parameters[i].getName(),new Var(parameters[i],node.isStatic()));
788         }
789 
790         currentScope = new VarScope(currentScope);
791         Statement code = node.getCode();
792         if (code!=null) code.visit(this);
793         currentScope = scope;
794     }
795 
796     public void visitField(FieldNode node) {
797         Expression init = node.getInitialValueExpression();
798         if (init != null) init.visit(this);
799     }
800 
801     public void visitProperty(PropertyNode node) {
802         Statement statement = node.getGetterBlock();
803         if (statement != null) statement.visit(this);
804         
805         statement = node.getSetterBlock();
806         if (statement != null) statement.visit(this);
807         
808         Expression init = node.getInitialValueExpression();
809         if (init != null) init.visit(this);
810     }
811 
812     public void visitPropertyExpression(PropertyExpression expression) {
813 
814     }
815 
816     public void visitCatchStatement(CatchStatement statement) {
817         VarScope scope = currentScope;
818         currentScope = new VarScope(currentScope);
819         declare(new Var(statement.getVariable()), statement);
820         super.visitCatchStatement(statement);
821         currentScope = scope;
822     }
823 }