View Javadoc

1   /*
2    * $Id: ResolveVisitor.java,v 1.7 2005/11/21 00:40:23 glaforge 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.ClassHelper;
53  import org.codehaus.groovy.ast.ClassNode;
54  import org.codehaus.groovy.ast.CodeVisitorSupport;
55  import org.codehaus.groovy.ast.CompileUnit;
56  import org.codehaus.groovy.ast.ConstructorNode;
57  import org.codehaus.groovy.ast.FieldNode;
58  import org.codehaus.groovy.ast.MethodNode;
59  import org.codehaus.groovy.ast.ModuleNode;
60  import org.codehaus.groovy.ast.Parameter;
61  import org.codehaus.groovy.ast.PropertyNode;
62  import org.codehaus.groovy.ast.expr.BinaryExpression;
63  import org.codehaus.groovy.ast.expr.BooleanExpression;
64  import org.codehaus.groovy.ast.expr.ClassExpression;
65  import org.codehaus.groovy.ast.expr.ClosureExpression;
66  import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
67  import org.codehaus.groovy.ast.expr.DeclarationExpression;
68  import org.codehaus.groovy.ast.expr.Expression;
69  import org.codehaus.groovy.ast.expr.ExpressionTransformer;
70  import org.codehaus.groovy.ast.expr.MethodCallExpression;
71  import org.codehaus.groovy.ast.expr.PropertyExpression;
72  import org.codehaus.groovy.ast.expr.VariableExpression;
73  import org.codehaus.groovy.ast.stmt.AssertStatement;
74  import org.codehaus.groovy.ast.stmt.CaseStatement;
75  import org.codehaus.groovy.ast.stmt.CatchStatement;
76  import org.codehaus.groovy.ast.stmt.DoWhileStatement;
77  import org.codehaus.groovy.ast.stmt.ExpressionStatement;
78  import org.codehaus.groovy.ast.stmt.ForStatement;
79  import org.codehaus.groovy.ast.stmt.IfStatement;
80  import org.codehaus.groovy.ast.stmt.ReturnStatement;
81  import org.codehaus.groovy.ast.stmt.Statement;
82  import org.codehaus.groovy.ast.stmt.SwitchStatement;
83  import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
84  import org.codehaus.groovy.ast.stmt.ThrowStatement;
85  import org.codehaus.groovy.ast.stmt.WhileStatement;
86  import org.codehaus.groovy.ast.GroovyClassVisitor;
87  import org.codehaus.groovy.classgen.Verifier;
88  import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
89  import org.codehaus.groovy.syntax.SyntaxException;
90  import org.codehaus.groovy.syntax.Types;
91  
92  /***
93   * Visitor to resolve Types and convert VariableExpression to
94   * ClassExpressions if needed.
95   *
96   * Note: the method to start the resolving is @see ResolveVisitor#startResolving(ClassNode, SourceUnit).
97   *
98   *
99   * @author Jochen Theodorou
100  */
101 public class ResolveVisitor extends CodeVisitorSupport implements ExpressionTransformer, GroovyClassVisitor {
102     private ClassNode currentClass;
103     private static final String[] DEFAULT_IMPORTS = {"java.lang.", "java.io.", "java.net.", "java.util.", "groovy.lang.", "groovy.util."};
104     private CompilationUnit compilationUnit;
105     private Map cachedClasses = new HashMap();
106     private static final Object NO_CLASS = new Object();
107     private SourceUnit source;
108 
109     private boolean isTopLevelProperty = true;
110 
111     public ResolveVisitor(CompilationUnit cu) {
112         compilationUnit = cu;
113     }
114 
115     public void startResolving(ClassNode node,SourceUnit source) {
116         this.source = source;
117         visitClass(node);
118     }
119 
120     public void visitConstructor(ConstructorNode node) {
121         Parameter[] paras = node.getParameters();
122         for (int i=0; i<paras.length; i++) {
123             ClassNode t = paras[i].getType();
124             resolveOrFail(t,node);
125         }
126         Statement code = node.getCode();
127         if (code!=null) code.visit(this);
128     }
129 
130     public void visitSwitch(SwitchStatement statement) {
131         Expression exp = statement.getExpression();
132         statement.setExpression(transform(exp));
133         List list = statement.getCaseStatements();
134         for (Iterator iter = list.iterator(); iter.hasNext(); ) {
135             CaseStatement caseStatement = (CaseStatement) iter.next();
136             caseStatement.visit(this);
137         }
138         statement.getDefaultStatement().visit(this);
139     }
140 
141     public void visitMethod(MethodNode node) {
142         Parameter[] paras = node.getParameters();
143         for (int i=0; i<paras.length; i++) {
144             ClassNode t = paras[i].getType();
145             resolveOrFail(t,node);
146         }
147         resolveOrFail(node.getReturnType(),node);
148         Statement code = node.getCode();
149         if (code!=null) code.visit(this);
150     }
151 
152     public void visitField(FieldNode node) {
153         ClassNode t = node.getType();
154         resolveOrFail(t,node);
155         Expression init = node.getInitialExpression();
156         node.setInitialValueExpression(transform(init));
157     }
158 
159     public void visitProperty(PropertyNode node) {
160         ClassNode t = node.getType();
161         resolveOrFail(t,node);
162         Statement code = node.getGetterBlock();
163         if (code!=null) code.visit(this);
164         code = node.getSetterBlock();
165         if (code!=null) code.visit(this);
166     }
167 
168     public void visitIfElse(IfStatement ifElse) {
169         ifElse.setBooleanExpression((BooleanExpression) (transform(ifElse.getBooleanExpression())));
170         super.visitIfElse(ifElse);
171     }
172 
173     private void resolveOrFail(ClassNode type, String msg, ASTNode node) {
174         if (resolve(type)) return;
175         addError("unable to resolve class "+type.getName()+" "+msg,node);
176     }
177 
178     private void resolveOrFail(ClassNode type, ASTNode node) {
179         resolveOrFail(type,"",node);
180     }
181 
182     private boolean resolve(ClassNode type) {
183         String name = type.getName();
184         return resolve(type,true,true,true);
185     }
186 
187     private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
188         if (type.isResolved()) return true;
189         if (type.isArray()) {
190             ClassNode element = type.getComponentType();
191             boolean resolved = resolve(element,testModuleImports,testDefaultImports,testStaticInnerClasses);
192             if (resolved) {
193                 ClassNode cn = element.makeArray();
194                 type.setRedirect(cn);
195             }
196             return resolved;
197         }
198 
199         // test if vanilla name is current class name
200         if (currentClass==type) return true;
201         if (currentClass.getNameWithoutPackage().equals(type.getName())) {
202             type.setRedirect(currentClass);
203             return true;
204         }
205 
206         return  resolveFromModule(type,testModuleImports) ||
207                 resolveFromCompileUnit(type) ||
208                 resovleFromDefaultImports(type,testDefaultImports) ||
209                 resolveFromStaticInnerClasses(type,testStaticInnerClasses) ||
210                 resolveFromClassCache(type) ||
211                 resolveToClass(type) ||
212                 resolveToScript(type);
213 
214     }
215 
216     private boolean resolveFromClassCache(ClassNode type) {
217         String name = type.getName();
218         Object val = cachedClasses.get(name);
219         if (val==null || val==NO_CLASS){
220             return false;
221         } else {
222             setClass(type,(Class) val);
223             return true;
224         }
225     }
226 
227     // NOTE: copied from GroovyClassLoader
228     private long getTimeStamp(Class cls) {
229         Field field;
230         Long o;
231         try {
232             field = cls.getField(Verifier.__TIMESTAMP);
233             o = (Long) field.get(null);
234         } catch (Exception e) {
235             return Long.MAX_VALUE;
236         }
237         return o.longValue();
238     }
239 
240     // NOTE: copied from GroovyClassLoader
241     private boolean isSourceNewer(URL source, Class cls) {
242         try {
243             long lastMod;
244 
245             // Special handling for file:// protocol, as getLastModified() often reports
246             // incorrect results (-1)
247             if (source.getProtocol().equals("file")) {
248                 // Coerce the file URL to a File
249                 String path = source.getPath().replace('/', File.separatorChar).replace('|', ':');
250                 File file = new File(path);
251                 lastMod = file.lastModified();
252             }
253             else {
254                 lastMod = source.openConnection().getLastModified();
255             }
256             return lastMod > getTimeStamp(cls);            
257         } catch (IOException e) {
258             // if the stream can't be opened, let's keep the old reference
259             return false;
260         }
261     }
262 
263 
264     private boolean resolveToScript(ClassNode type) {
265         String name = type.getName();
266         if (cachedClasses.get(name)==NO_CLASS) return false;
267         if (name.startsWith("java.")) return type.isResolved();
268         //TODO: don't ignore inner static classes completly
269         if (name.indexOf('$')!=-1) return type.isResolved();
270         ModuleNode module = currentClass.getModule();
271         if (module.hasPackageName() && name.indexOf('.')==-1) return type.isResolved();
272         // try to find a script from classpath
273         GroovyClassLoader gcl = compilationUnit.getClassLoader();
274         URL url = null;
275         try {
276             url = gcl.getResourceLoader().loadGroovySource(name);
277         } catch (MalformedURLException e) {
278             // fall through and let the URL be null
279         }
280         if (url !=null) {
281             if (type.isResolved()) {
282                 Class cls = type.getTypeClass();
283                 // if the file is not newer we don't want to recompile
284                 if (!isSourceNewer(url,cls)) return true;
285                 cachedClasses.remove(type.getName());
286                 type.setRedirect(null);
287             }
288             compilationUnit.addSource(url);
289             currentClass.getCompileUnit().addClassNodeToCompile(type);
290             return true;
291         }
292         // type may be resolved through the classloader before
293         return type.isResolved();
294     }
295 
296 
297     private boolean resolveFromStaticInnerClasses(ClassNode type, boolean testStaticInnerClasses) {
298         // try to resolve a public static inner class' name
299         testStaticInnerClasses &= type.hasPackageName();
300         if (testStaticInnerClasses) {
301             String name = type.getName();
302             String replacedPointType = name;
303             int lastPoint = replacedPointType.lastIndexOf('.');
304             replacedPointType = new StringBuffer()
305                 .append(replacedPointType.substring(0, lastPoint))
306                 .append("$")
307                 .append(replacedPointType.substring(lastPoint + 1))
308                 .toString();
309             type.setName(replacedPointType);
310             if (resolve(type,false,false,true)) return true;
311             type.setName(name);
312         }
313         return false;
314     }
315 
316     private boolean resovleFromDefaultImports(ClassNode type, boolean testDefaultImports) {
317         // test default imports
318         testDefaultImports &= !type.hasPackageName();
319         if (testDefaultImports) {
320             for (int i = 0, size = DEFAULT_IMPORTS.length; i < size; i++) {
321                 String packagePrefix = DEFAULT_IMPORTS[i];
322                 String name = type.getName();
323                 String fqn = packagePrefix+name;
324                 type.setName(fqn);
325                 if (resolve(type,false,false,false)) return true;
326                 type.setName(name);
327             }
328         }
329         return false;
330     }
331 
332     private boolean resolveFromCompileUnit(ClassNode type) {
333         // look into the compile unit if there is a class with that name
334         CompileUnit compileUnit = currentClass.getCompileUnit();
335         if (compileUnit == null) return false;
336         ClassNode cuClass = compileUnit.getClass(type.getName());
337         if (cuClass!=null) {
338         	if (type!=cuClass) type.setRedirect(cuClass);
339         	return true;
340         }
341         return false;
342     }
343 
344 
345     private void setClass(ClassNode n, Class cls) {
346         ClassNode cn = ClassHelper.make(cls);
347         n.setRedirect(cn);
348     }
349 
350     private void ambigousClass(ClassNode type, ClassNode iType, String name, boolean resolved){
351         if (resolved && !type.getName().equals(iType.getName())) {
352             addError("reference to "+name+" is ambigous, both class "+type.getName()+" and "+iType.getName()+" match",type);
353         } else {
354             type.setRedirect(iType);
355         }
356     }
357 
358     private boolean resolveFromModule(ClassNode type, boolean testModuleImports) {
359         ModuleNode module = currentClass.getModule();
360         if (module==null) return false;
361 
362         String name = type.getName();
363 
364         if (!type.hasPackageName() && module.hasPackageName()){
365             type.setName(module.getPackageName()+name);
366         }
367         // look into the module node if there is a class with that name
368         List moduleClasses = module.getClasses();
369         for (Iterator iter = moduleClasses.iterator(); iter.hasNext();) {
370             ClassNode mClass = (ClassNode) iter.next();
371             if (mClass.getName().equals(type.getName())){
372                 if (mClass!=type) type.setRedirect(mClass);
373                 return true;
374             }
375         }
376         type.setName(name);
377 
378         {
379             // check module node imports aliases
380         	// the while loop enables a check for inner classes which are not fully imported,
381         	// but visible as the surrounding class is imported and the inner class is public/protected static
382         	String pname = name;
383         	int index = name.length();
384             /*
385              * we have a name foo.bar and an import foo.foo. This means foo.bar is possibly
386              * foo.foo.bar rather than foo.bar. This means to cut at the dot in foo.bar and
387              * foo for import
388              */
389 
390         	while (true) {
391         		pname = name.substring(0,index);
392         		String aliased = module.getImport(pname);
393         		if (aliased!=null && !aliased.equals(name)) {
394         			if (pname.length()<name.length()){
395         				aliased +=  name.substring(pname.length());
396         			}
397 	        		type.setName(aliased);
398 	                if (resolve(type,true,true,true)) return true;
399 	                type.setName(name);
400         		}
401         		index = pname.lastIndexOf('.');
402                 if (index==-1) break;
403         	}
404         }
405 
406         //testModuleImports &= !type.hasPackageName();
407         if (testModuleImports) {
408             String packageName = "";
409             >if (module.hasPackageName()) packageName = module.getPackageName();
410             // check package this class is defined in
411             type.setName(packageName+name);
412             boolean resolved = resolve(type,false,false,false);
413 
414             // check module node imports packages
415             List packages = module.getImportPackages();
416             ClassNode iType = ClassHelper.makeWithoutCaching(name);
417             for (Iterator iter = packages.iterator(); iter.hasNext();) {
418                 String packagePrefix = (String) iter.next();
419                 String fqn = packagePrefix+name;
420                 iType.setName(fqn);
421                 if (resolve(iType,false,false,true)) {
422                 	ambigousClass(type,iType,name,resolved);
423                     return true;
424                 }
425                 iType.setName(name);
426             }
427             if (!resolved) type.setName(name);
428             return resolved;
429         }
430         return false;
431     }
432 
433     private boolean resolveToClass(ClassNode type) {
434         String name = type.getName();
435         if (cachedClasses.get(name)==NO_CLASS) return false;
436         if (currentClass.getModule().hasPackageName() && name.indexOf('.')==-1) return false;
437         GroovyClassLoader loader  = compilationUnit.getClassLoader();
438         Class cls = null;
439         try {
440             // NOTE: it's important to do no lookup against script files
441             // here since the GroovyClassLoader would create a new
442             // CompilationUnit
443             cls = loader.loadClass(name,false,true);
444         } catch (ClassNotFoundException cnfe) {
445             cachedClasses.put(name,NO_CLASS);
446             return false;
447         } catch (NoClassDefFoundError ncdfe) {
448             cachedClasses.put(name,NO_CLASS);
449             return false;
450         }
451         if (cls==null) return false;
452         cachedClasses.put(name,cls);
453         setClass(type,cls);
454         //NOTE: we return false here even if we found a class,
455         //but we want to give a possible script a chance to recompile.
456         //this can only be done if the loader was not the instance
457         //defining the class.
458         return cls.getClassLoader()==loader;
459     }
460 
461 
462 
463     public Expression transform(Expression exp) {
464         if (exp==null) return null;
465         if (exp instanceof VariableExpression) {
466             return transformVariableExpression((VariableExpression) exp);
467         } else if (exp instanceof PropertyExpression) {
468             return transformPropertyExpression((PropertyExpression) exp);
469         } else if (exp instanceof DeclarationExpression) {
470             return transformDeclarationExpression((DeclarationExpression)exp);
471         } else if (exp instanceof BinaryExpression) {
472             return transformBinaryExpression((BinaryExpression)exp);
473         } else if (exp instanceof MethodCallExpression) {
474             return transformMethodCallExpression((MethodCallExpression)exp);
475         } else if (exp instanceof ClosureExpression) {
476         	return transformClosureExpression((ClosureExpression) exp);
477         } else if (exp instanceof ConstructorCallExpression) {
478         	return transformConstructorCallExpression((ConstructorCallExpression) exp);
479         } else {
480             resolveOrFail(exp.getType(),exp);
481             return exp.transformExpression(this);
482         }
483     }
484 
485 
486     private String lookupClassName(PropertyExpression pe) {
487         String name = "";
488         for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
489             if (it instanceof VariableExpression) {
490                 VariableExpression ve = (VariableExpression) it;
491                 // stop at super and this
492                 if (ve==VariableExpression.SUPER_EXPRESSION || ve==VariableExpression.THIS_EXPRESSION) {
493                     return null;
494                 }
495                 name= ve.getName()+"."+name;
496                 break;
497             }
498             // anything other than PropertyExpressions and VariableExpressions will stop resolving
499             else if (!(it instanceof PropertyExpression)) {
500                 return null;
501             } else {
502                 PropertyExpression current = (PropertyExpression) it;
503                 String propertyPart = current.getProperty();
504                 // the class property stops resolving
505                 if (propertyPart.equals("class")) {
506                     return null;
507                 }
508                 name = propertyPart+"."+name;
509             }
510         }
511         if (name.length()>0) return name.substring(0,name.length()-1);
512         return null;
513     }
514 
515     // iterate from the inner most to the outer and check for classes
516     // this check will ignore a .class property, for Exmaple Integer.class will be
517     // a PropertyExpression with the ClassExpression of Integer as objectExprsssion
518     // and class as property
519     private Expression correctClassClassChain(PropertyExpression pe){
520         LinkedList stack = new LinkedList();
521         ClassExpression found = null;
522         for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
523             if (it instanceof ClassExpression) {
524                 found = (ClassExpression) it;
525                 break;
526             } else if (! (it instanceof PropertyExpression)) {
527                 return pe;
528             }
529             stack.addFirst(it);
530         }
531         if (found==null) return pe;
532 
533         if (stack.isEmpty()) return pe;
534         Object stackElement = stack.removeFirst();
535         if (!(stackElement instanceof PropertyExpression)) return pe;
536         PropertyExpression classPropertyExpression = (PropertyExpression) stackElement;
537         if (! classPropertyExpression.getProperty().equals("class")) return pe;
538 
539         if (stack.isEmpty()) return found;
540         stackElement = stack.removeFirst();
541         if (!(stackElement instanceof PropertyExpression)) return pe;
542         PropertyExpression classPropertyExpressionContainer = (PropertyExpression) stackElement;
543 
544         classPropertyExpressionContainer.setObjectExpression(found);
545         return pe;
546     }
547 
548     protected Expression transformPropertyExpression(PropertyExpression pe) {
549         boolean itlp = isTopLevelProperty;
550         
551         Expression objectExpression = pe.getObjectExpression();
552         isTopLevelProperty = !(objectExpression instanceof PropertyExpression);
553         objectExpression = transform(objectExpression);
554         isTopLevelProperty = itlp;
555         
556         pe.setObjectExpression(objectExpression);
557         
558         String className = lookupClassName(pe);
559         if (className!=null) {
560             ClassNode type = ClassHelper.make(className);
561             if (resolve(type)) return new ClassExpression(type);
562         }
563         
564         if (isTopLevelProperty) return correctClassClassChain(pe);
565         
566         return pe;
567     }
568        
569     protected Expression transformVariableExpression(VariableExpression ve) {
570         if (ve.getName().equals("this"))  return VariableExpression.THIS_EXPRESSION;
571         if (ve.getName().equals("super")) return VariableExpression.SUPER_EXPRESSION;
572         ClassNode t = ClassHelper.make(ve.getName());
573         if (resolve(t)) return new ClassExpression(t);
574         resolveOrFail(ve.getType(),ve);
575         return ve;
576     }
577     
578     protected Expression transformBinaryExpression(BinaryExpression be) {
579         Expression left = transform(be.getLeftExpression());
580         if (be.getOperation().getType()==Types.ASSIGNMENT_OPERATOR && left instanceof ClassExpression){
581             ClassExpression  ce = (ClassExpression) left;
582             addError("you tried to assign a value to "+ce.getType().getName(),be.getLeftExpression());
583             return be;
584         }
585         Expression right = transform(be.getRightExpression());
586         return new BinaryExpression(left,be.getOperation(),right);
587     }
588     
589     protected Expression transformClosureExpression(ClosureExpression ce) {
590         Parameter[] paras = ce.getParameters();
591         for (int i=0; i<paras.length; i++) {
592             ClassNode t = paras[i].getType();
593             resolveOrFail(t,ce);
594         }
595         Statement code = ce.getCode();
596         if (code!=null) code.visit(this);
597     	return new ClosureExpression(paras,code);
598     }
599     
600     protected Expression transformConstructorCallExpression(ConstructorCallExpression cce){
601     	ClassNode type = cce.getType();
602     	resolveOrFail(type,cce);
603     	Expression args = cce.getArguments();
604     	args = transform(args);
605     	return new ConstructorCallExpression(type,args);
606     }
607     
608     protected Expression transformMethodCallExpression(MethodCallExpression mce) {
609         Expression obj = mce.getObjectExpression();
610         Expression newObject = transform(obj);
611         Expression args = transform(mce.getArguments());
612         /*if (! (newObject instanceof ClassExpression)) {
613             obj=newObject;
614         } else if (newObject!=obj) {
615             return new StaticMethodCallExpression(newObject.getType(),mce.getMethod(),args);
616         }
617         return new MethodCallExpression(obj,mce.getMethod(),args);*/
618         MethodCallExpression ret = new MethodCallExpression(newObject,mce.getMethod(),args);
619         ret.setSafe(mce.isSafe());
620         ret.setImplicitThis(mce.isImplicitThis());
621         ret.setSpreadSafe(mce.isSpreadSafe());
622         return ret;
623     }
624     
625     protected Expression transformDeclarationExpression(DeclarationExpression de) {
626         Expression oldLeft = de.getLeftExpression();
627         Expression left = transform(oldLeft);
628         if (left!=oldLeft){
629             ClassExpression  ce = (ClassExpression) left;
630             addError("you tried to assign a value to "+ce.getType().getName(),oldLeft);
631             return de;
632         }
633         Expression right = transform(de.getRightExpression());
634         if (right==de.getRightExpression()) return de;
635         return new DeclarationExpression((VariableExpression) left,de.getOperation(),right);
636     }
637     
638     public void visitAnnotations(AnnotatedNode node) {
639         Map annotionMap = node.getAnnotations();
640         if (annotionMap.isEmpty()) return;
641         Iterator it = annotionMap.values().iterator(); 
642         while (it.hasNext()) {
643             AnnotationNode an = (AnnotationNode) it.next();
644             //skip builtin properties
645             if (an.isBuiltIn()) continue;
646             ClassNode type = an.getClassNode();
647             resolveOrFail(type,"unable to find class for annotation",an);
648         }
649     }
650 
651     public void visitClass(ClassNode node) {
652         ClassNode oldNode = currentClass;
653         currentClass = node;
654         ClassNode sn = node.getSuperClass();
655         if (sn!=null) resolveOrFail(sn,node);
656         ClassNode[] interfaces = node.getInterfaces();
657         for (int i=0; i<interfaces.length; i++) {
658             resolveOrFail(interfaces[i],node);
659         }        
660         node.visitContents(this);
661         currentClass = oldNode;        
662     }
663     
664     public void visitReturnStatement(ReturnStatement statement) {
665        statement.setExpression(transform(statement.getExpression()));
666     }
667 
668     public void visitAssertStatement(AssertStatement as) {
669         as.setBooleanExpression((BooleanExpression) (transform(as.getBooleanExpression())));
670         as.setMessageExpression(transform(as.getMessageExpression()));
671     }
672     
673     public void visitCaseStatement(CaseStatement statement) {
674     	statement.setExpression(transform(statement.getExpression()));
675     	statement.getCode().visit(this);
676     }
677 
678     public void visitCatchStatement(CatchStatement cs) {
679         resolveOrFail(cs.getExceptionType(),cs);
680         super.visitCatchStatement(cs);
681     }
682 
683     public void visitDoWhileLoop(DoWhileStatement loop) {
684         loop.setBooleanExpression((BooleanExpression) (transform(loop.getBooleanExpression())));
685         super.visitDoWhileLoop(loop);
686     }
687     
688     public void visitForLoop(ForStatement forLoop) {
689         forLoop.setCollectionExpression(transform(forLoop.getCollectionExpression()));
690         resolveOrFail(forLoop.getVariableType(),forLoop);
691         super.visitForLoop(forLoop);
692     }
693     
694     public void visitSynchronizedStatement(SynchronizedStatement sync) {
695         sync.setExpression(transform(sync.getExpression()));
696         super.visitSynchronizedStatement(sync);
697     }
698     
699     public void visitThrowStatement(ThrowStatement ts) {
700         ts.setExpression(transform(ts.getExpression()));
701     }
702     
703     public void visitWhileLoop(WhileStatement loop) {
704     	loop.setBooleanExpression((BooleanExpression) transform(loop.getBooleanExpression()));
705     	super.visitWhileLoop(loop);
706     }
707     
708     public void visitExpressionStatement(ExpressionStatement es) {
709         es.setExpression(transform(es.getExpression()));
710     }
711     
712     private void addError(String msg, ASTNode expr) {
713         int line = expr.getLineNumber();
714         int col = expr.getColumnNumber();
715         compilationUnit.getErrorCollector().addErrorAndContinue(
716           new SyntaxErrorMessage(new SyntaxException(msg + '\n', line, col), source)
717         );
718     }
719 }