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 1cb9186002..900a948b0b 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 @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -310,12 +311,14 @@ public class PropertySetter { private final String parameterTypeString; private final boolean primitiveParameter; private final String copyOf; + private final String nullableAnnotation; public PropertySetter( ExecutableElement setter, TypeMirror propertyType, TypeSimplifier typeSimplifier) { this.access = AutoValueProcessor.access(setter); this.name = setter.getSimpleName().toString(); - TypeMirror parameterType = Iterables.getOnlyElement(setter.getParameters()).asType(); + VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters()); + TypeMirror parameterType = parameterElement.asType(); primitiveParameter = parameterType.getKind().isPrimitive(); String simplifiedParameterType = typeSimplifier.simplify(parameterType); if (setter.isVarArgs()) { @@ -327,10 +330,30 @@ public PropertySetter( boolean sameType = typeUtils.isSameType(typeUtils.erasure(parameterType), erasedPropertyType); if (sameType) { this.copyOf = null; + this.nullableAnnotation = ""; } else { String rawTarget = typeSimplifier.simplifyRaw(erasedPropertyType); - String of = Optionalish.isOptional(propertyType) ? "of" : "copyOf"; + Optionalish optional = Optionalish.createIfOptional(propertyType, rawTarget); + String nullableAnnotation = ""; + String of = null; + if (optional != null) { + for (AnnotationMirror annotationMirror : parameterElement.getAnnotationMirrors()) { + AnnotationOutput annotationOutput = new AnnotationOutput(typeSimplifier); + String annotationName = annotationOutput.sourceFormForAnnotation(annotationMirror); + if (annotationName.equals("@Nullable") || annotationName.endsWith(".Nullable")) { + of = optional.getNullable(); + nullableAnnotation = annotationName + " "; + break; + } + } + if (of == null) { + of = "of"; + } + } else { + of = "copyOf"; + } this.copyOf = rawTarget + "." + of + "(%s)"; + this.nullableAnnotation = nullableAnnotation; } } @@ -350,6 +373,10 @@ public boolean getPrimitiveParameter() { return primitiveParameter; } + public String getNullableAnnotation() { + return nullableAnnotation; + } + public String copy(AutoValueProcessor.Property property) { if (copyOf == null) { return property.toString(); @@ -358,7 +385,7 @@ public String copy(AutoValueProcessor.Property property) { String copy = String.format(copyOf, property); // Add a null guard only in cases where we are using copyOf and the property is @Nullable. - if (property.isNullable()) { + if (property.isNullable() || nullableAnnotation != null) { copy = String.format("(%s == null ? null : %s)", property, copy); } diff --git a/value/src/main/java/com/google/auto/value/processor/Optionalish.java b/value/src/main/java/com/google/auto/value/processor/Optionalish.java index e7c27b5fad..5687fabb8c 100644 --- a/value/src/main/java/com/google/auto/value/processor/Optionalish.java +++ b/value/src/main/java/com/google/auto/value/processor/Optionalish.java @@ -103,6 +103,24 @@ public String getEmpty() { return rawTypeSpelling + empty; } + /** + * Returns a string representing the method call to obtain the nullable version of this Optional. + * This will be something like {@code "fromNullable()"} or possibly {@code "ofNullable()"}. It does not have a final semicolon. + * + *

This method is public so that it can be referenced as {@code p.optional.nullable} from + * templates. + */ + public String getNullable() { + if (optionalType.getTypeArguments().isEmpty()) { + // No typeArguments means a primitive wrapper -- it has no nullable input + return "of"; + } + TypeElement typeElement = MoreElements.asType(optionalType.asElement()); + return typeElement.getQualifiedName().toString().startsWith("java.util.") + ? "ofNullable" + : "fromNullable"; + } + TypeMirror getContainedType(Types typeUtils) { List typeArguments = optionalType.getTypeArguments(); switch (typeArguments.size()) { diff --git a/value/src/main/java/com/google/auto/value/processor/autovalue.vm b/value/src/main/java/com/google/auto/value/processor/autovalue.vm index 1a5ed34b75..be88a64bac 100644 --- a/value/src/main/java/com/google/auto/value/processor/autovalue.vm +++ b/value/src/main/java/com/google/auto/value/processor/autovalue.vm @@ -244,11 +244,17 @@ $a #foreach ($setter in $builderSetters[$p.name]) + #if ($p.nullable) + #set ($nullableAnnotation = $p.nullableAnnotation) + #else + #set ($nullableAnnotation = $setter.nullableAnnotation) + #end + @Override ${setter.access}${builderTypeName}${builderActualTypes} ## - ${setter.name}(${p.nullableAnnotation}$setter.parameterType $p) { + ${setter.name}(${nullableAnnotation}$setter.parameterType $p) { - #if (!$setter.primitiveParameter && !$p.nullable) + #if (!$setter.primitiveParameter && !$nullableAnnotation) if ($p == null) { throw new NullPointerException("Null $p.name"); diff --git a/value/src/test/java/com/google/auto/value/processor/CompilationTest.java b/value/src/test/java/com/google/auto/value/processor/CompilationTest.java index 5392a6597f..2dd901fbef 100644 --- a/value/src/test/java/com/google/auto/value/processor/CompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/CompilationTest.java @@ -602,6 +602,7 @@ public void correctBuilder() throws Exception { " public abstract ImmutableList anImmutableList();", " public abstract Optional anOptionalString();", " public abstract NestedAutoValue aNestedAutoValue();", + " public abstract Optional anOptionalObject();", "", " public abstract Builder toBuilder();", "", @@ -616,6 +617,7 @@ public void correctBuilder() throws Exception { " public abstract Builder anOptionalString(Optional s);", " public abstract Builder anOptionalString(String s);", " public abstract NestedAutoValue.Builder aNestedAutoValueBuilder();", + " public abstract Builder anOptionalObject(@Nullable Object s);", "", " public Builder aList(ArrayList x) {", // ArrayList should not be imported in the generated class. @@ -675,6 +677,7 @@ public void correctBuilder() throws Exception { " private final ImmutableList anImmutableList;", " private final Optional anOptionalString;", " private final NestedAutoValue aNestedAutoValue;", + " private final Optional anOptionalObject;", "", " private AutoValue_Baz(", " int anInt,", @@ -683,7 +686,8 @@ public void correctBuilder() throws Exception { " List aList,", " ImmutableList anImmutableList,", " Optional anOptionalString,", - " NestedAutoValue aNestedAutoValue) {", + " NestedAutoValue aNestedAutoValue,", + " Optional anOptionalObject) {", " this.anInt = anInt;", " this.aByteArray = aByteArray;", " this.aNullableIntArray = aNullableIntArray;", @@ -691,6 +695,7 @@ public void correctBuilder() throws Exception { " this.anImmutableList = anImmutableList;", " this.anOptionalString = anOptionalString;", " this.aNestedAutoValue = aNestedAutoValue;", + " this.anOptionalObject = anOptionalObject;", " }", "", " @Override public int anInt() {", @@ -724,6 +729,10 @@ public void correctBuilder() throws Exception { " return aNestedAutoValue;", " }", "", + " @Override public Optional anOptionalObject() {", + " return anOptionalObject;", + " }", + "", " @Override public String toString() {", " return \"Baz{\"", " + \"anInt=\" + anInt + \", \"", @@ -732,7 +741,8 @@ public void correctBuilder() throws Exception { " + \"aList=\" + aList + \", \"", " + \"anImmutableList=\" + anImmutableList + \", \"", " + \"anOptionalString=\" + anOptionalString + \", \"", - " + \"aNestedAutoValue=\" + aNestedAutoValue", + " + \"aNestedAutoValue=\" + aNestedAutoValue + \", \"", + " + \"anOptionalObject=\" + anOptionalObject", " + \"}\";", " }", "", @@ -752,7 +762,8 @@ public void correctBuilder() throws Exception { " && (this.aList.equals(that.aList()))", " && (this.anImmutableList.equals(that.anImmutableList()))", " && (this.anOptionalString.equals(that.anOptionalString()))", - " && (this.aNestedAutoValue.equals(that.aNestedAutoValue()));", + " && (this.aNestedAutoValue.equals(that.aNestedAutoValue()))", + " && (this.anOptionalObject.equals(that.anOptionalObject()));", " }", " return false;", " }", @@ -773,6 +784,8 @@ public void correctBuilder() throws Exception { " h ^= this.anOptionalString.hashCode();", " h *= 1000003;", " h ^= this.aNestedAutoValue.hashCode();", + " h *= 1000003;", + " h ^= this.anOptionalObject.hashCode();", " return h;", " }", "", @@ -790,6 +803,7 @@ public void correctBuilder() throws Exception { " private Optional anOptionalString = Optional.absent();", " private NestedAutoValue.Builder aNestedAutoValueBuilder$;", " private NestedAutoValue aNestedAutoValue;", + " private Optional anOptionalObject = Optional.absent();", "", " Builder() {", " }", @@ -802,6 +816,7 @@ public void correctBuilder() throws Exception { " this.anImmutableList = source.anImmutableList();", " this.anOptionalString = source.anOptionalString();", " this.aNestedAutoValue = source.aNestedAutoValue();", + " this.anOptionalObject = source.anOptionalObject();", " }", "", " @Override", @@ -921,6 +936,12 @@ public void correctBuilder() throws Exception { " }", "", " @Override", + " public Baz.Builder anOptionalObject(@Nullable Object anOptionalObject) {", + " this.anOptionalObject = Optional.fromNullable(anOptionalObject);", + " return this;", + " }", + "", + " @Override", " public Baz build() {", " if (anImmutableListBuilder$ != null) {", " this.anImmutableList = anImmutableListBuilder$.build();", @@ -953,7 +974,8 @@ public void correctBuilder() throws Exception { " this.aList,", " this.anImmutableList,", " this.anOptionalString,", - " this.aNestedAutoValue);", + " this.aNestedAutoValue,", + " this.anOptionalObject);", " }", " }", "}");