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 }