private void checkImplementsAndExtends(ClassNode node) {
   ClassNode cn = node.getSuperClass();
   if (cn.isInterface() && !node.isInterface()) {
     addError(
         "You are not allowed to extend the " + getDescription(cn) + ", use implements instead.",
         node);
   }
   for (ClassNode anInterface : node.getInterfaces()) {
     cn = anInterface;
     if (!cn.isInterface()) {
       addError(
           "You are not allowed to implement the " + getDescription(cn) + ", use extends instead.",
           node);
     }
   }
 }
 private void checkClassForAbstractAndFinal(ClassNode node) {
   if (!isAbstract(node.getModifiers())) return;
   if (!isFinal(node.getModifiers())) return;
   if (node.isInterface()) {
     addError(
         "The " + getDescription(node) + " must not be final. It is by definition abstract.",
         node);
   } else {
     addError("The " + getDescription(node) + " must not be both final and abstract.", node);
   }
 }
예제 #3
0
 private static void writeGenericsBoundType(
     StringBuffer ret, ClassNode printType, boolean writeInterfaceMarker) {
   if (writeInterfaceMarker && printType.isInterface()) ret.append(":");
   if (printType.equals(ClassHelper.OBJECT_TYPE) && printType.getGenericsTypes() != null) {
     ret.append("T");
     ret.append(printType.getGenericsTypes()[0].getName());
     ret.append(";");
   } else {
     ret.append(getTypeDescription(printType, false));
     addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
     if (!ClassHelper.isPrimitiveType(printType)) ret.append(";");
   }
 }
 private void checkInterfaceFieldModifiers(FieldNode node) {
   if (!currentClass.isInterface()) return;
   if ((node.getModifiers() & (ACC_PUBLIC | ACC_STATIC | ACC_FINAL)) == 0
       || (node.getModifiers() & (ACC_PRIVATE | ACC_PROTECTED)) != 0) {
     addError(
         "The "
             + getDescription(node)
             + " is not 'public static final' but is defined in "
             + getDescription(currentClass)
             + ".",
         node);
   }
 }
 public static Set<ClassNode> getInterfacesAndSuperInterfaces(ClassNode type) {
   Set<ClassNode> res = new HashSet<ClassNode>();
   if (type.isInterface()) {
     res.add(type);
     return res;
   }
   ClassNode next = type;
   while (next != null) {
     Collections.addAll(res, next.getInterfaces());
     next = next.getSuperClass();
   }
   return res;
 }
 private void visitClassNode(ClassNode cNode, List<PackageScopeTarget> value) {
   String cName = cNode.getName();
   if (cNode.isInterface() && value.size() != 1 && value.get(0) != PackageScopeTarget.CLASS) {
     throw new RuntimeException(
         "Error processing interface '"
             + cName
             + "'. "
             + MY_TYPE_NAME
             + " not allowed for interfaces except when targeting Class level.");
   }
   if (value.contains(groovy.transform.PackageScopeTarget.CLASS)) {
     if (cNode.isSyntheticPublic()) revertVisibility(cNode);
     else
       throw new RuntimeException(
           "Can't use "
               + MY_TYPE_NAME
               + " for class '"
               + cNode.getName()
               + "' which has explicit visibility.");
   }
   if (value.contains(groovy.transform.PackageScopeTarget.METHODS)) {
     final List<MethodNode> mList = cNode.getMethods();
     for (MethodNode mNode : mList) {
       if (mNode.isSyntheticPublic()) revertVisibility(mNode);
     }
   }
   if (value.contains(PackageScopeTarget.FIELDS)) {
     final List<PropertyNode> pList = cNode.getProperties();
     List<PropertyNode> foundProps = new ArrayList<PropertyNode>();
     List<String> foundNames = new ArrayList<String>();
     for (PropertyNode pNode : pList) {
       foundProps.add(pNode);
       foundNames.add(pNode.getName());
     }
     for (PropertyNode pNode : foundProps) {
       pList.remove(pNode);
     }
     final List<FieldNode> fList = cNode.getFields();
     for (FieldNode fNode : fList) {
       if (foundNames.contains(fNode.getName())) {
         revertVisibility(fNode);
       }
     }
   }
 }
 private void printFields(PrintWriter out, ClassNode classNode) {
   boolean isInterface = classNode.isInterface();
   List<FieldNode> fields = classNode.getFields();
   if (fields == null) return;
   List<FieldNode> enumFields = new ArrayList<FieldNode>(fields.size());
   List<FieldNode> normalFields = new ArrayList<FieldNode>(fields.size());
   for (FieldNode field : fields) {
     boolean isSynthetic = (field.getModifiers() & Opcodes.ACC_SYNTHETIC) != 0;
     if (field.isEnum()) {
       enumFields.add(field);
     } else if (!isSynthetic) {
       normalFields.add(field);
     }
   }
   printEnumFields(out, enumFields);
   for (FieldNode normalField : normalFields) {
     printField(out, normalField, isInterface);
   }
 }
 private void checkInterfaceMethodVisibility(ClassNode node) {
   if (!node.isInterface()) return;
   for (MethodNode method : node.getMethods()) {
     if (method.isPrivate()) {
       addError(
           "Method '"
               + method.getName()
               + "' is private but should be public in "
               + getDescription(currentClass)
               + ".",
           method);
     } else if (method.isProtected()) {
       addError(
           "Method '"
               + method.getName()
               + "' is protected but should be public in "
               + getDescription(currentClass)
               + ".",
           method);
     }
   }
 }
 public static boolean implementsInterfaceOrIsSubclassOf(
     ClassNode type, ClassNode superOrInterface) {
   boolean result =
       type.equals(superOrInterface)
           || type.isDerivedFrom(superOrInterface)
           || type.implementsInterface(superOrInterface)
           || type == UNKNOWN_PARAMETER_TYPE;
   if (result) {
     return true;
   }
   if (superOrInterface instanceof WideningCategories.LowestUpperBoundClassNode) {
     WideningCategories.LowestUpperBoundClassNode cn =
         (WideningCategories.LowestUpperBoundClassNode) superOrInterface;
     result = implementsInterfaceOrIsSubclassOf(type, cn.getSuperClass());
     if (result) {
       for (ClassNode interfaceNode : cn.getInterfaces()) {
         result = type.implementsInterface(interfaceNode);
         if (!result) break;
       }
     }
     if (result) return true;
   } else if (superOrInterface instanceof UnionTypeClassNode) {
     UnionTypeClassNode union = (UnionTypeClassNode) superOrInterface;
     for (ClassNode delegate : union.getDelegates()) {
       if (implementsInterfaceOrIsSubclassOf(type, delegate)) return true;
     }
   }
   if (type.isArray() && superOrInterface.isArray()) {
     return implementsInterfaceOrIsSubclassOf(
         type.getComponentType(), superOrInterface.getComponentType());
   }
   if (GROOVY_OBJECT_TYPE.equals(superOrInterface)
       && !type.isInterface()
       && isBeingCompiled(type)) {
     return true;
   }
   return false;
 }
 private void checkMethodsForIncorrectModifiers(ClassNode cn) {
   if (!cn.isInterface()) return;
   for (MethodNode method : cn.getMethods()) {
     if (method.isFinal()) {
       addError(
           "The "
               + getDescription(method)
               + " from "
               + getDescription(cn)
               + " must not be final. It is by definition abstract.",
           method);
     }
     if (method.isStatic() && !isConstructor(method)) {
       addError(
           "The "
               + getDescription(method)
               + " from "
               + getDescription(cn)
               + " must not be static. Only fields may be static in an interface.",
           method);
     }
   }
 }
  private void printMethod(PrintWriter out, ClassNode clazz, MethodNode methodNode) {
    if (methodNode.getName().equals("<clinit>")) return;
    if (methodNode.isPrivate() || !Utilities.isJavaIdentifier(methodNode.getName())) return;
    if (methodNode.isSynthetic() && methodNode.getName().equals("$getStaticMetaClass")) return;

    printAnnotations(out, methodNode);
    if (!clazz.isInterface()) printModifiers(out, methodNode.getModifiers());

    printGenericsBounds(out, methodNode.getGenericsTypes());
    out.print(" ");
    printType(out, methodNode.getReturnType());
    out.print(" ");
    out.print(methodNode.getName());

    printParams(out, methodNode);

    ClassNode[] exceptions = methodNode.getExceptions();
    for (int i = 0; i < exceptions.length; i++) {
      ClassNode exception = exceptions[i];
      if (i == 0) {
        out.print("throws ");
      } else {
        out.print(", ");
      }
      printType(out, exception);
    }

    if ((methodNode.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) {
      out.println(";");
    } else {
      out.print(" { ");
      ClassNode retType = methodNode.getReturnType();
      printReturn(out, retType);
      out.println("}");
    }
  }
예제 #12
0
  @Override
  public void visit(ASTNode[] astNodes, SourceUnit source) {
    if (!(astNodes[0] instanceof AnnotationNode) || !(astNodes[1] instanceof AnnotatedNode)) {
      throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class");
    }

    AnnotatedNode parent = (AnnotatedNode) astNodes[1];
    AnnotationNode node = (AnnotationNode) astNodes[0];
    if (!MY_TYPE.equals(node.getClassNode()) || !(parent instanceof ClassNode)) {
      return;
    }

    ClassNode classNode = (ClassNode) parent;
    if (classNode.isInterface() || Modifier.isAbstract(classNode.getModifiers())) {
      return;
    }

    boolean junit3Test = isJunit3Test(classNode);
    boolean spockTest = isSpockTest(classNode);
    boolean isJunit = classNode.getName().endsWith("Tests");

    if (!junit3Test && !spockTest && !isJunit) return;

    Expression value = node.getMember("value");
    ClassExpression ce;
    if (value instanceof ClassExpression) {
      ce = (ClassExpression) value;
      testFor(classNode, ce);
    } else {
      if (!junit3Test) {
        List<AnnotationNode> annotations = classNode.getAnnotations(MY_TYPE);
        if (annotations.size() > 0)
          return; // bail out, in this case it was already applied as a local transform
        // no explicit class specified try by convention
        String fileName = source.getName();
        String className = GrailsResourceUtils.getClassName(new FileSystemResource(fileName));
        if (className != null) {
          boolean isSpock = className.endsWith("Spec");
          String targetClassName = null;

          if (isJunit) {
            targetClassName = className.substring(0, className.indexOf("Tests"));
          } else if (isSpock) {
            targetClassName = className.substring(0, className.indexOf("Spec"));
          }

          if (targetClassName != null) {
            Resource targetResource =
                getResourceLocator().findResourceForClassName(targetClassName);
            if (targetResource != null) {
              try {
                if (GrailsResourceUtils.isDomainClass(targetResource.getURL())) {
                  testFor(
                      classNode,
                      new ClassExpression(
                          new ClassNode(targetClassName, 0, ClassHelper.OBJECT_TYPE)));
                } else {
                  for (String artefactType : artefactTypeToTestMap.keySet()) {
                    if (targetClassName.endsWith(artefactType)) {
                      testFor(
                          classNode,
                          new ClassExpression(
                              new ClassNode(targetClassName, 0, ClassHelper.OBJECT_TYPE)));
                      break;
                    }
                  }
                }
              } catch (IOException e) {
                // ignore
              }
            }
          }
        }
      }
    }
  }
  @Override
  public void makeGetPropertySite(
      Expression receiver,
      final String methodName,
      final boolean safe,
      final boolean implicitThis) {
    Object dynamic =
        receiver.getNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY);
    if (dynamic != null) {
      MethodNode target =
          safe ? INVOKERHELPER_GETPROPERTYSAFE_METHOD : INVOKERHELPER_GETPROPERTY_METHOD;
      MethodCallExpression mce =
          new MethodCallExpression(
              new ClassExpression(INVOKERHELPER_TYPE),
              target.getName(),
              new ArgumentListExpression(receiver, new ConstantExpression(methodName)));
      mce.setSafe(false);
      mce.setImplicitThis(false);
      mce.setMethodTarget(target);
      mce.visit(controller.getAcg());
      return;
    }
    TypeChooser typeChooser = controller.getTypeChooser();
    ClassNode classNode = controller.getClassNode();
    ClassNode receiverType =
        (ClassNode) receiver.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
    if (receiverType == null) {
      receiverType = typeChooser.resolveType(receiver, classNode);
    }
    Object type = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
    if (type == null && receiver instanceof VariableExpression) {
      Variable variable = ((VariableExpression) receiver).getAccessedVariable();
      if (variable instanceof Expression) {
        type = ((Expression) variable).getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
      }
    }
    if (type != null) {
      // in case a "flow type" is found, it is preferred to use it instead of
      // the declaration type
      receiverType = (ClassNode) type;
    }
    boolean isClassReceiver = false;
    if (isClassClassNodeWrappingConcreteType(receiverType)) {
      isClassReceiver = true;
      receiverType = receiverType.getGenericsTypes()[0].getType();
    }
    MethodVisitor mv = controller.getMethodVisitor();

    if (receiverType.isArray() && methodName.equals("length")) {
      receiver.visit(controller.getAcg());
      ClassNode arrayGetReturnType = typeChooser.resolveType(receiver, classNode);
      controller.getOperandStack().doGroovyCast(arrayGetReturnType);
      mv.visitInsn(ARRAYLENGTH);
      controller.getOperandStack().replace(int_TYPE);
      return;
    } else if ((receiverType.implementsInterface(COLLECTION_TYPE)
            || COLLECTION_TYPE.equals(receiverType))
        && ("size".equals(methodName) || "length".equals(methodName))) {
      MethodCallExpression expr =
          new MethodCallExpression(receiver, "size", ArgumentListExpression.EMPTY_ARGUMENTS);
      expr.setMethodTarget(COLLECTION_SIZE_METHOD);
      expr.setImplicitThis(implicitThis);
      expr.setSafe(safe);
      expr.visit(controller.getAcg());
      return;
    }
    if (makeGetPropertyWithGetter(receiver, receiverType, methodName, safe, implicitThis)) return;
    if (makeGetField(
        receiver,
        receiverType,
        methodName,
        implicitThis,
        samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return;
    if (receiverType.isEnum()) {
      mv.visitFieldInsn(
          GETSTATIC,
          BytecodeHelper.getClassInternalName(receiverType),
          methodName,
          BytecodeHelper.getTypeDescription(receiverType));
      controller.getOperandStack().push(receiverType);
      return;
    }
    if (receiver instanceof ClassExpression) {
      if (makeGetField(
          receiver,
          receiver.getType(),
          methodName,
          implicitThis,
          samePackages(receiver.getType().getPackageName(), classNode.getPackageName()))) return;
      if (makeGetPropertyWithGetter(receiver, receiver.getType(), methodName, safe, implicitThis))
        return;
      if (makeGetPrivateFieldWithBridgeMethod(
          receiver, receiver.getType(), methodName, safe, implicitThis)) return;
    }
    if (isClassReceiver) {
      // we are probably looking for a property of the class
      if (makeGetPropertyWithGetter(receiver, CLASS_Type, methodName, safe, implicitThis)) return;
      if (makeGetField(receiver, CLASS_Type, methodName, false, true)) return;
    }
    if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, methodName, safe, implicitThis))
      return;

    // GROOVY-5580, it is still possible that we're calling a superinterface property
    String getterName = "get" + MetaClassHelper.capitalize(methodName);
    if (receiverType.isInterface()) {
      Set<ClassNode> allInterfaces = receiverType.getAllInterfaces();
      MethodNode getterMethod = null;
      for (ClassNode anInterface : allInterfaces) {
        getterMethod = anInterface.getGetterMethod(getterName);
        if (getterMethod != null) break;
      }
      // GROOVY-5585
      if (getterMethod == null) {
        getterMethod = OBJECT_TYPE.getGetterMethod(getterName);
      }

      if (getterMethod != null) {
        MethodCallExpression call =
            new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS);
        call.setMethodTarget(getterMethod);
        call.setImplicitThis(false);
        call.setSourcePosition(receiver);
        call.visit(controller.getAcg());
        return;
      }
    }

    // GROOVY-5568, we would be facing a DGM call, but instead of foo.getText(), have foo.text
    List<MethodNode> methods =
        findDGMMethodsByNameAndArguments(
            controller.getSourceUnit().getClassLoader(),
            receiverType,
            getterName,
            ClassNode.EMPTY_ARRAY);
    if (!methods.isEmpty()) {
      List<MethodNode> methodNodes = chooseBestMethod(receiverType, methods, ClassNode.EMPTY_ARRAY);
      if (methodNodes.size() == 1) {
        MethodNode getter = methodNodes.get(0);
        MethodCallExpression call =
            new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS);
        call.setMethodTarget(getter);
        call.setImplicitThis(false);
        call.setSourcePosition(receiver);
        call.visit(controller.getAcg());
        return;
      }
    }

    boolean isStaticProperty =
        receiver instanceof ClassExpression
            && (receiverType.isDerivedFrom(receiver.getType())
                || receiverType.implementsInterface(receiver.getType()));

    if (!isStaticProperty) {
      if (receiverType.implementsInterface(MAP_TYPE) || MAP_TYPE.equals(receiverType)) {
        // for maps, replace map.foo with map.get('foo')
        writeMapDotProperty(receiver, methodName, mv, safe);
        return;
      }
      if (receiverType.implementsInterface(LIST_TYPE) || LIST_TYPE.equals(receiverType)) {
        writeListDotProperty(receiver, methodName, mv, safe);
        return;
      }
    }

    controller
        .getSourceUnit()
        .addError(
            new SyntaxException(
                "Access to "
                    + (receiver instanceof ClassExpression ? receiver.getType() : receiverType)
                        .toString(false)
                    + "#"
                    + methodName
                    + " is forbidden",
                receiver.getLineNumber(),
                receiver.getColumnNumber(),
                receiver.getLastLineNumber(),
                receiver.getLastColumnNumber()));
    controller.getMethodVisitor().visitInsn(ACONST_NULL);
    controller.getOperandStack().push(OBJECT_TYPE);
  }
예제 #14
0
  private List getPrimaryClassNodes(boolean sort) {
    if (sort == true) {
      List<ModuleNode> sortedModules = this.ast.getSortedModules();
      if (sortedModules != null) {
        return sortedModules;
      }
    }
    // FIXASC (groovychange) rewritten
    /*old{
    List unsorted = new ArrayList();
    Iterator modules = this.ast.getModules().iterator();
    while (modules.hasNext()) {
        ModuleNode module = (ModuleNode) modules.next();

        Iterator classNodes = module.getClasses().iterator();
        while (classNodes.hasNext()) {
            ClassNode classNode = (ClassNode) classNodes.next();
            unsorted.add(classNode);
        }
    }
    */
    // new
    List<ClassNode> unsorted = new ArrayList<ClassNode>();
    for (ModuleNode module : this.ast.getModules()) {
      unsorted.addAll(module.getClasses());
    }
    // FIXASC (groovychange) end

    if (!sort) return unsorted;

    // GRECLIPSE: start: rewritten sort algorithm
    /*old{
            int[] indexClass = new int[unsorted.size()];
            int[] indexInterface = new int[unsorted.size()];
            {
                int i = 0;
                for (Iterator<ClassNode> iter = unsorted.iterator(); iter.hasNext(); i++) {
                    ClassNode element = iter.next();
                    if (element.isInterface()) {
                        indexInterface[i] = getSuperInterfaceCount(element);
                        indexClass[i] = -1;
                    } else {
                        indexClass[i] = getSuperClassCount(element);
                        indexInterface[i] = -1;
                    }
                }
            }

            List<ClassNode> sorted = getSorted(indexInterface, unsorted);
            sorted.addAll(getSorted(indexClass, unsorted));
    */
    // newcode:
    // Sort them by how many types are in their hierarchy, but all interfaces first.
    // Algorithm:
    // Create a list of integers.  Each integer captures the index into the unsorted
    // list (bottom 16bits) and the count of how many types are in that types
    // hierarchy (top 16bits).  For classes the count is augmented by 2000 so that
    // when sorting the classes will come out after the interfaces.
    // This list of integers is sorted.  We then just go through it and for the
    // lower 16bits of each entry (0xffff) that is the index of the next value to
    // pull from the unsorted list and put into the sorted list.
    // Will break down if more than 2000 interfaces in the type hierarchy for an
    // individual type, or a project contains > 65535 files... but if you've got
    // that kind of setup, you have other problems...
    List<Integer> countIndexPairs = new ArrayList<Integer>();
    {
      int i = 0;
      for (Iterator iter = unsorted.iterator(); iter.hasNext(); i++) {
        ClassNode node = (ClassNode) iter.next();
        if (node.isInterface()) {
          countIndexPairs.add((getSuperInterfaceCount(node) << 16) + i);
        } else {
          countIndexPairs.add(((getSuperClassCount(node) + 2000) << 16) + i);
        }
      }
    }
    Collections.sort(countIndexPairs);
    List sorted = new ArrayList();
    for (int i : countIndexPairs) {
      sorted.add(unsorted.get(i & 0xffff));
    }
    this.ast.setSortedModules(sorted);
    // end
    return sorted;
  }
 private String getDescription(ClassNode node) {
   return (node.isInterface() ? "interface" : "class") + " '" + node.getName() + "'";
 }
  private void printClassContents(PrintWriter out, ClassNode classNode)
      throws FileNotFoundException {
    if (classNode instanceof InnerClassNode && ((InnerClassNode) classNode).isAnonymous()) {
      // if it is an anonymous inner class, don't generate the stub code for it.
      return;
    }
    try {
      Verifier verifier =
          new Verifier() {
            public void addCovariantMethods(ClassNode cn) {}

            protected void addTimeStamp(ClassNode node) {}

            protected void addInitialization(ClassNode node) {}

            protected void addPropertyMethod(MethodNode method) {
              doAddMethod(method);
            }

            protected void addReturnIfNeeded(MethodNode node) {}

            protected void addMethod(
                ClassNode node,
                boolean shouldBeSynthetic,
                String name,
                int modifiers,
                ClassNode returnType,
                Parameter[] parameters,
                ClassNode[] exceptions,
                Statement code) {
              doAddMethod(
                  new MethodNode(name, modifiers, returnType, parameters, exceptions, code));
            }

            protected void addConstructor(
                Parameter[] newParams, ConstructorNode ctor, Statement code, ClassNode node) {
              if (code instanceof ExpressionStatement) { // GROOVY-4508
                Statement temp = code;
                code = new BlockStatement();
                ((BlockStatement) code).addStatement(temp);
              }
              ConstructorNode ctrNode =
                  new ConstructorNode(ctor.getModifiers(), newParams, ctor.getExceptions(), code);
              ctrNode.setDeclaringClass(node);
              constructors.add(ctrNode);
            }

            protected void addDefaultParameters(DefaultArgsAction action, MethodNode method) {
              final Parameter[] parameters = method.getParameters();
              final Expression[] saved = new Expression[parameters.length];
              for (int i = 0; i < parameters.length; i++) {
                if (parameters[i].hasInitialExpression())
                  saved[i] = parameters[i].getInitialExpression();
              }
              super.addDefaultParameters(action, method);
              for (int i = 0; i < parameters.length; i++) {
                if (saved[i] != null) parameters[i].setInitialExpression(saved[i]);
              }
            }

            private void doAddMethod(MethodNode method) {
              String sig = method.getTypeDescriptor();

              if (propertyMethodsWithSigs.containsKey(sig)) return;

              propertyMethods.add(method);
              propertyMethodsWithSigs.put(sig, method);
            }

            @Override
            protected void addDefaultConstructor(ClassNode node) {
              // not required for stub generation
            }
          };
      verifier.visitClass(classNode);
      currentModule = classNode.getModule();

      boolean isInterface = classNode.isInterface();
      boolean isEnum = (classNode.getModifiers() & Opcodes.ACC_ENUM) != 0;
      boolean isAnnotationDefinition = classNode.isAnnotationDefinition();
      printAnnotations(out, classNode);
      printModifiers(
          out,
          classNode.getModifiers()
              & ~(isInterface ? Opcodes.ACC_ABSTRACT : 0)
              & ~(isEnum ? Opcodes.ACC_FINAL : 0));

      if (isInterface) {
        if (isAnnotationDefinition) {
          out.print("@");
        }
        out.print("interface ");
      } else if (isEnum) {
        out.print("enum ");
      } else {
        out.print("class ");
      }

      String className = classNode.getNameWithoutPackage();
      if (classNode instanceof InnerClassNode)
        className = className.substring(className.lastIndexOf("$") + 1);
      out.println(className);
      printGenericsBounds(out, classNode, true);

      ClassNode superClass = classNode.getUnresolvedSuperClass(false);

      if (!isInterface && !isEnum) {
        out.print("  extends ");
        printType(out, superClass);
      }

      ClassNode[] interfaces = classNode.getInterfaces();
      if (interfaces != null && interfaces.length > 0 && !isAnnotationDefinition) {
        if (isInterface) {
          out.println("  extends");
        } else {
          out.println("  implements");
        }
        for (int i = 0; i < interfaces.length - 1; ++i) {
          out.print("    ");
          printType(out, interfaces[i]);
          out.print(",");
        }
        out.print("    ");
        printType(out, interfaces[interfaces.length - 1]);
      }
      out.println(" {");

      printFields(out, classNode);
      printMethods(out, classNode, isEnum);

      for (Iterator<InnerClassNode> inner = classNode.getInnerClasses(); inner.hasNext(); ) {
        // GROOVY-4004: Clear the methods from the outer class so that they don't get duplicated in
        // inner ones
        propertyMethods.clear();
        propertyMethodsWithSigs.clear();
        constructors.clear();
        printClassContents(out, inner.next());
      }

      out.println("}");
    } finally {
      propertyMethods.clear();
      propertyMethodsWithSigs.clear();
      constructors.clear();
      currentModule = null;
    }
  }