View Javadoc

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