View Javadoc

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