Skip to content

Commit 668c2dc

Browse files
alasiadsGoogle Java Core Libraries
authored andcommitted
AutoValue support for very large classes.
This change makes it so that if the AutoValue class defines a Builder method, the builder will be passed to the constructor. Effectively working around the java 255 method-arg limit. This is instead of defining a constructor where every field in the autovalue is an argument in it. This change will only apply to autovalues that don't have extensions: in which case the constructor is private and we can be assured that updating it shouldn't be problematic for current cases. RELNOTES=Support for AutoValues with +255 properties. PiperOrigin-RevId: 582096324
1 parent 99d964c commit 668c2dc

File tree

5 files changed

+72
-65
lines changed

5 files changed

+72
-65
lines changed

value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,10 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars {
153153
* subclass, followed by a space if they are not empty.
154154
*/
155155
String builderClassModifiers = "";
156+
157+
/**
158+
* Set if the code should generate a constructor that takes a Builder as an argument instead of
159+
* the usual per-field constructor.
160+
*/
161+
Boolean shouldGenerateBuilderConstructor = false;
156162
}

value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import javax.annotation.processing.ProcessingEnvironment;
4646
import javax.annotation.processing.Processor;
4747
import javax.annotation.processing.SupportedAnnotationTypes;
48+
import javax.lang.model.SourceVersion;
4849
import javax.lang.model.element.AnnotationMirror;
4950
import javax.lang.model.element.ExecutableElement;
5051
import javax.lang.model.element.TypeElement;
@@ -289,6 +290,10 @@ void processType(TypeElement type) {
289290
vars.subclass = TypeSimplifier.simpleNameOf(subclass);
290291
vars.finalSubclass = finalSubclass;
291292
vars.isFinal = (subclassDepth == 0);
293+
vars.shouldGenerateBuilderConstructor =
294+
applicableExtensions.isEmpty()
295+
&& builder.isPresent()
296+
&& processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_8) > 0;
292297
vars.modifiers = vars.isFinal ? "final " : "abstract ";
293298
vars.builderClassModifiers =
294299
consumedBuilderMethods.isEmpty()

value/src/main/java/com/google/auto/value/processor/autovalue.vm

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,31 +62,40 @@ ${modifiers}class $subclass$formalTypes extends $origClass$actualTypes {
6262
#if ($isFinal && $builderTypeName != "")
6363
private ##
6464
#end
65+
#if ($shouldGenerateBuilderConstructor)
66+
$subclass(${builderName}${actualTypes} builder) {
67+
#foreach ($p in $props)
68+
#if ($p.nullable || $p.kind.primitive)
69+
this.$p = builder.$p;
70+
#else
71+
this.$p = `java.util.Objects`.requireNonNull(builder.$p);
72+
#end
73+
#end
74+
}
75+
#else
6576
$subclass(
66-
#foreach ($p in $props)
67-
68-
${p.nullableAnnotation}$p.type $p #if ($foreach.hasNext) , #end
69-
#end ) {
70-
#foreach ($p in $props)
71-
#if (!$p.kind.primitive && !$p.nullable && ($builderTypeName == "" || !$isFinal))
72-
## We don't need a null check if the type is primitive or @Nullable. We also don't need it
73-
## if there is a builder, since the build() method will check for us. However, if there is a
74-
## builder but there are also extensions (!$isFinal) then we can't omit the null check because
75-
## the constructor is called from the extension code.
77+
#foreach ($p in $props)
78+
${p.nullableAnnotation}$p.type $p #if ($foreach.hasNext) , #end
79+
#end) {
80+
#foreach ($p in $props)
81+
#if (!$p.kind.primitive && !$p.nullable && ($builderTypeName == "" || !$isFinal))
82+
## We don't need a null check if the type is primitive or @Nullable. We also don't need it
83+
## if there is a builder, since the build() method will check for us. However, if there is a
84+
## builder but there are also extensions (!$isFinal) then we can't omit the null check because
85+
## the constructor is called from the extension code.
7686

77-
#if ($identifiers)
87+
#if ($identifiers)
7888
if ($p == null) {
7989
throw new NullPointerException("Null $p.name");
8090
}
81-
#else
91+
#else
8292
`java.util.Objects`.requireNonNull($p);
93+
#end
8394
#end
84-
85-
#end
86-
8795
this.$p = $p;
88-
#end
96+
#end
8997
}
98+
#end
9099

91100
## Property getters
92101

value/src/main/java/com/google/auto/value/processor/builder.vm

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,15 @@ ${builderClassModifiers}class ${builderName}${builderFormalTypes} ##
274274

275275
#end
276276

277-
#if ($builtType != "void") return #end ${build}(
278-
#foreach ($p in $props)
279-
280-
this.$p #if ($foreach.hasNext) , #end
281-
#end
282-
$builderRequiredProperties.defaultedBitmaskParameters );
277+
#if ($builtType != "void") return #end
278+
#if ($shouldGenerateBuilderConstructor)
279+
${build}(this);
280+
#else
281+
${build}(
282+
#foreach ($p in $props)
283+
this.$p #if ($foreach.hasNext) , #end
284+
#end
285+
$builderRequiredProperties.defaultedBitmaskParameters );
286+
#end
283287
}
284288
}

value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java

Lines changed: 26 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public class AutoValueCompilationTest {
6161
private boolean typeAnnotationsWork =
6262
Double.parseDouble(JAVA_SPECIFICATION_VERSION.value()) >= 9.0;
6363

64+
// Sadly we can't rely on JDK 8 to handle accessing the private methods of the builder.
65+
// So skip the test unless we are on at least JDK 9.
66+
private boolean constructorUsesBuilderFields =
67+
Double.parseDouble(JAVA_SPECIFICATION_VERSION.value()) >= 9.0;
68+
6469
@Test
6570
public void simpleSuccess() {
6671
// Positive test case that ensures we generate the expected code for at least one case.
@@ -1066,6 +1071,7 @@ public void nullablePrimitive() {
10661071

10671072
@Test
10681073
public void correctBuilder() {
1074+
assume().that(constructorUsesBuilderFields).isTrue();
10691075
JavaFileObject javaFileObject =
10701076
JavaFileObjects.forSourceLines(
10711077
"foo.bar.Baz",
@@ -1155,6 +1161,7 @@ public void correctBuilder() {
11551161
"import java.util.Arrays;",
11561162
"import java.util.List;",
11571163
"import java.util.Map;",
1164+
"import java.util.Objects;",
11581165
sorted(
11591166
GeneratedImport.importGeneratedAnnotationType(),
11601167
"import javax.annotation.Nullable;"),
@@ -1170,21 +1177,14 @@ public void correctBuilder() {
11701177
" private final Optional<String> anOptionalString;",
11711178
" private final NestedAutoValue<T> aNestedAutoValue;",
11721179
"",
1173-
" private AutoValue_Baz(",
1174-
" int anInt,",
1175-
" byte[] aByteArray,",
1176-
" @Nullable int[] aNullableIntArray,",
1177-
" List<T> aList,",
1178-
" ImmutableMap<T, String> anImmutableMap,",
1179-
" Optional<String> anOptionalString,",
1180-
" NestedAutoValue<T> aNestedAutoValue) {",
1181-
" this.anInt = anInt;",
1182-
" this.aByteArray = aByteArray;",
1183-
" this.aNullableIntArray = aNullableIntArray;",
1184-
" this.aList = aList;",
1185-
" this.anImmutableMap = anImmutableMap;",
1186-
" this.anOptionalString = anOptionalString;",
1187-
" this.aNestedAutoValue = aNestedAutoValue;",
1180+
" private AutoValue_Baz(Builder<T> builder) {",
1181+
" this.anInt = builder.anInt;",
1182+
" this.aByteArray = Objects.requireNonNull(builder.aByteArray);",
1183+
" this.aNullableIntArray = builder.aNullableIntArray;",
1184+
" this.aList = Objects.requireNonNull(builder.aList);",
1185+
" this.anImmutableMap = Objects.requireNonNull(builder.anImmutableMap);",
1186+
" this.anOptionalString = Objects.requireNonNull(builder.anOptionalString);",
1187+
" this.aNestedAutoValue = Objects.requireNonNull(builder.aNestedAutoValue);",
11881188
" }",
11891189
"",
11901190
" @Override public int anInt() {",
@@ -1439,14 +1439,7 @@ public void correctBuilder() {
14391439
" }",
14401440
" throw new IllegalStateException(\"Missing required properties:\" + missing);",
14411441
" }",
1442-
" return new AutoValue_Baz<T>(",
1443-
" this.anInt,",
1444-
" this.aByteArray,",
1445-
" this.aNullableIntArray,",
1446-
" this.aList,",
1447-
" this.anImmutableMap,",
1448-
" this.anOptionalString,",
1449-
" this.aNestedAutoValue);",
1442+
" return new AutoValue_Baz<T>(this);",
14501443
" }",
14511444
" }",
14521445
"}");
@@ -1465,6 +1458,7 @@ public void correctBuilder() {
14651458
@Test
14661459
public void builderWithNullableTypeAnnotation() {
14671460
assume().that(typeAnnotationsWork).isTrue();
1461+
assume().that(constructorUsesBuilderFields).isTrue();
14681462
JavaFileObject javaFileObject =
14691463
JavaFileObjects.forSourceLines(
14701464
"foo.bar.Baz",
@@ -1518,6 +1512,7 @@ public void builderWithNullableTypeAnnotation() {
15181512
"import java.util.Arrays;",
15191513
"import java.util.List;",
15201514
"import java.util.Map;",
1515+
"import java.util.Objects;",
15211516
sorted(
15221517
GeneratedImport.importGeneratedAnnotationType(),
15231518
"import org.checkerframework.checker.nullness.qual.Nullable;"),
@@ -1531,19 +1526,13 @@ public void builderWithNullableTypeAnnotation() {
15311526
" private final ImmutableMap<T, String> anImmutableMap;",
15321527
" private final Optional<String> anOptionalString;",
15331528
"",
1534-
" private AutoValue_Baz(",
1535-
" int anInt,",
1536-
" byte[] aByteArray,",
1537-
" int @Nullable [] aNullableIntArray,",
1538-
" List<T> aList,",
1539-
" ImmutableMap<T, String> anImmutableMap,",
1540-
" Optional<String> anOptionalString) {",
1541-
" this.anInt = anInt;",
1542-
" this.aByteArray = aByteArray;",
1543-
" this.aNullableIntArray = aNullableIntArray;",
1544-
" this.aList = aList;",
1545-
" this.anImmutableMap = anImmutableMap;",
1546-
" this.anOptionalString = anOptionalString;",
1529+
" private AutoValue_Baz(Builder<T> builder) {",
1530+
" this.anInt = builder.anInt;",
1531+
" this.aByteArray = Objects.requireNonNull(builder.aByteArray);",
1532+
" this.aNullableIntArray = builder.aNullableIntArray;",
1533+
" this.aList = Objects.requireNonNull(builder.aList);",
1534+
" this.anImmutableMap = Objects.requireNonNull(builder.anImmutableMap);",
1535+
" this.anOptionalString = Objects.requireNonNull(builder.anOptionalString);",
15471536
" }",
15481537
"",
15491538
" @Override public int anInt() {",
@@ -1733,13 +1722,7 @@ public void builderWithNullableTypeAnnotation() {
17331722
" }",
17341723
" throw new IllegalStateException(\"Missing required properties:\" + missing);",
17351724
" }",
1736-
" return new AutoValue_Baz<T>(",
1737-
" this.anInt,",
1738-
" this.aByteArray,",
1739-
" this.aNullableIntArray,",
1740-
" this.aList,",
1741-
" this.anImmutableMap,",
1742-
" this.anOptionalString);",
1725+
" return new AutoValue_Baz<T>(this);",
17431726
" }",
17441727
" }",
17451728
"}");

0 commit comments

Comments
 (0)