diff --git a/src/main/groovy/transform/TupleConstructor.java b/src/main/groovy/transform/TupleConstructor.java index 075fe2c5b96..cf4d38db011 100644 --- a/src/main/groovy/transform/TupleConstructor.java +++ b/src/main/groovy/transform/TupleConstructor.java @@ -251,4 +251,11 @@ * made null-safe wrt the parameter. */ boolean useSetters() default false; + + /** + * Add annotations to generated constructor, but only if {@code defaults=false} + * It is particularly helpful for generating constructor with all arguments and Spring {@code @Autowired} + * or CDI {@code @Inject} annotation. + */ + Class[] annotations() default {}; } diff --git a/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java b/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java index 3d971f2855f..a16d5aa3ef3 100644 --- a/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java +++ b/src/main/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java @@ -48,8 +48,8 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.args; import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS; import static org.codehaus.groovy.ast.tools.GeneralUtils.block; -import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; import static org.codehaus.groovy.ast.tools.GeneralUtils.equalsNullX; @@ -112,12 +112,13 @@ public void visit(ASTNode[] nodes, SourceUnit source) { boolean useSetters = memberHasValue(anno, "useSetters", true); List excludes = getMemberStringList(anno, "excludes"); List includes = getMemberStringList(anno, "includes"); + List annotations = defaults ? null : getMemberClassList(anno, "annotations"); if (!checkIncludeExcludeUndefinedAware(anno, excludes, includes, MY_TYPE_NAME)) return; if (!checkPropertyList(cNode, includes, "includes", anno, MY_TYPE_NAME, includeFields)) return; if (!checkPropertyList(cNode, excludes, "excludes", anno, MY_TYPE_NAME, includeFields)) return; // if @Immutable is found, let it pick up options and do work so we'll skip if (hasAnnotation(cNode, ImmutableASTTransformation.MY_TYPE)) return; - createConstructor(this, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters, defaults); + createConstructor(this, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters, defaults, annotations); } } @@ -126,6 +127,10 @@ public static void createConstructor(ClassNode cNode, boolean includeFields, boo } public static void createConstructor(AbstractASTTransformation xform, ClassNode cNode, boolean includeFields, boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties, boolean callSuper, boolean force, List excludes, List includes, boolean useSetters, boolean defaults) { + createConstructor(null, cNode, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force, excludes, includes, useSetters, defaults, null); + } + + public static void createConstructor(AbstractASTTransformation xform, ClassNode cNode, boolean includeFields, boolean includeProperties, boolean includeSuperFields, boolean includeSuperProperties, boolean callSuper, boolean force, List excludes, List includes, boolean useSetters, boolean defaults, List annotations) { // no processing if existing constructors found if (!cNode.getDeclaredConstructors().isEmpty() && !force) return; @@ -178,7 +183,9 @@ public static void createConstructor(AbstractASTTransformation xform, ClassNode body.addStatement(assignS(propX(varX("this"), name), varX(nextParam))); } } - cNode.addConstructor(new ConstructorNode(ACC_PUBLIC, params.toArray(new Parameter[params.size()]), ClassNode.EMPTY_ARRAY, body)); + ConstructorNode constructorNode = new ConstructorNode(ACC_PUBLIC, params.toArray(new Parameter[params.size()]), ClassNode.EMPTY_ARRAY, body); + addAnnotationsToConstructor(annotations, constructorNode); + cNode.addConstructor(constructorNode); // add map constructor if needed, don't do it for LinkedHashMap for now (would lead to duplicate signature) // or if there is only one Map property (for backwards compatibility) if (!params.isEmpty() && defaults) { @@ -201,13 +208,21 @@ public static void createConstructor(AbstractASTTransformation xform, ClassNode } } + private static void addAnnotationsToConstructor(List annotations, ConstructorNode constructorNode) { + if (annotations != null) { + for (ClassNode annotation : annotations) { + constructorNode.addAnnotation(new AnnotationNode(annotation)); + } + } + } + private static String getSetterName(String name) { return "set" + Verifier.capitalize(name); } private static Parameter createParam(FieldNode fNode, String name, boolean defaults, AbstractASTTransformation xform) { Parameter param = new Parameter(fNode.getType(), name); - if (defaults){ + if (defaults) { param.setInitialExpression(providedOrDefaultInitialValue(fNode)); } else { if (fNode.getInitialExpression() != null) { diff --git a/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy b/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy index 68284e46496..4fa2dbd198e 100644 --- a/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy +++ b/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy @@ -164,4 +164,33 @@ class TupleConstructorTransformTest extends GroovyShellTestCase { ''' } + void testAnnotationsOnConstructor() { + assertScript """ + import groovy.transform.TupleConstructor + + import java.lang.annotation.ElementType + import java.lang.annotation.Retention + import java.lang.annotation.RetentionPolicy + import java.lang.annotation.Target + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.CONSTRUCTOR) + public @interface Example1 {} + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.CONSTRUCTOR) + public @interface Example2 {} + + @TupleConstructor(annotations = [Example1, Example2], defaults = false) + class Person { + String firstName + String lastName + } + + Person.declaredConstructors.each { + assert it.declaredAnnotations[0].annotationType() == Example1 + assert it.declaredAnnotations[1].annotationType() == Example2 + } + """ + } } \ No newline at end of file