001    /*
002     * $Id: ResolveVisitor.java,v 1.7 2005/11/21 00:40:23 glaforge Exp $
003     *
004     * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005     *
006     * Redistribution and use of this software and associated documentation
007     * ("Software"), with or without modification, are permitted provided that the
008     * following conditions are met: 1. Redistributions of source code must retain
009     * copyright statements and notices. Redistributions must also contain a copy
010     * of this document. 2. Redistributions in binary form must reproduce the above
011     * copyright notice, this list of conditions and the following disclaimer in
012     * the documentation and/or other materials provided with the distribution. 3.
013     * The name "groovy" must not be used to endorse or promote products derived
014     * from this Software without prior written permission of The Codehaus. For
015     * written permission, please contact info@codehaus.org. 4. Products derived
016     * from this Software may not be called "groovy" nor may "groovy" appear in
017     * their names without prior written permission of The Codehaus. "groovy" is a
018     * registered trademark of The Codehaus. 5. Due credit should be given to The
019     * Codehaus - http://groovy.codehaus.org/
020     *
021     * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
022     * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
024     * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
025     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
027     * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
028     * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
029     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
030     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
031     * DAMAGE.
032     *
033     */
034    package org.codehaus.groovy.control;
035    
036    import groovy.lang.GroovyClassLoader;
037    
038    import java.io.IOException;
039    import java.io.File;
040    import java.lang.reflect.Field;
041    import java.util.HashMap;
042    import java.util.Iterator;
043    import java.util.LinkedList;
044    import java.util.List;
045    import java.util.Map;
046    import java.net.URL;
047    import java.net.MalformedURLException;
048    
049    import org.codehaus.groovy.ast.ASTNode;
050    import org.codehaus.groovy.ast.AnnotatedNode;
051    import org.codehaus.groovy.ast.AnnotationNode;
052    import org.codehaus.groovy.ast.ClassHelper;
053    import org.codehaus.groovy.ast.ClassNode;
054    import org.codehaus.groovy.ast.CodeVisitorSupport;
055    import org.codehaus.groovy.ast.CompileUnit;
056    import org.codehaus.groovy.ast.ConstructorNode;
057    import org.codehaus.groovy.ast.FieldNode;
058    import org.codehaus.groovy.ast.MethodNode;
059    import org.codehaus.groovy.ast.ModuleNode;
060    import org.codehaus.groovy.ast.Parameter;
061    import org.codehaus.groovy.ast.PropertyNode;
062    import org.codehaus.groovy.ast.expr.BinaryExpression;
063    import org.codehaus.groovy.ast.expr.BooleanExpression;
064    import org.codehaus.groovy.ast.expr.ClassExpression;
065    import org.codehaus.groovy.ast.expr.ClosureExpression;
066    import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
067    import org.codehaus.groovy.ast.expr.DeclarationExpression;
068    import org.codehaus.groovy.ast.expr.Expression;
069    import org.codehaus.groovy.ast.expr.ExpressionTransformer;
070    import org.codehaus.groovy.ast.expr.MethodCallExpression;
071    import org.codehaus.groovy.ast.expr.PropertyExpression;
072    import org.codehaus.groovy.ast.expr.VariableExpression;
073    import org.codehaus.groovy.ast.stmt.AssertStatement;
074    import org.codehaus.groovy.ast.stmt.CaseStatement;
075    import org.codehaus.groovy.ast.stmt.CatchStatement;
076    import org.codehaus.groovy.ast.stmt.DoWhileStatement;
077    import org.codehaus.groovy.ast.stmt.ExpressionStatement;
078    import org.codehaus.groovy.ast.stmt.ForStatement;
079    import org.codehaus.groovy.ast.stmt.IfStatement;
080    import org.codehaus.groovy.ast.stmt.ReturnStatement;
081    import org.codehaus.groovy.ast.stmt.Statement;
082    import org.codehaus.groovy.ast.stmt.SwitchStatement;
083    import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
084    import org.codehaus.groovy.ast.stmt.ThrowStatement;
085    import org.codehaus.groovy.ast.stmt.WhileStatement;
086    import org.codehaus.groovy.ast.GroovyClassVisitor;
087    import org.codehaus.groovy.classgen.Verifier;
088    import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
089    import org.codehaus.groovy.syntax.SyntaxException;
090    import org.codehaus.groovy.syntax.Types;
091    
092    /**
093     * Visitor to resolve Types and convert VariableExpression to
094     * ClassExpressions if needed.
095     *
096     * Note: the method to start the resolving is @see ResolveVisitor#startResolving(ClassNode, SourceUnit).
097     *
098     *
099     * @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    }