0
# Extension Framework
1
2
AutoValue provides an extension framework that allows you to customize the generated code by creating custom AutoValueExtension implementations.
3
4
## Basic Extension Structure
5
6
```java { .api }
7
public class MyExtension extends AutoValueExtension {
8
@Override
9
public boolean applicable(Context context) {
10
// Determine if this extension should apply to the given AutoValue class
11
return context.properties().containsKey("specialProperty");
12
}
13
14
@Override
15
public String generateClass(
16
Context context,
17
String className,
18
String classToExtend,
19
boolean isFinal) {
20
// Generate the extension class code
21
return "package " + context.packageName() + ";\n" +
22
"public " + (isFinal ? "final" : "abstract") + " class " + className +
23
" extends " + classToExtend + " {\n" +
24
" // Extension implementation\n" +
25
"}";
26
}
27
}
28
```
29
30
## Extension Registration
31
32
Register extensions using the ServiceLoader mechanism by creating:
33
`META-INF/services/com.google.auto.value.extension.AutoValueExtension`
34
35
```
36
com.example.MyExtension
37
com.example.AnotherExtension
38
```
39
40
## Context Interface
41
42
The Context provides access to AutoValue class metadata:
43
44
```java { .api }
45
public interface Context {
46
ProcessingEnvironment processingEnvironment();
47
String packageName();
48
TypeElement autoValueClass();
49
String finalAutoValueClassName();
50
Map<String, ExecutableElement> properties();
51
Map<String, TypeMirror> propertyTypes();
52
Set<ExecutableElement> abstractMethods();
53
Set<ExecutableElement> builderAbstractMethods();
54
List<AnnotationMirror> classAnnotationsToCopy(TypeElement classToCopyFrom);
55
List<AnnotationMirror> methodAnnotationsToCopy(ExecutableElement method);
56
Optional<BuilderContext> builder();
57
}
58
```
59
60
## Simple Extension Example
61
62
Create an extension that adds validation methods:
63
64
```java { .api }
65
public class ValidationExtension extends AutoValueExtension {
66
@Override
67
public boolean applicable(Context context) {
68
// Apply to classes with @Validated annotation
69
return context.autoValueClass()
70
.getAnnotationMirrors()
71
.stream()
72
.anyMatch(mirror -> mirror.getAnnotationType().toString().endsWith("Validated"));
73
}
74
75
@Override
76
public String generateClass(
77
Context context,
78
String className,
79
String classToExtend,
80
boolean isFinal) {
81
82
StringBuilder code = new StringBuilder();
83
code.append("package ").append(context.packageName()).append(";\n\n");
84
code.append("public ").append(isFinal ? "final" : "abstract")
85
.append(" class ").append(className)
86
.append(" extends ").append(classToExtend).append(" {\n");
87
88
// Constructor
89
code.append(" ").append(className).append("(");
90
boolean first = true;
91
for (Map.Entry<String, TypeMirror> property : context.propertyTypes().entrySet()) {
92
if (!first) code.append(", ");
93
code.append(property.getValue().toString()).append(" ").append(property.getKey());
94
first = false;
95
}
96
code.append(") {\n super(");
97
98
first = true;
99
for (String propertyName : context.properties().keySet()) {
100
if (!first) code.append(", ");
101
code.append(propertyName);
102
first = false;
103
}
104
code.append(");\n");
105
106
// Add validation
107
for (Map.Entry<String, TypeMirror> property : context.propertyTypes().entrySet()) {
108
if (property.getValue().toString().equals("java.lang.String")) {
109
code.append(" if (").append(property.getKey()).append(".isEmpty()) {\n");
110
code.append(" throw new IllegalArgumentException(\"")
111
.append(property.getKey()).append(" cannot be empty\");\n");
112
code.append(" }\n");
113
}
114
}
115
116
code.append(" }\n");
117
code.append("}\n");
118
119
return code.toString();
120
}
121
}
122
```
123
124
Usage:
125
126
```java
127
@Validated
128
@AutoValue
129
public abstract class Person {
130
public abstract String name();
131
public abstract String email();
132
133
public static Person create(String name, String email) {
134
return new AutoValue_Person(name, email);
135
}
136
}
137
138
// The extension will add validation to the constructor
139
Person person = Person.create("", "test@example.com"); // Throws IllegalArgumentException
140
```
141
142
## Consuming Properties and Methods
143
144
Extensions can consume properties to exclude them from the default implementation:
145
146
```java { .api }
147
public class TimestampExtension extends AutoValueExtension {
148
@Override
149
public boolean applicable(Context context) {
150
return context.properties().containsKey("timestamp");
151
}
152
153
@Override
154
public Set<String> consumeProperties(Context context) {
155
// Consume the timestamp property - AutoValue won't include it in equals/hashCode
156
return ImmutableSet.of("timestamp");
157
}
158
159
@Override
160
public String generateClass(
161
Context context,
162
String className,
163
String classToExtend,
164
boolean isFinal) {
165
166
return "package " + context.packageName() + ";\n" +
167
"public " + (isFinal ? "final" : "abstract") + " class " + className +
168
" extends " + classToExtend + " {\n" +
169
" private final long timestamp;\n" +
170
" \n" +
171
" " + className + "(/* constructor parameters */) {\n" +
172
" super(/* super parameters */);\n" +
173
" this.timestamp = System.currentTimeMillis();\n" +
174
" }\n" +
175
" \n" +
176
" @Override\n" +
177
" public long timestamp() {\n" +
178
" return timestamp;\n" +
179
" }\n" +
180
"}";
181
}
182
}
183
```
184
185
## Builder Extension
186
187
Extensions can also modify builder behavior:
188
189
```java { .api }
190
public class DefaultsExtension extends AutoValueExtension {
191
@Override
192
public boolean applicable(Context context) {
193
return context.builder().isPresent();
194
}
195
196
@Override
197
public String generateClass(
198
Context context,
199
String className,
200
String classToExtend,
201
boolean isFinal) {
202
203
Optional<BuilderContext> builderContext = context.builder();
204
if (!builderContext.isPresent()) {
205
return null; // No builder, no extension needed
206
}
207
208
StringBuilder code = new StringBuilder();
209
code.append("package ").append(context.packageName()).append(";\n\n");
210
code.append("public ").append(isFinal ? "final" : "abstract")
211
.append(" class ").append(className)
212
.append(" extends ").append(classToExtend).append(" {\n");
213
214
// Add builder with smart defaults
215
code.append(" public static class Builder extends ")
216
.append(classToExtend).append(".Builder {\n");
217
code.append(" public Builder() {\n");
218
219
// Set intelligent defaults based on property types
220
for (Map.Entry<String, TypeMirror> property : context.propertyTypes().entrySet()) {
221
String propertyType = property.getValue().toString();
222
String propertyName = property.getKey();
223
224
if (propertyType.equals("java.lang.String") && propertyName.equals("id")) {
225
code.append(" ").append(propertyName).append("(java.util.UUID.randomUUID().toString());\n");
226
} else if (propertyType.startsWith("java.time.") && propertyName.contains("timestamp")) {
227
code.append(" ").append(propertyName).append("(java.time.Instant.now());\n");
228
}
229
}
230
231
code.append(" }\n");
232
code.append(" }\n");
233
code.append("}\n");
234
235
return code.toString();
236
}
237
}
238
```
239
240
## Method Consumption
241
242
Extensions can consume abstract methods to provide custom implementations:
243
244
```java { .api }
245
public class JsonExtension extends AutoValueExtension {
246
@Override
247
public boolean applicable(Context context) {
248
return context.abstractMethods().stream()
249
.anyMatch(method -> method.getSimpleName().toString().equals("toJson"));
250
}
251
252
@Override
253
public Set<ExecutableElement> consumeMethods(Context context) {
254
return context.abstractMethods().stream()
255
.filter(method -> method.getSimpleName().toString().equals("toJson"))
256
.collect(Collectors.toSet());
257
}
258
259
@Override
260
public String generateClass(
261
Context context,
262
String className,
263
String classToExtend,
264
boolean isFinal) {
265
266
StringBuilder code = new StringBuilder();
267
code.append("package ").append(context.packageName()).append(";\n\n");
268
code.append("public ").append(isFinal ? "final" : "abstract")
269
.append(" class ").append(className)
270
.append(" extends ").append(classToExtend).append(" {\n");
271
272
// Constructor
273
code.append(" ").append(className).append("(");
274
// ... constructor parameters
275
code.append(") {\n super(");
276
// ... super call
277
code.append(");\n }\n");
278
279
// Generate toJson() method
280
code.append(" @Override\n");
281
code.append(" public String toJson() {\n");
282
code.append(" StringBuilder json = new StringBuilder();\n");
283
code.append(" json.append(\"{\");\n");
284
285
boolean first = true;
286
for (String propertyName : context.properties().keySet()) {
287
if (!first) {
288
code.append(" json.append(\",\");\n");
289
}
290
code.append(" json.append(\"\\\"\").append(\"").append(propertyName).append("\")");
291
code.append(".append(\"\\\":\\\"\").append(").append(propertyName).append("())");
292
code.append(".append(\"\\\"\");\n");
293
first = false;
294
}
295
296
code.append(" json.append(\"}\");\n");
297
code.append(" return json.toString();\n");
298
code.append(" }\n");
299
code.append("}\n");
300
301
return code.toString();
302
}
303
}
304
```
305
306
## Incremental Processing Support
307
308
Extensions can support incremental compilation:
309
310
```java { .api }
311
public class MyExtension extends AutoValueExtension {
312
@Override
313
public IncrementalExtensionType incrementalType(ProcessingEnvironment processingEnvironment) {
314
return IncrementalExtensionType.ISOLATING; // or AGGREGATING, UNKNOWN
315
}
316
317
@Override
318
public Set<String> getSupportedOptions() {
319
return ImmutableSet.of("myExtension.option1", "myExtension.option2");
320
}
321
322
// ... other methods
323
}
324
```
325
326
## Extension Ordering
327
328
Extensions are applied in the order they appear on the classpath. If you need specific ordering:
329
330
```java { .api }
331
public class FinalExtension extends AutoValueExtension {
332
@Override
333
public boolean mustBeFinal(Context context) {
334
return true; // This extension must be the final class in the hierarchy
335
}
336
337
// ... other methods
338
}
339
```
340
341
Only one extension can return `true` from `mustBeFinal()`.
342
343
## Code Generation Utilities
344
345
Use JavaPoet for sophisticated code generation:
346
347
```java { .api }
348
public class JavaPoetExtension extends AutoValueExtension {
349
@Override
350
public String generateClass(
351
Context context,
352
String className,
353
String classToExtend,
354
boolean isFinal) {
355
356
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className)
357
.superclass(ClassName.bestGuess(classToExtend))
358
.addModifiers(isFinal ? Modifier.FINAL : Modifier.ABSTRACT);
359
360
// Add constructor
361
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder();
362
CodeBlock.Builder superCallBuilder = CodeBlock.builder().add("super(");
363
364
boolean first = true;
365
for (Map.Entry<String, TypeMirror> property : context.propertyTypes().entrySet()) {
366
TypeName typeName = TypeName.get(property.getValue());
367
String paramName = property.getKey();
368
369
constructorBuilder.addParameter(typeName, paramName);
370
if (!first) superCallBuilder.add(", ");
371
superCallBuilder.add("$N", paramName);
372
first = false;
373
}
374
375
superCallBuilder.add(")");
376
constructorBuilder.addStatement(superCallBuilder.build());
377
classBuilder.addMethod(constructorBuilder.build());
378
379
TypeSpec generatedClass = classBuilder.build();
380
381
JavaFile javaFile = JavaFile.builder(context.packageName(), generatedClass)
382
.build();
383
384
return javaFile.toString();
385
}
386
}
387
```
388
389
## Testing Extensions
390
391
Test extensions using compile-testing:
392
393
```java
394
@Test
395
public void testMyExtension() {
396
JavaFileObject autoValueClass = JavaFileObjects.forSourceString("test.Test",
397
"package test;",
398
"",
399
"import com.google.auto.value.AutoValue;",
400
"",
401
"@AutoValue",
402
"abstract class Test {",
403
" abstract String value();",
404
"}");
405
406
Compilation compilation = Compiler.javac()
407
.withProcessors(new AutoValueProcessor())
408
.withClasspath(/* extension classpath */)
409
.compile(autoValueClass);
410
411
assertThat(compilation).succeeded();
412
assertThat(compilation).generatedSourceFile("test.AutoValue_Test")
413
.contentsAsUtf8String()
414
.contains("// Expected extension code");
415
}
416
```
417
418
## Built-in Extensions
419
420
AutoValue includes several built-in extensions:
421
- **MemoizeExtension**: Implements @Memoized functionality
422
- **SerializableAutoValueExtension**: Handles @SerializableAutoValue
423
- **ToPrettyStringExtension**: Implements @ToPrettyString
424
425
These serve as excellent examples for creating custom extensions.