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