private void createMethodsForSingleField(FieldNode fieldNode) {
    if (shouldFieldBeIgnored(fieldNode)) return;

    if (hasAnnotation(fieldNode.getType(), DSL_CONFIG_ANNOTATION)) {
      createSingleDSLObjectClosureMethod(fieldNode);
      createSingleFieldSetterMethod(fieldNode);
    } else if (ASTHelper.isMap(fieldNode.getType())) createMapMethod(fieldNode);
    else if (ASTHelper.isList(fieldNode.getType())) createListMethod(fieldNode);
    else createSingleFieldSetterMethod(fieldNode);
  }
  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 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 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);
      }
    }
  }
  @SuppressWarnings("ConstantConditions")
  private GenericsType[] getGenericsTypes(FieldNode fieldNode) {
    GenericsType[] types = fieldNode.getType().getGenericsTypes();

    if (types == null)
      addCompileError("Lists and Maps need to be assigned an explicit Generic Type", fieldNode);
    return types;
  }
  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 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 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 Statement createGetterBody(FieldNode fNode) {
   BlockStatement body = new BlockStatement();
   final ClassNode fieldType = fNode.getType();
   final Statement statement;
   if (fieldType.isArray() || isOrImplements(fieldType, CLONEABLE_TYPE)) {
     statement = createGetterBodyArrayOrCloneable(fNode);
   } else if (fieldType.isDerivedFrom(DATE_TYPE)) {
     statement = createGetterBodyDate(fNode);
   } else {
     statement = createGetterBodyDefault(fNode);
   }
   body.addStatement(statement);
   return body;
 }
 private Statement createConstructorStatementArrayOrCloneable(FieldNode fNode) {
   final Expression fieldExpr = new VariableExpression(fNode);
   Expression initExpr = fNode.getInitialValueExpression();
   ClassNode fieldType = fNode.getType();
   if (initExpr == null) initExpr = new ConstantExpression(null);
   final Expression array = findArg(fNode.getName());
   return new IfStatement(
       equalsNullExpr(array),
       new IfStatement(
           equalsNullExpr(initExpr),
           assignStatement(fieldExpr, new ConstantExpression(null)),
           assignStatement(fieldExpr, cloneArrayOrCloneableExpr(initExpr, fieldType))),
       assignStatement(fieldExpr, cloneArrayOrCloneableExpr(array, fieldType)));
 }
  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 Statement createGetterBodyArrayOrCloneable(FieldNode fNode) {
   final Expression fieldExpr = new VariableExpression(fNode);
   final Expression expression = cloneArrayOrCloneableExpr(fieldExpr, fNode.getType());
   return safeExpression(fieldExpr, expression);
 }
  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);
    }
  }