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
35 package org.codehaus.groovy.ast;
36
37 import groovy.lang.GroovyObject;
38 import groovy.lang.MissingClassException;
39 import groovy.lang.Script;
40 import org.codehaus.groovy.ast.expr.Expression;
41 import org.codehaus.groovy.ast.expr.TupleExpression;
42 import org.codehaus.groovy.ast.stmt.BlockStatement;
43 import org.codehaus.groovy.ast.stmt.EmptyStatement;
44 import org.codehaus.groovy.ast.stmt.Statement;
45 import org.objectweb.asm.Constants;
46
47 import java.lang.reflect.Constructor;
48 import java.lang.reflect.Method;
49 import java.security.AccessControlException;
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.Iterator;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.logging.Level;
56 import java.util.logging.Logger;
57
58 /***
59 * Represents a class declaration
60 *
61 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
62 * @version $Revision: 1.46 $
63 */
64 public class ClassNode extends AnnotatedNode implements Constants {
65
66 private static final String[] defaultImports = {"java.lang", "java.util", "groovy.lang", "groovy.util"};
67
68 private Logger log = Logger.getLogger(getClass().getName());
69
70 private String name;
71 private int modifiers;
72 private String superClass;
73 private String[] interfaces;
74 private MixinNode[] mixins;
75 private List constructors = new ArrayList();
76 private List methods = new ArrayList();
77 private List fields = new ArrayList();
78 private List properties = new ArrayList();
79 private Map fieldIndex = new HashMap();
80 private ModuleNode module;
81 private CompileUnit compileUnit;
82 private boolean staticClass = false;
83 private boolean scriptBody = false;
84 private boolean script;
85 private ClassNode superClassNode;
86
87
88
89 private MethodNode enclosingMethod = null;
90
91 public MethodNode getEnclosingMethod() {
92 return enclosingMethod;
93 }
94
95 public void setEnclosingMethod(MethodNode enclosingMethod) {
96 this.enclosingMethod = enclosingMethod;
97 }
98
99
100 /***
101 * @param name is the full name of the class
102 * @param modifiers the modifiers,
103 * @param superClass the base class name - use "java.lang.Object" if no direct
104 * base class
105 * @see org.objectweb.asm.Constants
106 */
107 public ClassNode(String name, int modifiers, String superClass) {
108 this(name, modifiers, superClass, EMPTY_STRING_ARRAY, MixinNode.EMPTY_ARRAY);
109 }
110
111 /***
112 * @param name is the full name of the class
113 * @param modifiers the modifiers,
114 * @param superClass the base class name - use "java.lang.Object" if no direct
115 * base class
116 * @see org.objectweb.asm.Constants
117 */
118 public ClassNode(String name, int modifiers, String superClass, String[] interfaces, MixinNode[] mixins) {
119 this.name = name;
120 this.modifiers = modifiers;
121 this.superClass = superClass;
122 this.interfaces = interfaces;
123 this.mixins = mixins;
124
125
126
127
128
129 }
130
131 public String getSuperClass() {
132 return superClass;
133 }
134
135 public void setSuperClass(String superClass) {
136 this.superClass = superClass;
137 }
138
139 public List getFields() {
140 return fields;
141 }
142
143 public String[] getInterfaces() {
144 return interfaces;
145 }
146
147 public MixinNode[] getMixins() {
148 return mixins;
149 }
150
151 public List getMethods() {
152 return methods;
153 }
154
155 public List getAbstractMethods() {
156
157 List result = new ArrayList();
158 for (Iterator methIt = getAllDeclaredMethods().iterator(); methIt.hasNext();) {
159 MethodNode method = (MethodNode) methIt.next();
160 if (method.isAbstract()) {
161 result.add(method);
162 }
163 }
164 if (result.size() == 0) {
165 return null;
166 }
167 else {
168 return result;
169 }
170 }
171
172 public List getAllDeclaredMethods() {
173 return new ArrayList(getDeclaredMethodsMap().values());
174 }
175
176
177 protected Map getDeclaredMethodsMap() {
178
179 ClassNode parent = getSuperClassNode();
180 Map result = null;
181 if (parent != null) {
182 result = parent.getDeclaredMethodsMap();
183 }
184 else {
185 result = new HashMap();
186 }
187
188
189 for (int i = 0; i < interfaces.length; i++) {
190 String interfaceName = interfaces[i];
191 ClassNode iface = findClassNode(interfaceName);
192 Map ifaceMethodsMap = iface.getDeclaredMethodsMap();
193 for (Iterator iter = ifaceMethodsMap.keySet().iterator(); iter.hasNext();) {
194 String methSig = (String) iter.next();
195 if (!result.containsKey(methSig)) {
196 MethodNode methNode = (MethodNode) ifaceMethodsMap.get(methSig);
197 result.put(methSig, methNode);
198 }
199 }
200 }
201
202
203 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
204 MethodNode method = (MethodNode) iter.next();
205 String sig = method.getTypeDescriptor();
206 if (result.containsKey(sig)) {
207 MethodNode inheritedMethod = (MethodNode) result.get(sig);
208 if (inheritedMethod.isAbstract()) {
209 result.put(sig, method);
210 }
211 }
212 else {
213 result.put(sig, method);
214 }
215 }
216 return result;
217 }
218
219 protected int findMatchingMethodInList(MethodNode method, List methods) {
220 for (int i = 0; i < methods.size(); i++) {
221 MethodNode someMeth = (MethodNode) methods.get(i);
222 if (someMeth.getName().equals(method.getName())
223 && parametersEqual(someMeth.getParameters(), method.getParameters())) {
224 return i;
225 }
226 }
227 return -1;
228 }
229
230 public String getName() {
231 return name;
232 }
233
234 public int getModifiers() {
235 return modifiers;
236 }
237
238 public List getProperties() {
239 return properties;
240 }
241
242 public List getDeclaredConstructors() {
243 return constructors;
244 }
245
246 public ModuleNode getModule() {
247 return module;
248 }
249
250 public void setModule(ModuleNode module) {
251 this.module = module;
252 if (module != null) {
253 this.compileUnit = module.getUnit();
254 }
255 }
256
257 public void addField(FieldNode node) {
258 node.setOwner(getName());
259 fields.add(node);
260 fieldIndex.put(node.getName(), node);
261 }
262
263 public void addProperty(PropertyNode node) {
264 FieldNode field = node.getField();
265 addField(field);
266
267 properties.add(node);
268 }
269
270 public PropertyNode addProperty(String name,
271 int modifiers,
272 String type,
273 Expression initialValueExpression,
274 Statement getterBlock,
275 Statement setterBlock) {
276 PropertyNode node =
277 new PropertyNode(name, modifiers, type, getName(), initialValueExpression, getterBlock, setterBlock);
278 addProperty(node);
279 return node;
280 }
281
282 public void addConstructor(ConstructorNode node) {
283 constructors.add(node);
284 }
285
286 public ConstructorNode addConstructor(int modifiers, Parameter[] parameters, Statement code) {
287 ConstructorNode node = new ConstructorNode(modifiers, parameters, code);
288 addConstructor(node);
289 return node;
290 }
291
292 public void addMethod(MethodNode node) {
293 methods.add(node);
294 node.declaringClass = this;
295 }
296
297 /***
298 * IF a method with the given name and parameters is already defined then it is returned
299 * otherwise the given method is added to this node. This method is useful for
300 * default method adding like getProperty() or invokeMethod() where there may already
301 * be a method defined in a class and so the default implementations should not be added
302 * if already present.
303 */
304 public MethodNode addMethod(String name,
305 int modifiers,
306 String returnType,
307 Parameter[] parameters,
308 Statement code) {
309 MethodNode other = getDeclaredMethod(name, parameters);
310
311 if (other != null) {
312 return other;
313 }
314 MethodNode node = new MethodNode(name, modifiers, returnType, parameters, code);
315 addMethod(node);
316 return node;
317 }
318
319 /***
320 * Adds a synthetic method as part of the compilation process
321 */
322 public MethodNode addSyntheticMethod(String name,
323 int modifiers,
324 String returnType,
325 Parameter[] parameters,
326 Statement code) {
327 MethodNode answer = addMethod(name, modifiers, returnType, parameters, code);
328 answer.setSynthetic(true);
329 return answer;
330 }
331
332 public FieldNode addField(String name, int modifiers, String type, Expression initialValue) {
333 FieldNode node = new FieldNode(name, modifiers, type, getName(), initialValue);
334 addField(node);
335 return node;
336 }
337
338 public void addInterface(String name) {
339
340 boolean skip = false;
341 for (int i = 0; i < interfaces.length; i++) {
342 if (name.equals(interfaces[i])) {
343 skip = true;
344 }
345 }
346 if (!skip) {
347 String[] newInterfaces = new String[interfaces.length + 1];
348 System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
349 newInterfaces[interfaces.length] = name;
350 interfaces = newInterfaces;
351 }
352 }
353
354 public void addMixin(MixinNode mixin) {
355
356 boolean skip = false;
357 String mixinName = mixin.getName();
358 for (int i = 0; i < mixins.length; i++) {
359 if (mixinName.equals(mixins[i].getName())) {
360 skip = true;
361 }
362 }
363 if (!skip) {
364 MixinNode[] newMixins = new MixinNode[mixins.length + 1];
365 System.arraycopy(mixins, 0, newMixins, 0, mixins.length);
366 newMixins[mixins.length] = mixin;
367 mixins = newMixins;
368 }
369 }
370
371 public FieldNode getField(String name) {
372 return (FieldNode) fieldIndex.get(name);
373 }
374
375 /***
376 * @return the field node on the outer class or null if this is not an
377 * inner class
378 */
379 public FieldNode getOuterField(String name) {
380 return null;
381 }
382
383 /***
384 * Helper method to avoid casting to inner class
385 *
386 * @return
387 */
388 public ClassNode getOuterClass() {
389 return null;
390 }
391
392 public void addStaticInitializerStatements(List staticStatements) {
393 MethodNode method = null;
394 List declaredMethods = getDeclaredMethods("<clinit>");
395 if (declaredMethods.isEmpty()) {
396 method =
397 addMethod("<clinit>", ACC_PUBLIC | ACC_STATIC, "void", Parameter.EMPTY_ARRAY, new BlockStatement());
398 }
399 else {
400 method = (MethodNode) declaredMethods.get(0);
401 }
402 BlockStatement block = null;
403 Statement statement = method.getCode();
404 if (statement == null) {
405 block = new BlockStatement();
406 }
407 else if (statement instanceof BlockStatement) {
408 block = (BlockStatement) statement;
409 }
410 else {
411 block = new BlockStatement();
412 block.addStatement(statement);
413 }
414 block.addStatements(staticStatements);
415 }
416
417 /***
418 * @return a list of methods which match the given name
419 */
420 public List getDeclaredMethods(String name) {
421 List answer = new ArrayList();
422 for (Iterator iter = methods.iterator(); iter.hasNext();) {
423 MethodNode method = (MethodNode) iter.next();
424 if (name.equals(method.getName())) {
425 answer.add(method);
426 }
427 }
428 return answer;
429 }
430
431 /***
432 * @return a list of methods which match the given name
433 */
434 public List getMethods(String name) {
435 List answer = new ArrayList();
436 ClassNode node = this;
437 do {
438 for (Iterator iter = node.methods.iterator(); iter.hasNext();) {
439 MethodNode method = (MethodNode) iter.next();
440 if (name.equals(method.getName())) {
441 answer.add(method);
442 }
443 }
444 node = node.getSuperClassNode();
445 }
446 while (node != null);
447 return answer;
448 }
449
450 /***
451 * @return the method matching the given name and parameters or null
452 */
453 public MethodNode getDeclaredMethod(String name, Parameter[] parameters) {
454 for (Iterator iter = methods.iterator(); iter.hasNext();) {
455 MethodNode method = (MethodNode) iter.next();
456 if (name.equals(method.getName()) && parametersEqual(method.getParameters(), parameters)) {
457 return method;
458 }
459 }
460 return null;
461 }
462
463 /***
464 * @return true if this node is derived from the given class node
465 */
466 public boolean isDerivedFrom(String name) {
467 ClassNode node = getSuperClassNode();
468 while (node != null) {
469 if (name.equals(node.getName())) {
470 return true;
471 }
472 node = node.getSuperClassNode();
473 }
474 return false;
475 }
476
477 /***
478 * @return true if this class is derived from a groovy object
479 * i.e. it implements GroovyObject
480 */
481 public boolean isDerivedFromGroovyObject() {
482 return implementsInteface(GroovyObject.class.getName());
483 }
484
485 /***
486 * @param name the fully qualified name of the interface
487 * @return true if this class or any base class implements the given interface
488 */
489 public boolean implementsInteface(String name) {
490 ClassNode node = this;
491 do {
492 if (node.declaresInterface(name)) {
493 return true;
494 }
495 node = node.getSuperClassNode();
496 }
497 while (node != null);
498 return false;
499 }
500
501 /***
502 * @param name the fully qualified name of the interface
503 * @return true if this class declares that it implements the given interface
504 */
505 public boolean declaresInterface(String name) {
506 int size = interfaces.length;
507 for (int i = 0; i < size; i++) {
508 if (name.equals(interfaces[i])) {
509 return true;
510 }
511 }
512 return false;
513 }
514
515 /***
516 * @return the ClassNode of the super class of this type
517 */
518 public ClassNode getSuperClassNode() {
519 if (superClass != null && superClass.length() > 0 && superClassNode == null && !name.equals("java.lang.Object")) {
520
521 String temp = resolveClassName(superClass);
522 if (temp == null) {
523 throw new MissingClassException(superClass, this, "No such superclass");
524 }
525 else {
526 superClass = temp;
527 }
528 superClassNode = findClassNode(superClass);
529 }
530 return superClassNode;
531 }
532
533 /***
534 * Attempts to lookup the fully qualified class name in the compile unit or classpath
535 *
536 * @param type fully qulified type name
537 * @return the ClassNode for this type or null if it could not be found
538 */
539 public ClassNode findClassNode(String type) {
540 ClassNode answer = null;
541 CompileUnit theCompileUnit = getCompileUnit();
542 if (theCompileUnit != null) {
543 answer = theCompileUnit.getClass(type);
544 if (answer == null) {
545 Class theClass;
546 try {
547 theClass = theCompileUnit.loadClass(type);
548 answer = createClassNode(theClass);
549 }
550 catch (ClassNotFoundException e) {
551
552 log.log(Level.WARNING, "Cannot find class: " + type, e);
553 }
554 }
555 }
556 return answer;
557 }
558
559 protected ClassNode createClassNode(Class theClass) {
560 Class[] classInterfaces = theClass.getInterfaces();
561 int size = classInterfaces.length;
562 String[] interfaceNames = new String[size];
563 for (int i = 0; i < size; i++) {
564 interfaceNames[i] = classInterfaces[i].getName();
565 }
566
567 String className = null;
568 if (theClass.getSuperclass() != null) {
569 className = theClass.getSuperclass().getName();
570 }
571 ClassNode answer =
572 new ClassNode(theClass.getName(),
573 theClass.getModifiers(),
574 className,
575 interfaceNames,
576 MixinNode.EMPTY_ARRAY);
577 answer.compileUnit = getCompileUnit();
578 Method[] declaredMethods = theClass.getDeclaredMethods();
579 for (int i = 0; i < declaredMethods.length; i++) {
580 answer.addMethod(createMethodNode(declaredMethods[i]));
581 }
582 Constructor[] declaredConstructors = theClass.getDeclaredConstructors();
583 for (int i = 0; i < declaredConstructors.length; i++) {
584 answer.addConstructor(createConstructorNode(declaredConstructors[i]));
585 }
586 return answer;
587 }
588
589
590 /***
591 * Factory method to create a new ConstructorNode via reflection
592 */
593 private ConstructorNode createConstructorNode(Constructor constructor) {
594 Parameter[] parameters = createParameters(constructor.getParameterTypes());
595 return new ConstructorNode(constructor.getModifiers(), parameters, EmptyStatement.INSTANCE);
596 }
597
598 /***
599 * Factory method to create a new MethodNode via reflection
600 */
601 protected MethodNode createMethodNode(Method method) {
602 Parameter[] parameters = createParameters(method.getParameterTypes());
603 return new MethodNode(method.getName(), method.getModifiers(), method.getReturnType().getName(), parameters, EmptyStatement.INSTANCE);
604 }
605
606 /***
607 * @param types
608 * @return
609 */
610 protected Parameter[] createParameters(Class[] types) {
611 Parameter[] parameters = Parameter.EMPTY_ARRAY;
612 int size = types.length;
613 if (size > 0) {
614 parameters = new Parameter[size];
615 for (int i = 0; i < size; i++) {
616 parameters[i] = createParameter(types[i], i);
617 }
618 }
619 return parameters;
620 }
621
622 protected Parameter createParameter(Class parameterType, int idx) {
623 return new Parameter(parameterType.getName(), "param" + idx);
624 }
625
626
627 public String resolveClassName(String type) {
628 String answer = null;
629 if (type != null) {
630 if (getName().equals(type) || getNameWithoutPackage().equals(type)) {
631 return getName();
632 }
633
634 answer = tryResolveClassAndInnerClass(type);
635
636
637 String replacedPointType = type;
638 while (answer == null && replacedPointType.indexOf('.') > -1) {
639 int lastPoint = replacedPointType.lastIndexOf('.');
640 replacedPointType = new StringBuffer()
641 .append(replacedPointType.substring(0, lastPoint)).append("$")
642 .append(replacedPointType.substring(lastPoint + 1)).toString();
643 answer = tryResolveClassAndInnerClass(replacedPointType);
644 }
645 }
646 return answer;
647 }
648
649 private String tryResolveClassAndInnerClass(String type) {
650 String answer = tryResolveClassFromCompileUnit(type);
651 if (answer == null) {
652
653 String packageName = getPackageName();
654 if (packageName != null && packageName.length() > 0) {
655 answer = tryResolveClassFromCompileUnit(packageName + "." + type);
656 }
657 }
658 if (answer == null) {
659
660 if (module != null) {
661
662
663 for (Iterator iter = module.getImportPackages().iterator(); iter.hasNext();) {
664 String packageName = (String) iter.next();
665 answer = tryResolveClassFromCompileUnit(packageName + type);
666 if (answer != null) {
667 return answer;
668 }
669 }
670 }
671 }
672 if (answer == null) {
673 for (int i = 0, size = defaultImports.length; i < size; i++) {
674 String packagePrefix = defaultImports[i];
675 answer = tryResolveClassFromCompileUnit(packagePrefix + "." + type);
676 if (answer != null) {
677 return answer;
678 }
679 }
680 }
681 return answer;
682 }
683
684 /***
685 * @param type
686 * @return
687 */
688 protected String tryResolveClassFromCompileUnit(String type) {
689 CompileUnit theCompileUnit = getCompileUnit();
690 if (theCompileUnit != null) {
691 if (theCompileUnit.getClass(type) != null) {
692 return type;
693 }
694
695 try {
696 theCompileUnit.loadClass(type);
697 return type;
698 }
699 catch (AccessControlException ace) {
700
701 throw ace;
702 }
703 catch (Throwable e) {
704
705 }
706 }
707 return null;
708 }
709
710 public CompileUnit getCompileUnit() {
711 if (compileUnit == null && module != null) {
712 compileUnit = module.getUnit();
713 }
714 return compileUnit;
715 }
716
717 /***
718 * @return true if the two arrays are of the same size and have the same contents
719 */
720 protected boolean parametersEqual(Parameter[] a, Parameter[] b) {
721 if (a.length == b.length) {
722 boolean answer = true;
723 for (int i = 0; i < a.length; i++) {
724 if (!a[i].getType().equals(b[i].getType())) {
725 answer = false;
726 break;
727 }
728 }
729 return answer;
730 }
731 return false;
732 }
733
734 /***
735 * @return the name of the class for the given identifier if it is a class
736 * otherwise return null
737 */
738 public String getClassNameForExpression(String identifier) {
739
740 String className = null;
741 if (module != null) {
742 className = module.getImport(identifier);
743 if (className == null) {
744 if (module.getUnit().getClass(identifier) != null) {
745 className = identifier;
746 }
747 else {
748
749
750 String packageName = getPackageName();
751 if (packageName != null) {
752 String guessName = packageName + "." + identifier;
753 if (module.getUnit().getClass(guessName) != null) {
754 className = guessName;
755 }
756 else if (guessName.equals(name)) {
757 className = name;
758 }
759 }
760 }
761 }
762 }
763 else {
764 System.out.println("No module for class: " + getName());
765 }
766 return className;
767 }
768
769 /***
770 * @return the package name of this class
771 */
772 public String getPackageName() {
773 int idx = name.lastIndexOf('.');
774 if (idx > 0) {
775 return name.substring(0, idx);
776 }
777 return null;
778 }
779
780 public String getNameWithoutPackage() {
781 int idx = name.lastIndexOf('.');
782 if (idx > 0) {
783 return name.substring(idx + 1);
784 }
785 return name;
786 }
787
788 public void visitContents(GroovyClassVisitor visitor) {
789
790 for (Iterator iter = getProperties().iterator(); iter.hasNext();) {
791 visitor.visitProperty((PropertyNode) iter.next());
792 }
793
794 for (Iterator iter = getFields().iterator(); iter.hasNext();) {
795 visitor.visitField((FieldNode) iter.next());
796 }
797
798 for (Iterator iter = getDeclaredConstructors().iterator(); iter.hasNext();) {
799 visitor.visitConstructor((ConstructorNode) iter.next());
800 }
801
802 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
803 visitor.visitMethod((MethodNode) iter.next());
804 }
805 }
806
807 public MethodNode getGetterMethod(String getterName) {
808 for (Iterator iter = methods.iterator(); iter.hasNext();) {
809 MethodNode method = (MethodNode) iter.next();
810 if (getterName.equals(method.getName())
811 && !"void".equals(method.getReturnType())
812 && method.getParameters().length == 0) {
813 return method;
814 }
815 }
816 return null;
817 }
818
819 public MethodNode getSetterMethod(String getterName) {
820 for (Iterator iter = methods.iterator(); iter.hasNext();) {
821 MethodNode method = (MethodNode) iter.next();
822 if (getterName.equals(method.getName())
823 && "void".equals(method.getReturnType())
824 && method.getParameters().length == 1) {
825 return method;
826 }
827 }
828 return null;
829 }
830
831 /***
832 * Is this class delcared in a static method (such as a closure / inner class declared in a static method)
833 *
834 * @return
835 */
836 public boolean isStaticClass() {
837 return staticClass;
838 }
839
840 public void setStaticClass(boolean staticClass) {
841 this.staticClass = staticClass;
842 }
843
844 /***
845 * @return Returns true if this inner class or closure was declared inside a script body
846 */
847 public boolean isScriptBody() {
848 return scriptBody;
849 }
850
851 public void setScriptBody(boolean scriptBody) {
852 this.scriptBody = scriptBody;
853 }
854
855 public boolean isScript() {
856 return script | isDerivedFrom(Script.class.getName());
857 }
858
859 public void setScript(boolean script) {
860 this.script = script;
861 }
862
863 public String toString() {
864 return super.toString() + "[name: " + name + "]";
865 }
866
867 /***
868 * Returns true if the given method has a possibly matching method with the given name and arguments
869 */
870 public boolean hasPossibleMethod(String name, Expression arguments) {
871 int count = 0;
872
873 if (arguments instanceof TupleExpression) {
874 TupleExpression tuple = (TupleExpression) arguments;
875
876 count = tuple.getExpressions().size();
877 }
878 ClassNode node = this;
879 do {
880 for (Iterator iter = node.methods.iterator(); iter.hasNext();) {
881 MethodNode method = (MethodNode) iter.next();
882 if (name.equals(method.getName()) && method.getParameters().length == count) {
883 return true;
884 }
885 }
886 node = node.getSuperClassNode();
887 }
888 while (node != null);
889 return false;
890 }
891 }