View Javadoc

1   /*
2    * $Id: ResolveVisitor.java 4295 2006-12-02 21:15:54Z blackdrag $
3    *
4    * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5    *
6    * Redistribution and use of this software and associated documentation
7    * ("Software"), with or without modification, are permitted provided that the
8    * following conditions are met: 1. Redistributions of source code must retain
9    * copyright statements and notices. Redistributions must also contain a copy
10   * of this document. 2. Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following disclaimer in
12   * the documentation and/or other materials provided with the distribution. 3.
13   * The name "groovy" must not be used to endorse or promote products derived
14   * from this Software without prior written permission of The Codehaus. For
15   * written permission, please contact info@codehaus.org. 4. Products derived
16   * from this Software may not be called "groovy" nor may "groovy" appear in
17   * their names without prior written permission of The Codehaus. "groovy" is a
18   * registered trademark of The Codehaus. 5. Due credit should be given to The
19   * Codehaus - http://groovy.codehaus.org/
20   *
21   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
22   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
25   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31   * DAMAGE.
32   *
33   */
34  package org.codehaus.groovy.control;
35  
36  import groovy.lang.GroovyClassLoader;
37  
38  import java.io.IOException;
39  import java.io.File;
40  import java.lang.reflect.Field;
41  import java.util.HashMap;
42  import java.util.Iterator;
43  import java.util.LinkedList;
44  import java.util.List;
45  import java.util.Map;
46  import java.net.URL;
47  import java.net.MalformedURLException;
48  
49  import org.codehaus.groovy.ast.ASTNode;
50  import org.codehaus.groovy.ast.AnnotatedNode;
51  import org.codehaus.groovy.ast.AnnotationNode;
52  import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
53  import org.codehaus.groovy.ast.ClassHelper;
54  import org.codehaus.groovy.ast.ClassNode;
55  import org.codehaus.groovy.ast.CompileUnit;
56  import org.codehaus.groovy.ast.ConstructorNode;
57  import org.codehaus.groovy.ast.DynamicVariable;
58  import org.codehaus.groovy.ast.FieldNode;
59  import org.codehaus.groovy.ast.ImportNode;
60  import org.codehaus.groovy.ast.MethodNode;
61  import org.codehaus.groovy.ast.ModuleNode;
62  import org.codehaus.groovy.ast.Parameter;
63  import org.codehaus.groovy.ast.PropertyNode;
64  import org.codehaus.groovy.ast.Variable;
65  import org.codehaus.groovy.ast.VariableScope;
66  import org.codehaus.groovy.ast.expr.BinaryExpression;
67  import org.codehaus.groovy.ast.expr.BooleanExpression;
68  import org.codehaus.groovy.ast.expr.ClassExpression;
69  import org.codehaus.groovy.ast.expr.ClosureExpression;
70  import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
71  import org.codehaus.groovy.ast.expr.DeclarationExpression;
72  import org.codehaus.groovy.ast.expr.Expression;
73  import org.codehaus.groovy.ast.expr.ExpressionTransformer;
74  import org.codehaus.groovy.ast.expr.ListExpression;
75  import org.codehaus.groovy.ast.expr.MethodCallExpression;
76  import org.codehaus.groovy.ast.expr.PropertyExpression;
77  import org.codehaus.groovy.ast.expr.VariableExpression;
78  import org.codehaus.groovy.ast.stmt.AssertStatement;
79  import org.codehaus.groovy.ast.stmt.BlockStatement;
80  import org.codehaus.groovy.ast.stmt.CaseStatement;
81  import org.codehaus.groovy.ast.stmt.CatchStatement;
82  import org.codehaus.groovy.ast.stmt.DoWhileStatement;
83  import org.codehaus.groovy.ast.stmt.ExpressionStatement;
84  import org.codehaus.groovy.ast.stmt.ForStatement;
85  import org.codehaus.groovy.ast.stmt.IfStatement;
86  import org.codehaus.groovy.ast.stmt.ReturnStatement;
87  import org.codehaus.groovy.ast.stmt.Statement;
88  import org.codehaus.groovy.ast.stmt.SwitchStatement;
89  import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
90  import org.codehaus.groovy.ast.stmt.ThrowStatement;
91  import org.codehaus.groovy.ast.stmt.WhileStatement;
92  import org.codehaus.groovy.classgen.Verifier;
93  import org.codehaus.groovy.control.messages.ExceptionMessage;
94  import org.codehaus.groovy.syntax.Types;
95  
96  /***
97   * Visitor to resolve Types and convert VariableExpression to
98   * ClassExpressions if needed. The ResolveVisitor will try to
99   * 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 }