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