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.42 $
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
633 answer = tryResolveClassAndInnerClass(type);
634
635
636 String replacedPointType = type;
637 while (answer == null && replacedPointType.indexOf('.') > -1) {
638 int lastPoint = replacedPointType.lastIndexOf('.');
639 replacedPointType = new StringBuffer()
640 .append(replacedPointType.substring(0, lastPoint)).append("$")
641 .append(replacedPointType.substring(lastPoint + 1)).toString();
642 answer = tryResolveClassAndInnerClass(replacedPointType);
643 }
644 }
645 return answer;
646 }
647
648 private String tryResolveClassAndInnerClass(String type) {
649 String answer = tryResolveClassFromCompileUnit(type);
650 if (answer == null)
651 {
652
653 String packageName = getPackageName();
654 if (packageName != null && packageName.length() > 0)
655 {
656 answer = tryResolveClassFromCompileUnit(packageName + "." + type);
657 }
658 }
659 if (answer == null)
660 {
661
662 if (module != null)
663 {
664
665
666 for (Iterator iter = module.getImportPackages().iterator(); iter.hasNext();)
667 {
668 String packageName = (String) iter.next();
669 answer = tryResolveClassFromCompileUnit(packageName + type);
670 if (answer != null)
671 {
672 return answer;
673 }
674 }
675 }
676 }
677 if (answer == null)
678 {
679 for (int i = 0, size = defaultImports.length; i < size; i++)
680 {
681 String packagePrefix = defaultImports[i];
682 answer = tryResolveClassFromCompileUnit(packagePrefix + "." + type);
683 if (answer != null)
684 {
685 return answer;
686 }
687 }
688 }
689 return answer;
690 }
691
692 /***
693 * @param type
694 * @return
695 */
696 protected String tryResolveClassFromCompileUnit(String type) {
697 CompileUnit theCompileUnit = getCompileUnit();
698 if (theCompileUnit != null) {
699 if (theCompileUnit.getClass(type) != null) {
700 return type;
701 }
702
703 try {
704 theCompileUnit.loadClass(type);
705 return type;
706 }
707 catch (AccessControlException ace) {
708
709 throw ace;
710 }
711 catch (Throwable e) {
712
713 }
714 }
715 return null;
716 }
717
718 public CompileUnit getCompileUnit() {
719 if (compileUnit == null && module != null) {
720 compileUnit = module.getUnit();
721 }
722 return compileUnit;
723 }
724
725 /***
726 * @return true if the two arrays are of the same size and have the same contents
727 */
728 protected boolean parametersEqual(Parameter[] a, Parameter[] b) {
729 if (a.length == b.length) {
730 boolean answer = true;
731 for (int i = 0; i < a.length; i++) {
732 if (!a[i].getType().equals(b[i].getType())) {
733 answer = false;
734 break;
735 }
736 }
737 return answer;
738 }
739 return false;
740 }
741
742 /***
743 * @return the name of the class for the given identifier if it is a class
744 * otherwise return null
745 */
746 public String getClassNameForExpression(String identifier) {
747
748 String className = null;
749 if (module != null) {
750 className = module.getImport(identifier);
751 if (className == null) {
752 if (module.getUnit().getClass(identifier) != null) {
753 className = identifier;
754 }
755 else {
756
757
758 String packageName = getPackageName();
759 if (packageName != null) {
760 String guessName = packageName + "." + identifier;
761 if (module.getUnit().getClass(guessName) != null) {
762 className = guessName;
763 }
764 else if (guessName.equals(name)) {
765 className = name;
766 }
767 }
768 }
769 }
770 }
771 else {
772 System.out.println("No module for class: " + getName());
773 }
774 return className;
775 }
776
777 /***
778 * @return the package name of this class
779 */
780 public String getPackageName() {
781 int idx = name.lastIndexOf('.');
782 if (idx > 0) {
783 return name.substring(0, idx);
784 }
785 return null;
786 }
787
788 public String getNameWithoutPackage() {
789 int idx = name.lastIndexOf('.');
790 if (idx > 0) {
791 return name.substring(idx + 1);
792 }
793 return name;
794 }
795
796 public void visitContents(GroovyClassVisitor visitor) {
797
798 for (Iterator iter = getProperties().iterator(); iter.hasNext();) {
799 visitor.visitProperty((PropertyNode) iter.next());
800 }
801
802 for (Iterator iter = getFields().iterator(); iter.hasNext();) {
803 visitor.visitField((FieldNode) iter.next());
804 }
805
806 for (Iterator iter = getDeclaredConstructors().iterator(); iter.hasNext();) {
807 visitor.visitConstructor((ConstructorNode) iter.next());
808 }
809
810 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
811 visitor.visitMethod((MethodNode) iter.next());
812 }
813 }
814
815 public MethodNode getGetterMethod(String getterName) {
816 for (Iterator iter = methods.iterator(); iter.hasNext();) {
817 MethodNode method = (MethodNode) iter.next();
818 if (getterName.equals(method.getName())
819 && !"void".equals(method.getReturnType())
820 && method.getParameters().length == 0) {
821 return method;
822 }
823 }
824 return null;
825 }
826
827 public MethodNode getSetterMethod(String getterName) {
828 for (Iterator iter = methods.iterator(); iter.hasNext();) {
829 MethodNode method = (MethodNode) iter.next();
830 if (getterName.equals(method.getName())
831 && "void".equals(method.getReturnType())
832 && method.getParameters().length == 1) {
833 return method;
834 }
835 }
836 return null;
837 }
838
839 /***
840 * Is this class delcared in a static method (such as a closure / inner class declared in a static method)
841 * @return
842 */
843 public boolean isStaticClass() {
844 return staticClass;
845 }
846
847 public void setStaticClass(boolean staticClass) {
848 this.staticClass = staticClass;
849 }
850
851 /***
852 * @return Returns true if this inner class or closure was declared inside a script body
853 */
854 public boolean isScriptBody() {
855 return scriptBody;
856 }
857
858 public void setScriptBody(boolean scriptBody) {
859 this.scriptBody = scriptBody;
860 }
861
862 public boolean isScript() {
863 return script | isDerivedFrom(Script.class.getName());
864 }
865
866 public void setScript(boolean script) {
867 this.script = script;
868 }
869
870 public String toString() {
871 return super.toString() + "[name: " + name + "]";
872 }
873
874 }