Skip to content

Commit 7d4b020

Browse files
java-team-github-botGoogle Java Core Libraries
authored andcommitted
Adds support for extending AutoValue.Builder via abstract methods.
Leveraging AutoValueExtension to extend a class didn't allow extending the AutoValue.Builder class via abstract methods. This change allows extensions to consume abstract methods in the builder so that they can provide their own implementations. RELNOTES=Added support for extending AutoValue.Builder with abstract methods. PiperOrigin-RevId: 565160145
1 parent fe2eb81 commit 7d4b020

File tree

7 files changed

+287
-53
lines changed

7 files changed

+287
-53
lines changed

value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@
4141
* compiler's {@code -classpath} or {@code -processorpath}.
4242
*
4343
* <p>When the AutoValue processor runs for a class {@code Foo}, it will ask each Extension whether
44-
* it is {@linkplain #applicable applicable}. Suppose two Extensions reply that they are. Then
45-
* the processor will generate the AutoValue logic in a direct subclass of {@code Foo}, and it
46-
* will ask the first Extension to generate a subclass of that, and the second Extension to generate
47-
* a subclass of the subclass. So we might have this hierarchy:
44+
* it is {@linkplain #applicable applicable}. Suppose two Extensions reply that they are. Then the
45+
* processor will generate the AutoValue logic in a direct subclass of {@code Foo}, and it will ask
46+
* the first Extension to generate a subclass of that, and the second Extension to generate a
47+
* subclass of the subclass. So we might have this hierarchy:
4848
*
4949
* <pre>
5050
* &#64;AutoValue abstract class Foo {...} // the hand-written class
@@ -63,17 +63,17 @@
6363
* <p>The first generated class in the hierarchy will always be the one generated by the AutoValue
6464
* processor and the last one will always be the one generated by the Extension that {@code
6565
* mustBeFinal}, if any. Other than that, the order of the classes in the hierarchy is unspecified.
66-
* The last class in the hierarchy is {@code AutoValue_Foo} and that is the one that the
67-
* {@code Foo} class will reference, for example with {@code new AutoValue_Foo(...)}.
66+
* The last class in the hierarchy is {@code AutoValue_Foo} and that is the one that the {@code Foo}
67+
* class will reference, for example with {@code new AutoValue_Foo(...)}.
6868
*
6969
* <p>Each Extension must also be sure to generate a constructor with arguments corresponding to all
7070
* properties in {@link com.google.auto.value.extension.AutoValueExtension.Context#propertyTypes()},
7171
* in order, and to call the superclass constructor with the same arguments. This constructor must
7272
* have at least package visibility.
7373
*
7474
* <p>Because the class generated by the AutoValue processor is at the top of the generated
75-
* hierarchy, Extensions can override its methods, for example {@code hashCode()},
76-
* {@code toString()}, or the implementations of the various {@code bar()} property methods.
75+
* hierarchy, Extensions can override its methods, for example {@code hashCode()}, {@code
76+
* toString()}, or the implementations of the various {@code bar()} property methods.
7777
*/
7878
public abstract class AutoValueExtension {
7979

@@ -99,10 +99,10 @@ public interface Context {
9999

100100
/**
101101
* The fully-qualified name of the last class in the {@code AutoValue} hierarchy. For an
102-
* {@code @AutoValue} class {@code foo.bar.Baz}, this will be {@code foo.bar.AutoValue_Baz}.
103-
* The class may be generated by an extension, which will be the current extension if the
104-
* {@code isFinal} parameter to {@link AutoValueExtension#generateClass} is true and the
105-
* returned string is not {@code null}.
102+
* {@code @AutoValue} class {@code foo.bar.Baz}, this will be {@code foo.bar.AutoValue_Baz}. The
103+
* class may be generated by an extension, which will be the current extension if the {@code
104+
* isFinal} parameter to {@link AutoValueExtension#generateClass} is true and the returned
105+
* string is not {@code null}.
106106
*
107107
* <p>For compatibility reasons, this method has a default implementation that throws an
108108
* exception. The AutoValue processor supplies an implementation that behaves as documented.
@@ -159,6 +159,14 @@ default Map<String, TypeMirror> propertyTypes() {
159159
*/
160160
Set<ExecutableElement> abstractMethods();
161161

162+
/**
163+
* Returns the complete set of abstract methods defined in or inherited by the {@code @Builder}
164+
* class. This includes methods that have been consumed by this or another Extension.
165+
*
166+
* <p>If there is no builder class, then this set is empty.
167+
*/
168+
Set<ExecutableElement> builderAbstractMethods();
169+
162170
/**
163171
* Returns the complete list of annotations defined on the {@code classToCopyFrom} that should
164172
* be added to any generated subclass. Only annotations visible to the {@code @AutoValue} will
@@ -203,9 +211,7 @@ default Optional<BuilderContext> builder() {
203211
}
204212
}
205213

206-
/**
207-
* Represents a {@code Builder} associated with an {@code @AutoValue} class.
208-
*/
214+
/** Represents a {@code Builder} associated with an {@code @AutoValue} class. */
209215
public interface BuilderContext {
210216
/**
211217
* Returns the {@code @AutoValue.Builder} interface or abstract class that this object
@@ -218,6 +224,7 @@ public interface BuilderContext {
218224
* type.
219225
*
220226
* <p>Consider a class like this:
227+
*
221228
* <pre>
222229
* {@code @AutoValue} abstract class Foo {
223230
* abstract String bar();
@@ -230,8 +237,8 @@ public interface BuilderContext {
230237
* }
231238
* </pre>
232239
*
233-
* <p>Here {@code toBuilderMethods()} will return a set containing the method
234-
* {@code Foo.toBuilder()}.
240+
* <p>Here {@code toBuilderMethods()} will return a set containing the method {@code
241+
* Foo.toBuilder()}.
235242
*/
236243
Set<ExecutableElement> toBuilderMethods();
237244

@@ -240,6 +247,7 @@ public interface BuilderContext {
240247
* type.
241248
*
242249
* <p>Consider a class like this:
250+
*
243251
* <pre>
244252
* {@code @AutoValue} abstract class Foo {
245253
* abstract String bar();
@@ -257,19 +265,19 @@ public interface BuilderContext {
257265
* }
258266
* </pre>
259267
*
260-
* <p>Here {@code builderMethods()} will return a set containing the method
261-
* {@code Foo.builder()}. Generated code should usually call this method in preference to
262-
* constructing {@code AutoValue_Foo.Builder()} directly, because this method can establish
263-
* default values for properties, as it does here.
268+
* <p>Here {@code builderMethods()} will return a set containing the method {@code
269+
* Foo.builder()}. Generated code should usually call this method in preference to constructing
270+
* {@code AutoValue_Foo.Builder()} directly, because this method can establish default values
271+
* for properties, as it does here.
264272
*/
265273
Set<ExecutableElement> builderMethods();
266274

267275
/**
268276
* Returns the method {@code build()} in the builder class, if it exists and returns the
269-
* {@code @AutoValue} type. This is the method that generated code for
270-
* {@code @AutoValue class Foo} should call in order to get an instance of {@code Foo} from its
271-
* builder. The returned method is called {@code build()}; if the builder uses some other name
272-
* then extensions have no good way to guess how they should build.
277+
* {@code @AutoValue} type. This is the method that generated code for {@code @AutoValue class
278+
* Foo} should call in order to get an instance of {@code Foo} from its builder. The returned
279+
* method is called {@code build()}; if the builder uses some other name then extensions have no
280+
* good way to guess how they should build.
273281
*
274282
* <p>A common convention is for {@code build()} to be a concrete method in the
275283
* {@code @AutoValue.Builder} class, which calls an abstract method {@code autoBuild()} that is
@@ -281,9 +289,9 @@ public interface BuilderContext {
281289
/**
282290
* Returns the abstract build method. If the {@code @AutoValue} class is {@code Foo}, this is an
283291
* abstract no-argument method in the builder class that returns {@code Foo}. This might be
284-
* called {@code build()}, or, following a common convention, it might be called
285-
* {@code autoBuild()} and used in the implementation of a {@code build()} method that is
286-
* defined in the builder class.
292+
* called {@code build()}, or, following a common convention, it might be called {@code
293+
* autoBuild()} and used in the implementation of a {@code build()} method that is defined in
294+
* the builder class.
287295
*
288296
* <p>Extensions should call the {@code build()} method in preference to this one. But they
289297
* should override this one if they want to customize build-time behaviour.
@@ -292,19 +300,18 @@ public interface BuilderContext {
292300

293301
/**
294302
* Returns a map from property names to the corresponding setters. A property may have more than
295-
* one setter. For example, an {@code ImmutableList<String>} might be set by
296-
* {@code setFoo(ImmutableList<String>)} and {@code setFoo(String[])}.
303+
* one setter. For example, an {@code ImmutableList<String>} might be set by {@code
304+
* setFoo(ImmutableList<String>)} and {@code setFoo(String[])}.
297305
*/
298306
Map<String, Set<ExecutableElement>> setters();
299307

300308
/**
301309
* Returns a map from property names to property builders. For example, if there is a property
302-
* {@code foo} defined by {@code abstract ImmutableList<String> foo();} or
303-
* {@code abstract ImmutableList<String> getFoo();} in the {@code @AutoValue} class,
304-
* then there can potentially be a builder defined by
305-
* {@code abstract ImmutableList.Builder<String> fooBuilder();} in the
306-
* {@code @AutoValue.Builder} class. This map would then map {@code "foo"} to the
307-
* {@link ExecutableElement} representing {@code fooBuilder()}.
310+
* {@code foo} defined by {@code abstract ImmutableList<String> foo();} or {@code abstract
311+
* ImmutableList<String> getFoo();} in the {@code @AutoValue} class, then there can potentially
312+
* be a builder defined by {@code abstract ImmutableList.Builder<String> fooBuilder();} in the
313+
* {@code @AutoValue.Builder} class. This map would then map {@code "foo"} to the {@link
314+
* ExecutableElement} representing {@code fooBuilder()}.
308315
*/
309316
Map<String, ExecutableElement> propertyBuilders();
310317
}
@@ -328,8 +335,8 @@ public enum IncrementalExtensionType {
328335

329336
/**
330337
* This extension is <i>aggregating</i>, meaning that it may generate outputs based on several
331-
* annotated input classes and it respects the constraints imposed on aggregating processors.
332-
* It is unusual for AutoValue extensions to be aggregating.
338+
* annotated input classes and it respects the constraints imposed on aggregating processors. It
339+
* is unusual for AutoValue extensions to be aggregating.
333340
*
334341
* @see <a
335342
* href="https://docs.gradle.org/current/userguide/java_plugin.html#aggregating_annotation_processors">Gradle
@@ -436,10 +443,10 @@ public Set<String> consumeProperties(Context context) {
436443
}
437444

438445
/**
439-
* Returns a possible empty set of abstract methods that this Extension intends to implement. This
446+
* Returns a possibly empty set of abstract methods that this Extension intends to implement. This
440447
* will prevent AutoValue from generating an implementation, in cases where it would have, and it
441-
* will also avoid warnings about abstract methods that AutoValue doesn't expect. The default set
442-
* returned by this method is empty.
448+
* will also avoid complaints about abstract methods that AutoValue doesn't expect. The default
449+
* set returned by this method is empty.
443450
*
444451
* <p>Each returned method must be one of the abstract methods in {@link
445452
* Context#abstractMethods()}.
@@ -457,6 +464,21 @@ public Set<ExecutableElement> consumeMethods(Context context) {
457464
return ImmutableSet.of();
458465
}
459466

467+
/**
468+
* Returns a possibly empty set of abstract methods that this Extension intends to implement. This
469+
* will prevent AutoValue from generating an implementation, in cases where it would have, and it
470+
* will also avoid complaints about abstract methods that AutoValue doesn't expect. The default
471+
* set returned by this method is empty.
472+
*
473+
* <p>Each returned method must be one of the abstract methods in {@link
474+
* Context#builderAbstractMethods()}.
475+
*
476+
* @param context the Context of the code generation for this class.
477+
*/
478+
public Set<ExecutableElement> consumeBuilderMethods(Context context) {
479+
return ImmutableSet.of();
480+
}
481+
460482
/**
461483
* Returns the generated source code of the class named {@code className} to extend {@code
462484
* classToExtend}, or {@code null} if this extension does not generate a class in the hierarchy.
@@ -476,7 +498,8 @@ public Set<ExecutableElement> consumeMethods(Context context) {
476498
* ...
477499
* }
478500
* ...
479-
* }}</pre>
501+
* }
502+
* }</pre>
480503
*
481504
* <p>Here, {@code <package>} is {@link Context#packageName()}; {@code <finalOrAbstract>} is the
482505
* keyword {@code final} if {@code isFinal} is true or {@code abstract} otherwise; and {@code

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
@@ -147,4 +147,10 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars {
147147
* subclasses)
148148
*/
149149
Boolean isFinal = false;
150+
151+
/**
152+
* The modifiers (for example {@code final} or {@code abstract}) for the generated builder
153+
* subclass, followed by a space if they are not empty.
154+
*/
155+
String builderClassModifiers = "";
150156
}

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

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,13 @@ void processType(TypeElement type) {
206206
BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter());
207207
Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder();
208208
ImmutableSet<ExecutableElement> toBuilderMethods;
209+
ImmutableSet<ExecutableElement> builderAbstractMethods;
209210
if (builder.isPresent()) {
210211
toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), type, abstractMethods);
212+
builderAbstractMethods = builder.get().builderAbstractMethods();
211213
} else {
212214
toBuilderMethods = ImmutableSet.of();
215+
builderAbstractMethods = ImmutableSet.of();
213216
}
214217

215218
ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes =
@@ -219,11 +222,19 @@ void processType(TypeElement type) {
219222

220223
ExtensionContext context =
221224
new ExtensionContext(
222-
processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods);
225+
processingEnv,
226+
type,
227+
properties,
228+
propertyMethodsAndTypes,
229+
abstractMethods,
230+
builderAbstractMethods);
223231
ImmutableList<AutoValueExtension> applicableExtensions = applicableExtensions(type, context);
224232
ImmutableSet<ExecutableElement> consumedMethods =
225233
methodsConsumedByExtensions(
226234
type, applicableExtensions, context, abstractMethods, properties);
235+
ImmutableSet<ExecutableElement> consumedBuilderMethods =
236+
builderMethodsConsumedByExtensions(
237+
type, applicableExtensions, context, builderAbstractMethods);
227238

228239
if (!consumedMethods.isEmpty()) {
229240
ImmutableSet<ExecutableElement> allAbstractMethods = abstractMethods;
@@ -234,7 +245,12 @@ void processType(TypeElement type) {
234245
properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
235246
context =
236247
new ExtensionContext(
237-
processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods);
248+
processingEnv,
249+
type,
250+
properties,
251+
propertyMethodsAndTypes,
252+
allAbstractMethods,
253+
builderAbstractMethods);
238254
}
239255

240256
ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet();
@@ -252,7 +268,8 @@ void processType(TypeElement type) {
252268
toBuilderMethods,
253269
propertyMethodsAndTypes,
254270
builder,
255-
nullables);
271+
nullables,
272+
consumedBuilderMethods);
256273
vars.builtType = vars.origClass + vars.actualTypes;
257274
vars.build = "new " + finalSubclass + vars.actualTypes;
258275

@@ -270,6 +287,10 @@ void processType(TypeElement type) {
270287
vars.subclass = TypeSimplifier.simpleNameOf(subclass);
271288
vars.isFinal = (subclassDepth == 0);
272289
vars.modifiers = vars.isFinal ? "final " : "abstract ";
290+
vars.builderClassModifiers =
291+
consumedBuilderMethods.isEmpty()
292+
? vars.isFinal ? "static final " : "static "
293+
: "abstract static ";
273294

274295
String text = vars.toText();
275296
text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType());
@@ -393,6 +414,40 @@ private ImmutableSet<ExecutableElement> methodsConsumedByExtensions(
393414
return ImmutableSet.copyOf(consumed);
394415
}
395416

417+
private ImmutableSet<ExecutableElement> builderMethodsConsumedByExtensions(
418+
TypeElement type,
419+
ImmutableList<AutoValueExtension> applicableExtensions,
420+
ExtensionContext context,
421+
ImmutableSet<ExecutableElement> builderAbstractMethods) {
422+
Set<ExecutableElement> consumed = new HashSet<>();
423+
for (AutoValueExtension extension : applicableExtensions) {
424+
Set<ExecutableElement> consumedHere = new HashSet<>();
425+
for (ExecutableElement consumedMethod : extension.consumeBuilderMethods(context)) {
426+
if (!builderAbstractMethods.contains(consumedMethod)) {
427+
errorReporter()
428+
.reportError(
429+
type,
430+
"[AutoValueBuilderConsumeNotAbstract] Extension %s wants to consume a method that"
431+
+ " is not one of the abstract methods in this class: %s",
432+
extensionName(extension),
433+
consumedMethod);
434+
} else {
435+
consumedHere.add(consumedMethod);
436+
}
437+
}
438+
for (ExecutableElement repeat : intersection(consumed, consumedHere)) {
439+
errorReporter()
440+
.reportError(
441+
repeat,
442+
"[AutoValueBuilderConsumeNotAbstract] Extension %s wants to consume a method that"
443+
+ " was already consumed by another extension",
444+
extensionName(extension));
445+
}
446+
consumed.addAll(consumedHere);
447+
}
448+
return ImmutableSet.copyOf(consumed);
449+
}
450+
396451
private void validateMethods(
397452
TypeElement type,
398453
ImmutableSet<ExecutableElement> abstractMethods,
@@ -430,7 +485,8 @@ private void defineVarsForType(
430485
ImmutableSet<ExecutableElement> toBuilderMethods,
431486
ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
432487
Optional<BuilderSpec.Builder> maybeBuilder,
433-
Nullables nullables) {
488+
Nullables nullables,
489+
ImmutableSet<ExecutableElement> consumedBuilderAbstractMethods) {
434490
ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet();
435491
vars.toBuilderMethods =
436492
toBuilderMethods.stream().map(SimpleMethod::new).collect(toImmutableList());
@@ -450,7 +506,8 @@ private void defineVarsForType(
450506
builder -> {
451507
ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
452508
propertyNameToMethodMap(propertyMethods).inverse();
453-
builder.defineVarsForAutoValue(vars, methodToPropertyName, nullables);
509+
builder.defineVarsForAutoValue(
510+
vars, methodToPropertyName, nullables, consumedBuilderAbstractMethods);
454511
vars.builderName = "Builder";
455512
vars.builderAnnotations = copiedClassAnnotations(builder.builderType());
456513
});

0 commit comments

Comments
 (0)