-
Notifications
You must be signed in to change notification settings - Fork 127
Taking control of code generation
SquiDB provides various options and APIs to give you a greater degree of control over the code generation process. There are compile-time flags you can set in your build.gradle to disable some default behaviors, or you can write plugins to generate additional code for an even greater degree of control.
SquiDB supports the following compile-time option flags to alter the default code generation behavior:
-
androidModels
: this option enables generating model classes with some additional Android-specific features, e.g., generated models will implementParcelable
. -
disableDefaultConstructors
: this option disables the four default constructors generated in each model class -
disableImplements
: this option disables processing of the@Implements
annotation -
disableModelMethod
: this option disables processing of the@ModelMethod
implementation, as well as the copying of instance and static methods from the model spec to the generated model class -
disableConstantCopying
: this option disables the copying of unhandled public static final fields from the model spec to the generated model as constants -
disableDefaultValues
: this option disables the in-memory default values used as for fallback values in empty models -
disableJavadoc
: this option disables the copying of Javadocs from model specs to the generated models -
disableEnumProperties
: this option disables support for columns using an enum data type -
disableGettersAndSetters
: this option disables the default getters and setters that are generated for each column in the model
Most users will not need to use these options unless they are specifically concerned with keeping their method count down or if they want to reimplement similar functionality using their own plugins. To use any of these option flags, you can declare them like so:
// Note: This example is for the Android Gradle plugin's built-in support for annotation processors,
// available as of plugin version 2.2.0. If using the deprecated android-apt plugin, see below
android {
...
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [
squidbPlugins : '<comma separated plugin list>',
squidbOptions : 'disableDefaultContentValues,disableGettersAndSetters'
]
}
}
}
}
If using the deprecated android-apt plugin, the configuration would look like this:
apt {
arguments {
// Use a comma-separated list
squidbOptions 'disableDefaultContentValues,disableGettersAndSetters'
squidbPlugins '<comma separated plugin list>'
}
}
Also see Adding user plugins to code generation for instructions on how to integrate plugins with the build process.
Users can also write plugins that add to the code generation process. Plugins are implemented as small jar files containing a subclass of Plugin
, and can add code before, during, or after any of the several phases that code generation goes through. Plugins are also given an opportunity to handle fields declared in model specs if they want to define custom logic for property generation.
Model generation is divided into several distinct phases:
- Class declaration (class name, extends, implements)
- Schema declaration
- Constructor declarations
- Method declarations
- SquiDB model helpers (for Parcelable, ViewModel mapping, etc.)
- Any additional code/helpers
The Plugin
class contains hooks allowing code to be added to the model before, during, or after these phases. Available hooks include:
-
addInterfacesToImplement
: for adding interfaces to the class definition -
beforeEmitSchema
: for generating code before the table schema declarations (constants often go here) afterEmitSchema
emitConstructors
-
beforeEmitMethods
,emitMethods
, andafterEmitMethods
-
emitAdditionalJava
: called as the final step before the class declaration is closed - See the Plugin class for all available hooks
Much of the core functionality of the code generator has been implemented as a collection of default plugins. See the classes in the plugins package for examples of how to implement plugins.
Plugins can also define custom handling for fields in a model spec, or add additional columns to the model that don't directly correspond to a field in the spec.
Plugins can add columns to models that don't correspond to a field in the spec itself using the afterProcessVariableElements
hook. For example, one could imagine that certain tables in a database would correspond to remote entities on a server, with their own remote guids. A plugin could define an additional annotation @Syncable
, and add a GUID
field to any model specs annotated with @Syncable
:
public class SyncablePlugin extends Plugin {
private final boolean isSyncable;
public SyncablePlugin(ModelSpec<?> modelSpec, PluginEnvironment pluginEnv) {
super(modelSpec, pluginEnv);
// Only makes sense for TableModels annotated with @Syncable
isSyncable = (modelSpec instanceof TableModelSpecWrapper) &&
modelSpec.getModelSpecElement().getAnnotation(Syncable.class) != null;
}
@Override
public boolean hasChangesForModelSpec() {
// Other plugin methods will only be called if this method returns true,
// so overriding it saves you from doing the checks in the other hooks
return isSyncable;
}
@Override
public void afterProcessVariableElements() {
// This will add the column to the model, along with getters and setters
modelSpec.addPropertyGenerator(
new BasicStringPropertyGenerator(modelSpec, "guid", utils));
}
@Override
public void emitMethods(JavaFileWriter writer) throws IOException {
// This emits a helper method "public boolean existsOnServer()", which
// delegates to the "containsNonNullValue method
MethodDeclarationParameters method = new MethodDeclarationParameters()
.setMethodName("existsOnServer")
.setModifiers(Modifier.PUBLIC)
.setReturnType(CoreTypes.PRIMITIVE_BOOLEAN);
writer.beginMethodDefinition(method);
writer.writeStatement(Expressions.returnExpr(
Expressions.callMethod("containsNonNullValue", "GUID")));
writer.finishMethodDefinition();
}
}
Plugins can also handle the VariableElements (i.e. fields) in model specs to extend the code generator's ability and handle more than just the basic primitive column types. For documentation on how to write such a plugin, see Writing plugins for custom data types.
Plugins may consist of a single jar, or (like SquiDB), they could consist of multiple modules--e.g. a core module, a set of annotations, and the compile-time code generator. You can apply code generator plugins using the same principle in your gradle file as you did for the SquiDB project itself:
dependencies {
compile 'com.yahoo.squidb:squidb:3.1.3' // SquiDB main project
compile 'com.yahoo.squidb:squidb-annotations:3.1.3' // SquiDB annotations project
annotationProcessor 'com.yahoo.squidb:squidb-processor:3.1.3' // SquiDB code generator
// Example using squidb-json addon; replace with your own plugin modules
compile project(':squidb-json-plugin') // JSON plugin code module
compile project(':squidb-json-annotations') // JSON plugin annotations
annotationProcessor project(':squidb-json-compiler') // JSON plugin compile-time code generator
}
The code generation component of the plugin (i.e. the class extending Plugin
) needs to be declared in the apt.arguments configuration of your build.gradle, using a comma separated list of fully qualified class names. If you want plugins to run in a particular order (or take precedence over default plugins), you can declare a priority of "high", "normal", or "low" on the plugins listed. Default is normal:
android {
...
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [
squidbPlugins : 'com.yahoo.squidb.json.JSONPlugin,com.example.MyPlugin:high,com.example.AnotherPlugin:low'
]
}
}
}
}
See also:
- Adding SquiDB as a dependency
- JSON support in SquiDB
- Writing plugins for custom data types
- User-created plugins: some code generation plugins created by the community of SquiDB users