blob: 685c0a5fd1314fdd074ae74bad96bdb7551aaf29 [file] [log] [blame]
/*
* $Id$
*
* Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided that the
* following conditions are met: 1. Redistributions of source code must retain
* copyright statements and notices. Redistributions must also contain a copy
* of this document. 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution. 3.
* The name "groovy" must not be used to endorse or promote products derived
* from this Software without prior written permission of The Codehaus. For
* written permission, please contact info@codehaus.org. 4. Products derived
* from this Software may not be called "groovy" nor may "groovy" appear in
* their names without prior written permission of The Codehaus. "groovy" is a
* registered trademark of The Codehaus. 5. Due credit should be given to The
* Codehaus - http://groovy.codehaus.org/
*
* THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*/
package org.codehaus.groovy.control;
import groovy.lang.GroovyClassLoader;
import java.io.IOException;
import java.io.File;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.net.URL;
import java.net.MalformedURLException;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CompileUnit;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.DynamicVariable;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.VariableScope;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ExpressionTransformer;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.AssertStatement;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.CaseStatement;
import org.codehaus.groovy.ast.stmt.CatchStatement;
import org.codehaus.groovy.ast.stmt.DoWhileStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.ForStatement;
import org.codehaus.groovy.ast.stmt.IfStatement;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.ast.stmt.SwitchStatement;
import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
import org.codehaus.groovy.ast.stmt.ThrowStatement;
import org.codehaus.groovy.ast.stmt.WhileStatement;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.control.messages.ExceptionMessage;
import org.codehaus.groovy.syntax.Types;
/**
* Visitor to resolve Types and convert VariableExpression to
* ClassExpressions if needed. The ResolveVisitor will try to
* find the Class for a ClassExpression and prints an error if
* it fails to do so. Constructions like C[], foo as C, (C) foo
* will force creation of a ClasssExpression for C
*
* Note: the method to start the resolving is startResolving(ClassNode, SourceUnit).
*
*
* @author Jochen Theodorou
*/
public class ResolveVisitor extends ClassCodeVisitorSupport implements ExpressionTransformer {
private ClassNode currentClass;
// note: BigInteger and BigDecimal are also imported by default
private static final String[] DEFAULT_IMPORTS = {"java.lang.", "java.io.", "java.net.", "java.util.", "groovy.lang.", "groovy.util."};
private CompilationUnit compilationUnit;
private Map cachedClasses = new HashMap();
private static final Object NO_CLASS = new Object();
private static final Object SCRIPT = new Object();
private SourceUnit source;
private VariableScope currentScope;
private boolean isTopLevelProperty = true;
private boolean inClosure = false;
public ResolveVisitor(CompilationUnit cu) {
compilationUnit = cu;
}
public void startResolving(ClassNode node,SourceUnit source) {
this.source = source;
visitClass(node);
}
public void visitConstructor(ConstructorNode node) {
visitAnnotations(node);
VariableScope oldScope = currentScope;
currentScope = node.getVariableScope();
Parameter[] paras = node.getParameters();
for (int i=0; i<paras.length; i++) {
ClassNode t = paras[i].getType();
resolveOrFail(t,node);
}
ClassNode[] exceptions = node.getExceptions();
for (int i=0; i<exceptions.length; i++) {
ClassNode t = exceptions[i];
resolveOrFail(t,node);
}
Statement code = node.getCode();
if (code!=null) code.visit(this);
currentScope = oldScope;
}
public void visitSwitch(SwitchStatement statement) {
Expression exp = statement.getExpression();
statement.setExpression(transform(exp));
List list = statement.getCaseStatements();
for (Iterator iter = list.iterator(); iter.hasNext(); ) {
CaseStatement caseStatement = (CaseStatement) iter.next();
caseStatement.visit(this);
}
statement.getDefaultStatement().visit(this);
}
public void visitMethod(MethodNode node) {
visitAnnotations(node);
VariableScope oldScope = currentScope;
currentScope = node.getVariableScope();
Parameter[] paras = node.getParameters();
for (int i=0; i<paras.length; i++) {
ClassNode t = paras[i].getType();
resolveOrFail(t,node);
if (paras[i].hasInitialExpression()) {
Expression init = paras[i].getInitialExpression();
paras[i].setInitialExpression(transform(init));
}
}
ClassNode[] exceptions = node.getExceptions();
for (int i=0; i<exceptions.length; i++) {
ClassNode t = exceptions[i];
resolveOrFail(t,node);
}
resolveOrFail(node.getReturnType(),node);
Statement code = node.getCode();
if (code!=null) code.visit(this);
currentScope = oldScope;
}
public void visitField(FieldNode node) {
visitAnnotations(node);
ClassNode t = node.getType();
resolveOrFail(t,node);
Expression init = node.getInitialExpression();
node.setInitialValueExpression(transform(init));
}
public void visitProperty(PropertyNode node) {
visitAnnotations(node);
ClassNode t = node.getType();
resolveOrFail(t,node);
Statement code = node.getGetterBlock();
if (code!=null) code.visit(this);
code = node.getSetterBlock();
if (code!=null) code.visit(this);
}
public void visitIfElse(IfStatement ifElse) {
visitStatement(ifElse);
ifElse.setBooleanExpression((BooleanExpression) (transform(ifElse.getBooleanExpression())));
ifElse.getIfBlock().visit(this);
ifElse.getElseBlock().visit(this);
}
private void resolveOrFail(ClassNode type, String msg, ASTNode node) {
if (resolve(type)) return;
addError("unable to resolve class "+type.getName()+" "+msg,node);
}
private void resolveOrFail(ClassNode type, ASTNode node, boolean prefereImports) {
if (prefereImports && resolveAliasFromModule(type)) return;
resolveOrFail(type,node);
}
private void resolveOrFail(ClassNode type, ASTNode node) {
resolveOrFail(type,"",node);
}
private boolean resolve(ClassNode type) {
String name = type.getName();
return resolve(type,true,true,true);
}
private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
if (type.isResolved()) return true;
if (type.isArray()) {
ClassNode element = type.getComponentType();
boolean resolved = resolve(element,testModuleImports,testDefaultImports,testStaticInnerClasses);
if (resolved) {
ClassNode cn = element.makeArray();
type.setRedirect(cn);
}
return resolved;
}
// test if vanilla name is current class name
if (currentClass==type) return true;
if (currentClass.getNameWithoutPackage().equals(type.getName())) {
type.setRedirect(currentClass);
return true;
}
return resolveFromModule(type,testModuleImports) ||
resolveFromCompileUnit(type) ||
resovleFromDefaultImports(type,testDefaultImports) ||
resolveFromStaticInnerClasses(type,testStaticInnerClasses) ||
resolveFromClassCache(type) ||
resolveToClass(type) ||
resolveToScript(type);
}
private boolean resolveFromClassCache(ClassNode type) {
String name = type.getName();
Object val = cachedClasses.get(name);
if (val==null || val==NO_CLASS){
return false;
} else {
setClass(type,(Class) val);
return true;
}
}
// NOTE: copied from GroovyClassLoader
private long getTimeStamp(Class cls) {
Field field;
Long o;
try {
field = cls.getField(Verifier.__TIMESTAMP);
o = (Long) field.get(null);
} catch (Exception e) {
return Long.MAX_VALUE;
}
return o.longValue();
}
// NOTE: copied from GroovyClassLoader
private boolean isSourceNewer(URL source, Class cls) {
try {
long lastMod;
// Special handling for file:// protocol, as getLastModified() often reports
// incorrect results (-1)
if (source.getProtocol().equals("file")) {
// Coerce the file URL to a File
String path = source.getPath().replace('/', File.separatorChar).replace('|', ':');
File file = new File(path);
lastMod = file.lastModified();
}
else {
lastMod = source.openConnection().getLastModified();
}
return lastMod > getTimeStamp(cls);
} catch (IOException e) {
// if the stream can't be opened, let's keep the old reference
return false;
}
}
private boolean resolveToScript(ClassNode type) {
String name = type.getName();
if (cachedClasses.get(name)==NO_CLASS) return false;
if (cachedClasses.get(name)==SCRIPT) cachedClasses.put(name,NO_CLASS);
if (name.startsWith("java.")) return type.isResolved();
//TODO: don't ignore inner static classes completly
if (name.indexOf('$')!=-1) return type.isResolved();
ModuleNode module = currentClass.getModule();
if (module.hasPackageName() && name.indexOf('.')==-1) return type.isResolved();
// try to find a script from classpath
GroovyClassLoader gcl = compilationUnit.getClassLoader();
URL url = null;
try {
url = gcl.getResourceLoader().loadGroovySource(name);
} catch (MalformedURLException e) {
// fall through and let the URL be null
}
if (url !=null) {
if (type.isResolved()) {
Class cls = type.getTypeClass();
// if the file is not newer we don't want to recompile
if (!isSourceNewer(url,cls)) return true;
cachedClasses.remove(type.getName());
type.setRedirect(null);
}
SourceUnit su = compilationUnit.addSource(url);
currentClass.getCompileUnit().addClassNodeToCompile(type,su);
return true;
}
// type may be resolved through the classloader before
return type.isResolved();
}
private boolean resolveFromStaticInnerClasses(ClassNode type, boolean testStaticInnerClasses) {
// try to resolve a public static inner class' name
testStaticInnerClasses &= type.hasPackageName();
if (testStaticInnerClasses) {
String name = type.getName();
String replacedPointType = name;
int lastPoint = replacedPointType.lastIndexOf('.');
replacedPointType = new StringBuffer()
.append(replacedPointType.substring(0, lastPoint))
.append("$")
.append(replacedPointType.substring(lastPoint + 1))
.toString();
type.setName(replacedPointType);
if (resolve(type,false,false,true)) return true;
type.setName(name);
}
return false;
}
private boolean resovleFromDefaultImports(ClassNode type, boolean testDefaultImports) {
// test default imports
testDefaultImports &= !type.hasPackageName();
if (testDefaultImports) {
for (int i = 0, size = DEFAULT_IMPORTS.length; i < size; i++) {
String packagePrefix = DEFAULT_IMPORTS[i];
String name = type.getName();
String fqn = packagePrefix+name;
type.setName(fqn);
if (resolve(type,false,false,false)) return true;
type.setName(name);
}
String name = type.getName();
if (name.equals("BigInteger")) {
type.setRedirect(ClassHelper.BigInteger_TYPE);
return true;
} else if (name.equals("BigDecimal")) {
type.setRedirect(ClassHelper.BigDecimal_TYPE);
return true;
}
}
return false;
}
private boolean resolveFromCompileUnit(ClassNode type) {
// look into the compile unit if there is a class with that name
CompileUnit compileUnit = currentClass.getCompileUnit();
if (compileUnit == null) return false;
ClassNode cuClass = compileUnit.getClass(type.getName());
if (cuClass!=null) {
if (type!=cuClass) type.setRedirect(cuClass);
return true;
}
return false;
}
private void setClass(ClassNode n, Class cls) {
ClassNode cn = ClassHelper.make(cls);
n.setRedirect(cn);
}
private void ambigousClass(ClassNode type, ClassNode iType, String name, boolean resolved){
if (resolved && !type.getName().equals(iType.getName())) {
addError("reference to "+name+" is ambigous, both class "+type.getName()+" and "+iType.getName()+" match",type);
} else {
type.setRedirect(iType);
}
}
private boolean resolveAliasFromModule(ClassNode type) {
ModuleNode module = currentClass.getModule();
if (module==null) return false;
String name = type.getName();
// check module node imports aliases
// the while loop enables a check for inner classes which are not fully imported,
// but visible as the surrounding class is imported and the inner class is public/protected static
String pname = name;
int index = name.length();
/*
* we have a name foo.bar and an import foo.foo. This means foo.bar is possibly
* foo.foo.bar rather than foo.bar. This means to cut at the dot in foo.bar and
* foo for import
*/
while (true) {
pname = name.substring(0,index);
ClassNode aliasedNode = module.getImport(pname);
if (aliasedNode!=null) {
if (pname.length()==name.length()){
// full match, no need to create a new class
type.setRedirect(aliasedNode);
return true;
} else {
//partial match
String newName = aliasedNode.getName()+name.substring(pname.length());
type.setName(newName);
if (resolve(type,true,true,true)) return true;
// was not resolved soit was a fake match
type.setName(name);
}
}
index = pname.lastIndexOf('.');
if (index==-1) break;
}
return false;
}
private boolean resolveFromModule(ClassNode type, boolean testModuleImports) {
ModuleNode module = currentClass.getModule();
if (module==null) return false;
String name = type.getName();
if (!type.hasPackageName() && module.hasPackageName()){
type.setName(module.getPackageName()+name);
}
// look into the module node if there is a class with that name
List moduleClasses = module.getClasses();
for (Iterator iter = moduleClasses.iterator(); iter.hasNext();) {
ClassNode mClass = (ClassNode) iter.next();
if (mClass.getName().equals(type.getName())){
if (mClass!=type) type.setRedirect(mClass);
return true;
}
}
type.setName(name);
if (testModuleImports) {
if (resolveAliasFromModule(type)) return true;
boolean resolved = false;
if (module.hasPackageName()) {
// check package this class is defined in
type.setName(module.getPackageName()+name);
resolved = resolve(type,false,false,false);
}
// check module node imports packages
List packages = module.getImportPackages();
ClassNode iType = ClassHelper.makeWithoutCaching(name);
for (Iterator iter = packages.iterator(); iter.hasNext();) {
String packagePrefix = (String) iter.next();
String fqn = packagePrefix+name;
iType.setName(fqn);
if (resolve(iType,false,false,true)) {
ambigousClass(type,iType,name,resolved);
return true;
}
iType.setName(name);
}
if (!resolved) type.setName(name);
return resolved;
}
return false;
}
private boolean resolveToClass(ClassNode type) {
String name = type.getName();
if (cachedClasses.get(name)==NO_CLASS) return false;
if (currentClass.getModule().hasPackageName() && name.indexOf('.')==-1) return false;
GroovyClassLoader loader = compilationUnit.getClassLoader();
Class cls = null;
try {
// NOTE: it's important to do no lookup against script files
// here since the GroovyClassLoader would create a new
// CompilationUnit
cls = loader.loadClass(name,false,true);
} catch (ClassNotFoundException cnfe) {
cachedClasses.put(name,SCRIPT);
return false;
} catch (CompilationFailedException cfe) {
compilationUnit.getErrorCollector().addErrorAndContinue(new ExceptionMessage(cfe,true,source));
return false;
}
//TODO: the case of a NoClassDefFoundError needs a bit more research
// a simple recompilation is not possible it seems. The current class
// we are searching for is there, so we should mark that somehow.
// Basically the missing class needs to be completly compiled before
// we can again search for the current name.
/*catch (NoClassDefFoundError ncdfe) {
cachedClasses.put(name,SCRIPT);
return false;
}*/
if (cls==null) return false;
cachedClasses.put(name,cls);
setClass(type,cls);
//NOTE: we return false here even if we found a class,
//but we want to give a possible script a chance to recompile.
//this can only be done if the loader was not the instance
//defining the class.
return cls.getClassLoader()==loader;
}
public Expression transform(Expression exp) {
if (exp==null) return null;
if (exp instanceof VariableExpression) {
return transformVariableExpression((VariableExpression) exp);
} else if (exp.getClass()==PropertyExpression.class) {
return transformPropertyExpression((PropertyExpression) exp);
} else if (exp instanceof DeclarationExpression) {
return transformDeclarationExpression((DeclarationExpression)exp);
} else if (exp instanceof BinaryExpression) {
return transformBinaryExpression((BinaryExpression)exp);
} else if (exp instanceof MethodCallExpression) {
return transformMethodCallExpression((MethodCallExpression)exp);
} else if (exp instanceof ClosureExpression) {
return transformClosureExpression((ClosureExpression) exp);
} else if (exp instanceof ConstructorCallExpression) {
return transformConstructorCallExpression((ConstructorCallExpression) exp);
} else {
resolveOrFail(exp.getType(),exp);
return exp.transformExpression(this);
}
}
private String lookupClassName(PropertyExpression pe) {
String name = "";
for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
if (it instanceof VariableExpression) {
VariableExpression ve = (VariableExpression) it;
// stop at super and this
if (ve==VariableExpression.SUPER_EXPRESSION || ve==VariableExpression.THIS_EXPRESSION) {
return null;
}
name= ve.getName()+"."+name;
break;
}
// anything other than PropertyExpressions, ClassExpression or
// VariableExpressions will stop resolving
else if (!(it.getClass()==PropertyExpression.class)) {
return null;
} else {
PropertyExpression current = (PropertyExpression) it;
String propertyPart = current.getPropertyAsString();
// the class property stops resolving, dynamic property names too
if (propertyPart==null || propertyPart.equals("class")) {
return null;
}
name = propertyPart+"."+name;
}
}
if (name.length()>0) return name.substring(0,name.length()-1);
return null;
}
// iterate from the inner most to the outer and check for classes
// this check will ignore a .class property, for Exmaple Integer.class will be
// a PropertyExpression with the ClassExpression of Integer as objectExpression
// and class as property
private Expression correctClassClassChain(PropertyExpression pe){
LinkedList stack = new LinkedList();
ClassExpression found = null;
for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
if (it instanceof ClassExpression) {
found = (ClassExpression) it;
break;
} else if (! (it.getClass()==PropertyExpression.class)) {
return pe;
}
stack.addFirst(it);
}
if (found==null) return pe;
if (stack.isEmpty()) return pe;
Object stackElement = stack.removeFirst();
if (!(stackElement.getClass()==PropertyExpression.class)) return pe;
PropertyExpression classPropertyExpression = (PropertyExpression) stackElement;
String propertyNamePart = classPropertyExpression.getPropertyAsString();
if (propertyNamePart==null || ! propertyNamePart.equals("class")) return pe;
if (stack.isEmpty()) return found;
stackElement = stack.removeFirst();
if (!(stackElement.getClass()==PropertyExpression.class)) return pe;
PropertyExpression classPropertyExpressionContainer = (PropertyExpression) stackElement;
classPropertyExpressionContainer.setObjectExpression(found);
return pe;
}
protected Expression transformPropertyExpression(PropertyExpression pe) {
boolean itlp = isTopLevelProperty;
Expression objectExpression = pe.getObjectExpression();
isTopLevelProperty = !(objectExpression.getClass()==PropertyExpression.class);
objectExpression = transform(objectExpression);
Expression property = transform(pe.getProperty());
isTopLevelProperty = itlp;
boolean spreadSafe = pe.isSpreadSafe();
pe = new PropertyExpression(objectExpression,property,pe.isSafe());
pe.setSpreadSafe(spreadSafe);
String className = lookupClassName(pe);
if (className!=null) {
ClassNode type = ClassHelper.make(className);
if (resolve(type)) return new ClassExpression(type);
}
if (objectExpression instanceof ClassExpression && pe.getPropertyAsString()!=null){
// possibly a inner class
ClassExpression ce = (ClassExpression) objectExpression;
ClassNode type = ClassHelper.make(ce.getType().getName()+"$"+pe.getPropertyAsString());
if (resolve(type,false,false,false)) return new ClassExpression(type);
}
if (isTopLevelProperty) return correctClassClassChain(pe);
return pe;
}
protected Expression transformVariableExpression(VariableExpression ve) {
if (ve.getName().equals("this")) return VariableExpression.THIS_EXPRESSION;
if (ve.getName().equals("super")) return VariableExpression.SUPER_EXPRESSION;
Variable v = ve.getAccessedVariable();
if (v instanceof DynamicVariable) {
ClassNode t = ClassHelper.make(ve.getName());
if (resolve(t)) {
// the name is a type so remove it from the scoping
// as it is only a classvariable, it is only in
// referencedClassVariables, but must be removed
// for each parentscope too
for (VariableScope scope = currentScope; scope!=null && !scope.isRoot(); scope = scope.getParent()) {
if (scope.isRoot()) break;
if (scope.getReferencedClassVariables().remove(ve.getName())==null) break;
}
ClassExpression ce = new ClassExpression(t);
ce.setSourcePosition(ve);
return ce;
} else if (!inClosure && ve.isInStaticContext()) {
addError("the name "+v.getName()+" doesn't refer to a declared variable or class. The static"+
" scope requires to declare variables before using them. If the variable should have"+
" been a class check the spelling.",ve);
}
}
resolveOrFail(ve.getType(),ve);
return ve;
}
protected Expression transformBinaryExpression(BinaryExpression be) {
Expression left = transform(be.getLeftExpression());
if (be.getOperation().getType()==Types.ASSIGNMENT_OPERATOR && left instanceof ClassExpression){
ClassExpression ce = (ClassExpression) left;
addError("you tried to assign a value to "+ce.getType().getName(),be.getLeftExpression());
return be;
}
if (left instanceof ClassExpression && be.getRightExpression() instanceof ListExpression) {
// we have C[] if the list is empty -> should be an array then!
ListExpression list = (ListExpression) be.getRightExpression();
ClassExpression ce = (ClassExpression) left;
if (list.getExpressions().isEmpty()) {
return new ClassExpression(left.getType().makeArray());
}
}
Expression right = transform(be.getRightExpression());
Expression ret = new BinaryExpression(left,be.getOperation(),right);
ret.setSourcePosition(be);
return ret;
}
protected Expression transformClosureExpression(ClosureExpression ce) {
boolean oldInClosure = inClosure;
inClosure = true;
Parameter[] paras = ce.getParameters();
if (paras!=null) {
for (int i=0; i<paras.length; i++) {
ClassNode t = paras[i].getType();
resolveOrFail(t,ce);
}
}
Statement code = ce.getCode();
if (code!=null) code.visit(this);
ClosureExpression newCe= new ClosureExpression(paras,code);
newCe.setVariableScope(ce.getVariableScope());
newCe.setSourcePosition(ce);
inClosure = oldInClosure;
return newCe;
}
protected Expression transformConstructorCallExpression(ConstructorCallExpression cce){
ClassNode type = cce.getType();
resolveOrFail(type,cce);
Expression expr = cce.transformExpression(this);
return expr;
}
protected Expression transformMethodCallExpression(MethodCallExpression mce) {
Expression obj = mce.getObjectExpression();
Expression newObject = transform(obj);
Expression args = transform(mce.getArguments());
Expression method = transform(mce.getMethod());
MethodCallExpression ret = new MethodCallExpression(newObject,method,args);
ret.setSafe(mce.isSafe());
ret.setImplicitThis(mce.isImplicitThis());
ret.setSpreadSafe(mce.isSpreadSafe());
ret.setSourcePosition(mce);
return ret;
}
protected Expression transformDeclarationExpression(DeclarationExpression de) {
Expression oldLeft = de.getLeftExpression();
Expression left = transform(oldLeft);
if (left!=oldLeft){
ClassExpression ce = (ClassExpression) left;
addError("you tried to assign a value to "+ce.getType().getName(),oldLeft);
return de;
}
Expression right = transform(de.getRightExpression());
if (right==de.getRightExpression()) return de;
return new DeclarationExpression((VariableExpression) left,de.getOperation(),right);
}
public void visitAnnotations(AnnotatedNode node) {
Map annotionMap = node.getAnnotations();
if (annotionMap.isEmpty()) return;
Iterator it = annotionMap.values().iterator();
while (it.hasNext()) {
AnnotationNode an = (AnnotationNode) it.next();
//skip builtin properties
if (an.isBuiltIn()) continue;
ClassNode type = an.getClassNode();
resolveOrFail(type,"unable to find class for annotation",an);
}
}
public void visitClass(ClassNode node) {
ClassNode oldNode = currentClass;
currentClass = node;
ModuleNode module = node.getModule();
if (!module.hasImportsResolved()) {
List l = module.getImports();
for (Iterator iter = l.iterator(); iter.hasNext();) {
ImportNode element = (ImportNode) iter.next();
ClassNode type = element.getType();
if (resolve(type,false,false,false)) continue;
addError("unable to resolve class "+type.getName(),type);
}
module.setImportsResolved(true);
}
ClassNode sn = node.getUnresolvedSuperClass();
if (sn!=null) resolveOrFail(sn,node,true);
ClassNode[] interfaces = node.getInterfaces();
for (int i=0; i<interfaces.length; i++) {
resolveOrFail(interfaces[i],node,true);
}
super.visitClass(node);
currentClass = oldNode;
}
public void visitReturnStatement(ReturnStatement statement) {
statement.setExpression(transform(statement.getExpression()));
}
public void visitAssertStatement(AssertStatement as) {
as.setBooleanExpression((BooleanExpression) (transform(as.getBooleanExpression())));
as.setMessageExpression(transform(as.getMessageExpression()));
}
public void visitCaseStatement(CaseStatement statement) {
statement.setExpression(transform(statement.getExpression()));
statement.getCode().visit(this);
}
public void visitCatchStatement(CatchStatement cs) {
resolveOrFail(cs.getExceptionType(),cs);
if (cs.getExceptionType()==ClassHelper.DYNAMIC_TYPE) {
cs.getVariable().setType(ClassHelper.make(Exception.class));
}
super.visitCatchStatement(cs);
}
public void visitDoWhileLoop(DoWhileStatement loop) {
loop.setBooleanExpression((BooleanExpression) (transform(loop.getBooleanExpression())));
super.visitDoWhileLoop(loop);
}
public void visitForLoop(ForStatement forLoop) {
forLoop.setCollectionExpression(transform(forLoop.getCollectionExpression()));
resolveOrFail(forLoop.getVariableType(),forLoop);
super.visitForLoop(forLoop);
}
public void visitSynchronizedStatement(SynchronizedStatement sync) {
sync.setExpression(transform(sync.getExpression()));
super.visitSynchronizedStatement(sync);
}
public void visitThrowStatement(ThrowStatement ts) {
ts.setExpression(transform(ts.getExpression()));
}
public void visitWhileLoop(WhileStatement loop) {
loop.setBooleanExpression((BooleanExpression) transform(loop.getBooleanExpression()));
super.visitWhileLoop(loop);
}
public void visitExpressionStatement(ExpressionStatement es) {
es.setExpression(transform(es.getExpression()));
}
public void visitBlockStatement(BlockStatement block) {
VariableScope oldScope = currentScope;
currentScope = block.getVariableScope();
super.visitBlockStatement(block);
currentScope = oldScope;
}
protected SourceUnit getSourceUnit() {
return source;
}
}