001    /*
002     * $Id: JSRVariableScopeCodeVisitor.java,v 1.16 2005/06/13 20:50:50 blackdrag Exp $
003     *
004     * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005     *
006     * Redistribution and use of this software and associated documentation
007     * ("Software"), with or without modification, are permitted provided that the
008     * following conditions are met: 1. Redistributions of source code must retain
009     * copyright statements and notices. Redistributions must also contain a copy
010     * of this document. 2. Redistributions in binary form must reproduce the above
011     * copyright notice, this list of conditions and the following disclaimer in
012     * the documentation and/or other materials provided with the distribution. 3.
013     * The name "groovy" must not be used to endorse or promote products derived
014     * from this Software without prior written permission of The Codehaus. For
015     * written permission, please contact info@codehaus.org. 4. Products derived
016     * from this Software may not be called "groovy" nor may "groovy" appear in
017     * their names without prior written permission of The Codehaus. "groovy" is a
018     * registered trademark of The Codehaus. 5. Due credit should be given to The
019     * Codehaus - http://groovy.codehaus.org/
020     *
021     * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
022     * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
024     * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
025     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
027     * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
028     * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
029     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
030     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
031     * DAMAGE.
032     *
033     */
034    
035    package org.codehaus.groovy.classgen;
036    
037    import java.lang.reflect.Modifier;
038    import java.lang.reflect.Field;
039    import java.lang.reflect.Method;
040    import java.util.HashMap;
041    import java.util.HashSet;
042    import java.util.Iterator;
043    import java.util.Set;
044    import java.util.List;
045    import org.codehaus.groovy.ast.ASTNode;
046    import org.codehaus.groovy.ast.ClassNode;
047    import org.codehaus.groovy.ast.CodeVisitorSupport;
048    import org.codehaus.groovy.ast.CompileUnit;
049    import org.codehaus.groovy.ast.ConstructorNode;
050    import org.codehaus.groovy.ast.FieldNode;
051    import org.codehaus.groovy.ast.GroovyClassVisitor;
052    import org.codehaus.groovy.ast.MethodNode;
053    import org.codehaus.groovy.ast.Parameter;
054    import org.codehaus.groovy.ast.PropertyNode;
055    import org.codehaus.groovy.ast.expr.ClosureExpression;
056    import org.codehaus.groovy.ast.expr.DeclarationExpression;
057    import org.codehaus.groovy.ast.expr.Expression;
058    import org.codehaus.groovy.ast.expr.FieldExpression;
059    import org.codehaus.groovy.ast.expr.PropertyExpression;
060    import org.codehaus.groovy.ast.expr.VariableExpression;
061    import org.codehaus.groovy.ast.stmt.BlockStatement;
062    import org.codehaus.groovy.ast.stmt.CatchStatement;
063    import org.codehaus.groovy.ast.stmt.DoWhileStatement;
064    import org.codehaus.groovy.ast.stmt.ForStatement;
065    import org.codehaus.groovy.ast.stmt.Statement;
066    import org.codehaus.groovy.ast.stmt.WhileStatement;
067    import org.codehaus.groovy.control.SourceUnit;
068    import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
069    import org.codehaus.groovy.syntax.SyntaxException;
070    
071    public class JSRVariableScopeCodeVisitor extends CodeVisitorSupport implements GroovyClassVisitor {
072    
073        private static class Var {
074            //TODO: support final and native
075            boolean isStatic=false, isFinal=false, isDynamicTyped=false;
076            String name, type=null;
077            Class typeClass=null;
078            boolean isInStaticContext=false;
079            
080            public boolean equals(Object o){
081                Var v = (Var) o;
082                return v.name.equals(name);
083            }
084            
085            public int hashCode() {
086                return name.hashCode();
087            }
088            
089            public Var(String name) {
090                // a Variable without type and other modifiers
091                // make it dynamic type, non final and non static
092                this.name=name;
093            }
094            
095            public Var(VariableExpression ve, VarScope scope) {
096                name = ve.getVariable();
097                if(ve.isDynamic()) {
098                    isDynamicTyped=true;
099                } 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    }