private Statement createConstructorStatement(
     ClassNode cNode,
     PropertyNode pNode,
     List<String> knownImmutableClasses,
     List<String> knownImmutables) {
   FieldNode fNode = pNode.getField();
   final ClassNode fieldType = fNode.getType();
   Statement statement = null;
   if (fieldType.isArray() || isOrImplements(fieldType, CLONEABLE_TYPE)) {
     statement = createConstructorStatementArrayOrCloneable(fNode);
   } else if (isKnownImmutableClass(fieldType, knownImmutableClasses)
       || isKnownImmutable(pNode.getName(), knownImmutables)) {
     statement = createConstructorStatementDefault(fNode);
   } else if (fieldType.isDerivedFrom(DATE_TYPE)) {
     statement = createConstructorStatementDate(fNode);
   } else if (isOrImplements(fieldType, COLLECTION_TYPE)
       || fieldType.isDerivedFrom(COLLECTION_TYPE)
       || isOrImplements(fieldType, MAP_TYPE)
       || fieldType.isDerivedFrom(MAP_TYPE)) {
     statement = createConstructorStatementCollection(fNode);
   } else if (fieldType.isResolved()) {
     addError(
         createErrorMessage(cNode.getName(), fNode.getName(), fieldType.getName(), "compiling"),
         fNode);
     statement = EmptyStatement.INSTANCE;
   } else {
     statement = createConstructorStatementGuarded(cNode, fNode);
   }
   return statement;
 }
 private void printTypeName(PrintWriter out, ClassNode type) {
   if (ClassHelper.isPrimitiveType(type)) {
     if (type == ClassHelper.boolean_TYPE) {
       out.print("boolean");
     } else if (type == ClassHelper.char_TYPE) {
       out.print("char");
     } else if (type == ClassHelper.int_TYPE) {
       out.print("int");
     } else if (type == ClassHelper.short_TYPE) {
       out.print("short");
     } else if (type == ClassHelper.long_TYPE) {
       out.print("long");
     } else if (type == ClassHelper.float_TYPE) {
       out.print("float");
     } else if (type == ClassHelper.double_TYPE) {
       out.print("double");
     } else if (type == ClassHelper.byte_TYPE) {
       out.print("byte");
     } else {
       out.print("void");
     }
   } else {
     String name = type.getName();
     // check for an alias
     ClassNode alias = currentModule.getImportType(name);
     if (alias != null) name = alias.getName();
     out.print(name.replace('$', '.'));
   }
 }
Beispiel #3
0
 public static void configureAnnotationFromDefinition(
     AnnotationNode definition, AnnotationNode root) {
   ClassNode type = definition.getClassNode();
   if ("java.lang.annotation.Retention".equals(type.getName())) {
     Expression exp = definition.getMember("value");
     if (!(exp instanceof PropertyExpression)) return;
     PropertyExpression pe = (PropertyExpression) exp;
     String name = pe.getPropertyAsString();
     RetentionPolicy policy = RetentionPolicy.valueOf(name);
     setRetentionPolicy(policy, root);
   } else if ("java.lang.annotation.Target".equals(type.getName())) {
     Expression exp = definition.getMember("value");
     if (!(exp instanceof ListExpression)) return;
     ListExpression le = (ListExpression) exp;
     int bitmap = 0;
     for (Expression e : le.getExpressions()) {
       if (!(e instanceof PropertyExpression)) return;
       PropertyExpression element = (PropertyExpression) e;
       String name = element.getPropertyAsString();
       ElementType value = ElementType.valueOf(name);
       bitmap |= getElementCode(value);
     }
     root.setAllowedTargets(bitmap);
   }
 }
  private String genericsBounds(ClassNode theType, Set<String> visited) {
    String ret =
        theType.isArray() ? theType.getComponentType().getName() + "[]" : theType.getName();
    GenericsType[] genericsTypes = theType.getGenericsTypes();
    if (genericsTypes == null || genericsTypes.length == 0) return ret;
    // TODO instead of catching Object<T> here stop it from being placed into type in first place
    if (genericsTypes.length == 1
        && genericsTypes[0].isPlaceholder()
        && theType.getName().equals("java.lang.Object")) {
      return genericsTypes[0].getName();
    }
    ret += "<";
    for (int i = 0; i < genericsTypes.length; i++) {
      if (i != 0) ret += ", ";

      GenericsType type = genericsTypes[i];
      if (type.isPlaceholder() && visited.contains(type.getName())) {
        ret += type.getName();
      } else {
        ret += type.toString(visited);
      }
    }
    ret += ">";
    return ret;
  }
 private boolean isKnownImmutableClass(ClassNode fieldType, List<String> knownImmutableClasses) {
   if (!fieldType.isResolved()) return false;
   return fieldType.isEnum()
       || ClassHelper.isPrimitiveType(fieldType)
       || fieldType.getAnnotations(MY_TYPE).size() != 0
       || inImmutableList(fieldType.getName())
       || knownImmutableClasses.contains(fieldType.getName());
 }
  /**
   * Lookup a ClassNode by its name from the source unit
   *
   * @param type the name of the class whose ClassNode we want to lookup
   * @return a ClassNode representing the class
   */
  public ClassNode lookupClassNodeFor(String type) {
    for (ClassNode cn : typeCheckingVisitor.getSourceUnit().getAST().getClasses()) {
      if (cn.getName().equals(type)) return cn;
    }

    return null;
  }
        public void call(SourceUnit source) throws CompilationFailedException {
          List<ClassNode> classes = source.ast.getClasses();
          for (ClassNode node : classes) {
            CompileUnit cu = node.getCompileUnit();
            for (Iterator iter = cu.iterateClassNodeToCompile(); iter.hasNext(); ) {
              String name = (String) iter.next();
              SourceUnit su = ast.getScriptSourceLocation(name);
              List<ClassNode> classesInSourceUnit = su.ast.getClasses();
              StringBuffer message = new StringBuffer();
              message
                  .append("Compilation incomplete: expected to find the class ")
                  .append(name)
                  .append(" in ")
                  .append(su.getName());
              if (classesInSourceUnit.isEmpty()) {
                message.append(", but the file seems not to contain any classes");
              } else {
                message.append(", but the file contains the classes: ");
                boolean first = true;
                for (ClassNode cn : classesInSourceUnit) {
                  if (!first) {
                    message.append(", ");
                  } else {
                    first = false;
                  }
                  message.append(cn.getName());
                }
              }

              getErrorCollector()
                  .addErrorAndContinue(new SimpleMessage(message.toString(), CompilationUnit.this));
              iter.remove();
            }
          }
        }
 public static String getClassInternalName(ClassNode t) {
   if (t.isPrimaryClassNode()) {
     if (t.isArray()) return "[L" + getClassInternalName(t.getComponentType()) + ";";
     return getClassInternalName(t.getName());
   }
   return getClassInternalName(t.getTypeClass());
 }
  /**
   * Main entry point for the calling the TestForTransformation programmatically.
   *
   * @param classNode The class node that represents th test
   * @param ce The class expression that represents the class to test
   */
  public void testFor(ClassNode classNode, ClassExpression ce) {
    boolean junit3Test = isJunit3Test(classNode);

    // make sure the 'log' property is not the one from GroovyTestCase
    FieldNode log = classNode.getField("log");
    if (log == null || log.getDeclaringClass().equals(GROOVY_TEST_CASE_CLASS)) {
      LoggingTransformer.addLogField(classNode, classNode.getName());
    }
    boolean isSpockTest = isSpockTest(classNode);

    if (!isSpockTest && !junit3Test) {
      // assume JUnit 4
      Map<String, MethodNode> declaredMethodsMap = classNode.getDeclaredMethodsMap();
      for (String methodName : declaredMethodsMap.keySet()) {
        MethodNode methodNode = declaredMethodsMap.get(methodName);
        if (isCandidateMethod(methodNode) && methodNode.getName().startsWith("test")) {
          if (methodNode.getAnnotations().size() == 0) {
            methodNode.addAnnotation(TEST_ANNOTATION);
          }
        }
      }
    }

    final MethodNode methodToAdd = weaveMock(classNode, ce, true);
    if (methodToAdd != null && junit3Test) {
      addMethodCallsToMethod(classNode, SET_UP_METHOD, Arrays.asList(methodToAdd));
    }
  }
  protected MethodNode weaveMock(
      ClassNode classNode, ClassExpression value, boolean isClassUnderTest) {

    ClassNode testTarget = value.getType();
    String className = testTarget.getName();
    MethodNode testForMethod = null;
    for (String artefactType : artefactTypeToTestMap.keySet()) {
      if (className.endsWith(artefactType)) {
        Class mixinClass = artefactTypeToTestMap.get(artefactType);
        if (!isAlreadyWoven(classNode, mixinClass)) {
          weaveMixinClass(classNode, mixinClass);
          if (isClassUnderTest) {
            testForMethod = addClassUnderTestMethod(classNode, value, artefactType);
          } else {
            addMockCollaboratorToSetup(classNode, value, artefactType);
          }
          return testForMethod;
        }

        addMockCollaboratorToSetup(classNode, value, artefactType);
        return null;
      }
    }

    // must be a domain class
    weaveMixinClass(classNode, DomainClassUnitTestMixin.class);
    if (isClassUnderTest) {
      testForMethod = addClassUnderTestMethod(classNode, value, DOMAIN_TYPE);
    } else {
      addMockCollaboratorToSetup(classNode, value, DOMAIN_TYPE);
    }

    return testForMethod;
  }
 /**
  * Snoops through the declaring class and all parents looking for methods
  *
  * <ul>
  *   <li><code>public String getMessage(java.lang.String)</code>
  *   <li><code>public String getMessage(java.lang.String, java.util.Locale)</code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.Object[])</code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.Object[], java.util.Locale)
  *       </code>
  *   <li><code>public String getMessage(java.lang.String, java.util.List)</code>
  *   <li><code>public String getMessage(java.lang.String, java.util.List, java.util.Locale)</code>
  *   <li><code>public String getMessage(java.lang.String, java.util.Map)</code>
  *   <li><code>public String getMessage(java.lang.String, java.util.Map, java.util.Locale)</code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.String)</code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.String, java.util.Locale)
  *       </code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.Object[], java.lang.String)
  *       </code>
  *   <li><code>
  *       public String getMessage(java.lang.String, java.lang.Object[], java.lang.String, java.util.Locale)
  *       </code>
  *   <li><code>public String getMessage(java.lang.String, java.util.List, java.lang.String)</code>
  *   <li><code>
  *       public String getMessage(java.lang.String, java.util.List, java.lang.String, java.util.Locale)
  *       </code>
  *   <li><code>public String getMessage(java.lang.String, java.util.Map, java.lang.String)</code>
  *   <li><code>
  *       public String getMessage(java.lang.String, java.util.Map, java.lang.String, java.util.Locale)
  *       </code>
  * </ul>
  *
  * If any are defined all must be defined or a compilation error results.
  *
  * @param declaringClass the class to search
  * @param sourceUnit the source unit, for error reporting. {@code @NotNull}.
  * @return true if property change support should be added
  */
 protected static boolean needsMessageSource(ClassNode declaringClass, SourceUnit sourceUnit) {
   boolean found = false;
   ClassNode consideredClass = declaringClass;
   while (consideredClass != null) {
     for (MethodNode method : consideredClass.getMethods()) {
       // just check length, MOP will match it up
       found = method.getName().equals(METHOD_GET_MESSAGE) && method.getParameters().length == 1;
       found |= method.getName().equals(METHOD_GET_MESSAGE) && method.getParameters().length == 2;
       found |= method.getName().equals(METHOD_GET_MESSAGE) && method.getParameters().length == 3;
       found |= method.getName().equals(METHOD_GET_MESSAGE) && method.getParameters().length == 4;
       if (found) return false;
     }
     consideredClass = consideredClass.getSuperClass();
   }
   if (found) {
     sourceUnit
         .getErrorCollector()
         .addErrorAndContinue(
             new SimpleMessage(
                 "@MessageSourceAware cannot be processed on "
                     + declaringClass.getName()
                     + " because some but not all of variants of getMessage() were declared in the current class or super classes.",
                 sourceUnit));
     return false;
   }
   return true;
 }
  /**
   * Compile the specified Groovy source files, applying any {@link CompilerAutoConfiguration}s. All
   * classes defined in the files will be returned from this method.
   *
   * @param file the file to compile
   * @return compiled classes
   * @throws CompilationFailedException
   * @throws IOException
   */
  public Class<?>[] compile(File... file) throws CompilationFailedException, IOException {

    this.loader.clearCache();
    List<Class<?>> classes = new ArrayList<Class<?>>();

    CompilerConfiguration configuration = this.loader.getConfiguration();

    CompilationUnit compilationUnit = new CompilationUnit(configuration, null, this.loader);
    SourceUnit sourceUnit =
        new SourceUnit(file[0], configuration, this.loader, compilationUnit.getErrorCollector());
    ClassCollector collector = this.loader.createCollector(compilationUnit, sourceUnit);
    compilationUnit.setClassgenCallback(collector);

    compilationUnit.addSources(file);

    addAstTransformations(compilationUnit);

    compilationUnit.compile(Phases.CLASS_GENERATION);
    for (Object loadedClass : collector.getLoadedClasses()) {
      classes.add((Class<?>) loadedClass);
    }
    ClassNode mainClassNode = (ClassNode) compilationUnit.getAST().getClasses().get(0);
    Class<?> mainClass = null;
    for (Class<?> loadedClass : classes) {
      if (mainClassNode.getName().equals(loadedClass.getName())) {
        mainClass = loadedClass;
      }
    }
    if (mainClass != null) {
      classes.remove(mainClass);
      classes.add(0, mainClass);
    }

    return classes.toArray(new Class<?>[classes.size()]);
  }
 public static void addResourceLocatorIfNeeded(SourceUnit source, ClassNode classNode) {
   if (needsMessageSource(classNode, source)) {
     if (LOG.isDebugEnabled()) {
       LOG.debug("Injecting " + ResourceLocator.class.getName() + " into " + classNode.getName());
     }
     apply(classNode);
   }
 }
 public GenericsType(ClassNode type, ClassNode[] upperBounds, ClassNode lowerBound) {
   this.type = type;
   this.name = type.isGenericsPlaceHolder() ? type.getUnresolvedName() : type.getName();
   this.upperBounds = upperBounds;
   this.lowerBound = lowerBound;
   placeholder = type.isGenericsPlaceHolder();
   resolved = false;
 }
 /**
  * Given a wrapped number type (Byte, Integer, Short, ...), generates bytecode to convert it to a
  * primitive number (int, long, double) using calls to wrapped.[targetType]Value()
  *
  * @param mv method visitor
  * @param sourceType the wrapped number type
  * @param targetType the primitive target type
  */
 public static void doCastToPrimitive(
     MethodVisitor mv, ClassNode sourceType, ClassNode targetType) {
   mv.visitMethodInsn(
       INVOKEVIRTUAL,
       BytecodeHelper.getClassInternalName(sourceType),
       targetType.getName() + "Value",
       "()" + BytecodeHelper.getTypeDescription(targetType));
 }
 public static void addMapConstructors(ClassNode enumClass, boolean hasNoArg) {
   TupleConstructorASTTransformation.addMapConstructors(
       enumClass,
       hasNoArg,
       "One of the enum constants for enum "
           + enumClass.getName()
           + " was initialized with null. Please use a non-null value or define your own constructor.");
 }
 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);
       }
     }
   }
 }
 public static void addEventPublisherIfNeeded(
     SourceUnit source, AnnotationNode annotationNode, ClassNode classNode) {
   if (needsDelegate(classNode, source, METHODS, "EventPublisher", EVENT_PUBLISHER_TYPE)) {
     LOG.debug("Injecting {} into {}", EVENT_PUBLISHER_TYPE, classNode.getName());
     ConstantExpression value = (ConstantExpression) annotationNode.getMember("value");
     String beanName = value != null ? value.getText() : null;
     beanName = isBlank(beanName) ? null : beanName;
     apply(classNode, beanName);
   }
 }
 public static void addMessageSourceIfNeeded(
     SourceUnit source, AnnotationNode annotationNode, ClassNode classNode) {
   if (needsDelegate(classNode, source, METHODS, "MessageSourceAware", MESSAGE_SOURCE_TYPE)) {
     LOG.debug("Injecting {} into {}", MESSAGE_SOURCE_TYPE, classNode.getName());
     ConstantExpression value = (ConstantExpression) annotationNode.getMember("value");
     String beanName = value != null ? value.getText() : null;
     beanName = isBlank(beanName) ? "applicationMessageSource" : beanName;
     apply(classNode, beanName);
   }
 }
  private void printReturn(PrintWriter out, ClassNode retType) {
    String retName = retType.getName();
    if (!retName.equals("void")) {
      out.print("return ");

      printDefaultValue(out, retType);

      out.print(";");
    }
  }
  private FieldNode getKeyField(ClassNode target) {

    List<FieldNode> annotatedFields = getAnnotatedFieldsOfHierarchy(target, KEY_ANNOTATION);

    if (annotatedFields.isEmpty()) return null;

    if (annotatedFields.size() > 1) {
      addCompileError(
          String.format(
              "Found more than one key fields, only one is allowed in hierarchy (%s, %s)",
              getQualifiedName(annotatedFields.get(0)), getQualifiedName(annotatedFields.get(1))),
          annotatedFields.get(0));
      return null;
    }

    FieldNode result = annotatedFields.get(0);

    if (!result.getType().equals(ClassHelper.STRING_TYPE)) {
      addCompileError(
          String.format(
              "Key field '%s' must be of type String, but is '%s' instead",
              result.getName(), result.getType().getName()),
          result);
      return null;
    }

    ClassNode ancestor = ASTHelper.getHighestAncestorDSLObject(target);

    if (target.equals(ancestor)) return result;

    FieldNode firstKey = getKeyField(ancestor);

    if (firstKey == null) {
      addCompileError(
          String.format(
              "Inconsistent hierarchy: Toplevel class %s has no key, but child class %s defines '%s'.",
              ancestor.getName(), target.getName(), result.getName()),
          result);
      return null;
    }

    return result;
  }
 private ClassNode createInnerHelperClass(
     ClassNode buildee, String builderClassName, int fieldsSize) {
   final String fullName = buildee.getName() + "$" + builderClassName;
   ClassNode builder = new InnerClassNode(buildee, fullName, PUBLIC_STATIC, OBJECT_TYPE);
   GenericsType[] gtypes = new GenericsType[fieldsSize];
   for (int i = 0; i < gtypes.length; i++) {
     gtypes[i] = makePlaceholder(i);
   }
   builder.setGenericsTypes(gtypes);
   return builder;
 }
 private Statement checkFinalArgNotOverridden(ClassNode cNode, FieldNode fNode) {
   final String name = fNode.getName();
   Expression value = findArg(name);
   return new IfStatement(
       equalsNullExpr(value),
       new EmptyStatement(),
       new ThrowStatement(
           new ConstructorCallExpression(
               READONLYEXCEPTION_TYPE,
               new ArgumentListExpression(
                   new ConstantExpression(name), new ConstantExpression(cNode.getName())))));
 }
 public static void doCast(MethodVisitor mv, ClassNode type) {
   if (type == ClassHelper.OBJECT_TYPE) return;
   if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) {
     unbox(mv, type);
   } else {
     mv.visitTypeInsn(
         CHECKCAST,
         type.isArray()
             ? BytecodeHelper.getTypeDescription(type)
             : BytecodeHelper.getClassInternalName(type.getName()));
   }
 }
 public static void addMybatisContributionIfNeeded(SourceUnit source, ClassNode classNode) {
   if (needsMybatisContribution(classNode, source)) {
     if (LOG.isDebugEnabled()) {
       LOG.debug(
           "Injecting "
               + MybatisContributionHandler.class.getName()
               + " into "
               + classNode.getName());
     }
     apply(classNode);
   }
 }
 /**
  * Sets this instance as proxy for the given ClassNode.
  *
  * @param cn the class to redirect to. If set to null the redirect will be removed
  */
 public void setRedirect(ClassNode cn) {
   if (isPrimaryNode)
     throw new GroovyBugError(
         "tried to set a redirect for a primary ClassNode ("
             + getName()
             + "->"
             + cn.getName()
             + ").");
   if (cn != null) cn = cn.redirect();
   if (cn == this) return;
   redirect = cn;
 }
 /**
  * This exists to avoid a recursive definition of toString. The default toString in GenericsType
  * calls ClassNode.toString(), which calls GenericsType.toString(), etc.
  *
  * @param genericsType
  * @param showRedirect
  * @return the string representing the generic type
  */
 private String genericTypeAsString(GenericsType genericsType, boolean showRedirect) {
   String ret = genericsType.getName();
   if (genericsType.getUpperBounds() != null) {
     ret += " extends ";
     for (int i = 0; i < genericsType.getUpperBounds().length; i++) {
       ClassNode classNode = genericsType.getUpperBounds()[i];
       if (classNode.equals(this)) {
         ret += classNode.getName();
       } else {
         ret += classNode.toString(showRedirect);
       }
       if (i + 1 < genericsType.getUpperBounds().length) ret += " & ";
     }
   } else if (genericsType.getLowerBound() != null) {
     ClassNode classNode = genericsType.getLowerBound();
     if (classNode.equals(this)) {
       ret += " super " + classNode.getName();
     } else {
       ret += " super " + classNode;
     }
   }
   return ret;
 }
Beispiel #28
0
 public static GenericsType configureTypeVariableDefinition(ClassNode base, ClassNode[] cBounds) {
   ClassNode redirect = base.redirect();
   base.setRedirect(null);
   GenericsType gt;
   if (cBounds == null || cBounds.length == 0) {
     gt = new GenericsType(base);
   } else {
     gt = new GenericsType(base, cBounds, null);
     gt.setName(base.getName());
     gt.setPlaceholder(true);
   }
   base.setRedirect(redirect);
   return gt;
 }
    protected Class createClass(byte[] code, ClassNode classNode) {
      BytecodeProcessor bytecodePostprocessor = unit.getConfiguration().getBytecodePostprocessor();
      byte[] fcode = code;
      if (bytecodePostprocessor != null) {
        fcode = bytecodePostprocessor.processBytecode(classNode.getName(), fcode);
      }
      GroovyClassLoader cl = getDefiningClassLoader();
      Class theClass =
          cl.defineClass(
              classNode.getName(), fcode, 0, fcode.length, unit.getAST().getCodeSource());
      this.loadedClasses.add(theClass);

      if (generatedClass == null) {
        ModuleNode mn = classNode.getModule();
        SourceUnit msu = null;
        if (mn != null) msu = mn.getContext();
        ClassNode main = null;
        if (mn != null) main = (ClassNode) mn.getClasses().get(0);
        if (msu == su && main == classNode) generatedClass = theClass;
      }

      return theClass;
    }
  private void injectToStringMethod(ClassNode classNode) {
    final boolean hasToString = /*GrailsASTUtils.*/ implementsZeroArgMethod(classNode, "toString");

    if (!hasToString) {
      GStringExpression ge = new GStringExpression(classNode.getName() + " : ${id}");
      ge.addString(new ConstantExpression(classNode.getName() + " : "));
      ge.addValue(new VariableExpression("id"));
      Statement s = new ReturnStatement(ge);
      MethodNode mn =
          new MethodNode(
              "toString",
              Modifier.PUBLIC,
              new ClassNode(String.class),
              new Parameter[0],
              new ClassNode[0],
              s);
      // if(LOG.isDebugEnabled()) {
      //    LOG.debug("[GrailsDomainInjector] Adding method [toString()] to class [" +
      // classNode.getName() + "]");
      // }
      classNode.addMethod(mn);
    }
  }