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