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 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 List<FieldNode> getAnnotatedFieldOfClass(ClassNode target, ClassNode annotation) { List<FieldNode> result = new ArrayList<FieldNode>(); for (FieldNode fieldNode : target.getFields()) if (!fieldNode.getAnnotations(annotation).isEmpty()) result.add(fieldNode); return result; }
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 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 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 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); } } }
@SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean isFinal(ClassNode classNode) { return (classNode.getModifiers() & ACC_FINAL) != 0; }
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 createFieldMethods() { for (FieldNode fieldNode : annotatedClass.getFields()) createMethodsForSingleField(fieldNode); }
private boolean annotedClassIsTopOfDSLHierarchy() { return ownerField != null && annotatedClass.getDeclaredField(ownerField.getName()) != null; }
private void validateCustomMethods(BlockStatement block) { if (!annotatedClass.hasMethod("doValidate", Parameter.EMPTY_ARRAY)) return; block.addStatement(stmt(callX(varX("this"), "doValidate"))); }