001 /*
002 * $Id: ClassNode.java 4216 2006-11-13 16:04:23Z blackdrag $
003 *
004 * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005 *
006 * Redistribution and use of this software and associated documentation
007 * ("Software"), with or without modification, are permitted provided that the
008 * following conditions are met:
009 * 1. Redistributions of source code must retain copyright statements and
010 * notices. Redistributions must also contain a copy of this document.
011 * 2. Redistributions in binary form must reproduce the above copyright
012 * notice, this list of conditions and the following disclaimer in the
013 * documentation and/or other materials provided with the distribution.
014 * 3. The name "groovy" must not be used to endorse or promote products
015 * derived from this Software without prior written permission of The Codehaus.
016 * For written permission, please contact info@codehaus.org.
017 * 4. Products derived from this Software may not be called "groovy" nor may
018 * "groovy" appear in their names without prior written permission of The
019 * Codehaus. "groovy" is a registered trademark of The Codehaus.
020 * 5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
021 *
022 * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
023 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
024 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
025 * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
026 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
027 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
028 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
029 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
030 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
031 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
032 * DAMAGE.
033 *
034 */
035 package org.codehaus.groovy.ast;
036
037 import groovy.lang.GroovyObject;
038
039 import org.codehaus.groovy.GroovyBugError;
040 import org.codehaus.groovy.ast.expr.Expression;
041 import org.codehaus.groovy.ast.expr.TupleExpression;
042 import org.codehaus.groovy.ast.stmt.BlockStatement;
043 import org.codehaus.groovy.ast.stmt.EmptyStatement;
044 import org.codehaus.groovy.ast.stmt.Statement;
045 import org.objectweb.asm.Opcodes;
046
047 import java.lang.reflect.Array;
048 import java.lang.reflect.Constructor;
049 import java.lang.reflect.Field;
050 import java.lang.reflect.Method;
051 import java.util.ArrayList;
052 import java.util.HashMap;
053 import java.util.HashSet;
054 import java.util.Iterator;
055 import java.util.List;
056 import java.util.Map;
057
058 /**
059 * Represents a class in the AST.<br/>
060 * A ClassNode should be created using the methods in ClassHelper.
061 * This ClassNode may be used to represent a class declaration or
062 * any other type. This class uses a proxy meschanism allowing to
063 * create a class for a plain name at ast creation time. In another
064 * phase of the compiler the real ClassNode for the plain name may be
065 * found. To avoid the need of exchanging this ClassNode with an
066 * instance of the correct ClassNode the correct ClassNode is set as
067 * redirect. All method calls are then redirected to that ClassNode.
068 * <br>
069 * Note: the proxy mechanism is only allowed for classes being marked
070 * as primary ClassNode which means they represent no actual class.
071 * The redirect itself can be any type of ClassNode
072 *
073 * @see org.codehaus.groovy.ast.ClassHelper
074 *
075 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
076 * @author Jochen Theodorou
077 * @version $Revision: 4216 $
078 */
079 public class ClassNode extends AnnotatedNode implements Opcodes {
080
081 public static ClassNode[] EMPTY_ARRAY = new ClassNode[0];
082
083 public static ClassNode THIS = new ClassNode(Object.class);
084 public static ClassNode SUPER = new ClassNode(Object.class);
085
086 private String name;
087 private int modifiers;
088 private ClassNode[] interfaces;
089 private MixinNode[] mixins;
090 private List constructors = new ArrayList();
091 private List objectInitializers = new ArrayList();
092 private List methods = new ArrayList();
093 private List fields = new ArrayList();
094 private List properties = new ArrayList();
095 private Map fieldIndex = new HashMap();
096 private ModuleNode module;
097 private CompileUnit compileUnit;
098 private boolean staticClass = false;
099 private boolean scriptBody = false;
100 private boolean script;
101 private ClassNode superClass;
102 boolean isPrimaryNode;
103
104 // use this to synchronize access for the lazy intit
105 protected Object lazyInitLock = new Object();
106
107 // clazz!=null when resolved
108 protected Class clazz;
109 // only false when this classNode is constructed from a class
110 private boolean lazyInitDone=true;
111 // not null if if the ClassNode is an array
112 private ClassNode componentType = null;
113 // if not null this instance is handled as proxy
114 // for the redirect
115 private ClassNode redirect=null;
116
117 /**
118 * Returns the ClassNode this ClassNode is redirecting to.
119 */
120 protected ClassNode redirect(){
121 if (redirect==null) return this;
122 return redirect.redirect();
123 }
124
125 /**
126 * Sets this instance as proxy for the given ClassNode.
127 * @param cn the class to redirect to. If set to null the redirect will be removed
128 */
129 public void setRedirect(ClassNode cn) {
130 if (isPrimaryNode) throw new GroovyBugError("tried to set a redirect for a primary ClassNode ("+getName()+"->"+cn.getName()+").");
131 if (cn!=null) cn = cn.redirect();
132 redirect = cn;
133 }
134
135 /**
136 * Returns a ClassNode representing an array of the class
137 * represented by this ClassNode
138 */
139 public ClassNode makeArray() {
140 if (redirect!=null) return redirect().makeArray();
141 ClassNode cn;
142 if (clazz!=null) {
143 Class ret = Array.newInstance(clazz,0).getClass();
144 // don't use the ClassHelper here!
145 cn = new ClassNode(ret,this);
146 } else {
147 cn = new ClassNode(this);
148 }
149 return cn;
150 }
151
152 /**
153 * Returns if this instance is a primary ClassNode
154 */
155 public boolean isPrimaryClassNode(){
156 return redirect().isPrimaryNode || (componentType!= null && componentType.isPrimaryClassNode());
157 }
158
159 /**
160 * Constructor used by makeArray() if no real class is available
161 */
162 private ClassNode(ClassNode componentType) {
163 this(componentType.getName()+"[]", ACC_PUBLIC, ClassHelper.OBJECT_TYPE);
164 this.componentType = componentType.redirect();
165 isPrimaryNode=false;
166 }
167
168 /**
169 * Constructor used by makeArray() if a real class is available
170 */
171 private ClassNode(Class c, ClassNode componentType) {
172 this(c);
173 this.componentType = componentType;
174 isPrimaryNode=false;
175 }
176
177 /**
178 * Creates a ClassNode from a real class. The resulting
179 * ClassNode will be no primary ClassNode.
180 */
181 public ClassNode(Class c) {
182 this(c.getName(), c.getModifiers(), null, null ,MixinNode.EMPTY_ARRAY);
183 clazz=c;
184 lazyInitDone=false;
185 CompileUnit cu = getCompileUnit();
186 if (cu!=null) cu.addClass(this);
187 isPrimaryNode=false;
188 }
189
190 /**
191 * The complete class structure will be initialized only when really
192 * needed to avoid having too much objects during compilation
193 */
194 private void lazyClassInit() {
195 synchronized (lazyInitLock) {
196 if (lazyInitDone) return;
197
198 Field[] fields = clazz.getDeclaredFields();
199 for (int i=0;i<fields.length;i++){
200 addField(fields[i].getName(),fields[i].getModifiers(),this,null);
201 }
202 Method[] methods = clazz.getDeclaredMethods();
203 for (int i=0;i<methods.length;i++){
204 Method m = methods[i];
205 MethodNode mn = new MethodNode(m.getName(), m.getModifiers(), ClassHelper.make(m.getReturnType()), createParameters(m.getParameterTypes()), ClassHelper.make(m.getExceptionTypes()), null);
206 addMethod(mn);
207 }
208 Constructor[] constructors = clazz.getDeclaredConstructors();
209 for (int i=0;i<constructors.length;i++){
210 Constructor ctor = constructors[i];
211 addConstructor(ctor.getModifiers(),createParameters(ctor.getParameterTypes()),ClassHelper.make(ctor.getExceptionTypes()),null);
212 }
213 Class sc = clazz.getSuperclass();
214 if (sc!=null) superClass = ClassHelper.make(sc);
215 buildInterfaceTypes(clazz);
216 lazyInitDone=true;
217 }
218 }
219
220 private void buildInterfaceTypes(Class c) {
221 Class[] interfaces = c.getInterfaces();
222 ClassNode[] ret = new ClassNode[interfaces.length];
223 for (int i=0;i<interfaces.length;i++){
224 ret[i] = ClassHelper.make(interfaces[i]);
225 }
226 this.interfaces = ret;
227 }
228
229
230 // added to track the enclosing method for local inner classes
231 private MethodNode enclosingMethod = null;
232
233 public MethodNode getEnclosingMethod() {
234 return redirect().enclosingMethod;
235 }
236
237 public void setEnclosingMethod(MethodNode enclosingMethod) {
238 redirect().enclosingMethod = enclosingMethod;
239 }
240
241
242 /**
243 * @param name is the full name of the class
244 * @param modifiers the modifiers,
245 * @param superClass the base class name - use "java.lang.Object" if no direct
246 * base class
247 * @see org.objectweb.asm.Opcodes
248 */
249 public ClassNode(String name, int modifiers, ClassNode superClass) {
250 this(name, modifiers, superClass, ClassHelper.EMPTY_TYPE_ARRAY, MixinNode.EMPTY_ARRAY);
251 }
252
253 /**
254 * @param name is the full name of the class
255 * @param modifiers the modifiers,
256 * @param superClass the base class name - use "java.lang.Object" if no direct
257 * base class
258 * @see org.objectweb.asm.Opcodes
259 */
260 public ClassNode(String name, int modifiers, ClassNode superClass, ClassNode[] interfaces, MixinNode[] mixins) {
261 this.name = name;
262 this.modifiers = modifiers;
263 this.superClass = superClass;
264 this.interfaces = interfaces;
265 this.mixins = mixins;
266 isPrimaryNode = true;
267 }
268
269
270 /**
271 * Sets the superclass of this ClassNode
272 */
273 public void setSuperClass(ClassNode superClass) {
274 redirect().superClass = superClass;
275 }
276
277 /**
278 * Returns a list containing FieldNode objects for
279 * each field in the class represented by this ClassNode
280 */
281 public List getFields() {
282 if (!lazyInitDone) {
283 lazyClassInit();
284 }
285 if (redirect!=null) return redirect().getFields();
286 return fields;
287 }
288
289 /**
290 * Returns an array of ClassNodes representing the
291 * interfaces the class implements
292 */
293 public ClassNode[] getInterfaces() {
294 if (!lazyInitDone) {
295 lazyClassInit();
296 }
297 if (redirect!=null) return redirect().getInterfaces();
298 return interfaces;
299 }
300
301 public MixinNode[] getMixins() {
302 return redirect().mixins;
303 }
304
305 /**
306 * Returns a list containing MethodNode objects for
307 * each method in the class represented by this ClassNode
308 */
309 public List getMethods() {
310 if (!lazyInitDone) {
311 lazyClassInit();
312 }
313 if (redirect!=null) return redirect().getMethods();
314 return methods;
315 }
316
317 /**
318 * Returns a list containing MethodNode objects for
319 * each abstract method in the class represented by
320 * this ClassNode
321 */
322 public List getAbstractMethods() {
323
324 HashSet abstractNodes = new HashSet();
325 // let us collect the abstract super classes and stop at the
326 // first non abstract super class. If such a class still
327 // contains abstract methods, then loading that class will fail.
328 // No need to be extra carefull here for that.
329 ClassNode parent = this.redirect();
330 do {
331 abstractNodes.add(parent);
332 ClassNode[] interfaces = parent.getInterfaces();
333 for (int i = 0; i < interfaces.length; i++) {
334 abstractNodes.add(interfaces[i].redirect());
335 }
336 parent = parent.getSuperClass().redirect();
337 } while (parent!=null && ((parent.getModifiers() & Opcodes.ACC_ABSTRACT) != 0));
338
339 List result = new ArrayList();
340 for (Iterator methIt = getAllDeclaredMethods().iterator(); methIt.hasNext();) {
341 MethodNode method = (MethodNode) methIt.next();
342 // add only abstract methods from abtract classes that
343 // are not overwritten
344 if ( abstractNodes.contains(method.getDeclaringClass().redirect()) &&
345 (method.getModifiers() & Opcodes.ACC_ABSTRACT) != 0
346 ) {
347 result.add(method);
348 }
349 }
350 if (result.size() == 0) {
351 return null;
352 }
353 else {
354 return result;
355 }
356 }
357
358 public List getAllDeclaredMethods() {
359 return new ArrayList(getDeclaredMethodsMap().values());
360 }
361
362
363 protected Map getDeclaredMethodsMap() {
364 // Start off with the methods from the superclass.
365 ClassNode parent = getSuperClass();
366 Map result = null;
367 if (parent != null) {
368 result = parent.getDeclaredMethodsMap();
369 }
370 else {
371 result = new HashMap();
372 }
373
374 // add in unimplemented abstract methods from the interfaces
375 ClassNode[] interfaces = getInterfaces();
376 for (int i = 0; i < interfaces.length; i++) {
377 ClassNode iface = interfaces[i];
378 Map ifaceMethodsMap = iface.getDeclaredMethodsMap();
379 for (Iterator iter = ifaceMethodsMap.keySet().iterator(); iter.hasNext();) {
380 String methSig = (String) iter.next();
381 if (!result.containsKey(methSig)) {
382 MethodNode methNode = (MethodNode) ifaceMethodsMap.get(methSig);
383 result.put(methSig, methNode);
384 }
385 }
386 }
387
388 // And add in the methods implemented in this class.
389 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
390 MethodNode method = (MethodNode) iter.next();
391 String sig = method.getTypeDescriptor();
392 result.put(sig, method);
393 }
394 return result;
395 }
396
397 public String getName() {
398 return redirect().name;
399 }
400
401 public String setName(String name) {
402 return redirect().name=name;
403 }
404
405 public int getModifiers() {
406 return redirect().modifiers;
407 }
408
409 public List getProperties() {
410 return redirect().properties;
411 }
412
413 public List getDeclaredConstructors() {
414 if (!lazyInitDone) {
415 lazyClassInit();
416 }
417 return redirect().constructors;
418 }
419
420 public ModuleNode getModule() {
421 return redirect().module;
422 }
423
424 public void setModule(ModuleNode module) {
425 redirect().module = module;
426 if (module != null) {
427 redirect().compileUnit = module.getUnit();
428 }
429 }
430
431 public void addField(FieldNode node) {
432 node.setDeclaringClass(redirect());
433 node.setOwner(redirect());
434 redirect().fields.add(node);
435 redirect().fieldIndex.put(node.getName(), node);
436 }
437
438 public void addProperty(PropertyNode node) {
439 node.setDeclaringClass(redirect());
440 FieldNode field = node.getField();
441 addField(field);
442
443 redirect().properties.add(node);
444 }
445
446 public PropertyNode addProperty(String name,
447 int modifiers,
448 ClassNode type,
449 Expression initialValueExpression,
450 Statement getterBlock,
451 Statement setterBlock) {
452 for (Iterator iter = getProperties().iterator(); iter.hasNext();) {
453 PropertyNode pn = (PropertyNode) iter.next();
454 if (pn.getName().equals(name)) return pn;
455 }
456 PropertyNode node =
457 new PropertyNode(name, modifiers, type, redirect(), initialValueExpression, getterBlock, setterBlock);
458 addProperty(node);
459 return node;
460 }
461
462 public void addConstructor(ConstructorNode node) {
463 node.setDeclaringClass(this);
464 redirect().constructors.add(node);
465 }
466
467 public ConstructorNode addConstructor(int modifiers, Parameter[] parameters, ClassNode[] exceptions, Statement code) {
468 ConstructorNode node = new ConstructorNode(modifiers, parameters, exceptions, code);
469 addConstructor(node);
470 return node;
471 }
472
473 public void addMethod(MethodNode node) {
474 node.setDeclaringClass(this);
475 redirect().methods.add(node);
476 }
477
478 /**
479 * IF a method with the given name and parameters is already defined then it is returned
480 * otherwise the given method is added to this node. This method is useful for
481 * default method adding like getProperty() or invokeMethod() where there may already
482 * be a method defined in a class and so the default implementations should not be added
483 * if already present.
484 */
485 public MethodNode addMethod(String name,
486 int modifiers,
487 ClassNode returnType,
488 Parameter[] parameters,
489 ClassNode[] exceptions,
490 Statement code) {
491 MethodNode other = getDeclaredMethod(name, parameters);
492 // lets not add duplicate methods
493 if (other != null) {
494 return other;
495 }
496 MethodNode node = new MethodNode(name, modifiers, returnType, parameters, exceptions, code);
497 addMethod(node);
498 return node;
499 }
500
501 /**
502 * Adds a synthetic method as part of the compilation process
503 */
504 public MethodNode addSyntheticMethod(String name,
505 int modifiers,
506 ClassNode returnType,
507 Parameter[] parameters,
508 ClassNode[] exceptions,
509 Statement code) {
510 MethodNode answer = addMethod(name, modifiers, returnType, parameters, exceptions, code);
511 answer.setSynthetic(true);
512 return answer;
513 }
514
515 public FieldNode addField(String name, int modifiers, ClassNode type, Expression initialValue) {
516 FieldNode node = new FieldNode(name, modifiers, type, redirect(), initialValue);
517 addField(node);
518 return node;
519 }
520
521 public void addInterface(ClassNode type) {
522 // lets check if it already implements an interface
523 boolean skip = false;
524 ClassNode[] interfaces = redirect().interfaces;
525 for (int i = 0; i < interfaces.length; i++) {
526 if (type.equals(interfaces[i])) {
527 skip = true;
528 }
529 }
530 if (!skip) {
531 ClassNode[] newInterfaces = new ClassNode[interfaces.length + 1];
532 System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
533 newInterfaces[interfaces.length] = type;
534 redirect().interfaces = newInterfaces;
535 }
536 }
537
538 public boolean equals(Object o) {
539 if (redirect!=null) return redirect().equals(o);
540 ClassNode cn = (ClassNode) o;
541 return (cn.getName().equals(getName()));
542 }
543
544 public void addMixin(MixinNode mixin) {
545 // lets check if it already uses a mixin
546 MixinNode[] mixins = redirect().mixins;
547 boolean skip = false;
548 for (int i = 0; i < mixins.length; i++) {
549 if (mixin.equals(mixins[i])) {
550 skip = true;
551 }
552 }
553 if (!skip) {
554 MixinNode[] newMixins = new MixinNode[mixins.length + 1];
555 System.arraycopy(mixins, 0, newMixins, 0, mixins.length);
556 newMixins[mixins.length] = mixin;
557 redirect().mixins = newMixins;
558 }
559 }
560
561 public FieldNode getField(String name) {
562 return (FieldNode) redirect().fieldIndex.get(name);
563 }
564
565 /**
566 * @return the field node on the outer class or null if this is not an
567 * inner class
568 */
569 public FieldNode getOuterField(String name) {
570 return null;
571 }
572
573 /**
574 * Helper method to avoid casting to inner class
575 */
576 public ClassNode getOuterClass() {
577 return null;
578 }
579
580 public void addObjectInitializerStatements(Statement statements) {
581 objectInitializers.add(statements);
582 }
583
584 public List getObjectInitializerStatements() {
585 return objectInitializers;
586 }
587
588 public void addStaticInitializerStatements(List staticStatements, boolean fieldInit) {
589 MethodNode method = null;
590 List declaredMethods = getDeclaredMethods("<clinit>");
591 if (declaredMethods.isEmpty()) {
592 method =
593 addMethod("<clinit>", ACC_PUBLIC | ACC_STATIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement());
594 method.setSynthetic(true);
595 }
596 else {
597 method = (MethodNode) declaredMethods.get(0);
598 }
599 BlockStatement block = null;
600 Statement statement = method.getCode();
601 if (statement == null) {
602 block = new BlockStatement();
603 }
604 else if (statement instanceof BlockStatement) {
605 block = (BlockStatement) statement;
606 }
607 else {
608 block = new BlockStatement();
609 block.addStatement(statement);
610 }
611
612 // while anything inside a static initializer block is appended
613 // we don't want to append in the case we have a initialization
614 // expression of a static field. In that case we want to add
615 // before the other statements
616 if (!fieldInit) {
617 block.addStatements(staticStatements);
618 } else {
619 List blockStatements = block.getStatements();
620 staticStatements.addAll(blockStatements);
621 blockStatements.clear();
622 blockStatements.addAll(staticStatements);
623 }
624 }
625
626 /**
627 * @return a list of methods which match the given name
628 */
629 public List getDeclaredMethods(String name) {
630 List answer = new ArrayList();
631 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
632 MethodNode method = (MethodNode) iter.next();
633 if (name.equals(method.getName())) {
634 answer.add(method);
635 }
636 }
637 return answer;
638 }
639
640 /**
641 * @return a list of methods which match the given name
642 */
643 public List getMethods(String name) {
644 List answer = new ArrayList();
645 ClassNode node = this;
646 do {
647 for (Iterator iter = node.getMethods().iterator(); iter.hasNext();) {
648 MethodNode method = (MethodNode) iter.next();
649 if (name.equals(method.getName())) {
650 answer.add(method);
651 }
652 }
653 node = node.getSuperClass();
654 }
655 while (node != null);
656 return answer;
657 }
658
659 /**
660 * @return the method matching the given name and parameters or null
661 */
662 public MethodNode getDeclaredMethod(String name, Parameter[] parameters) {
663 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
664 MethodNode method = (MethodNode) iter.next();
665 if (name.equals(method.getName()) && parametersEqual(method.getParameters(), parameters)) {
666 return method;
667 }
668 }
669 return null;
670 }
671
672 /**
673 * @return true if this node is derived from the given class node
674 */
675 public boolean isDerivedFrom(ClassNode type) {
676 ClassNode node = this;
677 while (node != null) {
678 if (type.equals(node)) {
679 return true;
680 }
681 node = node.getSuperClass();
682 }
683 return false;
684 }
685
686 /**
687 * @return true if this class is derived from a groovy object
688 * i.e. it implements GroovyObject
689 */
690 public boolean isDerivedFromGroovyObject() {
691 return implementsInterface(GroovyObject.class.getName());
692 }
693
694 /**
695 * @param name the fully qualified name of the interface
696 * @return true if this class or any base class implements the given interface
697 */
698 public boolean implementsInterface(String name) {
699 ClassNode node = redirect();
700 do {
701 if (node.declaresInterface(name)) {
702 return true;
703 }
704 node = node.getSuperClass();
705 }
706 while (node != null);
707 return false;
708 }
709
710 /**
711 * @param name the fully qualified name of the interface
712 * @return true if this class declares that it implements the given interface
713 */
714 public boolean declaresInterface(String name) {
715 ClassNode[] interfaces = redirect().getInterfaces();
716 int size = interfaces.length;
717 for (int i = 0; i < size; i++) {
718 if (interfaces[i].getName().equals(name)) {
719 return true;
720 }
721 }
722 return false;
723 }
724
725 /**
726 * @return the ClassNode of the super class of this type
727 */
728 public ClassNode getSuperClass() {
729 if (!lazyInitDone && !isResolved()) {
730 throw new GroovyBugError("Classnode#getSuperClass for "+getName()+" called before class resolving");
731 }
732 return redirect().getUnresolvedSuperClass();
733 }
734
735 public ClassNode getUnresolvedSuperClass() {
736 if (!lazyInitDone) {
737 lazyClassInit();
738 }
739 return redirect().superClass;
740 }
741
742 /**
743 * Factory method to create a new MethodNode via reflection
744 */
745 protected MethodNode createMethodNode(Method method) {
746 Parameter[] parameters = createParameters(method.getParameterTypes());
747 return new MethodNode(method.getName(), method.getModifiers(), ClassHelper.make(method.getReturnType()), parameters, ClassHelper.make(method.getExceptionTypes()), EmptyStatement.INSTANCE);
748 }
749
750 /**
751 * @param types
752 */
753 protected Parameter[] createParameters(Class[] types) {
754 Parameter[] parameters = Parameter.EMPTY_ARRAY;
755 int size = types.length;
756 if (size > 0) {
757 parameters = new Parameter[size];
758 for (int i = 0; i < size; i++) {
759 parameters[i] = createParameter(types[i], i);
760 }
761 }
762 return parameters;
763 }
764
765 protected Parameter createParameter(Class parameterType, int idx) {
766 return new Parameter(ClassHelper.make(parameterType), "param" + idx);
767 }
768
769 public CompileUnit getCompileUnit() {
770 if (redirect!=null) return redirect().getCompileUnit();
771 if (compileUnit == null && module != null) {
772 compileUnit = module.getUnit();
773 }
774 return compileUnit;
775 }
776
777 protected void setCompileUnit(CompileUnit cu) {
778 if (redirect!=null) redirect().setCompileUnit(cu);
779 if (compileUnit!= null) compileUnit = cu;
780 }
781
782 /**
783 * @return true if the two arrays are of the same size and have the same contents
784 */
785 protected boolean parametersEqual(Parameter[] a, Parameter[] b) {
786 if (a.length == b.length) {
787 boolean answer = true;
788 for (int i = 0; i < a.length; i++) {
789 if (!a[i].getType().equals(b[i].getType())) {
790 answer = false;
791 break;
792 }
793 }
794 return answer;
795 }
796 return false;
797 }
798
799 /**
800 * @return the package name of this class
801 */
802 public String getPackageName() {
803 int idx = getName().lastIndexOf('.');
804 if (idx > 0) {
805 return getName().substring(0, idx);
806 }
807 return null;
808 }
809
810 public String getNameWithoutPackage() {
811 int idx = getName().lastIndexOf('.');
812 if (idx > 0) {
813 return getName().substring(idx + 1);
814 }
815 return getName();
816 }
817
818 public void visitContents(GroovyClassVisitor visitor) {
819
820 // now lets visit the contents of the class
821 for (Iterator iter = getProperties().iterator(); iter.hasNext();) {
822 PropertyNode pn = (PropertyNode) iter.next();
823 visitor.visitProperty(pn);
824 }
825
826 for (Iterator iter = getFields().iterator(); iter.hasNext();) {
827 FieldNode fn = (FieldNode) iter.next();
828 visitor.visitField(fn);
829 }
830
831 for (Iterator iter = getDeclaredConstructors().iterator(); iter.hasNext();) {
832 ConstructorNode cn = (ConstructorNode) iter.next();
833 visitor.visitConstructor(cn);
834 }
835
836 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
837 MethodNode mn = (MethodNode) iter.next();
838 visitor.visitMethod(mn);
839 }
840 }
841
842 public MethodNode getGetterMethod(String getterName) {
843 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
844 MethodNode method = (MethodNode) iter.next();
845 if (getterName.equals(method.getName())
846 && ClassHelper.VOID_TYPE!=method.getReturnType()
847 && method.getParameters().length == 0) {
848 return method;
849 }
850 }
851 return null;
852 }
853
854 public MethodNode getSetterMethod(String getterName) {
855 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
856 MethodNode method = (MethodNode) iter.next();
857 if (getterName.equals(method.getName())
858 && ClassHelper.VOID_TYPE==method.getReturnType()
859 && method.getParameters().length == 1) {
860 return method;
861 }
862 }
863 return null;
864 }
865
866 /**
867 * Is this class delcared in a static method (such as a closure / inner class declared in a static method)
868 */
869 public boolean isStaticClass() {
870 return redirect().staticClass;
871 }
872
873 public void setStaticClass(boolean staticClass) {
874 redirect().staticClass = staticClass;
875 }
876
877 /**
878 * @return Returns true if this inner class or closure was declared inside a script body
879 */
880 public boolean isScriptBody() {
881 return redirect().scriptBody;
882 }
883
884 public void setScriptBody(boolean scriptBody) {
885 redirect().scriptBody = scriptBody;
886 }
887
888 public boolean isScript() {
889 return redirect().script || isDerivedFrom(ClassHelper.SCRIPT_TYPE);
890 }
891
892 public void setScript(boolean script) {
893 redirect().script = script;
894 }
895
896 public String toString() {
897 return super.toString() + "[name: " + getName() + "]";
898 }
899
900 /**
901 * Returns true if the given method has a possibly matching method with the given name and arguments
902 */
903 public boolean hasPossibleMethod(String name, Expression arguments) {
904 int count = 0;
905
906 if (arguments instanceof TupleExpression) {
907 TupleExpression tuple = (TupleExpression) arguments;
908 // TODO this won't strictly be true when using list expension in argument calls
909 count = tuple.getExpressions().size();
910 }
911 ClassNode node = this;
912 do {
913 for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
914 MethodNode method = (MethodNode) iter.next();
915 if (name.equals(method.getName()) && method.getParameters().length == count) {
916 return true;
917 }
918 }
919 node = node.getSuperClass();
920 }
921 while (node != null);
922 return false;
923 }
924
925 public boolean isInterface(){
926 return (getModifiers() & Opcodes.ACC_INTERFACE) > 0;
927 }
928
929 public boolean isResolved(){
930 return redirect().clazz!=null || (componentType != null && componentType.isResolved());
931 }
932
933 public boolean isArray(){
934 return componentType!=null;
935 }
936
937 public ClassNode getComponentType() {
938 return componentType;
939 }
940
941 public Class getTypeClass(){
942 Class c = redirect().clazz;
943 if (c!=null) return c;
944 ClassNode component = redirect().componentType;
945 if (component!=null && component.isResolved()){
946 ClassNode cn = component.makeArray();
947 setRedirect(cn);
948 return redirect().clazz;
949 }
950 throw new GroovyBugError("ClassNode#getTypeClass for "+getName()+" is called before the type class is set ");
951 }
952
953 public boolean hasPackageName(){
954 return redirect().name.indexOf('.')>0;
955 }
956 }