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; }