001    /*
002     * $Id: ClassNode.java,v 1.59 2005/11/15 23:44:55 blackdrag Exp $
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.Iterator;
054    import java.util.List;
055    import java.util.Map;
056    
057    /**
058     * Represents a class declaration
059     *
060     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
061     * @version $Revision: 1.59 $
062     */
063    public class ClassNode extends AnnotatedNode implements Opcodes {
064    
065        //private static final String[] defaultImports = {"java.lang", "java.util", "groovy.lang", "groovy.util"};
066        //private transient Logger log = Logger.getLogger(getClass().getName());
067    
068        private String name;
069        private int modifiers;
070        private ClassNode[] interfaces;
071        private MixinNode[] mixins;
072        private List constructors = new ArrayList();
073        private List methods = new ArrayList();
074        private List fields = new ArrayList();
075        private List properties = new ArrayList();
076        private Map fieldIndex = new HashMap();
077        private ModuleNode module;
078        private CompileUnit compileUnit;
079        private boolean staticClass = false;
080        private boolean scriptBody = false;
081        private boolean script;
082        private ClassNode superClass;
083        boolean isPrimaryNode;
084    
085        // clazz!=null when resolved
086        private Class clazz;
087        // only false when this classNode is constructed from a class 
088        private boolean lazyInitDone=true;
089        protected boolean resolved=true;
090        private ClassNode componentType = null;
091        
092        private ClassNode redirect=null; 
093        
094        protected ClassNode redirect(){
095            if (redirect==null) return this;
096            return redirect.redirect();
097        }
098        
099        public void setRedirect(ClassNode cn) {
100            if (isPrimaryNode) throw new GroovyBugError("tried to set a redirect for a primary ClassNode ("+getName()+"->"+cn.getName()+").");
101            redirect = cn.redirect();
102        }
103            
104        public ClassNode makeArray() {
105            if (redirect!=null) return redirect().makeArray();
106            ClassNode cn;
107            if (clazz!=null) {
108                Class ret = Array.newInstance(clazz,0).getClass();
109                // don't use the ClassHelper here!
110                cn = new ClassNode(ret,this);
111            } else {
112                cn = new ClassNode(this);
113            }
114            return cn;
115        }
116        
117        public boolean isPrimaryClassNode(){
118            return redirect().isPrimaryNode;
119        }
120        
121        private ClassNode(ClassNode componentType) {
122            this(componentType.getName()+"[]", ACC_PUBLIC, ClassHelper.OBJECT_TYPE);
123            this.componentType = componentType.redirect();
124            isPrimaryNode=false;
125            resolved = false;
126        }
127        
128        private ClassNode(Class c, ClassNode componentType) {
129            this(c);
130            this.componentType = componentType;
131            isPrimaryNode=false;
132        }
133        
134        public ClassNode(Class c) {
135            this(c.getName(), c.getModifiers(), null, null ,MixinNode.EMPTY_ARRAY);
136            clazz=c;
137            lazyInitDone=false;
138            resolved = true;
139            CompileUnit cu = getCompileUnit();
140            if (cu!=null) cu.addClass(this);
141            isPrimaryNode=false;
142        }    
143        
144        /**
145         * the complete class structure will be initialized only when really
146         * needed to avoid having too much objects during compilation
147         */
148        private void lazyClassInit() {       
149            
150            Field[] fields = clazz.getDeclaredFields();
151            for (int i=0;i<fields.length;i++){
152                addField(fields[i].getName(),fields[i].getModifiers(),this,null);
153            }
154            Method[] methods = clazz.getDeclaredMethods();
155            for (int i=0;i<methods.length;i++){
156                Method m = methods[i];
157                MethodNode mn = new MethodNode(m.getName(), m.getModifiers(), ClassHelper.make(m.getReturnType()), createParameters(m.getParameterTypes()), null);
158                addMethod(mn);
159            }
160            Constructor[] constructors = clazz.getConstructors();
161            for (int i=0;i<constructors.length;i++){
162                Constructor ctor = constructors[i];
163                addConstructor(ctor.getModifiers(),createParameters(ctor.getParameterTypes()),null);
164            }
165            Class sc = clazz.getSuperclass();
166            if (sc!=null) superClass = ClassHelper.make(sc);
167            buildInterfaceTypes(clazz);       
168            lazyInitDone=true;
169        }
170        
171        private void buildInterfaceTypes(Class c) {
172            Class[] interfaces = c.getInterfaces();
173            ClassNode[] ret = new ClassNode[interfaces.length];
174            for (int i=0;i<interfaces.length;i++){
175                ret[i] = ClassHelper.make(interfaces[i]);
176            }
177            this.interfaces = ret;
178        }
179        
180        
181        //br added to track the enclosing method for local inner classes
182        private MethodNode enclosingMethod = null;
183    
184        public MethodNode getEnclosingMethod() {
185            return redirect().enclosingMethod;
186        }
187    
188        public void setEnclosingMethod(MethodNode enclosingMethod) {
189            redirect().enclosingMethod = enclosingMethod;
190        }
191    
192    
193        /**
194         * @param name       is the full name of the class
195         * @param modifiers  the modifiers,
196         * @param superClass the base class name - use "java.lang.Object" if no direct
197         *                   base class
198         * @see org.objectweb.asm.Opcodes
199         */
200        public ClassNode(String name, int modifiers, ClassNode superClass) {
201            this(name, modifiers, superClass, ClassHelper.EMPTY_TYPE_ARRAY, MixinNode.EMPTY_ARRAY);
202        }
203    
204        /**
205         * @param name       is the full name of the class
206         * @param modifiers  the modifiers,
207         * @param superClass the base class name - use "java.lang.Object" if no direct
208         *                   base class
209         * @see org.objectweb.asm.Opcodes
210         */
211        public ClassNode(String name, int modifiers, ClassNode superClass, ClassNode[] interfaces, MixinNode[] mixins) {
212            this.name = name;
213            this.modifiers = modifiers;
214            this.superClass = superClass;
215            this.interfaces = interfaces;
216            this.mixins = mixins;
217            this.resolved = true;
218            isPrimaryNode = true;
219        }
220    
221        public void setSuperClass(ClassNode superClass) {
222            redirect().superClass = superClass;
223        }
224    
225        public List getFields() {
226            if (!lazyInitDone) {
227                lazyClassInit();
228            }
229            if (redirect!=null) return redirect().getFields();
230            return fields;
231        }
232    
233        public ClassNode[] getInterfaces() {
234            if (!lazyInitDone) {
235                lazyClassInit();
236            }
237            if (redirect!=null) return redirect().getInterfaces();
238            return interfaces;
239        }
240    
241        public MixinNode[] getMixins() {
242            return redirect().mixins;
243        }
244    
245        public List getMethods() {
246            if (!lazyInitDone) {
247                lazyClassInit();
248            }
249            if (redirect!=null) return redirect().getMethods();
250            return methods;
251        }
252    
253        public List getAbstractMethods() {
254    
255            List result = new ArrayList();
256            for (Iterator methIt = getAllDeclaredMethods().iterator(); methIt.hasNext();) {
257                MethodNode method = (MethodNode) methIt.next();
258                if (method.isAbstract()) {
259                    result.add(method);
260                }
261            }
262            if (result.size() == 0) {
263                return null;
264            }
265            else {
266                return result;
267            }
268        }
269    
270        public List getAllDeclaredMethods() {
271            return new ArrayList(getDeclaredMethodsMap().values());
272        }
273    
274    
275        protected Map getDeclaredMethodsMap() {
276            // Start off with the methods from the superclass.
277            ClassNode parent = getSuperClass();
278            Map result = null;
279            if (parent != null) {
280                result = parent.getDeclaredMethodsMap();
281            }
282            else {
283                result = new HashMap();
284            }
285    
286            // add in unimplemented abstract methods from the interfaces
287            ClassNode[] interfaces = getInterfaces();
288            for (int i = 0; i < interfaces.length; i++) {
289                ClassNode iface = interfaces[i];
290                Map ifaceMethodsMap = iface.getDeclaredMethodsMap();
291                for (Iterator iter = ifaceMethodsMap.keySet().iterator(); iter.hasNext();) {
292                    String methSig = (String) iter.next();
293                    if (!result.containsKey(methSig)) {
294                        MethodNode methNode = (MethodNode) ifaceMethodsMap.get(methSig);
295                        result.put(methSig, methNode);
296                    }
297                }
298            }
299    
300            // And add in the methods implemented in this class.
301            for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
302                MethodNode method = (MethodNode) iter.next();
303                String sig = method.getTypeDescriptor();
304                if (result.containsKey(sig)) {
305                    MethodNode inheritedMethod = (MethodNode) result.get(sig);
306                    if (inheritedMethod.isAbstract()) {
307                        result.put(sig, method);
308                    }
309                }
310                else {
311                    result.put(sig, method);
312                }
313            }
314            return result;
315        }
316    
317        public String getName() {
318            return redirect().name;
319        }
320        
321        public String setName(String name) {
322            return redirect().name=name;
323        }
324    
325        public int getModifiers() {
326            return redirect().modifiers;
327        }
328    
329        public List getProperties() {
330            return redirect().properties;
331        }
332    
333        public List getDeclaredConstructors() {
334            if (!lazyInitDone) {
335                lazyClassInit();
336            }
337            return redirect().constructors;
338        }
339    
340        public ModuleNode getModule() {
341            return redirect().module;
342        }
343    
344        public void setModule(ModuleNode module) {
345            redirect().module = module;
346            if (module != null) {
347                redirect().compileUnit = module.getUnit();
348            }
349        }
350    
351        public void addField(FieldNode node) {
352            node.setDeclaringClass(redirect());
353            node.setOwner(redirect());
354            redirect().fields.add(node);
355            redirect().fieldIndex.put(node.getName(), node);
356        }
357    
358        public void addProperty(PropertyNode node) {
359            node.setDeclaringClass(redirect());
360            FieldNode field = node.getField();
361            addField(field);
362    
363            redirect().properties.add(node);
364        }
365    
366        public PropertyNode addProperty(String name,
367                                        int modifiers,
368                                        ClassNode type,
369                                        Expression initialValueExpression,
370                                        Statement getterBlock,
371                                        Statement setterBlock) {
372            for (Iterator iter = getProperties().iterator(); iter.hasNext();) {
373                            PropertyNode pn = (PropertyNode) iter.next();
374                            if (pn.getName().equals(name)) return pn;
375                    }
376            PropertyNode node =
377                    new PropertyNode(name, modifiers, type, redirect(), initialValueExpression, getterBlock, setterBlock);
378            addProperty(node);
379            return node;
380        }
381    
382        public void addConstructor(ConstructorNode node) {
383            node.setDeclaringClass(this);
384            redirect().constructors.add(node);
385        }
386    
387        public ConstructorNode addConstructor(int modifiers, Parameter[] parameters, Statement code) {
388            ConstructorNode node = new ConstructorNode(modifiers, parameters, code);
389            addConstructor(node);
390            return node;
391        }
392    
393        public void addMethod(MethodNode node) {
394            node.setDeclaringClass(this);
395            redirect().methods.add(node);
396        }
397    
398        /**
399         * IF a method with the given name and parameters is already defined then it is returned
400         * otherwise the given method is added to this node. This method is useful for
401         * default method adding like getProperty() or invokeMethod() where there may already
402         * be a method defined in a class and  so the default implementations should not be added
403         * if already present.
404         */
405        public MethodNode addMethod(String name,
406                                    int modifiers,
407                                    ClassNode returnType,
408                                    Parameter[] parameters,
409                                    Statement code) {
410            MethodNode other = getDeclaredMethod(name, parameters);
411            // lets not add duplicate methods
412            if (other != null) {
413                return other;
414            }
415            MethodNode node = new MethodNode(name, modifiers, returnType, parameters, code);
416            addMethod(node);
417            return node;
418        }
419    
420        /**
421         * Adds a synthetic method as part of the compilation process
422         */
423        public MethodNode addSyntheticMethod(String name,
424                                             int modifiers,
425                                             ClassNode returnType,
426                                             Parameter[] parameters,
427                                             Statement code) {
428            MethodNode answer = addMethod(name, modifiers, returnType, parameters, code);
429            answer.setSynthetic(true);
430            return answer;
431        }
432    
433        public FieldNode addField(String name, int modifiers, ClassNode type, Expression initialValue) {
434            FieldNode node = new FieldNode(name, modifiers, type, redirect(), initialValue);
435            addField(node);
436            return node;
437        }
438    
439        public void addInterface(ClassNode type) {
440            // lets check if it already implements an interface
441            boolean skip = false;
442            ClassNode[] interfaces = redirect().interfaces;
443            for (int i = 0; i < interfaces.length; i++) {
444                if (type.equals(interfaces[i])) {
445                    skip = true;
446                }
447            }
448            if (!skip) {
449                ClassNode[] newInterfaces = new ClassNode[interfaces.length + 1];
450                System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
451                newInterfaces[interfaces.length] = type;
452                redirect().interfaces = newInterfaces;
453            }
454        }
455        
456        public boolean equals(Object o) {
457            if (redirect!=null) return redirect().equals(o);
458            ClassNode cn = (ClassNode) o;        
459            return (cn.getName().equals(getName()));
460        }
461    
462        public void addMixin(MixinNode mixin) {
463            // lets check if it already uses a mixin
464            MixinNode[] mixins = redirect().mixins;
465            boolean skip = false;
466            for (int i = 0; i < mixins.length; i++) {
467                if (mixin.equals(mixins[i])) {
468                    skip = true;
469                }
470            }
471            if (!skip) {
472                MixinNode[] newMixins = new MixinNode[mixins.length + 1];
473                System.arraycopy(mixins, 0, newMixins, 0, mixins.length);
474                newMixins[mixins.length] = mixin;
475                redirect().mixins = newMixins;
476            }
477        }
478    
479        public FieldNode getField(String name) {
480            return (FieldNode) redirect().fieldIndex.get(name);
481        }
482    
483        /**
484         * @return the field node on the outer class or null if this is not an
485         *         inner class
486         */
487        public FieldNode getOuterField(String name) {
488            return null;
489        }
490    
491        /**
492         * Helper method to avoid casting to inner class
493         *
494         * @return
495         */
496        public ClassNode getOuterClass() {
497            return null;
498        }
499    
500        public void addStaticInitializerStatements(List staticStatements) {
501            MethodNode method = null;
502            List declaredMethods = getDeclaredMethods("<clinit>");
503            if (declaredMethods.isEmpty()) {
504                method =
505                        addMethod("<clinit>", ACC_PUBLIC | ACC_STATIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, new BlockStatement());
506                method.setSynthetic(true);
507            }
508            else {
509                method = (MethodNode) declaredMethods.get(0);
510            }
511            BlockStatement block = null;
512            Statement statement = method.getCode();
513            if (statement == null) {
514                block = new BlockStatement();
515            }
516            else if (statement instanceof BlockStatement) {
517                block = (BlockStatement) statement;
518            }
519            else {
520                block = new BlockStatement();
521                block.addStatement(statement);
522            }
523            block.addStatements(staticStatements);
524        }
525    
526        /**
527         * @return a list of methods which match the given name
528         */
529        public List getDeclaredMethods(String name) {
530            List answer = new ArrayList();
531            for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
532                MethodNode method = (MethodNode) iter.next();
533                if (name.equals(method.getName())) {
534                    answer.add(method);
535                }
536            }
537            return answer;
538        }
539    
540        /**
541         * @return a list of methods which match the given name
542         */
543        public List getMethods(String name) {
544            List answer = new ArrayList();
545            ClassNode node = this;
546            do {
547                for (Iterator iter = node.getMethods().iterator(); iter.hasNext();) {
548                    MethodNode method = (MethodNode) iter.next();
549                    if (name.equals(method.getName())) {
550                        answer.add(method);
551                    }
552                }
553                node = node.getSuperClass();
554            }
555            while (node != null);
556            return answer;
557        }
558    
559        /**
560         * @return the method matching the given name and parameters or null
561         */
562        public MethodNode getDeclaredMethod(String name, Parameter[] parameters) {
563            for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
564                MethodNode method = (MethodNode) iter.next();
565                if (name.equals(method.getName()) && parametersEqual(method.getParameters(), parameters)) {
566                    return method;
567                }
568            }
569            return null;
570        }
571    
572        /**
573         * @return true if this node is derived from the given class node
574         */
575        public boolean isDerivedFrom(ClassNode type) {
576            ClassNode node = getSuperClass();
577            while (node != null) {
578                if (type.equals(node)) {
579                    return true;
580                }
581                node = node.getSuperClass();
582            }
583            return false;
584        }
585    
586        /**
587         * @return true if this class is derived from a groovy object
588         *         i.e. it implements GroovyObject
589         */
590        public boolean isDerivedFromGroovyObject() {
591            return implementsInteface(GroovyObject.class.getName());
592        }
593    
594        /**
595         * @param name the fully qualified name of the interface
596         * @return true if this class or any base class implements the given interface
597         */
598        public boolean implementsInteface(String name) {
599            ClassNode node = redirect();
600            do {
601                if (node.declaresInterface(name)) {
602                    return true;
603                }
604                node = node.getSuperClass();
605            }
606            while (node != null);
607            return false;
608        }
609    
610        /**
611         * @param name the fully qualified name of the interface
612         * @return true if this class declares that it implements the given interface
613         */
614        public boolean declaresInterface(String name) {
615            ClassNode[] interfaces = redirect().getInterfaces();
616            int size = interfaces.length;
617            for (int i = 0; i < size; i++) {
618                if (interfaces[i].getName().equals(name)) {
619                    return true;
620                }
621            }
622            return false;
623        }
624    
625        /**
626         * @return the ClassNode of the super class of this type
627         */
628        public ClassNode getSuperClass() {
629            if (!lazyInitDone) {
630                lazyClassInit();
631            }
632            if (!isResolved()) {
633                throw new GroovyBugError("Classnode#getSuperClass for "+getName()+" called before class resolving");
634            }
635            return redirect().superClass;
636        }
637    
638        /**
639         * Factory method to create a new MethodNode via reflection
640         */
641        protected MethodNode createMethodNode(Method method) {
642            Parameter[] parameters = createParameters(method.getParameterTypes());
643            return new MethodNode(method.getName(), method.getModifiers(), ClassHelper.make(method.getReturnType()), parameters, EmptyStatement.INSTANCE);
644        }
645    
646        /**
647         * @param types
648         * @return
649         */
650        protected Parameter[] createParameters(Class[] types) {
651            Parameter[] parameters = Parameter.EMPTY_ARRAY;
652            int size = types.length;
653            if (size > 0) {
654                parameters = new Parameter[size];
655                for (int i = 0; i < size; i++) {
656                    parameters[i] = createParameter(types[i], i);
657                }
658            }
659            return parameters;
660        }
661    
662        protected Parameter createParameter(Class parameterType, int idx) {
663            return new Parameter(ClassHelper.make(parameterType), "param" + idx);
664        }
665    
666        public CompileUnit getCompileUnit() {
667            if (redirect!=null) return redirect().getCompileUnit();
668            if (compileUnit == null && module != null) {
669                compileUnit = module.getUnit();
670            }
671            return compileUnit;
672        }
673        
674        protected void setCompileUnit(CompileUnit cu) {
675            if (redirect!=null) redirect().setCompileUnit(cu);
676            if (compileUnit!= null) compileUnit = cu;
677        }
678    
679        /**
680         * @return true if the two arrays are of the same size and have the same contents
681         */
682        protected boolean parametersEqual(Parameter[] a, Parameter[] b) {
683            if (a.length == b.length) {
684                boolean answer = true;
685                for (int i = 0; i < a.length; i++) {
686                    if (!a[i].getType().equals(b[i].getType())) {
687                        answer = false;
688                        break;
689                    }
690                }
691                return answer;
692            }
693            return false;
694        }
695    
696        /**
697         * @return the package name of this class
698         */
699        public String getPackageName() {
700            int idx = getName().lastIndexOf('.');
701            if (idx > 0) {
702                return getName().substring(0, idx);
703            }
704            return null;
705        }
706    
707        public String getNameWithoutPackage() {
708            int idx = getName().lastIndexOf('.');
709            if (idx > 0) {
710                return getName().substring(idx + 1);
711            }
712            return getName();
713        }
714    
715        public void visitContents(GroovyClassVisitor visitor) {
716            
717            // now lets visit the contents of the class
718            for (Iterator iter = getProperties().iterator(); iter.hasNext();) {
719                PropertyNode pn = (PropertyNode) iter.next();
720                visitor.visitProperty(pn);
721            }
722    
723            for (Iterator iter = getFields().iterator(); iter.hasNext();) {
724                FieldNode fn = (FieldNode) iter.next();
725                visitor.visitField(fn);
726            }
727    
728            for (Iterator iter = getDeclaredConstructors().iterator(); iter.hasNext();) {
729                ConstructorNode cn = (ConstructorNode) iter.next();
730                visitor.visitConstructor(cn);
731            }
732    
733            for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
734                MethodNode mn = (MethodNode) iter.next();
735                visitor.visitMethod(mn);
736            }
737        }
738    
739        public MethodNode getGetterMethod(String getterName) {
740            for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
741                MethodNode method = (MethodNode) iter.next();
742                if (getterName.equals(method.getName())
743                        && ClassHelper.VOID_TYPE!=method.getReturnType()
744                        && method.getParameters().length == 0) {
745                    return method;
746                }
747            }
748            return null;
749        }
750    
751        public MethodNode getSetterMethod(String getterName) {
752            for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
753                MethodNode method = (MethodNode) iter.next();
754                if (getterName.equals(method.getName())
755                        && ClassHelper.VOID_TYPE==method.getReturnType()
756                        && method.getParameters().length == 1) {
757                    return method;
758                }
759            }
760            return null;
761        }
762    
763        /**
764         * Is this class delcared in a static method (such as a closure / inner class declared in a static method)
765         *
766         * @return
767         */
768        public boolean isStaticClass() {
769            return redirect().staticClass;
770        }
771    
772        public void setStaticClass(boolean staticClass) {
773            redirect().staticClass = staticClass;
774        }
775    
776        /**
777         * @return Returns true if this inner class or closure was declared inside a script body
778         */
779        public boolean isScriptBody() {
780            return redirect().scriptBody;
781        }
782    
783        public void setScriptBody(boolean scriptBody) {
784            redirect().scriptBody = scriptBody;
785        }
786    
787        public boolean isScript() {
788            return redirect().script || isDerivedFrom(ClassHelper.SCRIPT_TYPE);
789        }
790    
791        public void setScript(boolean script) {
792            redirect().script = script;
793        }
794    
795        public String toString() {
796            return super.toString() + "[name: " + getName() + "]";
797        }
798    
799        /**
800         * Returns true if the given method has a possibly matching method with the given name and arguments
801         */
802        public boolean hasPossibleMethod(String name, Expression arguments) {
803            int count = 0;
804    
805            if (arguments instanceof TupleExpression) {
806                TupleExpression tuple = (TupleExpression) arguments;
807                // TODO this won't strictly be true when using list expension in argument calls
808                count = tuple.getExpressions().size();
809            }
810            ClassNode node = this;
811            do {
812                for (Iterator iter = getMethods().iterator(); iter.hasNext();) {
813                    MethodNode method = (MethodNode) iter.next();
814                    if (name.equals(method.getName()) && method.getParameters().length == count) {
815                        return true;
816                    }
817                }
818                node = node.getSuperClass();
819            }
820            while (node != null);
821            return false;
822        }
823        
824        public boolean isInterface(){
825            return (getModifiers() & Opcodes.ACC_INTERFACE) > 0; 
826        }
827        
828        public boolean isResolved(){
829            return redirect().resolved || (componentType != null && componentType.isResolved());
830        }
831        
832        public boolean isArray(){
833            return componentType!=null;
834        }
835        
836        public ClassNode getComponentType() {
837            return componentType;
838        }
839        
840        public Class getTypeClass(){
841            Class c = redirect().clazz;
842            if (c!=null) return c;
843            ClassNode component = redirect().componentType;
844            if (component!=null && component.isResolved()){
845                ClassNode cn = component.makeArray();
846                setRedirect(cn);
847                return redirect().clazz;
848            }
849            throw new GroovyBugError("ClassNode#getTypeClass for "+getName()+" is called before the type class is set ");
850        }
851        
852        public boolean hasPackageName(){
853            return redirect().name.indexOf('.')>0;
854        }
855    }