private void assertMembersNamesAreUnique() {
    Map<String, FieldNode> allDslCollectionFieldNodesOfHierarchy = new HashMap<String, FieldNode>();

    for (ClassNode level : ASTHelper.getHierarchyOfDSLObjectAncestors(annotatedClass)) {
      for (FieldNode field : level.getFields()) {
        if (!ASTHelper.isListOrMap(field.getType())) continue;

        String memberName = getElementNameForCollectionField(field);

        FieldNode conflictingField = allDslCollectionFieldNodesOfHierarchy.get(memberName);

        if (conflictingField != null) {
          addCompileError(
              String.format(
                  "Member name %s is used more than once: %s:%s and %s:%s",
                  memberName,
                  field.getOwner().getName(),
                  field.getName(),
                  conflictingField.getOwner().getName(),
                  conflictingField.getName()),
              field);
          return;
        }

        allDslCollectionFieldNodesOfHierarchy.put(memberName, field);
      }
    }
  }
  private void preventOwnerOverride() {

    MethodBuilder.createPublicMethod(setterName(ownerField))
        .param(OBJECT_TYPE, "value")
        .statement(
            ifS(
                andX(
                    isInstanceOfX(varX("value"), ownerField.getType()),
                    notX(propX(varX("this"), ownerField.getName()))),
                assignX(propX(varX("this"), ownerField.getName()), varX("value"))))
        .addTo(annotatedClass);
  }
  private void createSingleFieldSetterMethod(FieldNode fieldNode) {
    MethodBuilder.createPublicMethod(fieldNode.getName())
        .param(fieldNode.getType(), "value")
        .assignToProperty(fieldNode.getName(), varX("value"))
        .addTo(annotatedClass);

    if (fieldNode.getType().equals(ClassHelper.boolean_TYPE)) {
      MethodBuilder.createPublicMethod(fieldNode.getName())
          .callThis(fieldNode.getName(), constX(true))
          .addTo(annotatedClass);
    }
  }
  private void createListOfSimpleElementsMethods(FieldNode fieldNode, ClassNode elementType) {

    MethodBuilder.createPublicMethod(fieldNode.getName())
        .arrayParam(elementType, "values")
        .statement(callX(propX(varX("this"), fieldNode.getName()), "addAll", varX("values")))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod(fieldNode.getName())
        .param(fieldNode.getType(), "values")
        .statement(callX(propX(varX("this"), fieldNode.getName()), "addAll", varX("values")))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod(getElementNameForCollectionField(fieldNode))
        .param(elementType, "value")
        .statement(callX(propX(varX("this"), fieldNode.getName()), "add", varX("value")))
        .addTo(annotatedClass);
  }
  private void createMapOfSimpleElementsMethods(
      FieldNode fieldNode, ClassNode keyType, ClassNode valueType) {
    String methodName = fieldNode.getName();

    MethodBuilder.createPublicMethod(methodName)
        .param(fieldNode.getType(), "values")
        .callMethod(propX(varX("this"), fieldNode.getName()), "putAll", varX("values"))
        .addTo(annotatedClass);

    String singleElementMethod = getElementNameForCollectionField(fieldNode);

    MethodBuilder.createPublicMethod(singleElementMethod)
        .param(keyType, "key")
        .param(valueType, "value")
        .callMethod(propX(varX("this"), fieldNode.getName()), "put", args("key", "value"))
        .addTo(annotatedClass);
  }
 @SuppressWarnings("RedundantIfStatement")
 boolean shouldFieldBeIgnored(FieldNode fieldNode) {
   if (fieldNode == keyField) return true;
   if (fieldNode == ownerField) return true;
   if (getAnnotation(fieldNode, IGNORE_ANNOTATION) != null) return true;
   if (fieldNode.isFinal()) return true;
   if (fieldNode.getName().startsWith("$")) return true;
   if ((fieldNode.getModifiers() & ACC_TRANSIENT) != 0) return true;
   return false;
 }
  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 void validateFields(BlockStatement block) {
    Validation.Option mode =
        getEnumMemberValue(
            getAnnotation(annotatedClass, VALIDATION_ANNOTATION),
            "option",
            Validation.Option.class,
            Validation.Option.IGNORE_UNMARKED);
    for (FieldNode fieldNode : annotatedClass.getFields()) {
      if (shouldFieldBeIgnoredForValidation(fieldNode)) continue;

      ClosureExpression validationClosure =
          createGroovyTruthClosureExpression(block.getVariableScope());
      String message = null;

      AnnotationNode validateAnnotation = getAnnotation(fieldNode, VALIDATE_ANNOTATION);
      if (validateAnnotation != null) {
        message =
            getMemberStringValue(
                validateAnnotation, "message", "'" + fieldNode.getName() + "' must be set!");
        Expression member = validateAnnotation.getMember("value");
        if (member instanceof ClassExpression) {
          ClassNode memberType = member.getType();
          if (memberType.equals(ClassHelper.make(Validate.Ignore.class))) continue;
          else if (!memberType.equals(ClassHelper.make(Validate.GroovyTruth.class))) {
            addError(
                "value of Validate must be either Validate.GroovyTruth, Validate.Ignore or a closure.",
                fieldNode);
          }
        } else if (member instanceof ClosureExpression) {
          validationClosure = (ClosureExpression) member;
        }
      }

      if (validateAnnotation != null || mode == Validation.Option.VALIDATE_UNMARKED) {
        block.addStatement(
            new AssertStatement(
                new BooleanExpression(
                    callX(validationClosure, "call", args(varX(fieldNode.getName())))),
                message == null ? ConstantExpression.NULL : new ConstantExpression(message)));
      }
    }
  }
 private void createKeyConstructor() {
   annotatedClass.addConstructor(
       ACC_PUBLIC,
       params(param(STRING_TYPE, "key")),
       NO_EXCEPTIONS,
       block(
           ASTHelper.isDSLObject(annotatedClass.getSuperClass())
               ? ctorSuperS(args("key"))
               : ctorSuperS(),
           assignS(propX(varX("this"), keyField.getName()), varX("key"))));
 }
  private String getElementNameForCollectionField(FieldNode fieldNode) {
    AnnotationNode fieldAnnotation = getAnnotation(fieldNode, DSL_FIELD_ANNOTATION);

    String result = getNullSafeMemberStringValue(fieldAnnotation, "members", null);

    if (result != null && result.length() > 0) return result;

    String collectionMethodName = fieldNode.getName();

    if (collectionMethodName.endsWith("s"))
      return collectionMethodName.substring(0, collectionMethodName.length() - 1);

    return collectionMethodName;
  }
 private void createCanonicalMethods() {
   if (!hasAnnotation(annotatedClass, EQUALS_HASHCODE_ANNOT)) {
     createHashCode(annotatedClass, false, false, true, null, null);
     createEquals(annotatedClass, false, true, true, null, null);
   }
   if (!hasAnnotation(annotatedClass, TOSTRING_ANNOT)) {
     if (ownerField == null) createToString(annotatedClass, false, false, null, null, false);
     else
       createToString(
           annotatedClass,
           false,
           false,
           Collections.singletonList(ownerField.getName()),
           null,
           false);
   }
 }
  private void validateFieldAnnotations() {
    for (FieldNode fieldNode : annotatedClass.getFields()) {
      if (shouldFieldBeIgnored(fieldNode)) continue;

      AnnotationNode annotation = getAnnotation(fieldNode, DSL_FIELD_ANNOTATION);

      if (annotation == null) continue;

      if (ASTHelper.isListOrMap(fieldNode.getType())) return;

      if (annotation.getMember("members") != null) {
        addCompileError(
            String.format(
                "@Field.members is only valid for List or Map fields, but field %s is of type %s",
                fieldNode.getName(), fieldNode.getType().getName()),
            annotation);
      }
    }
  }
 private String getOwnerFieldName(ClassNode target) {
   FieldNode ownerFieldOfElement = getOwnerField(target);
   return ownerFieldOfElement != null ? ownerFieldOfElement.getName() : null;
 }
 private String getQualifiedName(FieldNode node) {
   return node.getOwner().getName() + "." + node.getName();
 }
  private void createSingleDSLObjectClosureMethod(FieldNode fieldNode) {
    String methodName = fieldNode.getName();

    ClassNode targetFieldType = fieldNode.getType();
    FieldNode targetTypeKeyField = getKeyField(targetFieldType);
    String targetOwnerFieldName = getOwnerFieldName(targetFieldType);

    if (!ASTHelper.isAbstract(targetFieldType)) {
      MethodBuilder.createPublicMethod(methodName)
          .returning(targetFieldType)
          .namedParams("values")
          .optionalStringParam("key", targetTypeKeyField)
          .delegatingClosureParam(targetFieldType)
          .declareVariable(
              "created",
              callX(classX(targetFieldType), "newInstance", optionalKeyArg(targetTypeKeyField)))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwnerFieldName, targetOwnerFieldName)
          .assignToProperty(
              fieldNode.getName(), callX(varX("created"), "apply", args("values", "closure")))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);

      MethodBuilder.createPublicMethod(methodName)
          .returning(targetFieldType)
          .optionalStringParam("key", targetTypeKeyField)
          .delegatingClosureParam(targetFieldType)
          .declareVariable(
              "created",
              callX(classX(targetFieldType), "newInstance", optionalKeyArg(targetTypeKeyField)))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwnerFieldName, targetOwnerFieldName)
          .assignToProperty(fieldNode.getName(), callX(varX("created"), "apply", varX("closure")))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
    }

    if (!isFinal(targetFieldType)) {
      MethodBuilder.createPublicMethod(methodName)
          .returning(targetFieldType)
          .namedParams("values")
          .classParam("typeToCreate", targetFieldType)
          .optionalStringParam("key", targetTypeKeyField)
          .delegatingClosureParam(targetFieldType)
          .declareVariable(
              "created",
              callX(varX("typeToCreate"), "newInstance", optionalKeyArg(targetTypeKeyField)))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwnerFieldName, targetOwnerFieldName)
          .assignToProperty(
              fieldNode.getName(), callX(varX("created"), "apply", args("values", "closure")))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);

      MethodBuilder.createPublicMethod(methodName)
          .returning(targetFieldType)
          .classParam("typeToCreate", targetFieldType)
          .optionalStringParam("key", targetTypeKeyField)
          .delegatingClosureParam(targetFieldType)
          .declareVariable(
              "created",
              callX(varX("typeToCreate"), "newInstance", optionalKeyArg(targetTypeKeyField)))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwnerFieldName, targetOwnerFieldName)
          .assignToProperty(fieldNode.getName(), callX(varX("created"), "apply", varX("closure")))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
    }
  }
  private void createMapOfDSLObjectMethods(
      FieldNode fieldNode, ClassNode keyType, ClassNode elementType) {
    if (getKeyField(elementType) == null) {
      addCompileError(
          String.format(
              "Value type of map %s (%s) has no key field",
              fieldNode.getName(), elementType.getName()),
          fieldNode);
      return;
    }

    MethodBuilder.createPublicMethod(fieldNode.getName())
        .closureParam("closure")
        .assignS(propX(varX("closure"), "delegate"), varX("this"))
        .assignS(
            propX(varX("closure"), "resolveStrategy"),
            propX(classX(ClassHelper.CLOSURE_TYPE), "DELEGATE_FIRST"))
        .callMethod("closure", "call")
        .addTo(annotatedClass);

    String methodName = getElementNameForCollectionField(fieldNode);
    String targetOwner = getOwnerFieldName(elementType);

    if (!ASTHelper.isAbstract(elementType)) {
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .namedParams("values")
          .param(keyType, "key")
          .delegatingClosureParam(elementType)
          .declareVariable("created", callX(classX(elementType), "newInstance", args("key")))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod(fieldNode.getName(), "put", args(varX("key"), varX("created")))
          .callMethod("created", "apply", args("values", "closure"))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .param(keyType, "key")
          .delegatingClosureParam(elementType)
          .declareVariable("created", callX(classX(elementType), "newInstance", args("key")))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod(fieldNode.getName(), "put", args(varX("key"), varX("created")))
          .callMethod("created", "apply", varX("closure"))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
    }

    if (!isFinal(elementType)) {
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .namedParams("values")
          .classParam("typeToCreate", elementType)
          .param(keyType, "key")
          .delegatingClosureParam(elementType)
          .declareVariable("created", callX(varX("typeToCreate"), "newInstance", args("key")))
          .callMethod("created", "copyFromTemplate")
          .callMethod(fieldNode.getName(), "put", args(varX("key"), varX("created")))
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod("created", "apply", args("values", "closure"))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .classParam("typeToCreate", elementType)
          .param(keyType, "key")
          .delegatingClosureParam(elementType)
          .declareVariable("created", callX(varX("typeToCreate"), "newInstance", args("key")))
          .callMethod("created", "copyFromTemplate")
          .callMethod(fieldNode.getName(), "put", args(varX("key"), varX("created")))
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod("created", "apply", varX("closure"))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
    }

    //noinspection ConstantConditions
    MethodBuilder.createPublicMethod(methodName)
        .param(elementType, "value")
        .callMethod(
            fieldNode.getName(),
            "put",
            args(propX(varX("value"), getKeyField(elementType).getName()), varX("value")))
        .optionalAssignThisToPropertyS("value", targetOwner, targetOwner)
        .addTo(annotatedClass);
  }
 private String setterName(FieldNode node) {
   char[] name = node.getName().toCharArray();
   name[0] = Character.toUpperCase(name[0]);
   return "set" + new String(name);
 }
  private void createListOfDSLObjectMethods(FieldNode fieldNode, ClassNode elementType) {
    String methodName = getElementNameForCollectionField(fieldNode);

    FieldNode fieldKey = getKeyField(elementType);
    String targetOwner = getOwnerFieldName(elementType);

    MethodBuilder.createPublicMethod(fieldNode.getName())
        .closureParam("closure")
        .assignS(propX(varX("closure"), "delegate"), varX("this"))
        .assignS(
            propX(varX("closure"), "resolveStrategy"),
            propX(classX(ClassHelper.CLOSURE_TYPE), "DELEGATE_FIRST"))
        .callMethod("closure", "call")
        .addTo(annotatedClass);

    if (!ASTHelper.isAbstract(elementType)) {
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .namedParams("values")
          .optionalStringParam("key", fieldKey)
          .delegatingClosureParam(elementType)
          .declareVariable(
              "created", callX(classX(elementType), "newInstance", optionalKeyArg(fieldKey)))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod(
              fieldNode.getName(),
              "add",
              callX(varX("created"), "apply", args("values", "closure")))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .optionalStringParam("key", fieldKey)
          .delegatingClosureParam(elementType)
          .declareVariable(
              "created", callX(classX(elementType), "newInstance", optionalKeyArg(fieldKey)))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod(fieldNode.getName(), "add", callX(varX("created"), "apply", varX("closure")))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
    }

    if (!isFinal(elementType)) {
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .namedParams("values")
          .classParam("typeToCreate", elementType)
          .optionalStringParam("key", fieldKey)
          .delegatingClosureParam(elementType)
          .declareVariable(
              "created", callX(varX("typeToCreate"), "newInstance", optionalKeyArg(fieldKey)))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod(
              fieldNode.getName(),
              "add",
              callX(varX("created"), "apply", args("values", "closure")))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .classParam("typeToCreate", elementType)
          .optionalStringParam("key", fieldKey)
          .delegatingClosureParam(elementType)
          .declareVariable(
              "created", callX(varX("typeToCreate"), "newInstance", optionalKeyArg(fieldKey)))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod(fieldNode.getName(), "add", callX(varX("created"), "apply", varX("closure")))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
    }

    MethodBuilder.createPublicMethod(methodName)
        .param(elementType, "value")
        .callMethod(fieldNode.getName(), "add", varX("value"))
        .optionalAssignThisToPropertyS("value", targetOwner, targetOwner)
        .addTo(annotatedClass);
  }
 boolean shouldFieldBeIgnoredForValidation(FieldNode fieldNode) {
   if (getAnnotation(fieldNode, IGNORE_ANNOTATION) != null) return true;
   if (fieldNode.getName().startsWith("$")) return true;
   if ((fieldNode.getModifiers() & ACC_TRANSIENT) != 0) return true;
   return false;
 }
 private boolean annotedClassIsTopOfDSLHierarchy() {
   return ownerField != null && annotatedClass.getDeclaredField(ownerField.getName()) != null;
 }