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