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 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);
  }
  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 createValidateMethod() {
    Validation.Mode mode =
        getEnumMemberValue(
            getAnnotation(annotatedClass, VALIDATION_ANNOTATION),
            "mode",
            Validation.Mode.class,
            Validation.Mode.AUTOMATIC);

    annotatedClass.addField(
        "$manualValidation",
        ACC_PRIVATE,
        ClassHelper.Boolean_TYPE,
        new ConstantExpression(mode == Validation.Mode.MANUAL));
    MethodBuilder.createPublicMethod("manualValidation")
        .param(Boolean_TYPE, "validation")
        .assignS(varX("$manualValidation"), varX("validation"))
        .addTo(annotatedClass);

    MethodBuilder methodBuilder = MethodBuilder.createPublicMethod(VALIDATE_METHOD);

    if (ASTHelper.isDSLObject(annotatedClass.getSuperClass())) {
      methodBuilder.statement(callSuperX(VALIDATE_METHOD));
    }

    BlockStatement block = new BlockStatement();
    validateFields(block);
    validateCustomMethods(block);

    TryCatchStatement tryCatchStatement = new TryCatchStatement(block, EmptyStatement.INSTANCE);
    tryCatchStatement.addCatch(
        new CatchStatement(
            param(ASSERTION_ERROR_TYPE, "e"),
            new ThrowStatement(
                ctorX(VALIDATION_EXCEPTION_TYPE, args(propX(varX("e"), "message"), varX("e"))))));
    tryCatchStatement.addCatch(
        new CatchStatement(
            param(EXCEPTION_TYPE, "e"),
            new ThrowStatement(
                ctorX(VALIDATION_EXCEPTION_TYPE, args(propX(varX("e"), "message"), varX("e"))))));

    methodBuilder.statement(tryCatchStatement).addTo(annotatedClass);
  }
  private void createApplyMethods() {
    MethodBuilder.createPublicMethod("apply")
        .returning(newClass(annotatedClass))
        .namedParams("values")
        .delegatingClosureParam(annotatedClass)
        .applyNamedParams("values")
        .assignS(propX(varX("closure"), "delegate"), varX("this"))
        .assignS(
            propX(varX("closure"), "resolveStrategy"),
            propX(classX(ClassHelper.CLOSURE_TYPE), "DELEGATE_FIRST"))
        .callMethod("closure", "call")
        .doReturn("this")
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("apply")
        .returning(newClass(annotatedClass))
        .delegatingClosureParam(annotatedClass)
        .callThis("apply", args(new MapExpression(), varX("closure")))
        .doReturn("this")
        .addTo(annotatedClass);
  }
  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 createFactoryMethods() {
    if (isAbstract(annotatedClass)) return;

    MethodBuilder.createPublicMethod("create")
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .namedParams("values")
        .optionalStringParam("name", keyField)
        .delegatingClosureParam(annotatedClass)
        .declareVariable(
            "result",
            keyField != null ? ctorX(annotatedClass, args("name")) : ctorX(annotatedClass))
        .callMethod("result", "copyFromTemplate")
        .callMethod("result", "apply", args("values", "closure"))
        .callValidationOn("result")
        .doReturn("result")
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("create")
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .optionalStringParam("name", keyField)
        .delegatingClosureParam(annotatedClass)
        .doReturn(
            callX(
                annotatedClass,
                "create",
                keyField != null
                    ? args(new MapExpression(), varX("name"), varX("closure"))
                    : args(new MapExpression(), varX("closure"))))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("createFromScript")
        .returning(newClass(annotatedClass))
        .deprecated()
        .mod(Opcodes.ACC_STATIC)
        .classParam("configType", ClassHelper.SCRIPT_TYPE)
        .doReturn(callX(callX(varX("configType"), "newInstance"), "run"))
        .addTo(annotatedClass);

    if (keyField != null) {
      MethodBuilder.createPublicMethod("createFrom")
          .returning(newClass(annotatedClass))
          .mod(Opcodes.ACC_STATIC)
          .stringParam("name")
          .stringParam("text")
          .declareVariable(
              "simpleName",
              callX(
                  callX(
                      callX(callX(varX("name"), "tokenize", args(constX("."))), "first"),
                      "tokenize",
                      args(constX("/"))),
                  "last"))
          .declareVariable("result", callX(annotatedClass, "create", args("simpleName")))
          .declareVariable(
              "loader",
              ctorX(
                  ClassHelper.make(GroovyClassLoader.class),
                  args(
                      callX(
                          callX(ClassHelper.make(Thread.class), "currentThread"),
                          "getContextClassLoader"))))
          .declareVariable("config", ctorX(ClassHelper.make(CompilerConfiguration.class)))
          .assignS(
              propX(varX("config"), "scriptBaseClass"), constX(DelegatingScript.class.getName()))
          .declareVariable("binding", ctorX(ClassHelper.make(Binding.class)))
          .declareVariable(
              "shell",
              ctorX(ClassHelper.make(GroovyShell.class), args("loader", "binding", "config")))
          .declareVariable("script", callX(varX("shell"), "parse", args("text")))
          .callMethod("script", "setDelegate", args("result"))
          .callMethod("script", "run")
          .doReturn("result")
          .addTo(annotatedClass);

      MethodBuilder.createPublicMethod("createFromSnippet")
          .deprecated()
          .returning(newClass(annotatedClass))
          .mod(Opcodes.ACC_STATIC)
          .stringParam("name")
          .stringParam("text")
          .doReturn(callX(annotatedClass, "createFrom", args("name", "text")))
          .addTo(annotatedClass);
    } else {
      MethodBuilder.createPublicMethod("createFrom")
          .returning(newClass(annotatedClass))
          .mod(Opcodes.ACC_STATIC)
          .stringParam("text")
          .declareVariable("result", callX(annotatedClass, "create"))
          .declareVariable(
              "loader",
              ctorX(
                  ClassHelper.make(GroovyClassLoader.class),
                  args(
                      callX(
                          callX(ClassHelper.make(Thread.class), "currentThread"),
                          "getContextClassLoader"))))
          .declareVariable("config", ctorX(ClassHelper.make(CompilerConfiguration.class)))
          .assignS(
              propX(varX("config"), "scriptBaseClass"), constX(DelegatingScript.class.getName()))
          .declareVariable("binding", ctorX(ClassHelper.make(Binding.class)))
          .declareVariable(
              "shell",
              ctorX(ClassHelper.make(GroovyShell.class), args("loader", "binding", "config")))
          .declareVariable("script", callX(varX("shell"), "parse", args("text")))
          .callMethod("script", "setDelegate", args("result"))
          .callMethod("script", "run")
          .doReturn("result")
          .addTo(annotatedClass);

      MethodBuilder.createPublicMethod("createFromSnippet")
          .deprecated()
          .returning(newClass(annotatedClass))
          .mod(Opcodes.ACC_STATIC)
          .stringParam("text")
          .doReturn(callX(annotatedClass, "createFrom", args("text")))
          .addTo(annotatedClass);
    }

    MethodBuilder.createPublicMethod("createFrom")
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .param(make(File.class), "src")
        .doReturn(
            callX(
                annotatedClass,
                "createFromSnippet",
                args(callX(callX(varX("src"), "toURI"), "toURL"))))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("createFromSnippet")
        .deprecated()
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .param(make(File.class), "src")
        .doReturn(callX(annotatedClass, "createFrom", args("src")))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("createFrom")
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .param(make(URL.class), "src")
        .declareVariable("text", propX(varX("src"), "text"))
        .doReturn(
            callX(
                annotatedClass,
                "createFromSnippet",
                keyField != null ? args(propX(varX("src"), "path"), varX("text")) : args("text")))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("createFromSnippet")
        .deprecated()
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .param(make(URL.class), "src")
        .doReturn(callX(annotatedClass, "createFrom", args("src")))
        .addTo(annotatedClass);
  }
  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 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);
  }