001 /* 002 * $Id: ClassNode.java 4216 2006-11-13 16:04:23Z blackdrag $ 003 * 004 * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 005 * 006 * Redistribution and use of this software and associated documentation 007 * ("Software"), with or without modification, are permitted provided that the 008 * following conditions are met: 009 * 1. Redistributions of source code must retain copyright statements and 010 * notices. Redistributions must also contain a copy of this document. 011 * 2. Redistributions in binary form must reproduce the above copyright 012 * notice, this list of conditions and the following disclaimer in the 013 * documentation and/or other materials provided with the distribution. 014 * 3. The name "groovy" must not be used to endorse or promote products 015 * derived from this Software without prior written permission of The Codehaus. 016 * For written permission, please contact info@codehaus.org. 017 * 4. Products derived from this Software may not be called "groovy" nor may 018 * "groovy" appear in their names without prior written permission of The 019 * Codehaus. "groovy" is a registered trademark of The Codehaus. 020 * 5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/ 021 * 022 * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY 023 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 024 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 025 * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR 026 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 027 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 028 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 029 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 030 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 031 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 032 * DAMAGE. 033 * 034 */ 035 package org.codehaus.groovy.ast; 036 037 import groovy.lang.GroovyObject; 038 039 import org.codehaus.groovy.GroovyBugError; 040 import org.codehaus.groovy.ast.expr.Expression; 041 import org.codehaus.groovy.ast.expr.TupleExpression; 042 import org.codehaus.groovy.ast.stmt.BlockStatement; 043 import org.codehaus.groovy.ast.stmt.EmptyStatement; 044 import org.codehaus.groovy.ast.stmt.Statement; 045 import org.objectweb.asm.Opcodes; 046 047 import java.lang.reflect.Array; 048 import java.lang.reflect.Constructor; 049 import java.lang.reflect.Field; 050 import java.lang.reflect.Method; 051 import java.util.ArrayList; 052 import java.util.HashMap; 053 import java.util.HashSet; 054 import java.util.Iterator; 055 import java.util.List; 056 import java.util.Map; 057 058 /** 059 * Represents a class in the AST.<br/> 060 * A ClassNode should be created using the methods in ClassHelper. 061 * This ClassNode may be used to represent a class declaration or 062 * any other type. This class uses a proxy meschanism allowing to 063 * create a class for a plain name at ast creation time. In another 064 * phase of the compiler the real ClassNode for the plain name may be 065 * found. To avoid the need of exchanging this ClassNode with an 066 * instance of the correct ClassNode the correct ClassNode is set as 067 * redirect. All method calls are then redirected to that ClassNode. 068 * <br> 069 * Note: the proxy mechanism is only allowed for classes being marked 070 * as primary ClassNode which means they represent no actual class. 071 * The redirect itself can be any type of ClassNode 072 * 073 * @see org.codehaus.groovy.ast.ClassHelper 074 * 075 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> 076 * @author Jochen Theodorou 077 * @version $Revision: 4216 $ 078 */ 079 public class ClassNode extends AnnotatedNode implements Opcodes { 080 081 public static ClassNode[] EMPTY_ARRAY = new ClassNode[0]; 082 083 public static ClassNode THIS = new ClassNode(Object.class); 084 public static ClassNode SUPER = new ClassNode(Object.class); 085 086 private String name; 087 private int modifiers; 088 private ClassNode[] interfaces; 089 private MixinNode[] mixins; 090 private List constructors = new ArrayList(); 091 private List objectInitializers = new ArrayList(); 092 private List methods = new ArrayList(); 093 private List fields = new ArrayList(); 094 private List properties = new ArrayList(); 095 private Map fieldIndex = new HashMap(); 096 private ModuleNode module; 097 private CompileUnit compileUnit; 098 private boolean staticClass = false; 099 private boolean scriptBody = false; 100 private boolean script; 101 private ClassNode superClass; 102 boolean isPrimaryNode; 103 104 // use this to synchronize access for the lazy intit 105 protected Object lazyInitLock = new Object(); 106 107 // clazz!=null when resolved 108 protected Class clazz; 109 // only false when this classNode is constructed from a class 110 private boolean lazyInitDone=true; 111 // not null if if the ClassNode is an array 112 private ClassNode componentType = null; 113 // if not null this instance is handled as proxy 114 // for the redirect 115 private ClassNode redirect=null; 116 117 /** 118 * Returns the ClassNode this ClassNode is redirecting to. 119 */ 120 protected ClassNode redirect(){ 121 if (redirect==null) return this; 122 return redirect.redirect(); 123 } 124 125 /** 126 * Sets this instance as proxy for the given ClassNode. 127 * @param cn the class to redirect to. If set to null the redirect will be removed 128 */ 129 public void setRedirect(ClassNode cn) { 130 if (isPrimaryNode) throw new GroovyBugError("tried to set a redirect for a primary ClassNode ("+getName()+"->"+cn.getName()+")."); 131 if (cn!=null) cn = cn.redirect(); 132 redirect = cn; 133 } 134 135 /** 136 * Returns a ClassNode representing an array of the class 137 * represented by this ClassNode 138 */ 139 public ClassNode makeArray() { 140 if (redirect!=null) return redirect().makeArray(); 141 ClassNode cn; 142 if (clazz!=null) { 143 Class ret = Array.newInstance(clazz,0).getClass(); 144 // don't use the ClassHelper here! 145 cn = new ClassNode(ret,this); 146 } else { 147 cn = new ClassNode(this); 148 } 149 return cn; 150 } 151 152 /** 153 * Returns if this instance is a primary ClassNode 154 */ 155 public boolean isPrimaryClassNode(){ 156 return redirect().isPrimaryNode || (componentType!= null && componentType.isPrimaryClassNode()); 157 } 158 159 /** 160 * Constructor used by makeArray() if no real class is available 161 */ 162 private ClassNode(ClassNode componentType) { 163 this(componentType.getName()+"[]", ACC_PUBLIC, ClassHelper.OBJECT_TYPE); 164 this.componentType = componentType.redirect(); 165 isPrimaryNode=false; 166 } 167 168 /** 169 * Constructor used by makeArray() if a real class is available 170 */ 171 private ClassNode(Class c, ClassNode componentType) { 172 this(c); 173 this.componentType = componentType; 174 isPrimaryNode=false; 175 } 176 177 /** 178 * Creates a ClassNode from a real class. The resulting 179 * ClassNode will be no primary ClassNode. 180 */ 181 public ClassNode(Class c) { 182 this(c.getName(), c.getModifiers(), null, null ,MixinNode.EMPTY_ARRAY); 183 clazz=c; 184 lazyInitDone=false; 185 CompileUnit cu = getCompileUnit(); 186 if (cu!=null) cu.addClass(this); 187 isPrimaryNode=false; 188 } 189 190 /** 191 * The complete class structure will be initialized only when really 192 * needed to avoid having too much objects during compilation 193 */ 194 private void lazyClassInit() { 195 synchronized (lazyInitLock) { 196 if (lazyInitDone) return; 197 198 Field[] fields = clazz.getDeclaredFields(); 199 for (int i=0;i<fields.length;i++){ 200 addField(fields[i].getName(),fields[i].getModifiers(),this,null); 201 } 202 Method[] methods = clazz.getDeclaredMethods(); 203 for (int i=0;i<methods.length;i++){ 204 Method m = methods[i]; 205 MethodNode mn = new MethodNode(m.getName(), m.getModifiers(), ClassHelper.make(m.getReturnType()), createParameters(m.getParameterTypes()), ClassHelper.make(m.getExceptionTypes()), null); 206 addMethod(mn); 207 } 208 Constructor[] constructors = clazz.getDeclaredConstructors(); 209 for (int i=0;i<constructors.length;i++){ 210 Constructor ctor = constructors[i]; 211 addConstructor(ctor.getModifiers(),createParameters(ctor.getParameterTypes()),ClassHelper.make(ctor.getExceptionTypes()),null); 212 } 213 Class sc = clazz.getSuperclass(); 214 if (sc!=null) superClass = ClassHelper.make(sc); 215 buildInterfaceTypes(clazz); 216 lazyInitDone=true; 217 } 218 } 219 220 private void buildInterfaceTypes(Class c) { 221 Class[] interfaces = c.getInterfaces(); 222 ClassNode[] ret = new ClassNode[interfaces.length]; 223 for (int i=0;i<interfaces.length;i++){ 224 ret[i] = ClassHelper.make(interfaces[i]); 225 } 226 this.interfaces = ret; 227 } 228 229 230 // added to track the enclosing method for local inner classes 231 private MethodNode enclosingMethod = null; 232 233 public MethodNode getEnclosingMethod() { 234 return redirect().enclosingMethod; 235 } 236 237 public void setEnclosingMethod(MethodNode enclosingMethod) { 238 redirect().enclosingMethod = enclosingMethod; 239 } 240 241 242 /** 243 * @param name is the full name of the class 244 * @param modifiers the modifiers, 245 * @param superClass the base class name - use "java.lang.Object" if no direct 246 * base class 247 * @see org.objectweb.asm.Opcodes 248 */ 249 public ClassNode(String name, int modifiers, ClassNode superClass) { 250 this(name, modifiers, superClass, ClassHelper.EMPTY_TYPE_ARRAY, MixinNode.EMPTY_ARRAY); 251 } 252 253 /** 254 * @param name is the full name of the class 255 * @param modifiers the modifiers, 256 * @param superClass the base class name - use "java.lang.Object" if no direct 257 * base class 258 * @see org.objectweb.asm.Opcodes 259 */ 260 public ClassNode(String name, int modifiers, ClassNode superClass, ClassNode[] interfaces, MixinNode[] mixins) { 261 this.name = name; 262 this.modifiers = modifiers; 263 this.superClass = superClass; 264 this.interfaces = interfaces; 265 this.mixins = mixins; 266 isPrimaryNode = true; 267 } 268 269 270 /** 271 * Sets the superclass of this ClassNode 272 */ 273 public void setSuperClass(ClassNode superClass) { 274 redirect().superClass = superClass; 275 } 276 277 /** 278 * Returns a list containing FieldNode objects for 279 * each field in the class represented by this ClassNode 280 */ 281 public List getFields() { 282 if (!lazyInitDone) { 283 lazyClassInit(); 284 } 285 if (redirect!=null) return redirect().getFields(); 286 return fields; 287 } 288 289 /** 290 * Returns an array of ClassNodes representing the 291 * interfaces the class implements 292 */ 293 public ClassNode[] getInterfaces() { 294 if (!lazyInitDone) { 295 lazyClassInit(); 296 } 297 if (redirect!=null) return redirect().getInterfaces(); 298 return interfaces; 299 } 300 301 public MixinNode[] getMixins() { 302 return redirect().mixins; 303 } 304 305 /** 306 * Returns a list containing MethodNode objects for 307 * each method in the class represented by this ClassNode 308 */ 309 public List getMethods() { 310 if (!lazyInitDone) { 311 lazyClassInit(); 312 } 313 if (redirect!=null) return redirect().getMethods(); 314 return methods; 315 } 316 317 /** 318 * Returns a list containing MethodNode objects for 319 * each abstract method in the class represented by 320 * this ClassNode 321 */ 322 public List getAbstractMethods() { 323 324 HashSet abstractNodes = new HashSet(); 325 // let us collect the abstract super classes and stop at the 326 // first non abstract super class. If such a class still 327 // contains abstract methods, then loading that class will fail. 328 // No need to be extra carefull here for that. 329 ClassNode parent = this.redirect(); 330 do { 331 abstractNodes.add(parent); 332 ClassNode[] interfaces = parent.getInterfaces(); 333 for (int i = 0; i < interfaces.length; i++) { 334 abstractNodes.add(interfaces[i].redirect()); 335 } 336 parent = parent.getSuperClass().redirect(); 337 } while (parent!=null && ((parent.getModifiers() & Opcodes.ACC_ABSTRACT) != 0)); 338 339 List result = new ArrayList(); 340 for (Iterator methIt = getAllDeclaredMethods().iterator(); methIt.hasNext();) { 341 MethodNode method = (MethodNode) methIt.next(); 342 // add only abstract methods from abtract classes that 343 // are not overwritten 344 if ( abstractNodes.contains(method.getDeclaringClass().redirect()) && 345 (method.getModifiers() & Opcodes.ACC_ABSTRACT) != 0 346 ) { 347 result.add(method); 348 } 349 } 350 if (result.size() == 0) { 351 return null; 352 } 353 else { 354 return result; 355 } 356 } 357 358 public List getAllDeclaredMethods() { 359 return new ArrayList(getDeclaredMethodsMap().values()); 360 } 361 362 363 protected Map getDeclaredMethodsMap() { 364 // Start off with the methods from the superclass. 365 ClassNode parent = getSuperClass(); 366 Map result = null; 367 if (parent != null) { 368 result = parent.getDeclaredMethodsMap(); 369 } 370 else { 371 result = new HashMap(); 372 } 373 374 // add in unimplemented abstract methods from the interfaces 375 ClassNode[] interfaces = getInterfaces(); 376 for (int i = 0; i < interfaces.length; i++) { 377 ClassNode iface = interfaces[i]; 378 Map ifaceMethodsMap = iface.getDeclaredMethodsMap(); 379 for (Iterator iter = ifaceMethodsMap.keySet().iterator(); iter.hasNext();) { 380 String methSig = (String) iter.next(); 381 if (!result.containsKey(methSig)) { 382 MethodNode methNode = (MethodNode) ifaceMethodsMap.get(methSig); 383 result.put(methSig, methNode); 384 } 385 } 386 } 387 388 // And add in the methods implemented in this class. 389 for (Iterator iter = getMethods().iterator(); iter.hasNext();) { 390 MethodNode method = (MethodNode) iter.next(); 391 String sig = method.getTypeDescriptor(); 392 result.put(sig, method); 393 } 394 return result; 395 } 396 397 public String getName() { 398 return redirect().name; 399 } 400 401 public String setName(String name) { 402 return redirect().name=name; 403 } 404 405 public int getModifiers() { 406 return redirect().modifiers; 407 } 408 409 public List getProperties() { 410 return redirect().properties; 411 } 412 413 public List getDeclaredConstructors() { 414 if (!lazyInitDone) { 415 lazyClassInit(); 416 } 417 return redirect().constructors; 418 } 419 420 public ModuleNode getModule() { 421 return redirect().module; 422 } 423 424 public void setModule(ModuleNode module) { 425 redirect().module = module; 426 if (module != null) { 427 redirect().compileUnit = module.getUnit(); 428 } 429 } 430 431 public void addField(FieldNode node) { 432 node.setDeclaringClass(redirect()); 433 node.setOwner(redirect()); 434 redirect().fields.add(node); 435 redirect().fieldIndex.put(node.getName(), node); 436 } 437 438 public void addProperty(PropertyNode node) { 439 node.setDeclaringClass(redirect()); 440 FieldNode field = node.getField(); 441 addField(field); 442 443 redirect().properties.add(node); 444 } 445 446 public PropertyNode addProperty(String name, 447 int modifiers, 448 ClassNode type, 449 Expression initialValueExpression, 450 Statement getterBlock, 451 Statement setterBlock) { 452 for (Iterator iter = getProperties().iterator(); iter.hasNext();) { 453 PropertyNode pn = (PropertyNode) iter.next(); 454 if (pn.getName().equals(name)) return pn; 455 } 456 PropertyNode node = 457 new PropertyNode(name, modifiers, type, redirect(), initialValueExpression, getterBlock, setterBlock); 458 addProperty(node); 459 return node; 460 } 461 462 public void addConstructor(ConstructorNode node) { 463 node.setDeclaringClass(this); 464 redirect().constructors.add(node); 465 } 466 467 public ConstructorNode addConstructor(int modifiers, Parameter[] parameters, ClassNode[] exceptions, Statement code) { 468 ConstructorNode node = new ConstructorNode(modifiers, parameters, exceptions, code); 469 addConstructor(node); 470 return node; 471 } 472 473 public void addMethod(MethodNode node) { 474 node.setDeclaringClass(this); 475 redirect().methods.add(node); 476 } 477 478 /** 479 * IF a method with the given name and parameters is already defined then it is returned 480 * otherwise the given method is added to this node. This method is useful for 481 * default method adding like getProperty() or invokeMethod() where there may already 482 * be a method defined in a class and so the default implementations should not be added 483 * if already present. 484 */ 485 public MethodNode addMethod(String name, 486 int modifiers, 487 ClassNode returnType, 488 Parameter[] parameters, 489 ClassNode[] exceptions, 490 Statement code) { 491 MethodNode other = getDeclaredMethod(name, parameters); 492 // lets not add duplicate methods 493 if (other != null) { 494 return other; 495 } 496 MethodNode node = new MethodNode(name, modifiers, returnType, parameters, exceptions, code); 497 addMethod(node); 498 return node; 499 } 500 501 /** 502 * Adds a synthetic method as part of the compilation process 503 */ 504 public MethodNode addSyntheticMethod(String name, 505 int modifiers, 506 ClassNode returnType, 507 Parameter[] parameters, 508 ClassNode[] exceptions, 509 Statement code) { 510 MethodNode answer = addMethod(name, modifiers, returnType, parameters, exceptions, code); 511 answer.setSynthetic(true); 512 return answer; 513 } 514 515 public FieldNode addField(String name, int modifiers, ClassNode type, Expression initialValue) { 516 FieldNode node = new FieldNode(name, modifiers, type, redirect(), initialValue); 517 addField(node); 518 return node; 519 } 520 521 public void addInterface(ClassNode type) { 522 // lets check if it already implements an interface 523 boolean skip = false; 524 ClassNode[] interfaces = redirect().interfaces; 525 for (int i = 0; i < interfaces.length; i++) { 526 if (type.equals(interfaces[i])) { 527 skip = true; 528 } 529 } 530 if (!skip) { 531 ClassNode[] newInterfaces = new ClassNode[interfaces.length + 1]; 532 System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length); 533 newInterfaces[interfaces.length] = type; 534 redirect().interfaces = newInterfaces; 535 } 536 } 537 538 public boolean equals(Object o) { 539 if (redirect!=null) return redirect().equals(o); 540 ClassNode cn = (ClassNode) o; 541 return (cn.getName().equals(getName())); 542 } 543 544 public void addMixin(MixinNode mixin) { 545 // lets check if it already uses a mixin 546 MixinNode[] mixins = redirect().mixins; 547 boolean skip = false; 548 for (int i = 0; i < mixins.length; i++) { 549 if (mixin.equals(mixins[i])) { 550 skip = true; 551 } 552 } 553 if (!skip) { 554 MixinNode[] newMixins = new MixinNode[mixins.length + 1]; 555 System.arraycopy(mixins, 0, newMixins, 0, mixins.length); 556 newMixins[mixins.length] = mixin; 557 redirect().mixins = newMixins; 558 } 559 } 560 561 public FieldNode getField(String name) { 562 return (FieldNode) redirect().fieldIndex.get(name); 563 } 564 565 /** 566 * @return the field node on the outer class or null if this is not an 567 * inner class 568 */ 569 public FieldNode getOuterField(String name) { 570 return null; 571 } 572 573 /** 574 * Helper method to avoid casting to inner class 575 */ 576 public ClassNode getOuterClass() { 577 return null; 578 } 579 580 public void addObjectInitializerStatements(Statement statements) { 581 objectInitializers.add(statements); 582 } 583 584 public List getObjectInitializerStatements() { 585 return objectInitializers; 586 } 587 588 public void addStaticInitializerStatements(List staticStatements, boolean fieldInit) { 589 MethodNode method = null; 590 List declaredMethods = getDeclaredMethods("<clinit>"); 591 if (declaredMethods.isEmpty()) { 592 method = 593 addMethod("<clinit>", ACC_PUBLIC | ACC_STATIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement()); 594 method.setSynthetic(true); 595 } 596 else { 597 method = (MethodNode) declaredMethods.get(0); 598 } 599 BlockStatement block = null; 600 Statement statement = method.getCode(); 601 if (statement == null) { 602 block = new BlockStatement(); 603 } 604 else if (statement instanceof BlockStatement) { 605 block = (BlockStatement) statement; 606 } 607 else { 608 block = new BlockStatement(); 609 block.addStatement(statement); 610 } 611 612 // while anything inside a static initializer block is appended 613 // we don't want to append in the case we have a initialization 614 // expression of a static field. In that case we want to add 615 // before the other statements 616 if (!fieldInit) { 617 block.addStatements(staticStatements); 618 } else { 619 List blockStatements = block.getStatements(); 620 staticStatements.addAll(blockStatements); 621 blockStatements.clear(); 622 blockStatements.addAll(staticStatements); 623 } 624 } 625 626 /** 627 * @return a list of methods which match the given name 628 */ 629 public List getDeclaredMethods(String name) { 630 List answer = new ArrayList(); 631 for (Iterator iter = getMethods().iterator(); iter.hasNext();) { 632 MethodNode method = (MethodNode) iter.next(); 633 if (name.equals(method.getName())) { 634 answer.add(method); 635 } 636 } 637 return answer; 638 } 639 640 /** 641 * @return a list of methods which match the given name 642 */ 643 public List getMethods(String name) { 644 List answer = new ArrayList(); 645 ClassNode node = this; 646 do { 647 for (Iterator iter = node.getMethods().iterator(); iter.hasNext();) { 648 MethodNode method = (MethodNode) iter.next(); 649 if (name.equals(method.getName())) { 650 answer.add(method); 651 } 652 } 653 node = node.getSuperClass(); 654 } 655 while (node != null); 656 return answer; 657 } 658 659 /** 660 * @return the method matching the given name and parameters or null 661 */ 662 public MethodNode getDeclaredMethod(String name, Parameter[] parameters) { 663 for (Iterator iter = getMethods().iterator(); iter.hasNext();) { 664 MethodNode method = (MethodNode) iter.next(); 665 if (name.equals(method.getName()) && parametersEqual(method.getParameters(), parameters)) { 666 return method; 667 } 668 } 669 return null; 670 } 671 672 /** 673 * @return true if this node is derived from the given class node 674 */ 675 public boolean isDerivedFrom(ClassNode type) { 676 ClassNode node = this; 677 while (node != null) { 678 if (type.equals(node)) { 679 return true; 680 } 681 node = node.getSuperClass(); 682 } 683 return false; 684 } 685 686 /** 687 * @return true if this class is derived from a groovy object 688 * i.e. it implements GroovyObject 689 */ 690 public boolean isDerivedFromGroovyObject() { 691 return implementsInterface(GroovyObject.class.getName()); 692 } 693 694 /** 695 * @param name the fully qualified name of the interface 696 * @return true if this class or any base class implements the given interface 697 */ 698 public boolean implementsInterface(String name) { 699 ClassNode node = redirect(); 700 do { 701 if (node.declaresInterface(name)) { 702 return true; 703 } 704 node = node.getSuperClass(); 705 } 706 while (node != null); 707 return false; 708 } 709 710 /** 711 * @param name the fully qualified name of the interface 712 * @return true if this class declares that it implements the given interface 713 */ 714 public boolean declaresInterface(String name) { 715 ClassNode[] interfaces = redirect().getInterfaces(); 716 int size = interfaces.length; 717 for (int i = 0; i < size; i++) { 718 if (interfaces[i].getName().equals(name)) { 719 return true; 720 } 721 } 722 return false; 723 } 724 725 /** 726 * @return the ClassNode of the super class of this type 727 */ 728 public ClassNode getSuperClass() { 729 if (!lazyInitDone && !isResolved()) { 730 throw new GroovyBugError("Classnode#getSuperClass for "+getName()+" called before class resolving"); 731 } 732 return redirect().getUnresolvedSuperClass(); 733 } 734 735 public ClassNode getUnresolvedSuperClass() { 736 if (!lazyInitDone) { 737 lazyClassInit(); 738 } 739 return redirect().superClass; 740 } 741 742 /** 743 * Factory method to create a new MethodNode via reflection 744 */ 745 protected MethodNode createMethodNode(Method method) { 746 Parameter[] parameters = createParameters(method.getParameterTypes()); 747 return new MethodNode(method.getName(), method.getModifiers(), ClassHelper.make(method.getReturnType()), parameters, ClassHelper.make(method.getExceptionTypes()), EmptyStatement.INSTANCE); 748 } 749 750 /** 751 * @param types 752 */ 753 protected Parameter[] createParameters(Class[] types) { 754 Parameter[] parameters = Parameter.EMPTY_ARRAY; 755 int size = types.length; 756 if (size > 0) { 757 parameters = new Parameter[size]; 758 for (int i = 0; i < size; i++) { 759 parameters[i] = createParameter(types[i], i); 760 } 761 } 762 return parameters; 763 } 764 765 protected Parameter createParameter(Class parameterType, int idx) { 766 return new Parameter(ClassHelper.make(parameterType), "param" + idx); 767 } 768 769 public CompileUnit getCompileUnit() { 770 if (redirect!=null) return redirect().getCompileUnit(); 771 if (compileUnit == null && module != null) { 772 compileUnit = module.getUnit(); 773 } 774 return compileUnit; 775 } 776 777 protected void setCompileUnit(CompileUnit cu) { 778 if (redirect!=null) redirect().setCompileUnit(cu); 779 if (compileUnit!= null) compileUnit = cu; 780 } 781 782 /** 783 * @return true if the two arrays are of the same size and have the same contents 784 */ 785 protected boolean parametersEqual(Parameter[] a, Parameter[] b) { 786 if (a.length == b.length) { 787 boolean answer = true; 788 for (int i = 0; i < a.length; i++) { 789 if (!a[i].getType().equals(b[i].getType())) { 790 answer = false; 791 break; 792 } 793 } 794 return answer; 795 } 796 return false; 797 } 798 799 /** 800 * @return the package name of this class 801 */ 802 public String getPackageName() { 803 int idx = getName().lastIndexOf('.'); 804 if (idx > 0) { 805 return getName().substring(0, idx); 806 } 807 return null; 808 } 809 810 public String getNameWithoutPackage() { 811 int idx = getName().lastIndexOf('.'); 812 if (idx > 0) { 813 return getName().substring(idx + 1); 814 } 815 return getName(); 816 } 817 818 public void visitContents(GroovyClassVisitor visitor) { 819 820 // now lets visit the contents of the class 821 for (Iterator iter = getProperties().iterator(); iter.hasNext();) { 822 PropertyNode pn = (PropertyNode) iter.next(); 823 visitor.visitProperty(pn); 824 } 825 826 for (Iterator iter = getFields().iterator(); iter.hasNext();) { 827 FieldNode fn = (FieldNode) iter.next(); 828 visitor.visitField(fn); 829 } 830 831 for (Iterator iter = getDeclaredConstructors().iterator(); iter.hasNext();) { 832 ConstructorNode cn = (ConstructorNode) iter.next(); 833 visitor.visitConstructor(cn); 834 } 835 836 for (Iterator iter = getMethods().iterator(); iter.hasNext();) { 837 MethodNode mn = (MethodNode) iter.next(); 838 visitor.visitMethod(mn); 839 } 840 } 841 842 public MethodNode getGetterMethod(String getterName) { 843 for (Iterator iter = getMethods().iterator(); iter.hasNext();) { 844 MethodNode method = (MethodNode) iter.next(); 845 if (getterName.equals(method.getName()) 846 && ClassHelper.VOID_TYPE!=method.getReturnType() 847 && method.getParameters().length == 0) { 848 return method; 849 } 850 } 851 return null; 852 } 853 854 public MethodNode getSetterMethod(String getterName) { 855 for (Iterator iter = getMethods().iterator(); iter.hasNext();) { 856 MethodNode method = (MethodNode) iter.next(); 857 if (getterName.equals(method.getName()) 858 && ClassHelper.VOID_TYPE==method.getReturnType() 859 && method.getParameters().length == 1) { 860 return method; 861 } 862 } 863 return null; 864 } 865 866 /** 867 * Is this class delcared in a static method (such as a closure / inner class declared in a static method) 868 */ 869 public boolean isStaticClass() { 870 return redirect().staticClass; 871 } 872 873 public void setStaticClass(boolean staticClass) { 874 redirect().staticClass = staticClass; 875 } 876 877 /** 878 * @return Returns true if this inner class or closure was declared inside a script body 879 */ 880 public boolean isScriptBody() { 881 return redirect().scriptBody; 882 } 883 884 public void setScriptBody(boolean scriptBody) { 885 redirect().scriptBody = scriptBody; 886 } 887 888 public boolean isScript() { 889 return redirect().script || isDerivedFrom(ClassHelper.SCRIPT_TYPE); 890 } 891 892 public void setScript(boolean script) { 893 redirect().script = script; 894 } 895 896 public String toString() { 897 return super.toString() + "[name: " + getName() + "]"; 898 } 899 900 /** 901 * Returns true if the given method has a possibly matching method with the given name and arguments 902 */ 903 public boolean hasPossibleMethod(String name, Expression arguments) { 904 int count = 0; 905 906 if (arguments instanceof TupleExpression) { 907 TupleExpression tuple = (TupleExpression) arguments; 908 // TODO this won't strictly be true when using list expension in argument calls 909 count = tuple.getExpressions().size(); 910 } 911 ClassNode node = this; 912 do { 913 for (Iterator iter = getMethods().iterator(); iter.hasNext();) { 914 MethodNode method = (MethodNode) iter.next(); 915 if (name.equals(method.getName()) && method.getParameters().length == count) { 916 return true; 917 } 918 } 919 node = node.getSuperClass(); 920 } 921 while (node != null); 922 return false; 923 } 924 925 public boolean isInterface(){ 926 return (getModifiers() & Opcodes.ACC_INTERFACE) > 0; 927 } 928 929 public boolean isResolved(){ 930 return redirect().clazz!=null || (componentType != null && componentType.isResolved()); 931 } 932 933 public boolean isArray(){ 934 return componentType!=null; 935 } 936 937 public ClassNode getComponentType() { 938 return componentType; 939 } 940 941 public Class getTypeClass(){ 942 Class c = redirect().clazz; 943 if (c!=null) return c; 944 ClassNode component = redirect().componentType; 945 if (component!=null && component.isResolved()){ 946 ClassNode cn = component.makeArray(); 947 setRedirect(cn); 948 return redirect().clazz; 949 } 950 throw new GroovyBugError("ClassNode#getTypeClass for "+getName()+" is called before the type class is set "); 951 } 952 953 public boolean hasPackageName(){ 954 return redirect().name.indexOf('.')>0; 955 } 956 }