diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java
index 952edaac3b..dba8199207 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java
@@ -149,6 +149,12 @@ static MyAnnotation myAnnotation(String value, Truthiness truthiness) {
return new AutoAnnotation_AutoBuilderTest_myAnnotation(value, truthiness);
}
+ // This method has parameters for all the annotation elements.
+ @AutoAnnotation
+ static MyAnnotation myAnnotationAll(String value, int id, Truthiness truthiness) {
+ return new AutoAnnotation_AutoBuilderTest_myAnnotationAll(value, id, truthiness);
+ }
+
@AutoBuilder(callMethod = "myAnnotation")
interface MyAnnotationBuilder {
MyAnnotationBuilder value(String x);
@@ -159,12 +165,28 @@ interface MyAnnotationBuilder {
}
static MyAnnotationBuilder myAnnotationBuilder() {
- return new AutoBuilder_AutoBuilderTest_MyAnnotationBuilder()
- .truthiness(MyAnnotation.DEFAULT_TRUTHINESS);
+ return new AutoBuilder_AutoBuilderTest_MyAnnotationBuilder();
+ }
+
+ @AutoBuilder(callMethod = "myAnnotationAll")
+ interface MyAnnotationAllBuilder {
+ MyAnnotationAllBuilder value(String x);
+
+ MyAnnotationAllBuilder id(int x);
+
+ MyAnnotationAllBuilder truthiness(Truthiness x);
+
+ MyAnnotation build();
+ }
+
+ static MyAnnotationAllBuilder myAnnotationAllBuilder() {
+ return new AutoBuilder_AutoBuilderTest_MyAnnotationAllBuilder();
}
@Test
public void simpleAutoAnnotation() {
+ // We haven't supplied a value for `truthiness`, so AutoBuilder should use the default one in
+ // the annotation.
MyAnnotation annotation1 = myAnnotationBuilder().value("foo").build();
assertThat(annotation1.value()).isEqualTo("foo");
assertThat(annotation1.id()).isEqualTo(MyAnnotation.DEFAULT_ID);
@@ -174,6 +196,15 @@ public void simpleAutoAnnotation() {
assertThat(annotation2.value()).isEqualTo("bar");
assertThat(annotation2.id()).isEqualTo(MyAnnotation.DEFAULT_ID);
assertThat(annotation2.truthiness()).isEqualTo(Truthiness.TRUTHY);
+
+ MyAnnotation annotation3 = myAnnotationAllBuilder().value("foo").build();
+ MyAnnotation annotation4 =
+ myAnnotationAllBuilder()
+ .value("foo")
+ .id(MyAnnotation.DEFAULT_ID)
+ .truthiness(MyAnnotation.DEFAULT_TRUTHINESS)
+ .build();
+ assertThat(annotation3).isEqualTo(annotation4);
}
static class Overload {
diff --git a/value/src/main/java/com/google/auto/value/AutoAnnotation.java b/value/src/main/java/com/google/auto/value/AutoAnnotation.java
index d36d8e285f..c6fab24043 100644
--- a/value/src/main/java/com/google/auto/value/AutoAnnotation.java
+++ b/value/src/main/java/com/google/auto/value/AutoAnnotation.java
@@ -71,6 +71,39 @@
* parameter corresponding to an array-valued annotation member, and the implementation of each such
* member will also return a clone of the array.
*
+ *
If your annotation has many elements, you may consider using {@code @AutoBuilder} to make it
+ * easier to construct instances. In that case, {@code default} values from the annotation will
+ * become default values for the parameters of the {@code @AutoAnnotation} method. For example:
+ *
+ *
+ * class Example {
+ * {@code @interface} MyAnnotation {
+ * String name() default "foo";
+ * int number() default 23;
+ * }
+ *
+ * {@code @AutoAnnotation}
+ * static MyAnnotation myAnnotation(String value) {
+ * return new AutoAnnotation_Example_myAnnotation(value);
+ * }
+ *
+ * {@code @AutoBuilder(callMethod = "myAnnotation")}
+ * interface MyAnnotationBuilder {
+ * MyAnnotationBuilder name(String name);
+ * MyAnnotationBuilder number(int number);
+ * MyAnnotation build();
+ * }
+ *
+ * static MyAnnotationBuilder myAnnotationBuilder() {
+ * return new AutoBuilder_Example_MyAnnotationBuilder();
+ * }
+ * }
+ *
+ *
+ * Here, {@code myAnnotationBuilder().build()} is the same as {@code
+ * myAnnotationBuilder().name("foo").number(23).build()} because those are the defaults in the
+ * annotation definition.
+ *
* @author emcmanus@google.com (Éamonn McManus)
*/
@Target(ElementType.METHOD)
diff --git a/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java
index ed986ab7b6..0c8b8f0f55 100644
--- a/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java
+++ b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java
@@ -130,13 +130,13 @@ public Void visitType(TypeMirror classConstant, StringBuilder sb) {
private static class InitializerSourceFormVisitor extends SourceFormVisitor {
private final ProcessingEnvironment processingEnv;
private final String memberName;
- private final Element context;
+ private final Element errorContext;
InitializerSourceFormVisitor(
- ProcessingEnvironment processingEnv, String memberName, Element context) {
+ ProcessingEnvironment processingEnv, String memberName, Element errorContext) {
this.processingEnv = processingEnv;
this.memberName = memberName;
- this.context = context;
+ this.errorContext = errorContext;
}
@Override
@@ -148,7 +148,7 @@ public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) {
"@AutoAnnotation cannot yet supply a default value for annotation-valued member '"
+ memberName
+ "'",
- context);
+ errorContext);
sb.append("null");
return null;
}
@@ -209,9 +209,9 @@ static String sourceFormForInitializer(
AnnotationValue annotationValue,
ProcessingEnvironment processingEnv,
String memberName,
- Element context) {
+ Element errorContext) {
SourceFormVisitor visitor =
- new InitializerSourceFormVisitor(processingEnv, memberName, context);
+ new InitializerSourceFormVisitor(processingEnv, memberName, errorContext);
StringBuilder sb = new StringBuilder();
visitor.visit(annotationValue, sb);
return sb.toString();
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
index b6a578fcf0..53fe836db2 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
@@ -20,6 +20,7 @@
import static com.google.auto.common.MoreStreams.toImmutableList;
import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION;
+import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME;
import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
@@ -38,6 +39,7 @@
import com.google.common.base.Ascii;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import java.lang.reflect.Field;
@@ -60,6 +62,7 @@
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
@@ -134,7 +137,7 @@ void processType(TypeElement autoBuilderType) {
Map propertyToGetterName =
Maps.transformValues(classifier.builderGetters(), PropertyGetter::getName);
AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars();
- vars.props = propertySet(executable, propertyToGetterName);
+ vars.props = propertySet(autoBuilderType, executable, propertyToGetterName);
builder.defineVars(vars, classifier);
vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_");
@@ -152,7 +155,15 @@ void processType(TypeElement autoBuilderType) {
}
private ImmutableSet propertySet(
- ExecutableElement executable, Map propertyToGetterName) {
+ TypeElement autoBuilderType,
+ ExecutableElement executable,
+ Map propertyToGetterName) {
+ boolean autoAnnotation =
+ MoreElements.getAnnotationMirror(executable, AUTO_ANNOTATION_NAME).isPresent();
+ ImmutableMap builderInitializers =
+ autoAnnotation
+ ? autoAnnotationInitializers(autoBuilderType, executable)
+ : ImmutableMap.of();
// Fix any parameter names that are reserved words in Java. Java source code can't have
// such parameter names, but Kotlin code might, for example.
Map identifiers =
@@ -161,18 +172,58 @@ private ImmutableSet propertySet(
fixReservedIdentifiers(identifiers);
return executable.getParameters().stream()
.map(
- v ->
- newProperty(
- v, identifiers.get(v), propertyToGetterName.get(v.getSimpleName().toString())))
+ v -> {
+ String name = v.getSimpleName().toString();
+ return newProperty(
+ v,
+ identifiers.get(v),
+ propertyToGetterName.get(name),
+ Optional.ofNullable(builderInitializers.get(name)));
+ })
.collect(toImmutableSet());
}
- private Property newProperty(VariableElement var, String identifier, String getterName) {
+ private Property newProperty(
+ VariableElement var,
+ String identifier,
+ String getterName,
+ Optional builderInitializer) {
String name = var.getSimpleName().toString();
TypeMirror type = var.asType();
Optional nullableAnnotation = nullableAnnotationFor(var, var.asType());
return new Property(
- name, identifier, TypeEncoder.encode(type), type, nullableAnnotation, getterName);
+ name,
+ identifier,
+ TypeEncoder.encode(type),
+ type,
+ nullableAnnotation,
+ getterName,
+ builderInitializer);
+ }
+
+ private ImmutableMap autoAnnotationInitializers(
+ TypeElement autoBuilderType, ExecutableElement autoAnnotationMethod) {
+ // We expect the return type of an @AutoAnnotation method to be an annotation type. If it isn't,
+ // AutoAnnotation will presumably complain, so we don't need to complain further.
+ TypeMirror returnType = autoAnnotationMethod.getReturnType();
+ if (!returnType.getKind().equals(TypeKind.DECLARED)) {
+ return ImmutableMap.of();
+ }
+ // This might not actually be an annotation (if the code is wrong), but if that's the case we
+ // just won't see any contained ExecutableElement where getDefaultValue() returns something.
+ TypeElement annotation = MoreTypes.asTypeElement(returnType);
+ ImmutableMap.Builder builder = ImmutableMap.builder();
+ for (ExecutableElement method : methodsIn(annotation.getEnclosedElements())) {
+ AnnotationValue defaultValue = method.getDefaultValue();
+ if (defaultValue != null) {
+ String memberName = method.getSimpleName().toString();
+ builder.put(
+ memberName,
+ AnnotationOutput.sourceFormForInitializer(
+ defaultValue, processingEnv, memberName, autoBuilderType));
+ }
+ }
+ return builder.build();
}
private ExecutableElement findExecutable(
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java
index 86cf4974aa..9fbc16523e 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java
@@ -109,7 +109,8 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars {
*
*
* - it is {@code @Nullable} (in which case it defaults to null);
- *
- it is {@code Optional} (in which case it defaults to empty);
+ *
- it has a builder initializer (for example it is {@code Optional}, which will have an
+ * initializer of {@code Optional.empty()});
*
- it has a property-builder method (in which case it defaults to empty).
*
*/
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
index 9b8605524b..ea08557552 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
@@ -160,6 +160,7 @@ public static class Property {
private final Optional nullableAnnotation;
private final Optionalish optional;
private final String getter;
+ private final String builderInitializer; // empty, or with initial ` = `.
Property(
String name,
@@ -167,16 +168,40 @@ public static class Property {
String type,
TypeMirror typeMirror,
Optional nullableAnnotation,
- String getter) {
+ String getter,
+ Optional maybeBuilderInitializer) {
this.name = name;
this.identifier = identifier;
this.type = type;
this.typeMirror = typeMirror;
this.nullableAnnotation = nullableAnnotation;
this.optional = Optionalish.createIfOptional(typeMirror);
+ this.builderInitializer =
+ maybeBuilderInitializer.isPresent()
+ ? " = " + maybeBuilderInitializer.get()
+ : builderInitializer();
this.getter = getter;
}
+ /**
+ * Returns the appropriate initializer for a builder property. Builder properties are never
+ * primitive; if the built property is an {@code int} the builder property will be an {@code
+ * Integer}. So the default value for a builder property will be null unless there is an
+ * initializer. The caller of the constructor may have supplied an initializer, but otherwise we
+ * supply one only if this property is an {@code Optional} and is not {@code @Nullable}. In that
+ * case the initializer sets it to {@code Optional.empty()}.
+ */
+ private String builderInitializer() {
+ if (nullableAnnotation.isPresent()) {
+ return "";
+ }
+ Optionalish optional = Optionalish.createIfOptional(typeMirror);
+ if (optional == null) {
+ return "";
+ }
+ return " = " + optional.getEmpty();
+ }
+
/**
* Returns the name of the property as it should be used when declaring identifiers (fields and
* parameters). If the original getter method was {@code foo()} then this will be {@code foo}.
@@ -218,6 +243,14 @@ public Optionalish getOptional() {
return optional;
}
+ /**
+ * Returns a string to be used as an initializer for a builder field for this property,
+ * including the leading {@code =}, or an empty string if there is no explicit initializer.
+ */
+ public String getBuilderInitializer() {
+ return builderInitializer;
+ }
+
/**
* Returns the string to use as a method annotation to indicate the nullability of this
* property. It is either the empty string, if the property is not nullable, or an annotation
@@ -266,7 +299,8 @@ public static class GetterProperty extends Property {
type,
method.getReturnType(),
nullableAnnotation,
- method.getSimpleName().toString());
+ method.getSimpleName().toString(),
+ Optional.empty());
this.method = method;
this.fieldAnnotations = fieldAnnotations;
this.methodAnnotations = methodAnnotations;
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
index 9f45d1720c..dfdbb8c7d3 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
@@ -333,7 +333,7 @@ void defineVars(AutoValueOrBuilderTemplateVars vars, BuilderMethodClassifier>
vars.builderRequiredProperties =
vars.props.stream()
.filter(p -> !p.isNullable())
- .filter(p -> p.getOptional() == null)
+ .filter(p -> p.getBuilderInitializer().isEmpty())
.filter(p -> !vars.builderPropertyBuilders.containsKey(p.getName()))
.collect(toImmutableSet());
}
diff --git a/value/src/main/java/com/google/auto/value/processor/builder.vm b/value/src/main/java/com/google/auto/value/processor/builder.vm
index 630330cafa..950f9838f1 100644
--- a/value/src/main/java/com/google/auto/value/processor/builder.vm
+++ b/value/src/main/java/com/google/auto/value/processor/builder.vm
@@ -40,7 +40,7 @@ class ${builderName}${builderFormalTypes} ##
#if ($p.kind.primitive)
- private $types.boxedClass($p.typeMirror).simpleName $p;
+ private $types.boxedClass($p.typeMirror).simpleName $p $p.builderInitializer;
#else
@@ -54,7 +54,7 @@ class ${builderName}${builderFormalTypes} ##
#end
- private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ;
+ private $p.type $p $p.builderInitializer;
#end
#end