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