001    /*
002     * $Id: ResolveVisitor.java 4295 2006-12-02 21:15:54Z blackdrag $
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    package org.codehaus.groovy.control;
035    
036    import groovy.lang.GroovyClassLoader;
037    
038    import java.io.IOException;
039    import java.io.File;
040    import java.lang.reflect.Field;
041    import java.util.HashMap;
042    import java.util.Iterator;
043    import java.util.LinkedList;
044    import java.util.List;
045    import java.util.Map;
046    import java.net.URL;
047    import java.net.MalformedURLException;
048    
049    import org.codehaus.groovy.ast.ASTNode;
050    import org.codehaus.groovy.ast.AnnotatedNode;
051    import org.codehaus.groovy.ast.AnnotationNode;
052    import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
053    import org.codehaus.groovy.ast.ClassHelper;
054    import org.codehaus.groovy.ast.ClassNode;
055    import org.codehaus.groovy.ast.CompileUnit;
056    import org.codehaus.groovy.ast.ConstructorNode;
057    import org.codehaus.groovy.ast.DynamicVariable;
058    import org.codehaus.groovy.ast.FieldNode;
059    import org.codehaus.groovy.ast.ImportNode;
060    import org.codehaus.groovy.ast.MethodNode;
061    import org.codehaus.groovy.ast.ModuleNode;
062    import org.codehaus.groovy.ast.Parameter;
063    import org.codehaus.groovy.ast.PropertyNode;
064    import org.codehaus.groovy.ast.Variable;
065    import org.codehaus.groovy.ast.VariableScope;
066    import org.codehaus.groovy.ast.expr.BinaryExpression;
067    import org.codehaus.groovy.ast.expr.BooleanExpression;
068    import org.codehaus.groovy.ast.expr.ClassExpression;
069    import org.codehaus.groovy.ast.expr.ClosureExpression;
070    import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
071    import org.codehaus.groovy.ast.expr.DeclarationExpression;
072    import org.codehaus.groovy.ast.expr.Expression;
073    import org.codehaus.groovy.ast.expr.ExpressionTransformer;
074    import org.codehaus.groovy.ast.expr.ListExpression;
075    import org.codehaus.groovy.ast.expr.MethodCallExpression;
076    import org.codehaus.groovy.ast.expr.PropertyExpression;
077    import org.codehaus.groovy.ast.expr.VariableExpression;
078    import org.codehaus.groovy.ast.stmt.AssertStatement;
079    import org.codehaus.groovy.ast.stmt.BlockStatement;
080    import org.codehaus.groovy.ast.stmt.CaseStatement;
081    import org.codehaus.groovy.ast.stmt.CatchStatement;
082    import org.codehaus.groovy.ast.stmt.DoWhileStatement;
083    import org.codehaus.groovy.ast.stmt.ExpressionStatement;
084    import org.codehaus.groovy.ast.stmt.ForStatement;
085    import org.codehaus.groovy.ast.stmt.IfStatement;
086    import org.codehaus.groovy.ast.stmt.ReturnStatement;
087    import org.codehaus.groovy.ast.stmt.Statement;
088    import org.codehaus.groovy.ast.stmt.SwitchStatement;
089    import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
090    import org.codehaus.groovy.ast.stmt.ThrowStatement;
091    import org.codehaus.groovy.ast.stmt.WhileStatement;
092    import org.codehaus.groovy.classgen.Verifier;
093    import org.codehaus.groovy.control.messages.ExceptionMessage;
094    import org.codehaus.groovy.syntax.Types;
095    
096    /**
097     * Visitor to resolve Types and convert VariableExpression to
098     * ClassExpressions if needed. The ResolveVisitor will try to
099     * find the Class for a ClassExpression and prints an error if
100     * it fails to do so. Constructions like C[], foo as C, (C) foo 
101     * will force creation of a ClasssExpression for C   
102     *
103     * Note: the method to start the resolving is  startResolving(ClassNode, SourceUnit).
104     *
105     *
106     * @author Jochen Theodorou
107     */
108    public class ResolveVisitor extends ClassCodeVisitorSupport implements ExpressionTransformer {
109        private ClassNode currentClass;
110        // note: BigInteger and BigDecimal are also imported by default
111        private static final String[] DEFAULT_IMPORTS = {"java.lang.", "java.io.", "java.net.", "java.util.", "groovy.lang.", "groovy.util."};
112        private CompilationUnit compilationUnit;
113        private Map cachedClasses = new HashMap();
114        private static final Object NO_CLASS = new Object();
115        private static final Object SCRIPT = new Object();
116        private SourceUnit source;
117        private VariableScope currentScope;
118    
119        private boolean isTopLevelProperty = true;
120        private boolean inClosure = false;
121    
122        public ResolveVisitor(CompilationUnit cu) {
123            compilationUnit = cu;
124        }
125    
126        public void startResolving(ClassNode node,SourceUnit source) {
127            this.source = source;
128            visitClass(node);
129        }
130    
131        public void visitConstructor(ConstructorNode node) {
132            visitAnnotations(node);
133            VariableScope oldScope = currentScope;
134            currentScope = node.getVariableScope();
135            Parameter[] paras = node.getParameters();
136            for (int i=0; i<paras.length; i++) {
137                ClassNode t = paras[i].getType();
138                resolveOrFail(t,node);
139            }
140            ClassNode[] exceptions = node.getExceptions();
141            for (int i=0; i<exceptions.length; i++) {
142                ClassNode t = exceptions[i];
143                resolveOrFail(t,node);
144            }
145            Statement code = node.getCode();
146            if (code!=null) code.visit(this);
147            currentScope = oldScope;
148        }
149    
150        public void visitSwitch(SwitchStatement statement) {
151            Expression exp = statement.getExpression();
152            statement.setExpression(transform(exp));
153            List list = statement.getCaseStatements();
154            for (Iterator iter = list.iterator(); iter.hasNext(); ) {
155                CaseStatement caseStatement = (CaseStatement) iter.next();
156                caseStatement.visit(this);
157            }
158            statement.getDefaultStatement().visit(this);
159        }
160    
161        public void visitMethod(MethodNode node) {
162            visitAnnotations(node);
163            VariableScope oldScope = currentScope;
164            currentScope = node.getVariableScope();
165            Parameter[] paras = node.getParameters();
166            for (int i=0; i<paras.length; i++) {
167                ClassNode t = paras[i].getType();
168                resolveOrFail(t,node);
169                if (paras[i].hasInitialExpression()) {
170                    Expression init = paras[i].getInitialExpression(); 
171                    paras[i].setInitialExpression(transform(init));
172                }
173            }
174            ClassNode[] exceptions = node.getExceptions();
175            for (int i=0; i<exceptions.length; i++) {
176                ClassNode t = exceptions[i];
177                resolveOrFail(t,node);
178            }       
179            resolveOrFail(node.getReturnType(),node);
180            Statement code = node.getCode();
181            if (code!=null) code.visit(this);
182            currentScope = oldScope;
183        }
184    
185        public void visitField(FieldNode node) {
186            visitAnnotations(node);
187            ClassNode t = node.getType();
188            resolveOrFail(t,node);
189            Expression init = node.getInitialExpression();
190            node.setInitialValueExpression(transform(init));
191        }
192    
193        public void visitProperty(PropertyNode node) {
194            visitAnnotations(node);
195            ClassNode t = node.getType();
196            resolveOrFail(t,node);
197            Statement code = node.getGetterBlock();
198            if (code!=null) code.visit(this);
199            code = node.getSetterBlock();
200            if (code!=null) code.visit(this);
201        }
202    
203        public void visitIfElse(IfStatement ifElse) {
204            visitStatement(ifElse);
205            ifElse.setBooleanExpression((BooleanExpression) (transform(ifElse.getBooleanExpression())));
206            ifElse.getIfBlock().visit(this);
207            ifElse.getElseBlock().visit(this);
208        }
209    
210        private void resolveOrFail(ClassNode type, String msg, ASTNode node) {
211            if (resolve(type)) return;
212            addError("unable to resolve class "+type.getName()+" "+msg,node);
213        }
214    
215        private void resolveOrFail(ClassNode type, ASTNode node, boolean prefereImports) {
216            if (prefereImports && resolveAliasFromModule(type)) return;
217            resolveOrFail(type,node);
218        }
219        
220        private void resolveOrFail(ClassNode type, ASTNode node) {
221            resolveOrFail(type,"",node);
222        }
223    
224        private boolean resolve(ClassNode type) {
225            String name = type.getName();
226            return resolve(type,true,true,true);
227        }
228    
229        private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
230            if (type.isResolved()) return true;
231            if (type.isArray()) {
232                ClassNode element = type.getComponentType();
233                boolean resolved = resolve(element,testModuleImports,testDefaultImports,testStaticInnerClasses);
234                if (resolved) {
235                    ClassNode cn = element.makeArray();
236                    type.setRedirect(cn);
237                }
238                return resolved;
239            }
240    
241            // test if vanilla name is current class name
242            if (currentClass==type) return true;
243            if (currentClass.getNameWithoutPackage().equals(type.getName())) {
244                type.setRedirect(currentClass);
245                return true;
246            }
247    
248            return  resolveFromModule(type,testModuleImports) ||
249                    resolveFromCompileUnit(type) ||
250                    resovleFromDefaultImports(type,testDefaultImports) ||
251                    resolveFromStaticInnerClasses(type,testStaticInnerClasses) ||
252                    resolveFromClassCache(type) ||
253                    resolveToClass(type) ||
254                    resolveToScript(type);
255    
256        }
257    
258        private boolean resolveFromClassCache(ClassNode type) {
259            String name = type.getName();
260            Object val = cachedClasses.get(name);
261            if (val==null || val==NO_CLASS){
262                return false;
263            } else {
264                setClass(type,(Class) val);
265                return true;
266            }
267        }
268    
269        // NOTE: copied from GroovyClassLoader
270        private long getTimeStamp(Class cls) {
271            Field field;
272            Long o;
273            try {
274                field = cls.getField(Verifier.__TIMESTAMP);
275                o = (Long) field.get(null);
276            } catch (Exception e) {
277                return Long.MAX_VALUE;
278            }
279            return o.longValue();
280        }
281    
282        // NOTE: copied from GroovyClassLoader
283        private boolean isSourceNewer(URL source, Class cls) {
284            try {
285                long lastMod;
286    
287                // Special handling for file:// protocol, as getLastModified() often reports
288                // incorrect results (-1)
289                if (source.getProtocol().equals("file")) {
290                    // Coerce the file URL to a File
291                    String path = source.getPath().replace('/', File.separatorChar).replace('|', ':');
292                    File file = new File(path);
293                    lastMod = file.lastModified();
294                }
295                else {
296                    lastMod = source.openConnection().getLastModified();
297                }
298                return lastMod > getTimeStamp(cls);            
299            } catch (IOException e) {
300                // if the stream can't be opened, let's keep the old reference
301                return false;
302            }
303        }
304    
305    
306        private boolean resolveToScript(ClassNode type) {
307            String name = type.getName();
308            if (cachedClasses.get(name)==NO_CLASS) return false;
309            if (cachedClasses.get(name)==SCRIPT) cachedClasses.put(name,NO_CLASS);
310            if (name.startsWith("java.")) return type.isResolved();
311            //TODO: don't ignore inner static classes completly
312            if (name.indexOf('$')!=-1) return type.isResolved();
313            ModuleNode module = currentClass.getModule();
314            if (module.hasPackageName() && name.indexOf('.')==-1) return type.isResolved();
315            // try to find a script from classpath
316            GroovyClassLoader gcl = compilationUnit.getClassLoader();
317            URL url = null;
318            try {
319                url = gcl.getResourceLoader().loadGroovySource(name);
320            } catch (MalformedURLException e) {
321                // fall through and let the URL be null
322            }
323            if (url !=null) {
324                if (type.isResolved()) {
325                    Class cls = type.getTypeClass();
326                    // if the file is not newer we don't want to recompile
327                    if (!isSourceNewer(url,cls)) return true;
328                    cachedClasses.remove(type.getName());
329                    type.setRedirect(null);
330                }
331                SourceUnit su = compilationUnit.addSource(url);
332                currentClass.getCompileUnit().addClassNodeToCompile(type,su);
333                return true;
334            }
335            // type may be resolved through the classloader before
336            return type.isResolved();
337        }
338    
339    
340        private boolean resolveFromStaticInnerClasses(ClassNode type, boolean testStaticInnerClasses) {
341            // try to resolve a public static inner class' name
342            testStaticInnerClasses &= type.hasPackageName();
343            if (testStaticInnerClasses) {
344                String name = type.getName();
345                String replacedPointType = name;
346                int lastPoint = replacedPointType.lastIndexOf('.');
347                replacedPointType = new StringBuffer()
348                    .append(replacedPointType.substring(0, lastPoint))
349                    .append("$")
350                    .append(replacedPointType.substring(lastPoint + 1))
351                    .toString();
352                type.setName(replacedPointType);
353                if (resolve(type,false,false,true)) return true;
354                type.setName(name);
355            }
356            return false;
357        }
358    
359        private boolean resovleFromDefaultImports(ClassNode type, boolean testDefaultImports) {
360            // test default imports
361            testDefaultImports &= !type.hasPackageName();
362            if (testDefaultImports) {
363                for (int i = 0, size = DEFAULT_IMPORTS.length; i < size; i++) {
364                    String packagePrefix = DEFAULT_IMPORTS[i];
365                    String name = type.getName();
366                    String fqn = packagePrefix+name;
367                    type.setName(fqn);
368                    if (resolve(type,false,false,false)) return true;
369                    type.setName(name);
370                }
371                String name = type.getName();
372                if (name.equals("BigInteger")) {
373                    type.setRedirect(ClassHelper.BigInteger_TYPE);
374                    return true;
375                } else if (name.equals("BigDecimal")) {
376                    type.setRedirect(ClassHelper.BigDecimal_TYPE);
377                    return true;    
378                }
379            }
380            return false;
381        }
382    
383        private boolean resolveFromCompileUnit(ClassNode type) {
384            // look into the compile unit if there is a class with that name
385            CompileUnit compileUnit = currentClass.getCompileUnit();
386            if (compileUnit == null) return false;
387            ClassNode cuClass = compileUnit.getClass(type.getName());
388            if (cuClass!=null) {
389                    if (type!=cuClass) type.setRedirect(cuClass);
390                    return true;
391            }
392            return false;
393        }
394    
395    
396        private void setClass(ClassNode n, Class cls) {
397            ClassNode cn = ClassHelper.make(cls);
398            n.setRedirect(cn);
399        }
400    
401        private void ambigousClass(ClassNode type, ClassNode iType, String name, boolean resolved){
402            if (resolved && !type.getName().equals(iType.getName())) {
403                addError("reference to "+name+" is ambigous, both class "+type.getName()+" and "+iType.getName()+" match",type);
404            } else {
405                type.setRedirect(iType);
406            }
407        }
408        
409        private boolean resolveAliasFromModule(ClassNode type) {
410            ModuleNode module = currentClass.getModule();
411            if (module==null) return false;
412            String name = type.getName();
413            
414            // check module node imports aliases
415            // the while loop enables a check for inner classes which are not fully imported,
416            // but visible as the surrounding class is imported and the inner class is public/protected static
417            String pname = name;
418            int index = name.length();
419            /*
420             * we have a name foo.bar and an import foo.foo. This means foo.bar is possibly
421             * foo.foo.bar rather than foo.bar. This means to cut at the dot in foo.bar and
422             * foo for import
423             */
424            while (true) {
425                pname = name.substring(0,index);
426                ClassNode aliasedNode = module.getImport(pname);
427                if (aliasedNode!=null) {
428                    if (pname.length()==name.length()){
429                        // full match, no need to create a new class
430                        type.setRedirect(aliasedNode);
431                        return true;
432                    } else {
433                        //partial match
434                        String newName = aliasedNode.getName()+name.substring(pname.length());
435                        type.setName(newName);
436                        if (resolve(type,true,true,true)) return true;
437                        // was not resolved soit was a fake match
438                        type.setName(name);
439                    }
440                }
441                index = pname.lastIndexOf('.');
442                if (index==-1) break;
443            }
444             return false;
445            
446        }
447    
448        private boolean resolveFromModule(ClassNode type, boolean testModuleImports) {
449            ModuleNode module = currentClass.getModule();
450            if (module==null) return false;
451    
452            String name = type.getName();
453    
454            if (!type.hasPackageName() && module.hasPackageName()){
455                type.setName(module.getPackageName()+name);
456            }
457            // look into the module node if there is a class with that name
458            List moduleClasses = module.getClasses();
459            for (Iterator iter = moduleClasses.iterator(); iter.hasNext();) {
460                ClassNode mClass = (ClassNode) iter.next();
461                if (mClass.getName().equals(type.getName())){
462                    if (mClass!=type) type.setRedirect(mClass);
463                    return true;
464                }
465            }
466            type.setName(name);
467    
468            if (testModuleImports) {
469                if (resolveAliasFromModule(type)) return true;
470                
471                boolean resolved = false;
472                if (module.hasPackageName()) { 
473                    // check package this class is defined in
474                    type.setName(module.getPackageName()+name);
475                    resolved = resolve(type,false,false,false);
476                }
477                // check module node imports packages
478                List packages = module.getImportPackages();
479                ClassNode iType = ClassHelper.makeWithoutCaching(name);
480                for (Iterator iter = packages.iterator(); iter.hasNext();) {
481                    String packagePrefix = (String) iter.next();
482                    String fqn = packagePrefix+name;
483                    iType.setName(fqn);
484                    if (resolve(iType,false,false,true)) {
485                            ambigousClass(type,iType,name,resolved);
486                        return true;
487                    }
488                    iType.setName(name);
489                }
490                if (!resolved) type.setName(name);
491                return resolved;
492            }
493            return false;
494        }
495    
496        private boolean resolveToClass(ClassNode type) {
497            String name = type.getName();
498            if (cachedClasses.get(name)==NO_CLASS) return false;
499            if (currentClass.getModule().hasPackageName() && name.indexOf('.')==-1) return false;
500            GroovyClassLoader loader  = compilationUnit.getClassLoader();
501            Class cls = null;
502            try {
503                // NOTE: it's important to do no lookup against script files
504                // here since the GroovyClassLoader would create a new
505                // CompilationUnit
506                cls = loader.loadClass(name,false,true);
507            } catch (ClassNotFoundException cnfe) {
508                cachedClasses.put(name,SCRIPT);
509                return false;
510            } catch (CompilationFailedException cfe) {
511                compilationUnit.getErrorCollector().addErrorAndContinue(new ExceptionMessage(cfe,true,source));
512                return false;
513            } 
514            //TODO: the case of a NoClassDefFoundError needs a bit more research
515            // a simple recompilation is not possible it seems. The current class
516            // we are searching for is there, so we should mark that somehow. 
517            // Basically the missing class needs to be completly compiled before
518            // we can again search for the current name.
519            /*catch (NoClassDefFoundError ncdfe) {
520                cachedClasses.put(name,SCRIPT);
521                return false;
522            }*/
523            if (cls==null) return false;
524            cachedClasses.put(name,cls);
525            setClass(type,cls);
526            //NOTE: we return false here even if we found a class,
527            //but we want to give a possible script a chance to recompile.
528            //this can only be done if the loader was not the instance
529            //defining the class.
530            return cls.getClassLoader()==loader;
531        }
532    
533    
534    
535        public Expression transform(Expression exp) {
536            if (exp==null) return null;
537            if (exp instanceof VariableExpression) {
538                return transformVariableExpression((VariableExpression) exp);
539            } else if (exp.getClass()==PropertyExpression.class) {
540                return transformPropertyExpression((PropertyExpression) exp);
541            } else if (exp instanceof DeclarationExpression) {
542                return transformDeclarationExpression((DeclarationExpression)exp);
543            } else if (exp instanceof BinaryExpression) {
544                return transformBinaryExpression((BinaryExpression)exp);
545            } else if (exp instanceof MethodCallExpression) {
546                return transformMethodCallExpression((MethodCallExpression)exp);
547            } else if (exp instanceof ClosureExpression) {
548                    return transformClosureExpression((ClosureExpression) exp);
549            } else if (exp instanceof ConstructorCallExpression) {
550                    return transformConstructorCallExpression((ConstructorCallExpression) exp);
551            } else {
552                resolveOrFail(exp.getType(),exp);
553                return exp.transformExpression(this);
554            }
555        }
556    
557    
558        private String lookupClassName(PropertyExpression pe) {
559            String name = "";
560            for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
561                if (it instanceof VariableExpression) {
562                    VariableExpression ve = (VariableExpression) it;
563                    // stop at super and this
564                    if (ve==VariableExpression.SUPER_EXPRESSION || ve==VariableExpression.THIS_EXPRESSION) {
565                        return null;
566                    }
567                    name= ve.getName()+"."+name;
568                    break;
569                } 
570                // anything other than PropertyExpressions, ClassExpression or
571                // VariableExpressions will stop resolving
572                else if (!(it.getClass()==PropertyExpression.class)) {
573                    return null;
574                } else {
575                    PropertyExpression current = (PropertyExpression) it;
576                    String propertyPart = current.getPropertyAsString();
577                    // the class property stops resolving, dynamic property names too
578                    if (propertyPart==null || propertyPart.equals("class")) {
579                        return null;
580                    }
581                    name = propertyPart+"."+name;
582                }
583            }
584            if (name.length()>0) return name.substring(0,name.length()-1);
585            return null;
586        }
587    
588        // iterate from the inner most to the outer and check for classes
589        // this check will ignore a .class property, for Exmaple Integer.class will be
590        // a PropertyExpression with the ClassExpression of Integer as objectExpression
591        // and class as property
592        private Expression correctClassClassChain(PropertyExpression pe){
593            LinkedList stack = new LinkedList();
594            ClassExpression found = null;
595            for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
596                if (it instanceof ClassExpression) {
597                    found = (ClassExpression) it;
598                    break;
599                } else if (! (it.getClass()==PropertyExpression.class)) {
600                    return pe;
601                }
602                stack.addFirst(it);
603            }
604            if (found==null) return pe;
605    
606            if (stack.isEmpty()) return pe;
607            Object stackElement = stack.removeFirst();
608            if (!(stackElement.getClass()==PropertyExpression.class)) return pe;
609            PropertyExpression classPropertyExpression = (PropertyExpression) stackElement;
610            String propertyNamePart = classPropertyExpression.getPropertyAsString();
611            if (propertyNamePart==null || ! propertyNamePart.equals("class")) return pe;
612    
613            if (stack.isEmpty()) return found;
614            stackElement = stack.removeFirst();
615            if (!(stackElement.getClass()==PropertyExpression.class)) return pe;
616            PropertyExpression classPropertyExpressionContainer = (PropertyExpression) stackElement;
617    
618            classPropertyExpressionContainer.setObjectExpression(found);
619            return pe;
620        }
621        
622        protected Expression transformPropertyExpression(PropertyExpression pe) {
623            boolean itlp = isTopLevelProperty;
624            
625            Expression objectExpression = pe.getObjectExpression();
626            isTopLevelProperty = !(objectExpression.getClass()==PropertyExpression.class);
627            objectExpression = transform(objectExpression);
628            Expression property = transform(pe.getProperty());
629            isTopLevelProperty = itlp;
630            
631            boolean spreadSafe = pe.isSpreadSafe();
632            pe = new PropertyExpression(objectExpression,property,pe.isSafe());
633            pe.setSpreadSafe(spreadSafe);
634            
635            String className = lookupClassName(pe);
636            if (className!=null) {
637                ClassNode type = ClassHelper.make(className);
638                if (resolve(type)) return new ClassExpression(type);
639            }  
640            if (objectExpression instanceof ClassExpression && pe.getPropertyAsString()!=null){
641                // possibly a inner class
642                ClassExpression ce = (ClassExpression) objectExpression;
643                ClassNode type = ClassHelper.make(ce.getType().getName()+"$"+pe.getPropertyAsString());
644                if (resolve(type,false,false,false)) return new ClassExpression(type);
645            }
646            if (isTopLevelProperty) return correctClassClassChain(pe);
647            
648            return pe;
649        }
650           
651        protected Expression transformVariableExpression(VariableExpression ve) {
652            if (ve.getName().equals("this"))  return VariableExpression.THIS_EXPRESSION;
653            if (ve.getName().equals("super")) return VariableExpression.SUPER_EXPRESSION;
654            Variable v = ve.getAccessedVariable();
655            if (v instanceof DynamicVariable) {
656                ClassNode t = ClassHelper.make(ve.getName());
657                if (resolve(t)) {
658                    // the name is a type so remove it from the scoping
659                    // as it is only a classvariable, it is only in 
660                    // referencedClassVariables, but must be removed
661                    // for each parentscope too
662                    for (VariableScope scope = currentScope; scope!=null && !scope.isRoot(); scope = scope.getParent()) {
663                        if (scope.isRoot()) break;
664                        if (scope.getReferencedClassVariables().remove(ve.getName())==null) break;
665                    }
666                    ClassExpression ce = new ClassExpression(t);
667                    ce.setSourcePosition(ve);
668                    return ce;
669                } else if (!inClosure && ve.isInStaticContext()) {
670                    addError("the name "+v.getName()+" doesn't refer to a declared variable or class. The static"+
671                             " scope requires to declare variables before using them. If the variable should have"+
672                             " been a class check the spelling.",ve);
673                }
674            }
675            resolveOrFail(ve.getType(),ve);
676            return ve;
677        }
678        
679        protected Expression transformBinaryExpression(BinaryExpression be) {
680            Expression left = transform(be.getLeftExpression());
681            if (be.getOperation().getType()==Types.ASSIGNMENT_OPERATOR && left instanceof ClassExpression){
682                ClassExpression  ce = (ClassExpression) left;
683                addError("you tried to assign a value to "+ce.getType().getName(),be.getLeftExpression());
684                return be;
685            }
686            if (left instanceof ClassExpression && be.getRightExpression() instanceof ListExpression) {
687                // we have C[] if the list is empty -> should be an array then!
688                ListExpression list = (ListExpression) be.getRightExpression();
689                ClassExpression ce = (ClassExpression) left;
690                if (list.getExpressions().isEmpty()) {
691                    return new ClassExpression(left.getType().makeArray());
692                }
693            }
694            Expression right = transform(be.getRightExpression());
695            Expression ret = new BinaryExpression(left,be.getOperation(),right);
696            ret.setSourcePosition(be);
697            return ret;
698        }
699        
700        protected Expression transformClosureExpression(ClosureExpression ce) {
701            boolean oldInClosure = inClosure;
702            inClosure = true;
703            Parameter[] paras = ce.getParameters();
704            if (paras!=null) {
705                    for (int i=0; i<paras.length; i++) {
706                        ClassNode t = paras[i].getType();
707                        resolveOrFail(t,ce);
708                    }
709            }
710            Statement code = ce.getCode();
711            if (code!=null) code.visit(this);
712            ClosureExpression newCe= new ClosureExpression(paras,code);
713            newCe.setVariableScope(ce.getVariableScope());
714            newCe.setSourcePosition(ce);
715            inClosure = oldInClosure;
716            return newCe;
717        }
718        
719        protected Expression transformConstructorCallExpression(ConstructorCallExpression cce){
720            ClassNode type = cce.getType();
721            resolveOrFail(type,cce);
722            Expression expr = cce.transformExpression(this);
723            return expr;
724        }
725        
726        protected Expression transformMethodCallExpression(MethodCallExpression mce) {
727            Expression obj = mce.getObjectExpression();
728            Expression newObject = transform(obj);
729            Expression args = transform(mce.getArguments());
730            Expression method = transform(mce.getMethod());
731            MethodCallExpression ret = new MethodCallExpression(newObject,method,args);
732            ret.setSafe(mce.isSafe());
733            ret.setImplicitThis(mce.isImplicitThis());
734            ret.setSpreadSafe(mce.isSpreadSafe());
735            ret.setSourcePosition(mce);
736            return ret;
737        }
738        
739        protected Expression transformDeclarationExpression(DeclarationExpression de) {
740            Expression oldLeft = de.getLeftExpression();
741            Expression left = transform(oldLeft);
742            if (left!=oldLeft){
743                ClassExpression  ce = (ClassExpression) left;
744                addError("you tried to assign a value to "+ce.getType().getName(),oldLeft);
745                return de;
746            }
747            Expression right = transform(de.getRightExpression());
748            if (right==de.getRightExpression()) return de;
749            return new DeclarationExpression((VariableExpression) left,de.getOperation(),right);
750        }
751        
752        public void visitAnnotations(AnnotatedNode node) {
753            Map annotionMap = node.getAnnotations();
754            if (annotionMap.isEmpty()) return;
755            Iterator it = annotionMap.values().iterator(); 
756            while (it.hasNext()) {
757                AnnotationNode an = (AnnotationNode) it.next();
758                //skip builtin properties
759                if (an.isBuiltIn()) continue;
760                ClassNode type = an.getClassNode();
761                resolveOrFail(type,"unable to find class for annotation",an);
762            }
763        }
764    
765        public void visitClass(ClassNode node) {
766            ClassNode oldNode = currentClass;
767            currentClass = node;
768            
769            ModuleNode module = node.getModule();
770            if (!module.hasImportsResolved()) {
771               List l = module.getImports();
772               for (Iterator iter = l.iterator(); iter.hasNext();) {
773                   ImportNode element = (ImportNode) iter.next();
774                   ClassNode type = element.getType();
775                   if (resolve(type,false,false,false)) continue;
776                   addError("unable to resolve class "+type.getName(),type);
777               }
778               module.setImportsResolved(true);
779            }
780            
781            ClassNode sn = node.getUnresolvedSuperClass();
782            if (sn!=null) resolveOrFail(sn,node,true);
783            ClassNode[] interfaces = node.getInterfaces();
784            for (int i=0; i<interfaces.length; i++) {
785                resolveOrFail(interfaces[i],node,true);
786            }        
787            super.visitClass(node);
788            currentClass = oldNode;        
789        }
790        
791        public void visitReturnStatement(ReturnStatement statement) {
792           statement.setExpression(transform(statement.getExpression()));
793        }
794    
795        public void visitAssertStatement(AssertStatement as) {
796            as.setBooleanExpression((BooleanExpression) (transform(as.getBooleanExpression())));
797            as.setMessageExpression(transform(as.getMessageExpression()));
798        }
799        
800        public void visitCaseStatement(CaseStatement statement) {
801            statement.setExpression(transform(statement.getExpression()));
802            statement.getCode().visit(this);
803        }
804    
805        public void visitCatchStatement(CatchStatement cs) {
806            resolveOrFail(cs.getExceptionType(),cs);
807            if (cs.getExceptionType()==ClassHelper.DYNAMIC_TYPE) {
808                cs.getVariable().setType(ClassHelper.make(Exception.class));
809            } 
810            super.visitCatchStatement(cs);
811        }
812    
813        public void visitDoWhileLoop(DoWhileStatement loop) {
814            loop.setBooleanExpression((BooleanExpression) (transform(loop.getBooleanExpression())));
815            super.visitDoWhileLoop(loop);
816        }
817        
818        public void visitForLoop(ForStatement forLoop) {
819            forLoop.setCollectionExpression(transform(forLoop.getCollectionExpression()));
820            resolveOrFail(forLoop.getVariableType(),forLoop);
821            super.visitForLoop(forLoop);
822        }
823        
824        public void visitSynchronizedStatement(SynchronizedStatement sync) {
825            sync.setExpression(transform(sync.getExpression()));
826            super.visitSynchronizedStatement(sync);
827        }
828        
829        public void visitThrowStatement(ThrowStatement ts) {
830            ts.setExpression(transform(ts.getExpression()));
831        }
832        
833        public void visitWhileLoop(WhileStatement loop) {
834            loop.setBooleanExpression((BooleanExpression) transform(loop.getBooleanExpression()));
835            super.visitWhileLoop(loop);
836        }
837        
838        public void visitExpressionStatement(ExpressionStatement es) {
839            es.setExpression(transform(es.getExpression()));
840        }
841        
842        public void visitBlockStatement(BlockStatement block) {
843            VariableScope oldScope = currentScope;
844            currentScope = block.getVariableScope();
845            super.visitBlockStatement(block);
846            currentScope = oldScope;
847        }
848    
849        protected SourceUnit getSourceUnit() {
850            return source;
851        }
852    }