Skip to content
This repository has been archived by the owner on May 1, 2020. It is now read-only.

Taking control of code generation

Sam Bosley edited this page Oct 28, 2016 · 9 revisions

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.

Compile-time options

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 implement Parcelable.
  • 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.

Writing code generation plugins

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.

Available hooks

Model generation is divided into several distinct phases:

  1. Class declaration (class name, extends, implements)
  2. Schema declaration
  3. Constructor declarations
  4. Method declarations
  5. SquiDB model helpers (for Parcelable, ViewModel mapping, etc.)
  6. 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, and afterEmitMethods
  • emitAdditionalJava: called as the final step before the class declaration is closed
  • See the Plugin class for all available hooks

Examples

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.

Handling custom fields in the model spec

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.

Adding additional columns

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();
    }
}

Handling custom field types

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.

Adding user plugins to code generation

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: