Add post-processing SPI for modifying generated types

We have the need internally to modify generated Thrift classes.  This is currently
done ad-hoc with a combination of Python and sed scripting, but we can and should
be more rigorous.

This commit introduces `TypeProcessor` as an SPI, implementations of which can be
provided on the command line.  Implementations will receive a JavaPoet `TypeSpec`
for every generated class, before that type is written, and must return a `TypeSpec`.
The returned type will be what is written to disk.

The returned `TypeSpec` will typically be augmented in some way - a new interface
implementation, or additional methods, or anything else one might wish.
This commit is contained in:
Ben Bader 2015-12-18 14:10:52 -08:00
Родитель 8df3c57d7f
Коммит 9a3c9be424
12 изменённых файлов: 135 добавлений и 13 удалений

Просмотреть файл

@ -4,3 +4,6 @@ include 'thrifty-runtime'
include 'thrifty-java-codegen'
include 'thrifty-gradle-plugin'
include 'thrifty-compiler'
include 'thrifty-example-postprocessor'
include 'thrifty-compiler-plugins'

Просмотреть файл

@ -0,0 +1,4 @@
dependencies {
compile libraries.javaPoet
testCompile group: 'junit', name: 'junit', version: '4.11'
}

Просмотреть файл

@ -0,0 +1,45 @@
package com.bendb.thrifty.compiler;
import com.bendb.thrifty.compiler.spi.TypeProcessor;
import java.util.Iterator;
import java.util.ServiceLoader;
public final class TypeProcessorService {
private static TypeProcessorService instance;
public static synchronized TypeProcessorService getInstance() {
if (instance == null) {
instance = new TypeProcessorService();
}
return instance;
}
private ServiceLoader<TypeProcessor> serviceLoader = ServiceLoader.load(TypeProcessor.class);
/**
* Gets the first {@link TypeProcessor} implementation loaded, or
* {@code null} if none are found.
*
* Because service ordering is non-deterministic, only the first instance
* is returned. A warning will be printed if more than one are found.
*
* @return The first located {@link TypeProcessor}, or {@code null}.
*/
public TypeProcessor get() {
TypeProcessor processor = null;
Iterator<TypeProcessor> iter = serviceLoader.iterator();
if (iter.hasNext()) {
processor = iter.next();
if (iter.hasNext()) {
System.err.println("Multiple TypeProcessors found; using "
+ processor.getClass().getTypeName());
}
}
return processor;
}
}

Просмотреть файл

@ -0,0 +1,38 @@
package com.bendb.thrifty.compiler.spi;
import com.squareup.javapoet.TypeSpec;
/**
* When specified as part of code generation, processes all types after they
* are computed, but before they are written to disk. This allows you to make
* arbitrary modifications to types such as implementing your own interfaces,
* renaming fields, or anything you might wish to do.
*
* <p>For example, a processor that implements java.lang.Serializable on all
* generated types:
*
* <pre><code>
* public class SerializableTypeProcessor implements TypeProcessor {
* {@literal @}Override
* public TypeSpec process(TypeSpec type) {
* TypeSpec builder = type.toBuilder();
*
* builder.addSuperinterface(Serializable.class);
* builder.addField(FieldSpec.builder(long.class, "serialVersionUID")
* .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
* .initializer("$L", -1)
* .build());
*
* return builder.build();
* }
* }
* </code></pre>
*/
public interface TypeProcessor {
/**
*
* @param type
* @return
*/
TypeSpec process(TypeSpec type);
}

Просмотреть файл

@ -7,6 +7,7 @@ repositories {
dependencies {
compile project(':thrifty-schema')
compile project(':thrifty-java-codegen')
compile project(':thrifty-compiler-plugins')
}
sourceSets {

Просмотреть файл

@ -1,5 +1,6 @@
package com.bendb.thrifty.compiler;
import com.bendb.thrifty.compiler.spi.TypeProcessor;
import com.bendb.thrifty.gen.ThriftyCodeGenerator;
import com.bendb.thrifty.schema.Loader;
import com.bendb.thrifty.schema.Schema;
@ -164,6 +165,12 @@ public class ThriftyCompiler {
gen = gen.withMapType(mapTypeName);
}
TypeProcessorService svc = TypeProcessorService.getInstance();
TypeProcessor processor = svc.get();
if (processor != null) {
gen = gen.usingTypeProcessor(processor);
}
gen.emitAndroidAnnotations(emitNullabilityAnnotations);
gen.generate(outputDirectory);

Просмотреть файл

@ -0,0 +1,6 @@
dependencies {
compile project(':thrifty-compiler-plugins')
compile libraries.javaPoet
testCompile group: 'junit', name: 'junit', version: '4.11'
}

Просмотреть файл

@ -0,0 +1,28 @@
package com.bendb.thrifty.compiler;
import com.bendb.thrifty.compiler.spi.TypeProcessor;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.TypeSpec;
import javax.lang.model.element.Modifier;
import java.io.Serializable;
/**
* An example {@link TypeProcessor} that implements {@link Serializable}
* on all types.
*/
public class SerializableTypeProcessor implements TypeProcessor {
@Override
public TypeSpec process(TypeSpec type) {
TypeSpec.Builder builder = type.toBuilder();
builder.addSuperinterface(Serializable.class);
builder.addField(FieldSpec.builder(long.class, "serialVersionUID")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("$L", -1)
.build());
return builder.build();
}
}

Просмотреть файл

@ -0,0 +1 @@
com.bendb.thrifty.compiler.SerializableTypeProcessor

Просмотреть файл

@ -3,6 +3,7 @@ description = 'Converts Thrifty Schemas into Java source files'
dependencies {
compile project(":thrifty-schema")
compile project(":thrifty-runtime")
compile project(":thrifty-compiler-plugins")
compile libraries.okio
compile libraries.javaPoet

Просмотреть файл

@ -30,6 +30,7 @@ import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import com.bendb.thrifty.compiler.spi.TypeProcessor;
import javax.lang.model.element.Modifier;
import java.io.File;

Просмотреть файл

@ -1,13 +0,0 @@
package com.bendb.thrifty.gen;
import com.squareup.javapoet.TypeSpec;
/**
* When specified as part of code generation, processes all types after they
* are computed, but before they are written to disk. This allows you to make
* arbitrary modifications to types such as implementing your own interfaces,
* renaming fields, or anything you might wish to do.
*/
public interface TypeProcessor {
TypeSpec process(TypeSpec type);
}