1 /*
2 * Copyright 2003-2007 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.codehaus.groovy.ast;
17
18 import org.codehaus.groovy.GroovyBugError;
19 import org.codehaus.groovy.ast.expr.ClassExpression;
20 import org.codehaus.groovy.ast.expr.Expression;
21 import org.codehaus.groovy.ast.expr.MapExpression;
22 import org.codehaus.groovy.ast.expr.TupleExpression;
23 import org.codehaus.groovy.ast.stmt.Statement;
24 import org.codehaus.groovy.ast.stmt.BlockStatement;
25 import org.codehaus.groovy.control.CompilePhase;
26 import org.codehaus.groovy.transform.ASTTransformation;
27 import org.codehaus.groovy.transform.GroovyASTTransformation;
28 import org.codehaus.groovy.vmplugin.VMPluginFactory;
29 import org.objectweb.asm.Opcodes;
30
31 import java.lang.reflect.Array;
32 import java.util;
33
34 import groovy.lang.GroovyObject;
35
36 /**
37 * Represents a class in the AST.<br/>
38 * A ClassNode should be created using the methods in ClassHelper.
39 * This ClassNode may be used to represent a class declaration or
40 * any other type. This class uses a proxy mechanism allowing to
41 * create a class for a plain name at AST creation time. In another
42 * phase of the compiler the real ClassNode for the plain name may be
43 * found. To avoid the need of exchanging this ClassNode with an
44 * instance of the correct ClassNode the correct ClassNode is set as
45 * redirect. Most method calls are then redirected to that ClassNode.
46 * <br>
47 * There are three types of ClassNodes:
48 * <br>
49 * <ol>
50 * <li> Primary ClassNodes:<br>
51 * A primary ClassNode is one where we have a source representation
52 * which is to be compiled by Groovy and which we have an AST for.
53 * The groovy compiler will output one class for each such ClassNode
54 * that passes through AsmBytecodeGenerator... not more, not less.
55 * That means for example Closures become such ClassNodes too at
56 * some point.
57 *
58 * <li> ClassNodes create through different sources (typically created
59 * from a java.lang.reflect.Class object):<br>
60 * The compiler will not output classes from these, the methods
61 * usually do not contain bodies. These kind of ClassNodes will be
62 * used in different checks, but not checks that work on the method
63 * bodies. For example if such a ClassNode is a super class to a primary
64 * ClassNode, then the abstract method test and others will be done
65 * with data based on these. Theoretically it is also possible to mix both
66 * (1 and 2) kind of classes in a hierarchy, but this probably works only
67 * in the newest Groovy versions. Such ClassNodes normally have to
68 * isResolved() returning true without having a redirect.In the Groovy
69 * compiler the only version of this, that exists, is a ClassNode created
70 * through a Class instance
71 *
72 * <li> Labels:<br>
73 * ClassNodes created through ClassHelper.makeWithoutCaching. They
74 * are place holders, its redirect points to the real structure, which can
75 * be a label too, but following all redirects it should end with a ClassNode
76 * from one of the other two categories. If ResolveVisitor finds such a
77 * node, it tries to set the redirects. Any such label created after
78 * ResolveVisitor has done its work needs to have a redirect pointing to
79 * case 1 or 2. If not the compiler may react strange... this can be considered
80 * as a kind of dangling pointer.
81 * <br>
82 * <b>Note:</b> the redirect mechanism is only allowed for classes
83 * that are not primary ClassNodes. Typically this is done for classes
84 * created by name only. The redirect itself can be any type of ClassNode.
85 * <br>
86 * To describe generic type signature see {@link #getGenericsTypes()} and
87 * {@link #setGenericsTypes(GenericsType[])}. These methods are not proxied,
88 * they describe the type signature used at the point of declaration or the
89 * type signatures provided by the class. If the type signatures provided
90 * by the class are needed, then a call to {@link #redirect()} will help.
91 *
92 * @see org.codehaus.groovy.ast.ClassHelper
93 *
94 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
95 * @author Jochen Theodorou
96 * @version $Revision: 16205 $
97 */
98 public class ClassNode extends AnnotatedNode implements Opcodes {
99 private static class MapOfLists {
100 private Map map = new HashMap();
101 public List get(Object key) {
102 return (List) map.get(key);
103 }
104 public List getNotNull(Object key) {
105 List ret = get(key);
106 if (ret==null) ret = Collections.EMPTY_LIST;
107 return ret;
108 }
109 public void put(Object key, Object value) {
110 if (map.containsKey(key)) {
111 get(key).add(value);
112 } else {
113 ArrayList list = new ArrayList(2);
114 list.add(value);
115 map.put(key, list);
116 }
117 }
118 }
119
120 public static ClassNode[] EMPTY_ARRAY = new ClassNode[0];
121
122 public static ClassNode THIS = new ClassNode(Object.class);
123 public static ClassNode SUPER = new ClassNode(Object.class);
124
125 private String name;
126 private final int modifiers;
127 private ClassNode[] interfaces;
128 private MixinNode[] mixins;
129 private List constructors;
130 private List objectInitializers;
131 private MapOfLists methods;
132 private List<MethodNode> methodsList;
133 private LinkedList<FieldNode> fields;
134 private List properties;
135 private Map fieldIndex;
136 private ModuleNode module;
137 private CompileUnit compileUnit;
138 private boolean staticClass = false;
139 private boolean scriptBody = false;
140 private boolean script;
141 private ClassNode superClass;
142 protected boolean isPrimaryNode;
143
144 /**
145 * The ASTTransformations to be applied to the Class
146 */
147 private Map<CompilePhase, Map<Class<? extends ASTTransformation>, Set<ASTNode>>> transformInstances;
148
149
150 // use this to synchronize access for the lazy init
151 protected Object lazyInitLock = new Object();
152
153 // clazz!=null when resolved
154 protected Class clazz;
155 // only false when this classNode is constructed from a class
156 private boolean lazyInitDone=true;
157 // not null if if the ClassNode is an array
158 private ClassNode componentType = null;
159 // if not null this instance is handled as proxy
160 // for the redirect
161 private ClassNode redirect=null;
162 // flag if the classes or its members are annotated
163 private boolean annotated;
164
165 // type spec for generics
166 private GenericsType[] genericsTypes=null;
167 private boolean usesGenerics=false;
168
169 // if set to true the name getGenericsTypes consists
170 // of 1 element describing the name of the placeholder
171 private boolean placeholder;
172
173 /**
174 * Returns the ClassNode this ClassNode is redirecting to.
175 */
176 public ClassNode redirect(){
177 ClassNode res = this;
178 while (res.redirect != null) res = res.redirect;
179 return res;
180 }
181
182 /**
183 * Sets this instance as proxy for the given ClassNode.
184 * @param cn the class to redirect to. If set to null the redirect will be removed
185 */
186 public void setRedirect(ClassNode cn) {
187 if (isPrimaryNode) throw new GroovyBugError("tried to set a redirect for a primary ClassNode ("+getName()+"->"+cn.getName()+").");
188 if (cn!=null) cn = cn.redirect();
189 if (cn==this) return;
190 redirect = cn;
191 }
192
193 /**
194 * Returns a ClassNode representing an array of the class
195 * represented by this ClassNode
196 */
197 public ClassNode makeArray() {
198 if (redirect!=null) return redirect().makeArray();
199 ClassNode cn;
200 if (clazz!=null) {
201 Class ret = Array.newInstance(clazz,0).getClass();
202 // don't use the ClassHelper here!
203 cn = new ClassNode(ret,this);
204 } else {
205 cn = new ClassNode(this);
206 }
207 return cn;
208 }
209
210 /**
211 * Returns if this instance is a primary ClassNode
212 */
213 public boolean isPrimaryClassNode(){
214 return redirect().isPrimaryNode || (componentType!= null && componentType.isPrimaryClassNode());
215 }
216
217 /**
218 * Constructor used by makeArray() if no real class is available
219 */
220 private ClassNode(ClassNode componentType) {
221 this(componentType.getName()+"[]", ACC_PUBLIC, ClassHelper.OBJECT_TYPE);
222 this.componentType = componentType.redirect();
223 isPrimaryNode=false;
224 }
225
226 /**
227 * Constructor used by makeArray() if a real class is available
228 */
229 private ClassNode(Class c, ClassNode componentType) {
230 this(c);
231 this.componentType = componentType;
232 isPrimaryNode=false;
233 }
234
235 /**
236 * Creates a ClassNode from a real class. The resulting
237 * ClassNode will not be a primary ClassNode.
238 */
239 public ClassNode(Class c) {
240 this(c.getName(), c.getModifiers(), null, null ,MixinNode.EMPTY_ARRAY);
241 clazz=c;
242 lazyInitDone=false;
243 CompileUnit cu = getCompileUnit();
244 if (cu!=null) cu.addClass(this);
245 isPrimaryNode=false;
246 }
247
248 /**
249 * The complete class structure will be initialized only when really
250 * needed to avoid having too many objects during compilation
251 */
252 private void lazyClassInit() {
253 synchronized (lazyInitLock) {
254 if (redirect!=null) {
255 throw new GroovyBugError("lazyClassInit called on a proxy ClassNode, that must not happen."+
256 "A redirect() call is missing somewhere!");
257 }
258 if (lazyInitDone) return;
259 VMPluginFactory.getPlugin().configureClassNode(compileUnit,this);
260 lazyInitDone = true;
261 }
262 }
263
264 // added to track the enclosing method for local inner classes
265 private MethodNode enclosingMethod = null;
266
267 public MethodNode getEnclosingMethod() {
268 return redirect().enclosingMethod;
269 }
270
271 public void setEnclosingMethod(MethodNode enclosingMethod) {
272 redirect().enclosingMethod = enclosingMethod;
273 }
274
275
276 /**
277 * @param name is the full name of the class
278 * @param modifiers the modifiers,
279 * @param superClass the base class name - use "java.lang.Object" if no direct
280 * base class
281 * @see org.objectweb.asm.Opcodes
282 */
283 public ClassNode(String name, int modifiers, ClassNode superClass) {
284 this(name, modifiers, superClass, EMPTY_ARRAY, MixinNode.EMPTY_ARRAY);
285 }
286
287 /**
288 * @param name is the full name of the class
289 * @param modifiers the modifiers,
290 * @param superClass the base class name - use "java.lang.Object" if no direct
291 * base class
292 * @see org.objectweb.asm.Opcodes
293 */
294 public ClassNode(String name, int modifiers, ClassNode superClass, ClassNode[] interfaces, MixinNode[] mixins) {
295 this.name = name;
296 this.modifiers = modifiers;
297 this.superClass = superClass;
298 this.interfaces = interfaces;
299 this.mixins = mixins;
300 isPrimaryNode = true;
301 if (superClass!=null) {
302 usesGenerics = superClass.isUsingGenerics();
303 }
304 if (!usesGenerics && interfaces!=null) {
305 for (int i = 0; i < interfaces.length; i++) {
306 usesGenerics = usesGenerics || interfaces[i].isUsingGenerics();
307 }
308 }
309
310 if ((modifiers & ACC_INTERFACE) == 0)
311 addField("$ownClass", ACC_STATIC|ACC_PUBLIC|ACC_FINAL|ACC_SYNTHETIC, ClassHelper.CLASS_Type, new ClassExpression(this)).setSynthetic(true);
312 }
313
314 private void getTransformInstancesLazy() {
315 transformInstances = new EnumMap<CompilePhase, Map<Class <? extends ASTTransformation>, Set<ASTNode>>>(CompilePhase.class);
316 for (CompilePhase phase : CompilePhase.values()) {
317 transformInstances.put(phase, new HashMap<Class <? extends ASTTransformation>, Set<ASTNode>>());
318 }
319 }
320
321 /**
322 * Sets the superclass of this ClassNode
323 */
324 public void setSuperClass(ClassNode superClass) {
325 redirect().superClass = superClass;
326 }
327
328 /**
329 * Returns a list containing FieldNode objects for
330 * each field in the class represented by this ClassNode
331 */
332 public List<FieldNode> getFields() {
333 if (!redirect().lazyInitDone) redirect().lazyClassInit();
334 if (redirect!=null) return redirect().getFields();
335 return getFieldsLazy();
336 }
337
338 private List<FieldNode> getFieldsLazy() {
339 if (fields == null) {
340 fields = new LinkedList<FieldNode>();
341 }
342 return fields;
343 }
344
345 /**
346 * Returns an array of ClassNodes representing the
347 * interfaces the class implements
348 */
349 public ClassNode[] getInterfaces() {
350 if (!redirect().lazyInitDone) redirect().lazyClassInit();
351 if (redirect!=null) return redirect().getInterfaces();
352 return interfaces;
353 }
354
355 public void setInterfaces(ClassNode[] interfaces) {
356 if (redirect!=null) {
357 redirect().setInterfaces(interfaces);
358 } else {
359 this.interfaces = interfaces;
360 }
361 }
362
363 public MixinNode[] getMixins() {
364 return redirect().mixins;
365 }
366
367 /**
368 * Returns a list containing MethodNode objects for
369 * each method in the class represented by this ClassNode
370 */
371 public List<MethodNode> getMethods() {
372 if (!redirect().lazyInitDone) redirect().lazyClassInit();
373 if (redirect!=null) return redirect().getMethods();
374 return getMethodsListLazy();
375 }
376
377 /**
378 * Returns a list containing MethodNode objects for
379 * each abstract method in the class represented by
380 * this ClassNode
381 */
382 public List getAbstractMethods() {
383 List result = new ArrayList(3);
384 Map declaredMethods = getDeclaredMethodsMap();
385 for (Iterator it = declaredMethods.values().iterator(); it.hasNext();) {
386 MethodNode method = (MethodNode) it.next();
387 if (method.isAbstract()) {
388 result.add(method);
389 }
390 }
391
392 if (result.isEmpty()) {
393 return null;
394 } else {
395 return result;
396 }
397 }
398
399 public List getAllDeclaredMethods() {
400 return new ArrayList(getDeclaredMethodsMap().values());
401 }
402
403 public Set getAllInterfaces () {
404 Set res = new HashSet ();
405 getAllInterfaces(res);
406 return res;
407 }
408
409 private void getAllInterfaces(Set res) {
410 if (isInterface())
411 res.add(this);
412
413 ClassNode[] interfaces = getInterfaces();
414 for (int i = 0; i < interfaces.length; i++) {
415 res.add(interfaces[i]);
416 interfaces[i].getAllInterfaces(res);
417 }
418 }
419
420 public Map getDeclaredMethodsMap() {
421 // Start off with the methods from the superclass.
422 ClassNode parent = getSuperClass();
423 Map result = null;
424 if (parent != null) {
425 result = parent.getDeclaredMethodsMap();
426 } else {
427 result = new HashMap();
428 }
429
430 // add in unimplemented abstract methods from the interfaces
431 ClassNode[] interfaces = getInterfaces();
432 for (int i = 0; i < interfaces.length; i++) {
433 ClassNode iface = interfaces[i];
434 Map ifaceMethodsMap = iface.getDeclaredMethodsMap();
435 for (Object o : ifaceMethodsMap.keySet()) {
436 String methSig = (String) o;
437 if (!result.containsKey(methSig)) {
438 MethodNode methNode = (MethodNode) ifaceMethodsMap.get(methSig);
439 result.put(methSig, methNode);
440 }
441 }
442 }
443
444 // And add in the methods implemented in this class.
445 for (Object o : getMethods()) {
446 MethodNode method = (MethodNode) o;
447 String sig = method.getTypeDescriptor();
448 result.put(sig, method);
449 }
450 return result;
451 }
452
453 public String getName() {
454 return redirect().name;
455 }
456
457 public String setName(String name) {
458 return redirect().name=name;
459 }
460
461 public int getModifiers() {
462 return redirect().modifiers;
463 }
464
465 public List getProperties() {
466 return redirect().getPropertiesLazy();
467 }
468
469 private List getPropertiesLazy() {
470 if (properties == null)
471 properties = new LinkedList ();
472 return properties;
473 }
474
475 public List getDeclaredConstructors() {
476 if (!redirect().lazyInitDone) redirect().lazyClassInit();
477 return redirect().getDeclaredConstructorsLazy();
478 }
479
480 private List getDeclaredConstructorsLazy () {
481 if (constructors == null)
482 constructors = new LinkedList();
483 return constructors;
484 }
485
486 public ModuleNode getModule() {
487 return redirect().module;
488 }
489
490 public void setModule(ModuleNode module) {
491 redirect().module = module;
492 if (module != null) {
493 redirect().compileUnit = module.getUnit();
494 }
495 }
496
497 public void addField(FieldNode node) {
498 node.setDeclaringClass(redirect());
499 node.setOwner(redirect());
500 redirect().getFieldsLazy().add(node);
501 redirect().getFieldIndexLazy().put(node.getName(), node);
502 }
503
504 private Map getFieldIndexLazy() {
505 if (fieldIndex == null)
506 fieldIndex = new HashMap();
507 return fieldIndex;
508 }
509
510 public void addProperty(PropertyNode node) {
511 node.setDeclaringClass(redirect());
512 FieldNode field = node.getField();
513 addField(field);
514 redirect().getPropertiesLazy().add(node);
515 }
516
517 public PropertyNode addProperty(String name,
518 int modifiers,
519 ClassNode type,
520 Expression initialValueExpression,
521 Statement getterBlock,
522 Statement setterBlock) {
523 for (Object o : getProperties()) {
524 PropertyNode pn = (PropertyNode) o;
525 if (pn.getName().equals(name)) {
526 if (pn.getInitialExpression() == null && initialValueExpression != null)
527 pn.getField().setInitialValueExpression(initialValueExpression);
528
529 if (pn.getGetterBlock() == null && getterBlock != null)
530 pn.setGetterBlock(getterBlock);
531
532 if (pn.getSetterBlock() == null && setterBlock != null)
533 pn.setSetterBlock(setterBlock);
534
535 return pn;
536 }
537 }
538 PropertyNode node =
539 new PropertyNode(name, modifiers, type, redirect(), initialValueExpression, getterBlock, setterBlock);
540 addProperty(node);
541 return node;
542 }
543
544 public boolean hasProperty(String name) {
545 return getProperty(name)!=null;
546 }
547
548 public PropertyNode getProperty(String name) {
549 for (Object o : getProperties()) {
550 PropertyNode pn = (PropertyNode) o;
551 if (pn.getName().equals(name)) return pn;
552 }
553 return null;
554 }
555
556 public void addConstructor(ConstructorNode node) {
557 node.setDeclaringClass(this);
558 redirect().getDeclaredConstructorsLazy().add(node);
559 }
560
561 public ConstructorNode addConstructor(int modifiers, Parameter[] parameters, ClassNode[] exceptions, Statement code) {
562 ConstructorNode node = new ConstructorNode(modifiers, parameters, exceptions, code);
563 addConstructor(node);
564 return node;
565 }
566
567 public void addMethod(MethodNode node) {
568 node.setDeclaringClass(this);
569 redirect().getMethodsListLazy().add(node);
570 redirect().getMethodsLazy().put(node.getName(), node);
571 }
572
573 private MapOfLists getMethodsLazy() {
574 if (methods == null)
575 methods = new MapOfLists();
576 return methods;
577 }
578
579 private List<MethodNode> getMethodsListLazy() {
580 if (methodsList == null)
581 methodsList = new LinkedList<MethodNode>();
582 return methodsList;
583 }
584
585 /**
586 * If a method with the given name and parameters is already defined then it is returned
587 * otherwise the given method is added to this node. This method is useful for
588 * default method adding like getProperty() or invokeMethod() where there may already
589 * be a method defined in a class and so the default implementations should not be added
590 * if already present.
591 */
592 public MethodNode addMethod(String name,
593 int modifiers,
594 ClassNode returnType,
595 Parameter[] parameters,
596 ClassNode[] exceptions,
597 Statement code) {
598 MethodNode other = getDeclaredMethod(name, parameters);
599 // let's not add duplicate methods
600 if (other != null) {
601 return other;
602 }
603 MethodNode node = new MethodNode(name, modifiers, returnType, parameters, exceptions, code);
604 addMethod(node);
605 return node;
606 }
607
608 /**
609 * @see #getDeclaredMethod(String, Parameter[])
610 */
611 public boolean hasDeclaredMethod(String name, Parameter[] parameters) {
612 MethodNode other = getDeclaredMethod(name, parameters);
613 return other != null;
614 }
615
616 /**
617 * @see #getMethod(String, Parameter[])
618 */
619 public boolean hasMethod(String name, Parameter[] parameters) {
620 MethodNode other = getMethod(name, parameters);
621 return other != null;
622 }
623
624 /**
625 * Adds a synthetic method as part of the compilation process
626 */
627 public MethodNode addSyntheticMethod(String name,
628 int modifiers,
629 ClassNode returnType,
630 Parameter[] parameters,
631 ClassNode[] exceptions,
632 Statement code) {
633 MethodNode answer = addMethod(name, modifiers|ACC_SYNTHETIC, returnType, parameters, exceptions, code);
634 answer.setSynthetic(true);
635 return answer;
636 }
637
638 public FieldNode addField(String name, int modifiers, ClassNode type, Expression initialValue) {
639 FieldNode node = new FieldNode(name, modifiers, type, redirect(), initialValue);
640 addField(node);
641 return node;
642 }
643
644 public void addInterface(ClassNode type) {
645 // lets check if it already implements an interface
646 boolean skip = false;
647 ClassNode[] interfaces = redirect().interfaces;
648 for (int i = 0; i < interfaces.length; i++) {
649 if (type.equals(interfaces[i])) {
650 skip = true;
651 }
652 }
653 if (!skip) {
654 ClassNode[] newInterfaces = new ClassNode[interfaces.length + 1];
655 System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
656 newInterfaces[interfaces.length] = type;
657 redirect().interfaces = newInterfaces;
658 }
659 }
660
661 public boolean equals(Object o) {
662 if (redirect!=null) return redirect().equals(o);
663 ClassNode cn = (ClassNode) o;
664 return (cn.getName().equals(getName()));
665 }
666
667 public int hashCode() {
668 if (redirect!=null) return redirect().hashCode();
669 return getName().hashCode();
670 }
671
672 public void addMixin(MixinNode mixin) {
673 // lets check if it already uses a mixin
674 MixinNode[] mixins = redirect().mixins;
675 boolean skip = false;
676 for (int i = 0; i < mixins.length; i++) {
677 if (mixin.equals(mixins[i])) {
678 skip = true;
679 }
680 }
681 if (!skip) {
682 MixinNode[] newMixins = new MixinNode[mixins.length + 1];
683 System.arraycopy(mixins, 0, newMixins, 0, mixins.length);
684 newMixins[mixins.length] = mixin;
685 redirect().mixins = newMixins;
686 }
687 }
688
689 /**
690 * Finds a field matching the given name in this class.
691 *
692 * @param name the name of the field of interest
693 * @return the method matching the given name and parameters or null
694 */
695 public FieldNode getDeclaredField(String name) {
696 return (FieldNode) redirect().getFieldIndexLazy().get(name);
697 }
698
699 /**
700 * Finds a field matching the given name in this class or a parent class.
701 *
702 * @param name the name of the field of interest
703 * @return the method matching the given name and parameters or null
704 */
705 public FieldNode getField(String name) {
706 ClassNode node = this;
707 while (node != null) {
708 FieldNode fn = node.getDeclaredField(name);
709 if (fn != null) return fn;
710 node = node.getSuperClass();
711 }
712 return null;
713 }
714
715 /**
716 * @return the field node on the outer class or null if this is not an
717 * inner class
718 */
719 public FieldNode getOuterField(String name) {
720 return null;
721 }
722
723 /**
724 * Helper method to avoid casting to inner class
725 */
726 public ClassNode getOuterClass() {
727 return null;
728 }
729
730 public void addObjectInitializerStatements(Statement statements) {
731 if (objectInitializers == null)
732 objectInitializers = new LinkedList();
733 objectInitializers.add(statements);
734 }
735
736 public List getObjectInitializerStatements() {
737 if (objectInitializers == null)
738 objectInitializers = new LinkedList();
739 return objectInitializers;
740 }
741
742 public void addStaticInitializerStatements(List staticStatements, boolean fieldInit) {
743 MethodNode method = null;
744 List declaredMethods = getDeclaredMethods("<clinit>");
745 if (declaredMethods.isEmpty()) {
746 method =
747 addMethod("<clinit>", ACC_STATIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement());
748 method.setSynthetic(true);
749 }
750 else {
751 method = (MethodNode) declaredMethods.get(0);
752 }
753 BlockStatement block = null;
754 Statement statement = method.getCode();
755 if (statement == null) {
756 block = new BlockStatement();
757 }
758 else if (statement instanceof BlockStatement) {
759 block = (BlockStatement) statement;
760 }
761 else {
762 block = new BlockStatement();
763 block.addStatement(statement);
764 }
765
766 // while anything inside a static initializer block is appended
767 // we don't want to append in the case we have a initialization
768 // expression of a static field. In that case we want to add
769 // before the other statements
770 if (!fieldInit) {
771 block.addStatements(staticStatements);
772 } else {
773 List blockStatements = block.getStatements();
774 staticStatements.addAll(blockStatements);
775 blockStatements.clear();
776 blockStatements.addAll(staticStatements);
777 }
778 }
779
780 /**
781 * This methods returns a list of all methods of the given name
782 * defined in the current class
783 * @return the method list
784 * @see #getMethods(String)
785 */
786 public List getDeclaredMethods(String name) {
787 if (!redirect().lazyInitDone) redirect().lazyClassInit();
788 if (redirect!=null) return redirect().getDeclaredMethods(name);
789 return getMethodsLazy().getNotNull(name);
790 }
791
792 /**
793 * This methods creates a list of all methods with this name of the
794 * current class and of all super classes
795 * @return the methods list
796 * @see #getDeclaredMethods(String)
797 */
798 public List getMethods(String name) {
799 List answer = new ArrayList();
800 ClassNode node = this;
801 while (node != null) {
802 answer.addAll(node.getDeclaredMethods(name));
803 node = node.getSuperClass();
804 }
805 return answer;
806 }
807
808 /**
809 * Finds a method matching the given name and parameters in this class.
810 *
811 * @return the method matching the given name and parameters or null
812 */
813 public MethodNode getDeclaredMethod(String name, Parameter[] parameters) {
814 for (Object o : getDeclaredMethods(name)) {
815 MethodNode method = (MethodNode) o;
816 if (parametersEqual(method.getParameters(), parameters)) {
817 return method;
818 }
819 }
820 return null;
821 }
822
823 /**
824 * Finds a method matching the given name and parameters in this class
825 * or any parent class.
826 *
827 * @return the method matching the given name and parameters or null
828 */
829 public MethodNode getMethod(String name, Parameter[] parameters) {
830 for (Object o : getMethods(name)) {
831 MethodNode method = (MethodNode) o;
832 if (parametersEqual(method.getParameters(), parameters)) {
833 return method;
834 }
835 }
836 return null;
837 }
838
839 /**
840 * @param type the ClassNode of interest
841 * @return true if this node is derived from the given ClassNode
842 */
843 public boolean isDerivedFrom(ClassNode type) {
844 if (type.equals(ClassHelper.OBJECT_TYPE)) return true;
845 ClassNode node = this;
846 while (node != null) {
847 if (type.equals(node)) {
848 return true;
849 }
850 node = node.getSuperClass();
851 }
852 return false;
853 }
854
855 /**
856 * @return true if this class is derived from a groovy object
857 * i.e. it implements GroovyObject
858 */
859 public boolean isDerivedFromGroovyObject() {
860 return implementsInterface(ClassHelper.make(GroovyObject.class));
861 }
862
863 /**
864 * @param classNode the class node for the interface
865 * @return true if this class or any base class implements the given interface
866 */
867 public boolean implementsInterface(ClassNode classNode) {
868 ClassNode node = redirect();
869 do {
870 if (node.declaresInterface(classNode)) {
871 return true;
872 }
873 node = node.getSuperClass();
874 }
875 while (node != null);
876 return false;
877 }
878
879 /**
880 * @param classNode the class node for the interface
881 * @return true if this class declares that it implements the given interface
882 * or if one of its interfaces extends directly or indirectly the interface
883 */
884 public boolean declaresInterface(ClassNode classNode) {
885 ClassNode[] interfaces = redirect().getInterfaces();
886 if (declaresInterfaceDirect(interfaces, classNode)) return true;
887 List superInterfaces = Arrays.asList(interfaces);
888 while (superInterfaces.size() > 0) {
889 List keep = new ArrayList();
890 for (int i = 0; i < superInterfaces.size(); i++) {
891 ClassNode cn = (ClassNode) superInterfaces.get(i);
892 if (cn.declaresInterface(classNode)) return true;
893 keep.addAll(Arrays.asList(cn.getInterfaces()));
894 }
895 superInterfaces = keep;
896 }
897 return false;
898 }
899
900 private boolean declaresInterfaceDirect(ClassNode[] interfaces, ClassNode classNode) {
901 int size = interfaces.length;
902 for (int i = 0; i < size; i++) {
903 if (interfaces[i].equals(classNode)) {
904 return true;
905 }
906 }
907 return false;
908 }
909
910 /**
911 * @return the ClassNode of the super class of this type
912 */
913 public ClassNode getSuperClass() {
914 if (!lazyInitDone && !isResolved()) {
915 throw new GroovyBugError("ClassNode#getSuperClass for "+getName()+" called before class resolving");
916 }
917 ClassNode sn = redirect().getUnresolvedSuperClass();
918 if (sn!=null) sn=sn.redirect();
919 return sn;
920 }
921
922 public ClassNode getUnresolvedSuperClass() {
923 return getUnresolvedSuperClass(true);
924 }
925
926 public ClassNode getUnresolvedSuperClass(boolean useRedirect) {
927 if (!useRedirect) return superClass;
928 if (!redirect().lazyInitDone) redirect().lazyClassInit();
929 return redirect().superClass;
930 }
931
932 public void setUnresolvedSuperClass(ClassNode sn) {
933 superClass = sn;
934 }
935
936 public CompileUnit getCompileUnit() {
937 if (redirect!=null) return redirect().getCompileUnit();
938 if (compileUnit == null && module != null) {
939 compileUnit = module.getUnit();
940 }
941 return compileUnit;
942 }
943
944 protected void setCompileUnit(CompileUnit cu) {
945 if (redirect!=null) redirect().setCompileUnit(cu);
946 if (compileUnit!= null) compileUnit = cu;
947 }
948
949 /**
950 * @return true if the two arrays are of the same size and have the same contents
951 */
952 protected boolean parametersEqual(Parameter[] a, Parameter[] b) {
953 if (a.length == b.length) {
954 boolean answer = true;
955 for (int i = 0; i < a.length; i++) {
956 if (!a[i].getType().equals(b[i].getType())) {
957 answer = false;
958 break;
959 }
960 }
961 return answer;
962 }
963 return false;
964 }
965
966 /**
967 * @return the package name of this class
968 */
969 public String getPackageName() {
970 int idx = getName().lastIndexOf('.');
971 if (idx > 0) {
972 return getName().substring(0, idx);
973 }
974 return null;
975 }
976
977 public String getNameWithoutPackage() {
978 int idx = getName().lastIndexOf('.');
979 if (idx > 0) {
980 return getName().substring(idx + 1);
981 }
982 return getName();
983 }
984
985 public void visitContents(GroovyClassVisitor visitor) {
986 // now let's visit the contents of the class
987 for (Object o : getProperties()) {
988 PropertyNode pn = (PropertyNode) o;
989 visitor.visitProperty(pn);
990 }
991
992 for (Object o : getFields()) {
993 FieldNode fn = (FieldNode) o;
994 visitor.visitField(fn);
995 }
996
997 for (Object o : getDeclaredConstructors()) {
998 ConstructorNode cn = (ConstructorNode) o;
999 visitor.visitConstructor(cn);
1000 }
1001
1002 for (Object o : getMethods()) {
1003 MethodNode mn = (MethodNode) o;
1004 visitor.visitMethod(mn);
1005 }
1006 }
1007
1008 public MethodNode getGetterMethod(String getterName) {
1009 for (Object o : getDeclaredMethods(getterName)) {
1010 MethodNode method = (MethodNode) o;
1011 if (getterName.equals(method.getName())
1012 && ClassHelper.VOID_TYPE!=method.getReturnType()
1013 && method.getParameters().length == 0) {
1014 return method;
1015 }
1016 }
1017 ClassNode parent = getSuperClass();
1018 if (parent!=null) return parent.getGetterMethod(getterName);
1019 return null;
1020 }
1021
1022 public MethodNode getSetterMethod(String setterName) {
1023 for (Object o : getDeclaredMethods(setterName)) {
1024 MethodNode method = (MethodNode) o;
1025 if (setterName.equals(method.getName())
1026 && ClassHelper.VOID_TYPE==method.getReturnType()
1027 && method.getParameters().length == 1) {
1028 return method;
1029 }
1030 }
1031 ClassNode parent = getSuperClass();
1032 if (parent!=null) return parent.getSetterMethod(setterName);
1033 return null;
1034 }
1035
1036 /**
1037 * Is this class delcared in a static method (such as a closure / inner class declared in a static method)
1038 */
1039 public boolean isStaticClass() {
1040 return redirect().staticClass;
1041 }
1042
1043 public void setStaticClass(boolean staticClass) {
1044 redirect().staticClass = staticClass;
1045 }
1046
1047 /**
1048 * @return Returns true if this inner class or closure was declared inside a script body
1049 */
1050 public boolean isScriptBody() {
1051 return redirect().scriptBody;
1052 }
1053
1054 public void setScriptBody(boolean scriptBody) {
1055 redirect().scriptBody = scriptBody;
1056 }
1057
1058 public boolean isScript() {
1059 return redirect().script || isDerivedFrom(ClassHelper.SCRIPT_TYPE);
1060 }
1061
1062 public void setScript(boolean script) {
1063 redirect().script = script;
1064 }
1065
1066 public String toString() {
1067 String ret = getName();
1068 if (genericsTypes != null) {
1069 ret += " <";
1070 for (int i = 0; i < genericsTypes.length; i++) {
1071 if (i != 0) ret += ", ";
1072 ret += genericsTypes[i];
1073 }
1074 ret += ">";
1075 }
1076 if (redirect != null) {
1077 ret += " -> " + redirect().toString();
1078 }
1079 return ret;
1080 }
1081
1082 /**
1083 * Returns true if the given method has a possibly matching instance method with the given name and arguments.
1084 *
1085 * @param name the name of the method of interest
1086 * @param arguments the arguments to match against
1087 * @return true if a matching method was found
1088 */
1089 public boolean hasPossibleMethod(String name, Expression arguments) {
1090 int count = 0;
1091
1092 if (arguments instanceof TupleExpression) {
1093 TupleExpression tuple = (TupleExpression) arguments;
1094 // TODO this won't strictly be true when using list expansion in argument calls
1095 count = tuple.getExpressions().size();
1096 }
1097 ClassNode node = this;
1098 do {
1099 for (Object o : getMethods(name)) {
1100 MethodNode method = (MethodNode) o;
1101 if (method.getParameters().length == count) {
1102 return true;
1103 }
1104 }
1105 node = node.getSuperClass();
1106 }
1107 while (node != null);
1108 return false;
1109 }
1110
1111 public MethodNode tryFindPossibleMethod(String name, Expression arguments) {
1112 int count = 0;
1113
1114 if (arguments instanceof TupleExpression) {
1115 TupleExpression tuple = (TupleExpression) arguments;
1116 // TODO this won't strictly be true when using list expansion in argument calls
1117 count = tuple.getExpressions().size();
1118 } else
1119 return null;
1120
1121 MethodNode res = null;
1122 ClassNode node = this;
1123 TupleExpression args = (TupleExpression) arguments;
1124 do {
1125 for (Object o : node.getMethods(name)) {
1126 MethodNode method = (MethodNode) o;
1127 if (method.getParameters().length == count) {
1128 boolean match = true;
1129 for (int i = 0; i != count; ++i)
1130 if (!args.getType().isDerivedFrom(method.getParameters()[i].getType())) {
1131 match = false;
1132 break;
1133 }
1134
1135 if (match) {
1136 if (res == null)
1137 res = method;
1138 else {
1139 if (res.getParameters().length != count)
1140 return null;
1141 if (node.equals(this))
1142 return null;
1143
1144 match = true;
1145 for (int i = 0; i != count; ++i)
1146 if (!res.getParameters()[i].getType().equals(method.getParameters()[i].getType())) {
1147 match = false;
1148 break;
1149 }
1150 if (!match)
1151 return null;
1152 }
1153 }
1154 }
1155 }
1156 node = node.getSuperClass();
1157 }
1158 while (node != null);
1159
1160 return res;
1161 }
1162
1163 /**
1164 * Returns true if the given method has a possibly matching static method with the given name and arguments.
1165 *
1166 * @param name the name of the method of interest
1167 * @param arguments the arguments to match against
1168 * @return true if a matching method was found
1169 */
1170 public boolean hasPossibleStaticMethod(String name, Expression arguments) {
1171 int count = 0;
1172
1173 if (arguments instanceof TupleExpression) {
1174 TupleExpression tuple = (TupleExpression) arguments;
1175 // TODO this won't strictly be true when using list expansion in argument calls
1176 count = tuple.getExpressions().size();
1177 } else if (arguments instanceof MapExpression) {
1178 count = 1;
1179 }
1180
1181 for (Object o : getMethods(name)) {
1182 MethodNode method = (MethodNode) o;
1183 if(method.isStatic()) {
1184 Parameter[] parameters = method.getParameters();
1185 if (parameters.length == count) return true;
1186
1187 // handle varargs case
1188 if (parameters.length > 0 && parameters[parameters.length - 1].getType().isArray()) {
1189 if (count >= parameters.length - 1) return true;
1190 }
1191
1192 // handle parameters with default values
1193 int nonDefaultParameters = 0;
1194 for(int i = 0; i < parameters.length; i++) {
1195 if(parameters[i].hasInitialExpression() == false) {
1196 nonDefaultParameters++;
1197 }
1198 }
1199
1200 if(count < parameters.length && nonDefaultParameters <= count) {
1201 return true;
1202 }
1203 }
1204 }
1205 return false;
1206 }
1207
1208 public boolean isInterface(){
1209 return (getModifiers() & Opcodes.ACC_INTERFACE) > 0;
1210 }
1211
1212 public boolean isResolved(){
1213 return redirect().clazz!=null || (componentType != null && componentType.isResolved());
1214 }
1215
1216 public boolean isArray(){
1217 return componentType!=null;
1218 }
1219
1220 public ClassNode getComponentType() {
1221 return componentType;
1222 }
1223
1224 public Class getTypeClass(){
1225 Class c = redirect().clazz;
1226 if (c!=null) return c;
1227 ClassNode component = redirect().componentType;
1228 if (component!=null && component.isResolved()){
1229 ClassNode cn = component.makeArray();
1230 setRedirect(cn);
1231 return redirect().clazz;
1232 }
1233 throw new GroovyBugError("ClassNode#getTypeClass for "+getName()+" is called before the type class is set ");
1234 }
1235
1236 public boolean hasPackageName(){
1237 return redirect().name.indexOf('.')>0;
1238 }
1239
1240 /**
1241 * Marks if the current class uses annotations or not
1242 * @param flag
1243 */
1244 public void setAnnotated(boolean flag) {
1245 this.annotated = flag;
1246 }
1247
1248 public boolean isAnnotated() {
1249 return this.annotated;
1250 }
1251
1252 public GenericsType[] getGenericsTypes() {
1253 return genericsTypes;
1254 }
1255
1256 public void setGenericsTypes(GenericsType[] genericsTypes) {
1257 usesGenerics = usesGenerics || genericsTypes!=null;
1258 this.genericsTypes = genericsTypes;
1259 }
1260
1261 public void setGenericsPlaceHolder(boolean b) {
1262 usesGenerics = usesGenerics || b;
1263 placeholder = b;
1264 }
1265
1266 public boolean isGenericsPlaceHolder() {
1267 return placeholder;
1268 }
1269
1270 public boolean isUsingGenerics() {
1271 return usesGenerics;
1272 }
1273
1274 public void setUsingGenerics(boolean b) {
1275 usesGenerics = b;
1276 }
1277
1278 public ClassNode getPlainNodeReference() {
1279 if (ClassHelper.isPrimitiveType(this)) return this;
1280 ClassNode n = new ClassNode(getName(),getModifiers(),getSuperClass(),null,null);
1281 n.isPrimaryNode = false;
1282 n.setRedirect(this.redirect);
1283 return n;
1284 }
1285
1286 public boolean isAnnotationDefinition() {
1287 return redirect().isPrimaryNode &&
1288 isInterface() &&
1289 (getModifiers() & Opcodes.ACC_ANNOTATION)!=0;
1290 }
1291
1292 public List getAnnotations() {
1293 if (redirect!=null) return redirect.getAnnotations();
1294 lazyClassInit();
1295 return super.getAnnotations();
1296 }
1297
1298 public List getAnnotations(ClassNode type) {
1299 if (redirect!=null) return redirect.getAnnotations(type);
1300 lazyClassInit();
1301 return super.getAnnotations(type);
1302 }
1303
1304 public void addTransform(Class<? extends ASTTransformation> transform, ASTNode node) {
1305 if (transformInstances == null)
1306 getTransformInstancesLazy();
1307
1308 GroovyASTTransformation annotation = transform.getAnnotation(GroovyASTTransformation.class);
1309 Set<ASTNode> nodes = transformInstances.get(annotation.phase()).get(transform);
1310 if (nodes == null) {
1311 nodes = new LinkedHashSet();
1312 transformInstances.get(annotation.phase()).put(transform, nodes);
1313 }
1314 nodes.add(node);
1315 }
1316
1317 public Map<Class <? extends ASTTransformation>, Set<ASTNode>> getTransforms(CompilePhase phase) {
1318 if (transformInstances == null)
1319 return Collections.EMPTY_MAP;
1320
1321 return transformInstances.get(phase);
1322 }
1323
1324 public void renameField(String oldName, String newName) {
1325 final Map index = redirect().getFieldIndexLazy();
1326 index.put(newName, index.remove(oldName));
1327 }
1328 }