001    /*
002     * $Id: JSRVariableScopeCodeVisitor.java,v 1.18 2005/07/06 13:44:32 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            // 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    }