Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate tuple constructor with all args and annotations #311

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/main/groovy/transform/TupleConstructor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -112,12 +112,13 @@ public void visit(ASTNode[] nodes, SourceUnit source) {
boolean useSetters = memberHasValue(anno, "useSetters", true);
List<String> excludes = getMemberStringList(anno, "excludes");
List<String> includes = getMemberStringList(anno, "includes");
List<ClassNode> 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);
}
}

Expand All @@ -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<String> excludes, List<String> 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<String> excludes, List<String> includes, boolean useSetters, boolean defaults, List<ClassNode> annotations) {
// no processing if existing constructors found
if (!cNode.getDeclaredConstructors().isEmpty() && !force) return;

Expand Down Expand Up @@ -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) {
Expand All @@ -201,13 +208,21 @@ public static void createConstructor(AbstractASTTransformation xform, ClassNode
}
}

private static void addAnnotationsToConstructor(List<ClassNode> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
"""
}
}