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