1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.ClassHelper;
53 import org.codehaus.groovy.ast.ClassNode;
54 import org.codehaus.groovy.ast.CodeVisitorSupport;
55 import org.codehaus.groovy.ast.CompileUnit;
56 import org.codehaus.groovy.ast.ConstructorNode;
57 import org.codehaus.groovy.ast.FieldNode;
58 import org.codehaus.groovy.ast.MethodNode;
59 import org.codehaus.groovy.ast.ModuleNode;
60 import org.codehaus.groovy.ast.Parameter;
61 import org.codehaus.groovy.ast.PropertyNode;
62 import org.codehaus.groovy.ast.expr.BinaryExpression;
63 import org.codehaus.groovy.ast.expr.BooleanExpression;
64 import org.codehaus.groovy.ast.expr.ClassExpression;
65 import org.codehaus.groovy.ast.expr.ClosureExpression;
66 import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
67 import org.codehaus.groovy.ast.expr.DeclarationExpression;
68 import org.codehaus.groovy.ast.expr.Expression;
69 import org.codehaus.groovy.ast.expr.ExpressionTransformer;
70 import org.codehaus.groovy.ast.expr.MethodCallExpression;
71 import org.codehaus.groovy.ast.expr.PropertyExpression;
72 import org.codehaus.groovy.ast.expr.VariableExpression;
73 import org.codehaus.groovy.ast.stmt.AssertStatement;
74 import org.codehaus.groovy.ast.stmt.CaseStatement;
75 import org.codehaus.groovy.ast.stmt.CatchStatement;
76 import org.codehaus.groovy.ast.stmt.DoWhileStatement;
77 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
78 import org.codehaus.groovy.ast.stmt.ForStatement;
79 import org.codehaus.groovy.ast.stmt.IfStatement;
80 import org.codehaus.groovy.ast.stmt.ReturnStatement;
81 import org.codehaus.groovy.ast.stmt.Statement;
82 import org.codehaus.groovy.ast.stmt.SwitchStatement;
83 import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
84 import org.codehaus.groovy.ast.stmt.ThrowStatement;
85 import org.codehaus.groovy.ast.stmt.WhileStatement;
86 import org.codehaus.groovy.ast.GroovyClassVisitor;
87 import org.codehaus.groovy.classgen.Verifier;
88 import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
89 import org.codehaus.groovy.syntax.SyntaxException;
90 import org.codehaus.groovy.syntax.Types;
91
92 /***
93 * Visitor to resolve Types and convert VariableExpression to
94 * ClassExpressions if needed.
95 *
96 * Note: the method to start the resolving is @see ResolveVisitor#startResolving(ClassNode, SourceUnit).
97 *
98 *
99 * @author Jochen Theodorou
100 */
101 public class ResolveVisitor extends CodeVisitorSupport implements ExpressionTransformer, GroovyClassVisitor {
102 private ClassNode currentClass;
103 private static final String[] DEFAULT_IMPORTS = {"java.lang.", "java.io.", "java.net.", "java.util.", "groovy.lang.", "groovy.util."};
104 private CompilationUnit compilationUnit;
105 private Map cachedClasses = new HashMap();
106 private static final Object NO_CLASS = new Object();
107 private SourceUnit source;
108
109 private boolean isTopLevelProperty = true;
110
111 public ResolveVisitor(CompilationUnit cu) {
112 compilationUnit = cu;
113 }
114
115 public void startResolving(ClassNode node,SourceUnit source) {
116 this.source = source;
117 visitClass(node);
118 }
119
120 public void visitConstructor(ConstructorNode node) {
121 Parameter[] paras = node.getParameters();
122 for (int i=0; i<paras.length; i++) {
123 ClassNode t = paras[i].getType();
124 resolveOrFail(t,node);
125 }
126 Statement code = node.getCode();
127 if (code!=null) code.visit(this);
128 }
129
130 public void visitSwitch(SwitchStatement statement) {
131 Expression exp = statement.getExpression();
132 statement.setExpression(transform(exp));
133 List list = statement.getCaseStatements();
134 for (Iterator iter = list.iterator(); iter.hasNext(); ) {
135 CaseStatement caseStatement = (CaseStatement) iter.next();
136 caseStatement.visit(this);
137 }
138 statement.getDefaultStatement().visit(this);
139 }
140
141 public void visitMethod(MethodNode node) {
142 Parameter[] paras = node.getParameters();
143 for (int i=0; i<paras.length; i++) {
144 ClassNode t = paras[i].getType();
145 resolveOrFail(t,node);
146 }
147 resolveOrFail(node.getReturnType(),node);
148 Statement code = node.getCode();
149 if (code!=null) code.visit(this);
150 }
151
152 public void visitField(FieldNode node) {
153 ClassNode t = node.getType();
154 resolveOrFail(t,node);
155 Expression init = node.getInitialExpression();
156 node.setInitialValueExpression(transform(init));
157 }
158
159 public void visitProperty(PropertyNode node) {
160 ClassNode t = node.getType();
161 resolveOrFail(t,node);
162 Statement code = node.getGetterBlock();
163 if (code!=null) code.visit(this);
164 code = node.getSetterBlock();
165 if (code!=null) code.visit(this);
166 }
167
168 public void visitIfElse(IfStatement ifElse) {
169 ifElse.setBooleanExpression((BooleanExpression) (transform(ifElse.getBooleanExpression())));
170 super.visitIfElse(ifElse);
171 }
172
173 private void resolveOrFail(ClassNode type, String msg, ASTNode node) {
174 if (resolve(type)) return;
175 addError("unable to resolve class "+type.getName()+" "+msg,node);
176 }
177
178 private void resolveOrFail(ClassNode type, ASTNode node) {
179 resolveOrFail(type,"",node);
180 }
181
182 private boolean resolve(ClassNode type) {
183 String name = type.getName();
184 return resolve(type,true,true,true);
185 }
186
187 private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
188 if (type.isResolved()) return true;
189 if (type.isArray()) {
190 ClassNode element = type.getComponentType();
191 boolean resolved = resolve(element,testModuleImports,testDefaultImports,testStaticInnerClasses);
192 if (resolved) {
193 ClassNode cn = element.makeArray();
194 type.setRedirect(cn);
195 }
196 return resolved;
197 }
198
199
200 if (currentClass==type) return true;
201 if (currentClass.getNameWithoutPackage().equals(type.getName())) {
202 type.setRedirect(currentClass);
203 return true;
204 }
205
206 return resolveFromModule(type,testModuleImports) ||
207 resolveFromCompileUnit(type) ||
208 resovleFromDefaultImports(type,testDefaultImports) ||
209 resolveFromStaticInnerClasses(type,testStaticInnerClasses) ||
210 resolveFromClassCache(type) ||
211 resolveToClass(type) ||
212 resolveToScript(type);
213
214 }
215
216 private boolean resolveFromClassCache(ClassNode type) {
217 String name = type.getName();
218 Object val = cachedClasses.get(name);
219 if (val==null || val==NO_CLASS){
220 return false;
221 } else {
222 setClass(type,(Class) val);
223 return true;
224 }
225 }
226
227
228 private long getTimeStamp(Class cls) {
229 Field field;
230 Long o;
231 try {
232 field = cls.getField(Verifier.__TIMESTAMP);
233 o = (Long) field.get(null);
234 } catch (Exception e) {
235 return Long.MAX_VALUE;
236 }
237 return o.longValue();
238 }
239
240
241 private boolean isSourceNewer(URL source, Class cls) {
242 try {
243 long lastMod;
244
245
246
247 if (source.getProtocol().equals("file")) {
248
249 String path = source.getPath().replace('/', File.separatorChar).replace('|', ':');
250 File file = new File(path);
251 lastMod = file.lastModified();
252 }
253 else {
254 lastMod = source.openConnection().getLastModified();
255 }
256 return lastMod > getTimeStamp(cls);
257 } catch (IOException e) {
258
259 return false;
260 }
261 }
262
263
264 private boolean resolveToScript(ClassNode type) {
265 String name = type.getName();
266 if (cachedClasses.get(name)==NO_CLASS) return false;
267 if (name.startsWith("java.")) return type.isResolved();
268
269 if (name.indexOf('$')!=-1) return type.isResolved();
270 ModuleNode module = currentClass.getModule();
271 if (module.hasPackageName() && name.indexOf('.')==-1) return type.isResolved();
272
273 GroovyClassLoader gcl = compilationUnit.getClassLoader();
274 URL url = null;
275 try {
276 url = gcl.getResourceLoader().loadGroovySource(name);
277 } catch (MalformedURLException e) {
278
279 }
280 if (url !=null) {
281 if (type.isResolved()) {
282 Class cls = type.getTypeClass();
283
284 if (!isSourceNewer(url,cls)) return true;
285 cachedClasses.remove(type.getName());
286 type.setRedirect(null);
287 }
288 compilationUnit.addSource(url);
289 currentClass.getCompileUnit().addClassNodeToCompile(type);
290 return true;
291 }
292
293 return type.isResolved();
294 }
295
296
297 private boolean resolveFromStaticInnerClasses(ClassNode type, boolean testStaticInnerClasses) {
298
299 testStaticInnerClasses &= type.hasPackageName();
300 if (testStaticInnerClasses) {
301 String name = type.getName();
302 String replacedPointType = name;
303 int lastPoint = replacedPointType.lastIndexOf('.');
304 replacedPointType = new StringBuffer()
305 .append(replacedPointType.substring(0, lastPoint))
306 .append("$")
307 .append(replacedPointType.substring(lastPoint + 1))
308 .toString();
309 type.setName(replacedPointType);
310 if (resolve(type,false,false,true)) return true;
311 type.setName(name);
312 }
313 return false;
314 }
315
316 private boolean resovleFromDefaultImports(ClassNode type, boolean testDefaultImports) {
317
318 testDefaultImports &= !type.hasPackageName();
319 if (testDefaultImports) {
320 for (int i = 0, size = DEFAULT_IMPORTS.length; i < size; i++) {
321 String packagePrefix = DEFAULT_IMPORTS[i];
322 String name = type.getName();
323 String fqn = packagePrefix+name;
324 type.setName(fqn);
325 if (resolve(type,false,false,false)) return true;
326 type.setName(name);
327 }
328 }
329 return false;
330 }
331
332 private boolean resolveFromCompileUnit(ClassNode type) {
333
334 CompileUnit compileUnit = currentClass.getCompileUnit();
335 if (compileUnit == null) return false;
336 ClassNode cuClass = compileUnit.getClass(type.getName());
337 if (cuClass!=null) {
338 if (type!=cuClass) type.setRedirect(cuClass);
339 return true;
340 }
341 return false;
342 }
343
344
345 private void setClass(ClassNode n, Class cls) {
346 ClassNode cn = ClassHelper.make(cls);
347 n.setRedirect(cn);
348 }
349
350 private void ambigousClass(ClassNode type, ClassNode iType, String name, boolean resolved){
351 if (resolved && !type.getName().equals(iType.getName())) {
352 addError("reference to "+name+" is ambigous, both class "+type.getName()+" and "+iType.getName()+" match",type);
353 } else {
354 type.setRedirect(iType);
355 }
356 }
357
358 private boolean resolveFromModule(ClassNode type, boolean testModuleImports) {
359 ModuleNode module = currentClass.getModule();
360 if (module==null) return false;
361
362 String name = type.getName();
363
364 if (!type.hasPackageName() && module.hasPackageName()){
365 type.setName(module.getPackageName()+name);
366 }
367
368 List moduleClasses = module.getClasses();
369 for (Iterator iter = moduleClasses.iterator(); iter.hasNext();) {
370 ClassNode mClass = (ClassNode) iter.next();
371 if (mClass.getName().equals(type.getName())){
372 if (mClass!=type) type.setRedirect(mClass);
373 return true;
374 }
375 }
376 type.setName(name);
377
378 {
379
380
381
382 String pname = name;
383 int index = name.length();
384
385
386
387
388
389
390 while (true) {
391 pname = name.substring(0,index);
392 String aliased = module.getImport(pname);
393 if (aliased!=null && !aliased.equals(name)) {
394 if (pname.length()<name.length()){
395 aliased += name.substring(pname.length());
396 }
397 type.setName(aliased);
398 if (resolve(type,true,true,true)) return true;
399 type.setName(name);
400 }
401 index = pname.lastIndexOf('.');
402 if (index==-1) break;
403 }
404 }
405
406
407 if (testModuleImports) {
408 String packageName = "";
409 >if (module.hasPackageName()) packageName = module.getPackageName();
410
411 type.setName(packageName+name);
412 boolean resolved = resolve(type,false,false,false);
413
414
415 List packages = module.getImportPackages();
416 ClassNode iType = ClassHelper.makeWithoutCaching(name);
417 for (Iterator iter = packages.iterator(); iter.hasNext();) {
418 String packagePrefix = (String) iter.next();
419 String fqn = packagePrefix+name;
420 iType.setName(fqn);
421 if (resolve(iType,false,false,true)) {
422 ambigousClass(type,iType,name,resolved);
423 return true;
424 }
425 iType.setName(name);
426 }
427 if (!resolved) type.setName(name);
428 return resolved;
429 }
430 return false;
431 }
432
433 private boolean resolveToClass(ClassNode type) {
434 String name = type.getName();
435 if (cachedClasses.get(name)==NO_CLASS) return false;
436 if (currentClass.getModule().hasPackageName() && name.indexOf('.')==-1) return false;
437 GroovyClassLoader loader = compilationUnit.getClassLoader();
438 Class cls = null;
439 try {
440
441
442
443 cls = loader.loadClass(name,false,true);
444 } catch (ClassNotFoundException cnfe) {
445 cachedClasses.put(name,NO_CLASS);
446 return false;
447 } catch (NoClassDefFoundError ncdfe) {
448 cachedClasses.put(name,NO_CLASS);
449 return false;
450 }
451 if (cls==null) return false;
452 cachedClasses.put(name,cls);
453 setClass(type,cls);
454
455
456
457
458 return cls.getClassLoader()==loader;
459 }
460
461
462
463 public Expression transform(Expression exp) {
464 if (exp==null) return null;
465 if (exp instanceof VariableExpression) {
466 return transformVariableExpression((VariableExpression) exp);
467 } else if (exp instanceof PropertyExpression) {
468 return transformPropertyExpression((PropertyExpression) exp);
469 } else if (exp instanceof DeclarationExpression) {
470 return transformDeclarationExpression((DeclarationExpression)exp);
471 } else if (exp instanceof BinaryExpression) {
472 return transformBinaryExpression((BinaryExpression)exp);
473 } else if (exp instanceof MethodCallExpression) {
474 return transformMethodCallExpression((MethodCallExpression)exp);
475 } else if (exp instanceof ClosureExpression) {
476 return transformClosureExpression((ClosureExpression) exp);
477 } else if (exp instanceof ConstructorCallExpression) {
478 return transformConstructorCallExpression((ConstructorCallExpression) exp);
479 } else {
480 resolveOrFail(exp.getType(),exp);
481 return exp.transformExpression(this);
482 }
483 }
484
485
486 private String lookupClassName(PropertyExpression pe) {
487 String name = "";
488 for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
489 if (it instanceof VariableExpression) {
490 VariableExpression ve = (VariableExpression) it;
491
492 if (ve==VariableExpression.SUPER_EXPRESSION || ve==VariableExpression.THIS_EXPRESSION) {
493 return null;
494 }
495 name= ve.getName()+"."+name;
496 break;
497 }
498
499 else if (!(it instanceof PropertyExpression)) {
500 return null;
501 } else {
502 PropertyExpression current = (PropertyExpression) it;
503 String propertyPart = current.getProperty();
504
505 if (propertyPart.equals("class")) {
506 return null;
507 }
508 name = propertyPart+"."+name;
509 }
510 }
511 if (name.length()>0) return name.substring(0,name.length()-1);
512 return null;
513 }
514
515
516
517
518
519 private Expression correctClassClassChain(PropertyExpression pe){
520 LinkedList stack = new LinkedList();
521 ClassExpression found = null;
522 for (Expression it = pe; it!=null; it = ((PropertyExpression)it).getObjectExpression()) {
523 if (it instanceof ClassExpression) {
524 found = (ClassExpression) it;
525 break;
526 } else if (! (it instanceof PropertyExpression)) {
527 return pe;
528 }
529 stack.addFirst(it);
530 }
531 if (found==null) return pe;
532
533 if (stack.isEmpty()) return pe;
534 Object stackElement = stack.removeFirst();
535 if (!(stackElement instanceof PropertyExpression)) return pe;
536 PropertyExpression classPropertyExpression = (PropertyExpression) stackElement;
537 if (! classPropertyExpression.getProperty().equals("class")) return pe;
538
539 if (stack.isEmpty()) return found;
540 stackElement = stack.removeFirst();
541 if (!(stackElement instanceof PropertyExpression)) return pe;
542 PropertyExpression classPropertyExpressionContainer = (PropertyExpression) stackElement;
543
544 classPropertyExpressionContainer.setObjectExpression(found);
545 return pe;
546 }
547
548 protected Expression transformPropertyExpression(PropertyExpression pe) {
549 boolean itlp = isTopLevelProperty;
550
551 Expression objectExpression = pe.getObjectExpression();
552 isTopLevelProperty = !(objectExpression instanceof PropertyExpression);
553 objectExpression = transform(objectExpression);
554 isTopLevelProperty = itlp;
555
556 pe.setObjectExpression(objectExpression);
557
558 String className = lookupClassName(pe);
559 if (className!=null) {
560 ClassNode type = ClassHelper.make(className);
561 if (resolve(type)) return new ClassExpression(type);
562 }
563
564 if (isTopLevelProperty) return correctClassClassChain(pe);
565
566 return pe;
567 }
568
569 protected Expression transformVariableExpression(VariableExpression ve) {
570 if (ve.getName().equals("this")) return VariableExpression.THIS_EXPRESSION;
571 if (ve.getName().equals("super")) return VariableExpression.SUPER_EXPRESSION;
572 ClassNode t = ClassHelper.make(ve.getName());
573 if (resolve(t)) return new ClassExpression(t);
574 resolveOrFail(ve.getType(),ve);
575 return ve;
576 }
577
578 protected Expression transformBinaryExpression(BinaryExpression be) {
579 Expression left = transform(be.getLeftExpression());
580 if (be.getOperation().getType()==Types.ASSIGNMENT_OPERATOR && left instanceof ClassExpression){
581 ClassExpression ce = (ClassExpression) left;
582 addError("you tried to assign a value to "+ce.getType().getName(),be.getLeftExpression());
583 return be;
584 }
585 Expression right = transform(be.getRightExpression());
586 return new BinaryExpression(left,be.getOperation(),right);
587 }
588
589 protected Expression transformClosureExpression(ClosureExpression ce) {
590 Parameter[] paras = ce.getParameters();
591 for (int i=0; i<paras.length; i++) {
592 ClassNode t = paras[i].getType();
593 resolveOrFail(t,ce);
594 }
595 Statement code = ce.getCode();
596 if (code!=null) code.visit(this);
597 return new ClosureExpression(paras,code);
598 }
599
600 protected Expression transformConstructorCallExpression(ConstructorCallExpression cce){
601 ClassNode type = cce.getType();
602 resolveOrFail(type,cce);
603 Expression args = cce.getArguments();
604 args = transform(args);
605 return new ConstructorCallExpression(type,args);
606 }
607
608 protected Expression transformMethodCallExpression(MethodCallExpression mce) {
609 Expression obj = mce.getObjectExpression();
610 Expression newObject = transform(obj);
611 Expression args = transform(mce.getArguments());
612
613
614
615
616
617
618 MethodCallExpression ret = new MethodCallExpression(newObject,mce.getMethod(),args);
619 ret.setSafe(mce.isSafe());
620 ret.setImplicitThis(mce.isImplicitThis());
621 ret.setSpreadSafe(mce.isSpreadSafe());
622 return ret;
623 }
624
625 protected Expression transformDeclarationExpression(DeclarationExpression de) {
626 Expression oldLeft = de.getLeftExpression();
627 Expression left = transform(oldLeft);
628 if (left!=oldLeft){
629 ClassExpression ce = (ClassExpression) left;
630 addError("you tried to assign a value to "+ce.getType().getName(),oldLeft);
631 return de;
632 }
633 Expression right = transform(de.getRightExpression());
634 if (right==de.getRightExpression()) return de;
635 return new DeclarationExpression((VariableExpression) left,de.getOperation(),right);
636 }
637
638 public void visitAnnotations(AnnotatedNode node) {
639 Map annotionMap = node.getAnnotations();
640 if (annotionMap.isEmpty()) return;
641 Iterator it = annotionMap.values().iterator();
642 while (it.hasNext()) {
643 AnnotationNode an = (AnnotationNode) it.next();
644
645 if (an.isBuiltIn()) continue;
646 ClassNode type = an.getClassNode();
647 resolveOrFail(type,"unable to find class for annotation",an);
648 }
649 }
650
651 public void visitClass(ClassNode node) {
652 ClassNode oldNode = currentClass;
653 currentClass = node;
654 ClassNode sn = node.getSuperClass();
655 if (sn!=null) resolveOrFail(sn,node);
656 ClassNode[] interfaces = node.getInterfaces();
657 for (int i=0; i<interfaces.length; i++) {
658 resolveOrFail(interfaces[i],node);
659 }
660 node.visitContents(this);
661 currentClass = oldNode;
662 }
663
664 public void visitReturnStatement(ReturnStatement statement) {
665 statement.setExpression(transform(statement.getExpression()));
666 }
667
668 public void visitAssertStatement(AssertStatement as) {
669 as.setBooleanExpression((BooleanExpression) (transform(as.getBooleanExpression())));
670 as.setMessageExpression(transform(as.getMessageExpression()));
671 }
672
673 public void visitCaseStatement(CaseStatement statement) {
674 statement.setExpression(transform(statement.getExpression()));
675 statement.getCode().visit(this);
676 }
677
678 public void visitCatchStatement(CatchStatement cs) {
679 resolveOrFail(cs.getExceptionType(),cs);
680 super.visitCatchStatement(cs);
681 }
682
683 public void visitDoWhileLoop(DoWhileStatement loop) {
684 loop.setBooleanExpression((BooleanExpression) (transform(loop.getBooleanExpression())));
685 super.visitDoWhileLoop(loop);
686 }
687
688 public void visitForLoop(ForStatement forLoop) {
689 forLoop.setCollectionExpression(transform(forLoop.getCollectionExpression()));
690 resolveOrFail(forLoop.getVariableType(),forLoop);
691 super.visitForLoop(forLoop);
692 }
693
694 public void visitSynchronizedStatement(SynchronizedStatement sync) {
695 sync.setExpression(transform(sync.getExpression()));
696 super.visitSynchronizedStatement(sync);
697 }
698
699 public void visitThrowStatement(ThrowStatement ts) {
700 ts.setExpression(transform(ts.getExpression()));
701 }
702
703 public void visitWhileLoop(WhileStatement loop) {
704 loop.setBooleanExpression((BooleanExpression) transform(loop.getBooleanExpression()));
705 super.visitWhileLoop(loop);
706 }
707
708 public void visitExpressionStatement(ExpressionStatement es) {
709 es.setExpression(transform(es.getExpression()));
710 }
711
712 private void addError(String msg, ASTNode expr) {
713 int line = expr.getLineNumber();
714 int col = expr.getColumnNumber();
715 compilationUnit.getErrorCollector().addErrorAndContinue(
716 new SyntaxErrorMessage(new SyntaxException(msg + '\n', line, col), source)
717 );
718 }
719 }