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.50 $
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 }
403 else {
404 method = (MethodNode) declaredMethods.get(0);
405 }
406 BlockStatement block = null;
407 Statement statement = method.getCode();
408 if (statement == null) {
409 block = new BlockStatement();
410 }
411 else if (statement instanceof BlockStatement) {
412 block = (BlockStatement) statement;
413 }
414 else {
415 block = new BlockStatement();
416 block.addStatement(statement);
417 }
418 block.addStatements(staticStatements);
419 }
420
421 /***
422 * @return a list of methods which match the given name
423 */
424 public List getDeclaredMethods(String name) {
425 List answer = new ArrayList();
426 for (Iterator iter = methods.iterator(); iter.hasNext();) {
427 MethodNode method = (MethodNode) iter.next();
428 if (name.equals(method.getName())) {
429 answer.add(method);
430 }
431 }
432 return answer;
433 }
434
435 /***
436 * @return a list of methods which match the given name
437 */
438 public List getMethods(String name) {
439 List answer = new ArrayList();
440 ClassNode node = this;
441 do {
442 for (Iterator iter = node.methods.iterator(); iter.hasNext();) {
443 MethodNode method = (MethodNode) iter.next();
444 if (name.equals(method.getName())) {
445 answer.add(method);
446 }
447 }
448 node = node.getSuperClassNode();
449 }
450 while (node != null);
451 return answer;
452 }
453
454 /***
455 * @return the method matching the given name and parameters or null
456 */
457 public MethodNode getDeclaredMethod(String name, Parameter[] parameters) {
458 for (Iterator iter = methods.iterator(); iter.hasNext();) {
459 MethodNode method = (MethodNode) iter.next();
460 if (name.equals(method.getName()) && parametersEqual(method.getParameters(), parameters)) {
461 return method;
462 }
463 }
464 return null;
465 }
466
467 /***
468 * @return true if this node is derived from the given class node
469 */
470 public boolean isDerivedFrom(String name) {
471 ClassNode node = getSuperClassNode();
472 while (node != null) {
473 if (name.equals(node.getName())) {
474 return true;
475 }
476 node = node.getSuperClassNode();
477 }
478 return false;
479 }
480
481 /***
482 * @return true if this class is derived from a groovy object
483 * i.e. it implements GroovyObject
484 */
485 public boolean isDerivedFromGroovyObject() {
486 return implementsInteface(GroovyObject.class.getName());
487 }
488
489 /***
490 * @param name the fully qualified name of the interface
491 * @return true if this class or any base class implements the given interface
492 */
493 public boolean implementsInteface(String name) {
494 ClassNode node = this;
495 do {
496 if (node.declaresInterface(name)) {
497 return true;
498 }
499 node = node.getSuperClassNode();
500 }
501 while (node != null);
502 return false;
503 }
504
505 /***
506 * @param name the fully qualified name of the interface
507 * @return true if this class declares that it implements the given interface
508 */
509 public boolean declaresInterface(String name) {
510 int size = interfaces.length;
511 for (int i = 0; i < size; i++) {
512 if (name.equals(interfaces[i])) {
513 return true;
514 }
515 }
516 return false;
517 }
518
519 /***
520 * @return the ClassNode of the super class of this type
521 */
522 public ClassNode getSuperClassNode() {
523 if (superClass != null && superClass.length() > 0 && superClassNode == null && !name.equals("java.lang.Object")) {
524
525 String temp = resolveClassName(superClass);
526 if (temp == null) {
527 throw new MissingClassException(superClass, this, "No such superclass");
528 }
529 else {
530 superClass = temp;
531 }
532 superClassNode = findClassNode(superClass);
533 }
534 return superClassNode;
535 }
536
537 /***
538 * Attempts to lookup the fully qualified class name in the compile unit or classpath
539 *
540 * @param type fully qulified type name
541 * @return the ClassNode for this type or null if it could not be found
542 */
543 public ClassNode findClassNode(String type) {
544 ClassNode answer = null;
545 CompileUnit theCompileUnit = getCompileUnit();
546 if (theCompileUnit != null) {
547 answer = theCompileUnit.getClass(type);
548 if (answer == null) {
549 Class theClass;
550 try {
551 theClass = theCompileUnit.loadClass(type);
552 answer = createClassNode(theClass);
553 }
554 catch (ClassNotFoundException e) {
555
556 log.log(Level.WARNING, "Cannot find class: " + type, e);
557 }
558 }
559 }
560 return answer;
561 }
562
563 protected ClassNode createClassNode(Class theClass) {
564 Class[] classInterfaces = theClass.getInterfaces();
565 int size = classInterfaces.length;
566 String[] interfaceNames = new String[size];
567 for (int i = 0; i < size; i++) {
568 interfaceNames[i] = classInterfaces[i].getName();
569 }
570
571 String className = null;
572 if (theClass.getSuperclass() != null) {
573 className = theClass.getSuperclass().getName();
574 }
575 ClassNode answer =
576 new ClassNode(theClass.getName(),
577 theClass.getModifiers(),
578 className,
579 interfaceNames,
580 MixinNode.EMPTY_ARRAY);
581 answer.compileUnit = getCompileUnit();
582 Method[] declaredMethods = theClass.getDeclaredMethods();
583 for (int i = 0; i < declaredMethods.length; i++) {
584 answer.addMethod(createMethodNode(declaredMethods[i]));
585 }
586 Constructor[] declaredConstructors = theClass.getDeclaredConstructors();
587 for (int i = 0; i < declaredConstructors.length; i++) {
588 answer.addConstructor(createConstructorNode(declaredConstructors[i]));
589 }
590 return answer;
591 }
592
593
594 /***
595 * Factory method to create a new ConstructorNode via reflection
596 */
597 private ConstructorNode createConstructorNode(Constructor constructor) {
598 Parameter[] parameters = createParameters(constructor.getParameterTypes());
599 return new ConstructorNode(constructor.getModifiers(), parameters, EmptyStatement.INSTANCE);
600 }
601
602 /***
603 * Factory method to create a new MethodNode via reflection
604 */
605 protected MethodNode createMethodNode(Method method) {
606 Parameter[] parameters = createParameters(method.getParameterTypes());
607 return new MethodNode(method.getName(), method.getModifiers(), method.getReturnType().getName(), parameters, EmptyStatement.INSTANCE);
608 }
609
610 /***
611 * @param types
612 * @return
613 */
614 protected Parameter[] createParameters(Class[] types) {
615 Parameter[] parameters = Parameter.EMPTY_ARRAY;
616 int size = types.length;
617 if (size > 0) {
618 parameters = new Parameter[size];
619 for (int i = 0; i < size; i++) {
620 parameters[i] = createParameter(types[i], i);
621 }
622 }
623 return parameters;
624 }
625
626 protected Parameter createParameter(Class parameterType, int idx) {
627 return new Parameter(parameterType.getName(), "param" + idx);
628 }
629
630
631 public String resolveClassName(String type) {
632 String answer = null;
633 if (type != null) {
634 if (getName().equals(type) || getNameWithoutPackage().equals(type)) {
635 return getName();
636 }
637
638 answer = tryResolveClassAndInnerClass(type);
639
640
641 String replacedPointType = type;
642 while (answer == null && replacedPointType.indexOf('.') > -1) {
643 int lastPoint = replacedPointType.lastIndexOf('.');
644 replacedPointType = new StringBuffer()
645 .append(replacedPointType.substring(0, lastPoint)).append("$")
646 .append(replacedPointType.substring(lastPoint + 1)).toString();
647 answer = tryResolveClassAndInnerClass(replacedPointType);
648 }
649 }
650 return answer;
651 }
652
653 private String tryResolveClassAndInnerClass(String type) {
654 String answer = tryResolveClassFromCompileUnit(type);
655 if (answer == null) {
656
657 String packageName = getPackageName();
658 if (packageName != null && packageName.length() > 0) {
659 answer = tryResolveClassFromCompileUnit(packageName + "." + type);
660 }
661 }
662 if (answer == null) {
663
664 if (module != null) {
665
666
667 for (Iterator iter = module.getImportPackages().iterator(); iter.hasNext();) {
668 String packageName = (String) iter.next();
669 answer = tryResolveClassFromCompileUnit(packageName + type);
670 if (answer != null) {
671 return answer;
672 }
673 }
674 }
675 }
676 if (answer == null) {
677 for (int i = 0, size = defaultImports.length; i < size; i++) {
678 String packagePrefix = defaultImports[i];
679 answer = tryResolveClassFromCompileUnit(packagePrefix + "." + type);
680 if (answer != null) {
681 return answer;
682 }
683 }
684 }
685 return answer;
686 }
687
688 /***
689 * @param type
690 * @return
691 */
692 protected String tryResolveClassFromCompileUnit(String type) {
693 CompileUnit theCompileUnit = getCompileUnit();
694 if (theCompileUnit != null) {
695 if (theCompileUnit.getClass(type) != null) {
696 return type;
697 }
698
699 try {
700 theCompileUnit.loadClass(type);
701 return type;
702 } catch (AccessControlException ace) {
703
704 throw ace;
705 } catch (ClassGeneratorException cge) {
706 throw cge;
707 }catch (Throwable e) {
708
709 }
710 }
711 return null;
712 }
713
714 public CompileUnit getCompileUnit() {
715 if (compileUnit == null && module != null) {
716 compileUnit = module.getUnit();
717 }
718 return compileUnit;
719 }
720
721 /***
722 * @return true if the two arrays are of the same size and have the same contents
723 */
724 protected boolean parametersEqual(Parameter[] a, Parameter[] b) {
725 if (a.length == b.length) {
726 boolean answer = true;
727 for (int i = 0; i < a.length; i++) {
728 if (!a[i].getType().equals(b[i].getType())) {
729 answer = false;
730 break;
731 }
732 }
733 return answer;
734 }
735 return false;
736 }
737
738 /***
739 * @return the name of the class for the given identifier if it is a class
740 * otherwise return null
741 */
742 public String getClassNameForExpression(String identifier) {
743
744 String className = null;
745 if (module != null) {
746 className = module.getImport(identifier);
747 if (className == null) {
748 if (module.getUnit().getClass(identifier) != null) {
749 className = identifier;
750 }
751 else {
752
753
754 String packageName = getPackageName();
755 if (packageName != null) {
756 String guessName = packageName + "." + identifier;
757 if (module.getUnit().getClass(guessName) != null) {
758 className = guessName;
759 }
760 else if (guessName.equals(name)) {
761 className = name;
762 }
763 }
764 }
765 }
766 }
767 else {
768 System.out.println("No module for class: " + getName());
769 }
770 return className;
771 }
772
773 /***
774 * @return the package name of this class
775 */
776 public String getPackageName() {
777 int idx = name.lastIndexOf('.');
778 if (idx > 0) {
779 return name.substring(0, idx);
780 }
781 return null;
782 }
783
784 public String getNameWithoutPackage() {
785 int idx = name.lastIndexOf('.');
786 if (idx > 0) {
787 return name.substring(idx + 1);
788 }
789 return name;
790 }
791
792 public void visitContents(GroovyClassVisitor visitor) {
793
794 for (Iterator iter = getProperties().iterator(); iter.hasNext();) {
795 visitor.visitProperty((PropertyNode) iter.next());
796 }
797
798 for (Iterator iter = getFields().iterator(); iter.hasNext();) {
799 visitor.visitField((FieldNode) iter.next());
800 }
801
802 for (Iterator iter = getDeclaredConstructors().iterator(); iter.hasNext();) {
803 visitor.visitConstructor((ConstructorNode) iter.next());
804 }
805
806 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
807 visitor.visitMethod((MethodNode) iter.next());
808 }
809 }
810
811 public MethodNode getGetterMethod(String getterName) {
812 for (Iterator iter = methods.iterator(); iter.hasNext();) {
813 MethodNode method = (MethodNode) iter.next();
814 if (getterName.equals(method.getName())
815 && !"void".equals(method.getReturnType())
816 && method.getParameters().length == 0) {
817 return method;
818 }
819 }
820 return null;
821 }
822
823 public MethodNode getSetterMethod(String getterName) {
824 for (Iterator iter = methods.iterator(); iter.hasNext();) {
825 MethodNode method = (MethodNode) iter.next();
826 if (getterName.equals(method.getName())
827 && "void".equals(method.getReturnType())
828 && method.getParameters().length == 1) {
829 return method;
830 }
831 }
832 return null;
833 }
834
835 /***
836 * Is this class delcared in a static method (such as a closure / inner class declared in a static method)
837 *
838 * @return
839 */
840 public boolean isStaticClass() {
841 return staticClass;
842 }
843
844 public void setStaticClass(boolean staticClass) {
845 this.staticClass = staticClass;
846 }
847
848 /***
849 * @return Returns true if this inner class or closure was declared inside a script body
850 */
851 public boolean isScriptBody() {
852 return scriptBody;
853 }
854
855 public void setScriptBody(boolean scriptBody) {
856 this.scriptBody = scriptBody;
857 }
858
859 public boolean isScript() {
860 return script | isDerivedFrom(Script.class.getName());
861 }
862
863 public void setScript(boolean script) {
864 this.script = script;
865 }
866
867 public String toString() {
868 return super.toString() + "[name: " + name + "]";
869 }
870
871 /***
872 * Returns true if the given method has a possibly matching method with the given name and arguments
873 */
874 public boolean hasPossibleMethod(String name, Expression arguments) {
875 int count = 0;
876
877 if (arguments instanceof TupleExpression) {
878 TupleExpression tuple = (TupleExpression) arguments;
879
880 count = tuple.getExpressions().size();
881 }
882 ClassNode node = this;
883 do {
884 for (Iterator iter = node.methods.iterator(); iter.hasNext();) {
885 MethodNode method = (MethodNode) iter.next();
886 if (name.equals(method.getName()) && method.getParameters().length == count) {
887 return true;
888 }
889 }
890 node = node.getSuperClassNode();
891 }
892 while (node != null);
893 return false;
894 }
895 }