View Javadoc

1   /*
2    * $Id: ResolveVisitor.java,v 1.22 2006/06/25 19:58:43 blackdrag Exp $
3    *
4    * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5    *
6    * Redistribution and use of this software and associated documentation
7    * ("Software"), with or without modification, are permitted provided that the
8    * following conditions are met: 1. Redistributions of source code must retain
9    * copyright statements and notices. Redistributions must also contain a copy
10   * of this document. 2. Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following disclaimer in
12   * the documentation and/or other materials provided with the distribution. 3.
13   * The name "groovy" must not be used to endorse or promote products derived
14   * from this Software without prior written permission of The Codehaus. For
15   * written permission, please contact info@codehaus.org. 4. Products derived
16   * from this Software may not be called "groovy" nor may "groovy" appear in
17   * their names without prior written permission of The Codehaus. "groovy" is a
18   * registered trademark of The Codehaus. 5. Due credit should be given to The
19   * Codehaus - http://groovy.codehaus.org/
20   *
21   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
22   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
25   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31   * DAMAGE.
32   *
33   */
34  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.CodeVisitorSupport;
56  import org.codehaus.groovy.ast.CompileUnit;
57  import org.codehaus.groovy.ast.ConstructorNode;
58  import org.codehaus.groovy.ast.DynamicVariable;
59  import org.codehaus.groovy.ast.FieldNode;
60  import org.codehaus.groovy.ast.ImportNode;
61  import org.codehaus.groovy.ast.MethodNode;
62  import org.codehaus.groovy.ast.ModuleNode;
63  import org.codehaus.groovy.ast.Parameter;
64  import org.codehaus.groovy.ast.PropertyNode;
65  import org.codehaus.groovy.ast.Variable;
66  import org.codehaus.groovy.ast.VariableScope;
67  import org.codehaus.groovy.ast.expr.BinaryExpression;
68  import org.codehaus.groovy.ast.expr.BooleanExpression;
69  import org.codehaus.groovy.ast.expr.ClassExpression;
70  import org.codehaus.groovy.ast.expr.ClosureExpression;
71  import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
72  import org.codehaus.groovy.ast.expr.DeclarationExpression;
73  import org.codehaus.groovy.ast.expr.Expression;
74  import org.codehaus.groovy.ast.expr.ExpressionTransformer;
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.ast.GroovyClassVisitor;
93  import org.codehaus.groovy.classgen.Verifier;
94  import org.codehaus.groovy.control.messages.ExceptionMessage;
95  import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
96  import org.codehaus.groovy.syntax.SyntaxException;
97  import org.codehaus.groovy.syntax.Types;
98  
99  /***
100  * Visitor to resolve Types and convert VariableExpression to
101  * ClassExpressions if needed.
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 
121     public ResolveVisitor(CompilationUnit cu) {
122         compilationUnit = cu;
123     }
124 
125     public void startResolving(ClassNode node,SourceUnit source) {
126         this.source = source;
127         visitClass(node);
128     }
129 
130     public void visitConstructor(ConstructorNode node) {
131         VariableScope oldScope = currentScope;
132         currentScope = node.getVariableScope();
133         Parameter[] paras = node.getParameters();
134         for (int i=0; i<paras.length; i++) {
135             ClassNode t = paras[i].getType();
136             resolveOrFail(t,node);
137         }
138         ClassNode[] exceptions = node.getExceptions();
139         for (int i=0; i<exceptions.length; i++) {
140             ClassNode t = exceptions[i];
141             resolveOrFail(t,node);
142         }
143         Statement code = node.getCode();
144         if (code!=null) code.visit(this);
145         currentScope = oldScope;
146     }
147 
148     public void visitSwitch(SwitchStatement statement) {
149         Expression exp = statement.getExpression();
150         statement.setExpression(transform(exp));
151         List list = statement.getCaseStatements();
152         for (Iterator iter = list.iterator(); iter.hasNext(); ) {
153             CaseStatement caseStatement = (CaseStatement) iter.next();
154             caseStatement.visit(this);
155         }
156         statement.getDefaultStatement().visit(this);
157     }
158 
159     public void visitMethod(MethodNode node) {
160         VariableScope oldScope = currentScope;
161         currentScope = node.getVariableScope();
162         Parameter[] paras = node.getParameters();
163         for (int i=0; i<paras.length; i++) {
164             ClassNode t = paras[i].getType();
165             resolveOrFail(t,node);
166             if (paras[i].hasInitialExpression()) {
167                 Expression init = paras[i].getInitialExpression(); 
168                 paras[i].setInitialExpression(transform(init));
169             }
170         }
171         ClassNode[] exceptions = node.getExceptions();
172         for (int i=0; i<exceptions.length; i++) {
173             ClassNode t = exceptions[i];
174             resolveOrFail(t,node);
175         }       
176         resolveOrFail(node.getReturnType(),node);
177         Statement code = node.getCode();
178         if (code!=null) code.visit(this);
179         currentScope = oldScope;
180     }
181 
182     public void visitField(FieldNode node) {
183         ClassNode t = node.getType();
184         resolveOrFail(t,node);
185         Expression init = node.getInitialExpression();
186         node.setInitialValueExpression(transform(init));
187     }
188 
189     public void visitProperty(PropertyNode node) {
190         ClassNode t = node.getType();
191         resolveOrFail(t,node);
192         Statement code = node.getGetterBlock();
193         if (code!=null) code.visit(this);
194         code = node.getSetterBlock();
195         if (code!=null) code.visit(this);
196     }
197 
198     public void visitIfElse(IfStatement ifElse) {
199         ifElse.setBooleanExpression((BooleanExpression) (transform(ifElse.getBooleanExpression())));
200         super.visitIfElse(ifElse);
201     }
202 
203     private void resolveOrFail(ClassNode type, String msg, ASTNode node) {
204         if (resolve(type)) return;
205         addError("unable to resolve class "+type.getName()+" "+msg,node);
206     }
207 
208     private void resolveOrFail(ClassNode type, ASTNode node, boolean prefereImports) {
209         if (prefereImports && resolveAliasFromModule(type)) return;
210         resolveOrFail(type,node);
211     }
212     
213     private void resolveOrFail(ClassNode type, ASTNode node) {
214         resolveOrFail(type,"",node);
215     }
216 
217     private boolean resolve(ClassNode type) {
218         String name = type.getName();
219         return resolve(type,true,true,true);
220     }
221 
222     private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
223         if (type.isResolved()) return true;
224         if (type.isArray()) {
225             ClassNode element = type.getComponentType();
226             boolean resolved = resolve(element,testModuleImports,testDefaultImports,testStaticInnerClasses);
227             if (resolved) {
228                 ClassNode cn = element.makeArray();
229                 type.setRedirect(cn);
230             }
231             return resolved;
232         }
233 
234         // test if vanilla name is current class name
235         if (currentClass==type) return true;
236         if (currentClass.getNameWithoutPackage().equals(type.getName())) {
237             type.setRedirect(currentClass);
238             return true;
239         }
240 
241         return  resolveFromModule(type,testModuleImports) ||
242                 resolveFromCompileUnit(type) ||
243                 resovleFromDefaultImports(type,testDefaultImports) ||
244                 resolveFromStaticInnerClasses(type,testStaticInnerClasses) ||
245                 resolveFromClassCache(type) ||
246                 resolveToClass(type) ||
247                 resolveToScript(type);
248 
249     }
250 
251     private boolean resolveFromClassCache(ClassNode type) {
252         String name = type.getName();
253         Object val = cachedClasses.get(name);
254         if (val==null || val==NO_CLASS){
255             return false;
256         } else {
257             setClass(type,(Class) val);
258             return true;
259         }
260     }
261 
262     // NOTE: copied from GroovyClassLoader
263     private long getTimeStamp(Class cls) {
264         Field field;
265         Long o;
266         try {
267             field = cls.getField(Verifier.__TIMESTAMP);
268             o = (Long) field.get(null);
269         } catch (Exception e) {
270             return Long.MAX_VALUE;
271         }
272         return o.longValue();
273     }
274 
275     // NOTE: copied from GroovyClassLoader
276     private boolean isSourceNewer(URL source, Class cls) {
277         try {
278             long lastMod;
279 
280             // Special handling for file:// protocol, as getLastModified() often reports
281             // incorrect results (-1)
282             if (source.getProtocol().equals("file")) {
283                 // Coerce the file URL to a File
284                 String path = source.getPath().replace('/', File.separatorChar).replace('|', ':');
285                 File file = new File(path);
286                 lastMod = file.lastModified();
287             }
288             else {
289                 lastMod = source.openConnection().getLastModified();
290             }
291             return lastMod > getTimeStamp(cls);            
292         } catch (IOException e) {
293             // if the stream can't be opened, let's keep the old reference
294             return false;
295         }
296     }
297 
298 
299     private boolean resolveToScript(ClassNode type) {
300         String name = type.getName();
301         if (cachedClasses.get(name)==NO_CLASS) return false;
302         if (cachedClasses.get(name)==SCRIPT) cachedClasses.put(name,NO_CLASS);
303         if (name.startsWith("java.")) return type.isResolved();
304         //TODO: don't ignore inner static classes completly
305         if (name.indexOf('$')!=-1) return type.isResolved();
306         ModuleNode module = currentClass.getModule();
307         if (module.hasPackageName() && name.indexOf('.')==-1) return type.isResolved();
308         // try to find a script from classpath
309         GroovyClassLoader gcl = compilationUnit.getClassLoader();
310         URL url = null;
311         try {
312             url = gcl.getResourceLoader().loadGroovySource(name);
313         } catch (MalformedURLException e) {
314             // fall through and let the URL be null
315         }
316         if (url !=null) {
317             if (type.isResolved()) {
318                 Class cls = type.getTypeClass();
319                 // if the file is not newer we don't want to recompile
320                 if (!isSourceNewer(url,cls)) return true;
321                 cachedClasses.remove(type.getName());
322                 type.setRedirect(null);
323             }
324             compilationUnit.addSource(url);
325             currentClass.getCompileUnit().addClassNodeToCompile(type,url.toString());
326             return true;
327         }
328         // type may be resolved through the classloader before
329         return type.isResolved();
330     }
331 
332 
333     private boolean resolveFromStaticInnerClasses(ClassNode type, boolean testStaticInnerClasses) {
334         // try to resolve a public static inner class' name
335         testStaticInnerClasses &= type.hasPackageName();
336         if (testStaticInnerClasses) {
337             String name = type.getName();
338             String replacedPointType = name;
339             int lastPoint = replacedPointType.lastIndexOf('.');
340             replacedPointType = new StringBuffer()
341                 .append(replacedPointType.substring(0, lastPoint))
342                 .append("$")
343                 .append(replacedPointType.substring(lastPoint + 1))
344                 .toString();
345             type.setName(replacedPointType);
346             if (resolve(type,false,false,true)) return true;
347             type.setName(name);
348         }
349         return false;
350     }
351 
352     private boolean resovleFromDefaultImports(ClassNode type, boolean testDefaultImports) {
353         // test default imports
354         testDefaultImports &= !type.hasPackageName();
355         if (testDefaultImports) {
356             for (int i = 0, size = DEFAULT_IMPORTS.length; i < size; i++) {
357                 String packagePrefix = DEFAULT_IMPORTS[i];
358                 String name = type.getName();
359                 String fqn = packagePrefix+name;
360                 type.setName(fqn);
361                 if (resolve(type,false,false,false)) return true;
362                 type.setName(name);
363             }
364             String name = type.getName();
365             if (name.equals("BigInteger")) {
366                 type.setRedirect(ClassHelper.BigInteger_TYPE);
367                 return true;
368             } else if (name.equals("BigDecimal")) {
369                 type.setRedirect(ClassHelper.BigDecimal_TYPE);
370                 return true;    
371             }
372         }
373         return false;
374     }
375 
376     private boolean resolveFromCompileUnit(ClassNode type) {
377         // look into the compile unit if there is a class with that name
378         CompileUnit compileUnit = currentClass.getCompileUnit();
379         if (compileUnit == null) return false;
380         ClassNode cuClass = compileUnit.getClass(type.getName());
381         if (cuClass!=null) {
382         	if (type!=cuClass) type.setRedirect(cuClass);
383         	return true;
384         }
385         return false;
386     }
387 
388 
389     private void setClass(ClassNode n, Class cls) {
390         ClassNode cn = ClassHelper.make(cls);
391         n.setRedirect(cn);
392     }
393 
394     private void ambigousClass(ClassNode type, ClassNode iType, String name, boolean resolved){
395         if (resolved && !type.getName().equals(iType.getName())) {
396             addError("reference to "+name+" is ambigous, both class "+type.getName()+" and "+iType.getName()+" match",type);
397         } else {
398             type.setRedirect(iType);
399         }
400     }
401     
402     private boolean resolveAliasFromModule(ClassNode type) {
403         ModuleNode module = currentClass.getModule();
404         if (module==null) return false;
405         String name = type.getName();
406         
407         // check module node imports aliases
408         // the while loop enables a check for inner classes which are not fully imported,
409         // but visible as the surrounding class is imported and the inner class is public/protected static
410         String pname = name;
411         int index = name.length();
412         /*
413          * we have a name foo.bar and an import foo.foo. This means foo.bar is possibly
414          * foo.foo.bar rather than foo.bar. This means to cut at the dot in foo.bar and
415          * foo for import
416          */
417         while (true) {
418             pname = name.substring(0,index);
419             ClassNode aliasedNode = module.getImport(pname);
420             if (aliasedNode!=null) {
421                 if (pname.length()==name.length()){
422                     // full match, no need to create a new class
423                     type.setRedirect(aliasedNode);
424                     return true;
425                 } else {
426                     //partial match
427                     String newName = aliasedNode.getName()+name.substring(pname.length());
428                     type.setName(newName);
429                     if (resolve(type,true,true,true)) return true;
430                     // was not resolved soit was a fake match
431                     type.setName(name);
432                 }
433             }
434             index = pname.lastIndexOf('.');
435             if (index==-1) break;
436         }
437          return false;
438         
439     }
440 
441     private boolean resolveFromModule(ClassNode type, boolean testModuleImports) {
442         ModuleNode module = currentClass.getModule();
443         if (module==null) return false;
444 
445         String name = type.getName();
446 
447         if (!type.hasPackageName() && module.hasPackageName()){
448             type.setName(module.getPackageName()+name);
449         }
450         // look into the module node if there is a class with that name
451         List moduleClasses = module.getClasses();
452         for (Iterator iter = moduleClasses.iterator(); iter.hasNext();) {
453             ClassNode mClass = (ClassNode) iter.next();
454             if (mClass.getName().equals(type.getName())){
455                 if (mClass!=type) type.setRedirect(mClass);
456                 return true;
457             }
458         }
459         type.setName(name);
460 
461         if (testModuleImports) {
462             if (resolveAliasFromModule(type)) return true;
463             
464             boolean resolved = false;
465             if (module.hasPackageName()) { 
466                 // check package this class is defined in
467                 type.setName(module.getPackageName()+name);
468                 resolved = resolve(type,false,false,false);
469             }
470             // check module node imports packages
471             List packages = module.getImportPackages();
472             ClassNode iType = ClassHelper.makeWithoutCaching(name);
473             for (Iterator iter = packages.iterator(); iter.hasNext();) {
474                 String packagePrefix = (String) iter.next();
475                 String fqn = packagePrefix+name;
476                 iType.setName(fqn);
477                 if (resolve(iType,false,false,true)) {
478                 	ambigousClass(type,iType,name,resolved);
479                     return true;
480                 }
481                 iType.setName(name);
482             }
483             if (!resolved) type.setName(name);
484             return resolved;
485         }
486         return false;
487     }
488 
489     private boolean resolveToClass(ClassNode type) {
490         String name = type.getName();
491         if (cachedClasses.get(name)==NO_CLASS) return false;
492         if (currentClass.getModule().hasPackageName() && name.indexOf('.')==-1) return false;
493         GroovyClassLoader loader  = compilationUnit.getClassLoader();
494         Class cls = null;
495         try {
496             // NOTE: it's important to do no lookup against script files
497             // here since the GroovyClassLoader would create a new
498             // CompilationUnit
499             cls = loader.loadClass(name,false,true);
500         } catch (ClassNotFoundException cnfe) {
501             cachedClasses.put(name,SCRIPT);
502             return false;
503         } catch (CompilationFailedException cfe) {
504             compilationUnit.getErrorCollector().addErrorAndContinue(new ExceptionMessage(cfe,true,source));
505             return false;
506         } 
507         //TODO: the case of a NoClassDefFoundError needs a bit more research
508         // a simple recompilation is not possible it seems. The current class
509         // we are searching for is there, so we should mark that somehow. 
510         // Basically the missing class needs to be completly compiled before
511         // we can again search for the current name.
512         /*catch (NoClassDefFoundError ncdfe) {
513             cachedClasses.put(name,SCRIPT);
514             return false;
515         }*/
516         if (cls==null) return false;
517         cachedClasses.put(name,cls);
518         setClass(type,cls);
519         //NOTE: we return false here even if we found a class,
520         //but we want to give a possible script a chance to recompile.
521         //this can only be done if the loader was not the instance
522         //defining the class.
523         return cls.getClassLoader()==loader;
524     }
525 
526 
527 
528     public Expression transform(Expression exp) {
529         if (exp==null) return null;
530         if (exp instanceof VariableExpression) {
531             return transformVariableExpression((VariableExpression) exp);
532         } else if (exp instanceof PropertyExpression) {
533             return transformPropertyExpression((PropertyExpression) exp);
534         } else if (exp instanceof DeclarationExpression) {
535             return transformDeclarationExpression((DeclarationExpression)exp);
536         } else if (exp instanceof BinaryExpression) {
537             return transformBinaryExpression((BinaryExpression)exp);
538         } else if (exp instanceof MethodCallExpression) {
539             return transformMethodCallExpression((MethodCallExpression)exp);
540         } else if (exp instanceof ClosureExpression) {
541         	return transformClosureExpression((ClosureExpression) exp);
542         } else if (exp instanceof ConstructorCallExpression) {
543         	return transformConstructorCallExpression((ConstructorCallExpression) exp);
544         } else {
545             resolveOrFail(exp.getType(),exp);
546             return exp.transformExpression(this);
547         }
548     }
549 
550 
551     private String lookupClassName(PropertyExpression pe) {
552         String name = "";
553         for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
554             if (it instanceof VariableExpression) {
555                 VariableExpression ve = (VariableExpression) it;
556                 // stop at super and this
557                 if (ve==VariableExpression.SUPER_EXPRESSION || ve==VariableExpression.THIS_EXPRESSION) {
558                     return null;
559                 }
560                 name= ve.getName()+"."+name;
561                 break;
562             } 
563             // anything other than PropertyExpressions, ClassExpression or
564             // VariableExpressions will stop resolving
565             else if (!(it instanceof PropertyExpression)) {
566                 return null;
567             } else {
568                 PropertyExpression current = (PropertyExpression) it;
569                 String propertyPart = current.getProperty();
570                 // the class property stops resolving
571                 if (propertyPart.equals("class")) {
572                     return null;
573                 }
574                 name = propertyPart+"."+name;
575             }
576         }
577         if (name.length()>0) return name.substring(0,name.length()-1);
578         return null;
579     }
580 
581     // iterate from the inner most to the outer and check for classes
582     // this check will ignore a .class property, for Exmaple Integer.class will be
583     // a PropertyExpression with the ClassExpression of Integer as objectExpression
584     // and class as property
585     private Expression correctClassClassChain(PropertyExpression pe){
586         LinkedList stack = new LinkedList();
587         ClassExpression found = null;
588         for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
589             if (it instanceof ClassExpression) {
590                 found = (ClassExpression) it;
591                 break;
592             } else if (! (it instanceof PropertyExpression)) {
593                 return pe;
594             }
595             stack.addFirst(it);
596         }
597         if (found==null) return pe;
598 
599         if (stack.isEmpty()) return pe;
600         Object stackElement = stack.removeFirst();
601         if (!(stackElement instanceof PropertyExpression)) return pe;
602         PropertyExpression classPropertyExpression = (PropertyExpression) stackElement;
603         if (! classPropertyExpression.getProperty().equals("class")) return pe;
604 
605         if (stack.isEmpty()) return found;
606         stackElement = stack.removeFirst();
607         if (!(stackElement instanceof PropertyExpression)) return pe;
608         PropertyExpression classPropertyExpressionContainer = (PropertyExpression) stackElement;
609 
610         classPropertyExpressionContainer.setObjectExpression(found);
611         return pe;
612     }
613 
614     protected Expression transformPropertyExpression(PropertyExpression pe) {
615         boolean itlp = isTopLevelProperty;
616         
617         Expression objectExpression = pe.getObjectExpression();
618         isTopLevelProperty = !(objectExpression instanceof PropertyExpression);
619         objectExpression = transform(objectExpression);
620         isTopLevelProperty = itlp;
621         
622         pe.setObjectExpression(objectExpression);
623         
624         String className = lookupClassName(pe);
625         if (className!=null) {
626             ClassNode type = ClassHelper.make(className);
627             if (resolve(type)) return new ClassExpression(type);
628         }  
629         if (objectExpression instanceof ClassExpression ){
630             // possibly a inner class
631             ClassExpression ce = (ClassExpression) objectExpression;
632             ClassNode type = ClassHelper.make(ce.getType().getName()+"$"+pe.getProperty());
633             if (resolve(type,false,false,false)) return new ClassExpression(type);
634         }
635         if (isTopLevelProperty) return correctClassClassChain(pe);
636         
637         return pe;
638     }
639        
640     protected Expression transformVariableExpression(VariableExpression ve) {
641         if (ve.getName().equals("this"))  return VariableExpression.THIS_EXPRESSION;
642         if (ve.getName().equals("super")) return VariableExpression.SUPER_EXPRESSION;
643         Variable v = ve.getAccessedVariable();
644         if (v instanceof DynamicVariable) {
645             ClassNode t = ClassHelper.make(ve.getName());
646             if (resolve(t)) {
647                 // the name is a type so remove it from the scoping
648                 // as it is only a classvariable, it is only in 
649                 // referencedClassVariables, but must be removed
650                 // for each parentscope too
651                 for (VariableScope scope = currentScope; scope!=null && !scope.isRoot(); scope = scope.getParent()) {
652                     if (scope.isRoot()) break;
653                     if (scope.getReferencedClassVariables().remove(ve.getName())==null) break;
654                 }
655                 return new ClassExpression(t);
656             }
657         }
658         resolveOrFail(ve.getType(),ve);
659         return ve;
660     }
661     
662     protected Expression transformBinaryExpression(BinaryExpression be) {
663         Expression left = transform(be.getLeftExpression());
664         if (be.getOperation().getType()==Types.ASSIGNMENT_OPERATOR && left instanceof ClassExpression){
665             ClassExpression  ce = (ClassExpression) left;
666             addError("you tried to assign a value to "+ce.getType().getName(),be.getLeftExpression());
667             return be;
668         }
669         Expression right = transform(be.getRightExpression());
670         Expression ret = new BinaryExpression(left,be.getOperation(),right);
671         ret.setSourcePosition(be);
672         return ret;
673     }
674     
675     protected Expression transformClosureExpression(ClosureExpression ce) {
676         Parameter[] paras = ce.getParameters();
677         if (paras!=null) {
678 	        for (int i=0; i<paras.length; i++) {
679 	            ClassNode t = paras[i].getType();
680 	            resolveOrFail(t,ce);
681 	        }
682         }
683         Statement code = ce.getCode();
684         if (code!=null) code.visit(this);
685     	ClosureExpression newCe= new ClosureExpression(paras,code);
686         newCe.setVariableScope(ce.getVariableScope());
687         newCe.setSourcePosition(ce);
688         return newCe;
689     }
690     
691     protected Expression transformConstructorCallExpression(ConstructorCallExpression cce){
692     	ClassNode type = cce.getType();
693     	resolveOrFail(type,cce);
694     	Expression expr = cce.transformExpression(this);
695         return expr;
696     }
697     
698     protected Expression transformMethodCallExpression(MethodCallExpression mce) {
699         Expression obj = mce.getObjectExpression();
700         Expression newObject = transform(obj);
701         Expression args = transform(mce.getArguments());
702         MethodCallExpression ret = new MethodCallExpression(newObject,mce.getMethod(),args);
703         ret.setSafe(mce.isSafe());
704         ret.setImplicitThis(mce.isImplicitThis());
705         ret.setSpreadSafe(mce.isSpreadSafe());
706         ret.setSourcePosition(mce);
707         return ret;
708     }
709     
710     protected Expression transformDeclarationExpression(DeclarationExpression de) {
711         Expression oldLeft = de.getLeftExpression();
712         Expression left = transform(oldLeft);
713         if (left!=oldLeft){
714             ClassExpression  ce = (ClassExpression) left;
715             addError("you tried to assign a value to "+ce.getType().getName(),oldLeft);
716             return de;
717         }
718         Expression right = transform(de.getRightExpression());
719         if (right==de.getRightExpression()) return de;
720         return new DeclarationExpression((VariableExpression) left,de.getOperation(),right);
721     }
722     
723     public void visitAnnotations(AnnotatedNode node) {
724         Map annotionMap = node.getAnnotations();
725         if (annotionMap.isEmpty()) return;
726         Iterator it = annotionMap.values().iterator(); 
727         while (it.hasNext()) {
728             AnnotationNode an = (AnnotationNode) it.next();
729             //skip builtin properties
730             if (an.isBuiltIn()) continue;
731             ClassNode type = an.getClassNode();
732             resolveOrFail(type,"unable to find class for annotation",an);
733         }
734     }
735 
736     public void visitClass(ClassNode node) {
737         ClassNode oldNode = currentClass;
738         currentClass = node;
739         
740         ModuleNode module = node.getModule();
741         if (!module.hasImportsResolved()) {
742            List l = module.getImports();
743            for (Iterator iter = l.iterator(); iter.hasNext();) {
744                ImportNode element = (ImportNode) iter.next();
745                ClassNode type = element.getType();
746                if (resolve(type,false,false,false)) continue;
747                addError("unable to resolve class "+type.getName(),type);
748            }
749            module.setImportsResolved(true);
750         }
751         
752         ClassNode sn = node.getUnresolvedSuperClass();
753         if (sn!=null) resolveOrFail(sn,node,true);
754         ClassNode[] interfaces = node.getInterfaces();
755         for (int i=0; i<interfaces.length; i++) {
756             resolveOrFail(interfaces[i],node,true);
757         }        
758         super.visitClass(node);
759         currentClass = oldNode;        
760     }
761     
762     public void visitReturnStatement(ReturnStatement statement) {
763        statement.setExpression(transform(statement.getExpression()));
764     }
765 
766     public void visitAssertStatement(AssertStatement as) {
767         as.setBooleanExpression((BooleanExpression) (transform(as.getBooleanExpression())));
768         as.setMessageExpression(transform(as.getMessageExpression()));
769     }
770     
771     public void visitCaseStatement(CaseStatement statement) {
772     	statement.setExpression(transform(statement.getExpression()));
773     	statement.getCode().visit(this);
774     }
775 
776     public void visitCatchStatement(CatchStatement cs) {
777         resolveOrFail(cs.getExceptionType(),cs);
778         super.visitCatchStatement(cs);
779     }
780 
781     public void visitDoWhileLoop(DoWhileStatement loop) {
782         loop.setBooleanExpression((BooleanExpression) (transform(loop.getBooleanExpression())));
783         super.visitDoWhileLoop(loop);
784     }
785     
786     public void visitForLoop(ForStatement forLoop) {
787         forLoop.setCollectionExpression(transform(forLoop.getCollectionExpression()));
788         resolveOrFail(forLoop.getVariableType(),forLoop);
789         super.visitForLoop(forLoop);
790     }
791     
792     public void visitSynchronizedStatement(SynchronizedStatement sync) {
793         sync.setExpression(transform(sync.getExpression()));
794         super.visitSynchronizedStatement(sync);
795     }
796     
797     public void visitThrowStatement(ThrowStatement ts) {
798         ts.setExpression(transform(ts.getExpression()));
799     }
800     
801     public void visitWhileLoop(WhileStatement loop) {
802     	loop.setBooleanExpression((BooleanExpression) transform(loop.getBooleanExpression()));
803     	super.visitWhileLoop(loop);
804     }
805     
806     public void visitExpressionStatement(ExpressionStatement es) {
807         es.setExpression(transform(es.getExpression()));
808     }
809     
810     public void visitBlockStatement(BlockStatement block) {
811         VariableScope oldScope = currentScope;
812         currentScope = block.getVariableScope();
813         super.visitBlockStatement(block);
814         currentScope = oldScope;
815     }
816 
817     protected SourceUnit getSourceUnit() {
818         return source;
819     }
820 }