View Javadoc

    1   /*
    2    * $Id: ClassNode.java,v 1.73 2006/06/25 19:58:43 blackdrag Exp $
    3    *
    4    * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5    *
    6    * Redistribution and use of this software and associated documentation
    7    * ("Software"), with or without modification, are permitted provided that the
    8    * following conditions are met:
    9    *  1. Redistributions of source code must retain copyright statements and
    10   * notices. Redistributions must also contain a copy of this document.
    11   *  2. Redistributions in binary form must reproduce the above copyright
    12   * notice, this list of conditions and the following disclaimer in the
    13   * documentation and/or other materials provided with the distribution.
    14   *  3. The name "groovy" must not be used to endorse or promote products
    15   * derived from this Software without prior written permission of The Codehaus.
    16   * For written permission, please contact info@codehaus.org.
    17   *  4. Products derived from this Software may not be called "groovy" nor may
    18   * "groovy" appear in their names without prior written permission of The
    19   * Codehaus. "groovy" is a registered trademark of The Codehaus.
    20   *  5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
    21   *
    22   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
    23   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    24   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    25   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
    26   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    27   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    28   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    29   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    30   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    31   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
    32   * DAMAGE.
    33   *
    34   */
    35  package org.codehaus.groovy.ast;
    36
    37  import groovy.lang.GroovyObject;
    38
    39  import org.codehaus.groovy.GroovyBugError;
    40  import org.codehaus.groovy.ast.expr.Expression;
    41  import org.codehaus.groovy.ast.expr.TupleExpression;
    42  import org.codehaus.groovy.ast.stmt.BlockStatement;
    43  import org.codehaus.groovy.ast.stmt.EmptyStatement;
    44  import org.codehaus.groovy.ast.stmt.Statement;
    45  import org.objectweb.asm.Opcodes;
    46
    47  import java.lang.reflect.Array;
    48  import java.lang.reflect.Constructor;
    49  import java.lang.reflect.Field;
    50  import java.lang.reflect.Method;
    51  import java.util.ArrayList;
    52  import java.util.HashMap;
    53  import java.util.HashSet;
    54  import java.util.Iterator;
    55  import java.util.List;
    56  import java.util.Map;
    57
    58  /***
    59   * Represents a class in the AST.<br/>
    60   * A ClassNode should be created using the methods in ClassHelper.
    61   * This ClassNode may be used to represent a class declaration or
    62   * any other type. This class uses a proxy meschanism allowing to
    63   * create a class for a plain name at ast creation time. In another
    64   * phase of the compiler the real ClassNode for the plain name may be
    65   * found. To avoid the need of exchanging this ClassNode with an
    66   * instance of the correct ClassNode the correct ClassNode is set as
    67   * redirect. All method calls are then redirected to that ClassNode.
    68   * <br>
    69   * Note: the proxy mechanism is only allowed for classes being marked
    70   * as primary ClassNode which means they represent no actual class.
    71   * The redirect itself can be any type of ClassNode
    72   *
    73   * @see org.codehaus.groovy.ast.ClassHelper
    74   *
    75   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
    76   * @author Jochen Theodorou
    77   * @version $Revision: 1.73 $
    78   */
    79  public class ClassNode extends AnnotatedNode implements Opcodes {
    80
    81  	public static ClassNode[] EMPTY_ARRAY = new ClassNode[0];
    82
    83      public static ClassNode THIS = new ClassNode(Object.class);
    84      public static ClassNode SUPER = new ClassNode(Object.class);
    85
    86      private String name;
    87      private int modifiers;
    88      private ClassNode[] interfaces;
    89      private MixinNode[] mixins;
    90      private List constructors = new ArrayList();
    91      private List  objectInitializers = new ArrayList();
    92      private List methods = new ArrayList();
    93      private List fields = new ArrayList();
    94      private List properties = new ArrayList();
    95      private Map fieldIndex = new HashMap();
    96      private ModuleNode module;
    97      private CompileUnit compileUnit;
    98      private boolean staticClass = false;
    99      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.getConstructors();
    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             if (result.containsKey(sig)) {
    393                 MethodNode inheritedMethod = (MethodNode) result.get(sig);
    394                 if (inheritedMethod.isAbstract()) {
    395                     result.put(sig, method);
    396                 }
    397             }
    398             else {
    399                 result.put(sig, method);
    400             }
    401         }
    402         return result;
    403     }
    404
    405     public String getName() {
    406         return redirect().name;
    407     }
    408
    409     public String setName(String name) {
    410         return redirect().name=name;
    411     }
    412
    413     public int getModifiers() {
    414         return redirect().modifiers;
    415     }
    416
    417     public List getProperties() {
    418         return redirect().properties;
    419     }
    420
    421     public List getDeclaredConstructors() {
    422         if (!lazyInitDone) {
    423             lazyClassInit();
    424         }
    425         return redirect().constructors;
    426     }
    427
    428     public ModuleNode getModule() {
    429         return redirect().module;
    430     }
    431
    432     public void setModule(ModuleNode module) {
    433         redirect().module = module;
    434         if (module != null) {
    435             redirect().compileUnit = module.getUnit();
    436         }
    437     }
    438
    439     public void addField(FieldNode node) {
    440         node.setDeclaringClass(redirect());
    441         node.setOwner(redirect());
    442         redirect().fields.add(node);
    443         redirect().fieldIndex.put(node.getName(), node);
    444     }
    445
    446     public void addProperty(PropertyNode node) {
    447         node.setDeclaringClass(redirect());
    448         FieldNode field = node.getField();
    449         addField(field);
    450
    451         redirect().properties.add(node);
    452     }
    453
    454     public PropertyNode addProperty(String name,
    455                                     int modifiers,
    456                                     ClassNode type,
    457                                     Expression initialValueExpression,
    458                                     Statement getterBlock,
    459                                     Statement setterBlock) {
    460     	for (Iterator iter = getProperties().iterator(); iter.hasNext();) {
    461 			PropertyNode pn = (PropertyNode) iter.next();
    462 			if (pn.getName().equals(name)) return pn;
    463 		}
    464         PropertyNode node =
    465                 new PropertyNode(name, modifiers, type, redirect(), initialValueExpression, getterBlock, setterBlock);
    466         addProperty(node);
    467         return node;
    468     }
    469
    470     public void addConstructor(ConstructorNode node) {
    471         node.setDeclaringClass(this);
    472         redirect().constructors.add(node);
    473     }
    474
    475     public ConstructorNode addConstructor(int modifiers, Parameter[] parameters, ClassNode[] exceptions, Statement code) {
    476         ConstructorNode node = new ConstructorNode(modifiers, parameters, exceptions, code);
    477         addConstructor(node);
    478         return node;
    479     }
    480
    481     public void addMethod(MethodNode node) {
    482         node.setDeclaringClass(this);
    483         redirect().methods.add(node);
    484     }
    485
    486     /***
    487      * IF a method with the given name and parameters is already defined then it is returned
    488      * otherwise the given method is added to this node. This method is useful for
    489      * default method adding like getProperty() or invokeMethod() where there may already
    490      * be a method defined in a class and  so the default implementations should not be added
    491      * if already present.
    492      */
    493     public MethodNode addMethod(String name,
    494                                 int modifiers,
    495                                 ClassNode returnType,
    496                                 Parameter[] parameters,
    497                                 ClassNode[] exceptions,
    498                                 Statement code) {
    499         MethodNode other = getDeclaredMethod(name, parameters);
    500         // lets not add duplicate methods
    501         if (other != null) {
    502             return other;
    503         }
    504         MethodNode node = new MethodNode(name, modifiers, returnType, parameters, exceptions, code);
    505         addMethod(node);
    506         return node;
    507     }
    508
    509     /***
    510      * Adds a synthetic method as part of the compilation process
    511      */
    512     public MethodNode addSyntheticMethod(String name,
    513                                          int modifiers,
    514                                          ClassNode returnType,
    515                                          Parameter[] parameters,
    516                                          ClassNode[] exceptions,
    517                                          Statement code) {
    518         MethodNode answer = addMethod(name, modifiers, returnType, parameters, exceptions, code);
    519         answer.setSynthetic(true);
    520         return answer;
    521     }
    522
    523     public FieldNode addField(String name, int modifiers, ClassNode type, Expression initialValue) {
    524         FieldNode node = new FieldNode(name, modifiers, type, redirect(), initialValue);
    525         addField(node);
    526         return node;
    527     }
    528
    529     public void addInterface(ClassNode type) {
    530         // lets check if it already implements an interface
    531         boolean skip = false;
    532         ClassNode[] interfaces = redirect().interfaces;
    533         for (int i = 0; i < interfaces.length; i++) {
    534             if (type.equals(interfaces[i])) {
    535                 skip = true;
    536             }
    537         }
    538         if (!skip) {
    539             ClassNode[] newInterfaces = new ClassNode[interfaces.length + 1];
    540             System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
    541             newInterfaces[interfaces.length] = type;
    542             redirect().interfaces = newInterfaces;
    543         }
    544     }
    545
    546     public boolean equals(Object o) {
    547         if (redirect!=null) return redirect().equals(o);
    548         ClassNode cn = (ClassNode) o;
    549         return (cn.getName().equals(getName()));
    550     }
    551
    552     public void addMixin(MixinNode mixin) {
    553         // lets check if it already uses a mixin
    554         MixinNode[] mixins = redirect().mixins;
    555         boolean skip = false;
    556         for (int i = 0; i < mixins.length; i++) {
    557             if (mixin.equals(mixins[i])) {
    558                 skip = true;
    559             }
    560         }
    561         if (!skip) {
    562             MixinNode[] newMixins = new MixinNode[mixins.length + 1];
    563             System.arraycopy(mixins, 0, newMixins, 0, mixins.length);
    564             newMixins[mixins.length] = mixin;
    565             redirect().mixins = newMixins;
    566         }
    567     }
    568
    569     public FieldNode getField(String name) {
    570         return (FieldNode) redirect().fieldIndex.get(name);
    571     }
    572
    573     /***
    574      * @return the field node on the outer class or null if this is not an
    575      *         inner class
    576      */
    577     public FieldNode getOuterField(String name) {
    578         return null;
    579     }
    580
    581     /***
    582      * Helper method to avoid casting to inner class
    583      *
    584      * @return
    585      */
    586     public ClassNode getOuterClass() {
    587         return null;
    588     }
    589
    590     public void addObjectInitializerStatements(Statement statements) {
    591         objectInitializers.add(statements);
    592     }
    593
    594     public List getObjectInitializerStatements() {
    595         return objectInitializers;
    596     }
    597
    598     public void addStaticInitializerStatements(List staticStatements, boolean fieldInit) {
    599         MethodNode method = null;
    600         List declaredMethods = getDeclaredMethods("<clinit>");
    601         if (declaredMethods.isEmpty()) {
    602             method =
    603                     addMethod("<clinit>", ACC_PUBLIC | ACC_STATIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement());
    604             method.setSynthetic(true);
    605         }
    606         else {
    607             method = (MethodNode) declaredMethods.get(0);
    608         }
    609         BlockStatement block = null;
    610         Statement statement = method.getCode();
    611         if (statement == null) {
    612             block = new BlockStatement();
    613         }
    614         else if (statement instanceof BlockStatement) {
    615             block = (BlockStatement) statement;
    616         }
    617         else {
    618             block = new BlockStatement();
    619             block.addStatement(statement);
    620         }
    621
    622         // while anything inside a static initializer block is appended
    623         // we don't want to append in the case we have a initialization
    624         // expression of a static field. In that case we want to add
    625         // before the other statements
    626         if (!fieldInit) {
    627             block.addStatements(staticStatements);
    628         } else {
    629             List blockStatements = block.getStatements();
    630             staticStatements.addAll(blockStatements);
    631             blockStatements.clear();
    632             blockStatements.addAll(staticStatements);
    633         }
    634     }
    635
    636     /***
    637      * @return a list of methods which match the given name
    638      */
    639     public List getDeclaredMethods(String name) {
    640         List answer = new ArrayList();
    641         for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
    642             MethodNode method = (MethodNode) iter.next();
    643             if (name.equals(method.getName())) {
    644                 answer.add(method);
    645             }
    646         }
    647         return answer;
    648     }
    649
    650     /***
    651      * @return a list of methods which match the given name
    652      */
    653     public List getMethods(String name) {
    654         List answer = new ArrayList();
    655         ClassNode node = this;
    656         do {
    657             for (Iterator iter = node.getMethods().iterator(); iter.hasNext();) {
    658                 MethodNode method = (MethodNode) iter.next();
    659                 if (name.equals(method.getName())) {
    660                     answer.add(method);
    661                 }
    662             }
    663             node = node.getSuperClass();
    664         }
    665         while (node != null);
    666         return answer;
    667     }
    668
    669     /***
    670      * @return the method matching the given name and parameters or null
    671      */
    672     public MethodNode getDeclaredMethod(String name, Parameter[] parameters) {
    673         for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
    674             MethodNode method = (MethodNode) iter.next();
    675             if (name.equals(method.getName()) && parametersEqual(method.getParameters(), parameters)) {
    676                 return method;
    677             }
    678         }
    679         return null;
    680     }
    681
    682     /***
    683      * @return true if this node is derived from the given class node
    684      */
    685     public boolean isDerivedFrom(ClassNode type) {
    686         ClassNode node = getSuperClass();
    687         while (node != null) {
    688             if (type.equals(node)) {
    689                 return true;
    690             }
    691             node = node.getSuperClass();
    692         }
    693         return false;
    694     }
    695
    696     /***
    697      * @return true if this class is derived from a groovy object
    698      *         i.e. it implements GroovyObject
    699      */
    700     public boolean isDerivedFromGroovyObject() {
    701         return implementsInteface(GroovyObject.class.getName());
    702     }
    703
    704     /***
    705      * @param name the fully qualified name of the interface
    706      * @return true if this class or any base class implements the given interface
    707      */
    708     public boolean implementsInteface(String name) {
    709         ClassNode node = redirect();
    710         do {
    711             if (node.declaresInterface(name)) {
    712                 return true;
    713             }
    714             node = node.getSuperClass();
    715         }
    716         while (node != null);
    717         return false;
    718     }
    719
    720     /***
    721      * @param name the fully qualified name of the interface
    722      * @return true if this class declares that it implements the given interface
    723      */
    724     public boolean declaresInterface(String name) {
    725         ClassNode[] interfaces = redirect().getInterfaces();
    726         int size = interfaces.length;
    727         for (int i = 0; i < size; i++) {
    728             if (interfaces[i].getName().equals(name)) {
    729                 return true;
    730             }
    731         }
    732         return false;
    733     }
    734
    735     /***
    736      * @return the ClassNode of the super class of this type
    737      */
    738     public ClassNode getSuperClass() {
    739         if (!lazyInitDone && !isResolved()) {
    740             throw new GroovyBugError("Classnode#getSuperClass for "+getName()+" called before class resolving");
    741         }
    742         return getUnresolvedSuperClass();
    743     }
    744
    745     public ClassNode getUnresolvedSuperClass() {
    746         if (!lazyInitDone) {
    747             lazyClassInit();
    748         }
    749         return redirect().superClass;
    750     }
    751
    752     /***
    753      * Factory method to create a new MethodNode via reflection
    754      */
    755     protected MethodNode createMethodNode(Method method) {
    756         Parameter[] parameters = createParameters(method.getParameterTypes());
    757         return new MethodNode(method.getName(), method.getModifiers(), ClassHelper.make(method.getReturnType()), parameters, ClassHelper.make(method.getExceptionTypes()), EmptyStatement.INSTANCE);
    758     }
    759
    760     /***
    761      * @param types
    762      * @return
    763      */
    764     protected Parameter[] createParameters(Class[] types) {
    765         Parameter[] parameters = Parameter.EMPTY_ARRAY;
    766         int size = types.length;
    767         if (size > 0) {
    768             parameters = new Parameter[size];
    769             for (int i = 0; i < size; i++) {
    770                 parameters[i] = createParameter(types[i], i);
    771             }
    772         }
    773         return parameters;
    774     }
    775
    776     protected Parameter createParameter(Class parameterType, int idx) {
    777         return new Parameter(ClassHelper.make(parameterType), "param" + idx);
    778     }
    779
    780     public CompileUnit getCompileUnit() {
    781         if (redirect!=null) return redirect().getCompileUnit();
    782         if (compileUnit == null && module != null) {
    783             compileUnit = module.getUnit();
    784         }
    785         return compileUnit;
    786     }
    787
    788     protected void setCompileUnit(CompileUnit cu) {
    789         if (redirect!=null) redirect().setCompileUnit(cu);
    790         if (compileUnit!= null) compileUnit = cu;
    791     }
    792
    793     /***
    794      * @return true if the two arrays are of the same size and have the same contents
    795      */
    796     protected boolean parametersEqual(Parameter[] a, Parameter[] b) {
    797         if (a.length == b.length) {
    798             boolean answer = true;
    799             for (int i = 0; i < a.length; i++) {
    800                 if (!a[i].getType().equals(b[i].getType())) {
    801                     answer = false;
    802                     break;
    803                 }
    804             }
    805             return answer;
    806         }
    807         return false;
    808     }
    809
    810     /***
    811      * @return the package name of this class
    812      */
    813     public String getPackageName() {
    814         int idx = getName().lastIndexOf('.');
    815         if (idx > 0) {
    816             return getName().substring(0, idx);
    817         }
    818         return null;
    819     }
    820
    821     public String getNameWithoutPackage() {
    822         int idx = getName().lastIndexOf('.');
    823         if (idx > 0) {
    824             return getName().substring(idx + 1);
    825         }
    826         return getName();
    827     }
    828
    829     public void visitContents(GroovyClassVisitor visitor) {
    830
    831         // now lets visit the contents of the class
    832         for (Iterator iter = getProperties().iterator(); iter.hasNext();) {
    833             PropertyNode pn = (PropertyNode) iter.next();
    834             visitor.visitProperty(pn);
    835         }
    836
    837         for (Iterator iter = getFields().iterator(); iter.hasNext();) {
    838             FieldNode fn = (FieldNode) iter.next();
    839             visitor.visitField(fn);
    840         }
    841
    842         for (Iterator iter = getDeclaredConstructors().iterator(); iter.hasNext();) {
    843             ConstructorNode cn = (ConstructorNode) iter.next();
    844             visitor.visitConstructor(cn);
    845         }
    846
    847         for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
    848             MethodNode mn = (MethodNode) iter.next();
    849             visitor.visitMethod(mn);
    850         }
    851     }
    852
    853     public MethodNode getGetterMethod(String getterName) {
    854         for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
    855             MethodNode method = (MethodNode) iter.next();
    856             if (getterName.equals(method.getName())
    857                     && ClassHelper.VOID_TYPE!=method.getReturnType()
    858                     && method.getParameters().length == 0) {
    859                 return method;
    860             }
    861         }
    862         return null;
    863     }
    864
    865     public MethodNode getSetterMethod(String getterName) {
    866         for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
    867             MethodNode method = (MethodNode) iter.next();
    868             if (getterName.equals(method.getName())
    869                     && ClassHelper.VOID_TYPE==method.getReturnType()
    870                     && method.getParameters().length == 1) {
    871                 return method;
    872             }
    873         }
    874         return null;
    875     }
    876
    877     /***
    878      * Is this class delcared in a static method (such as a closure / inner class declared in a static method)
    879      *
    880      * @return
    881      */
    882     public boolean isStaticClass() {
    883         return redirect().staticClass;
    884     }
    885
    886     public void setStaticClass(boolean staticClass) {
    887         redirect().staticClass = staticClass;
    888     }
    889
    890     /***
    891      * @return Returns true if this inner class or closure was declared inside a script body
    892      */
    893     public boolean isScriptBody() {
    894         return redirect().scriptBody;
    895     }
    896
    897     public void setScriptBody(boolean scriptBody) {
    898         redirect().scriptBody = scriptBody;
    899     }
    900
    901     public boolean isScript() {
    902         return redirect().script || isDerivedFrom(ClassHelper.SCRIPT_TYPE);
    903     }
    904
    905     public void setScript(boolean script) {
    906         redirect().script = script;
    907     }
    908
    909     public String toString() {
    910         return super.toString() + "[name: " + getName() + "]";
    911     }
    912
    913     /***
    914      * Returns true if the given method has a possibly matching method with the given name and arguments
    915      */
    916     public boolean hasPossibleMethod(String name, Expression arguments) {
    917         int count = 0;
    918
    919         if (arguments instanceof TupleExpression) {
    920             TupleExpression tuple = (TupleExpression) arguments;
    921             // TODO this won't strictly be true when using list expension in argument calls
    922             count = tuple.getExpressions().size();
    923         }
    924         ClassNode node = this;
    925         do {
    926             for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
    927                 MethodNode method = (MethodNode) iter.next();
    928                 if (name.equals(method.getName()) && method.getParameters().length == count) {
    929                     return true;
    930                 }
    931             }
    932             node = node.getSuperClass();
    933         }
    934         while (node != null);
    935         return false;
    936     }
    937
    938     public boolean isInterface(){
    939         return (getModifiers() & Opcodes.ACC_INTERFACE) > 0;
    940     }
    941
    942     public boolean isResolved(){
    943         return redirect().clazz!=null || (componentType != null && componentType.isResolved());
    944     }
    945
    946     public boolean isArray(){
    947         return componentType!=null;
    948     }
    949
    950     public ClassNode getComponentType() {
    951         return componentType;
    952     }
    953
    954     public Class getTypeClass(){
    955         Class c = redirect().clazz;
    956         if (c!=null) return c;
    957         ClassNode component = redirect().componentType;
    958         if (component!=null && component.isResolved()){
    959             ClassNode cn = component.makeArray();
    960             setRedirect(cn);
    961             return redirect().clazz;
    962         }
    963         throw new GroovyBugError("ClassNode#getTypeClass for "+getName()+" is called before the type class is set ");
    964     }
    965
    966     public boolean hasPackageName(){
    967         return redirect().name.indexOf('.')>0;
    968     }
    969 }