or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotation-generation.mdbuilders.mdextensions.mdindex.mdmemoization.mdpretty-strings.mdserialization.mdstandalone-builders.mdtagged-unions.mdvalue-classes.md

extensions.mddocs/

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.