The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Code Generation

One common thing to do in a Gradle plugin is to generate Java code. Code that writes other code based upon supplied inputs is referred to as a “code generator”.

At its core, Java code is just text. Developers generate text on the fly quite a bit, particularly in Web development. There are two main approaches to such text generation:

  1. Use templates, when the text is largely fixed but has some values that need to be replaced at runtime
  2. Use regular code to generate the text, perhaps using APIs that facilitate writing out text formats with as little coding overhead as possible

In this chapter, we will examine what it takes to generate Java code from a Gradle plugin. In particular, we will look at the second approach, where we will have Java code that generates other Java code, using a library that makes this a lot easier than writing a lot of append() calls to a StringBuilder.

Prerequisites

Understanding this chapter requires that you have read the preceding chapter and all of its prerequisites.

What Drives the Custom Code?

You could generate code based off of random numbers. More likely, there is some sort of input that you are using to determine the code to be generated. This input can be divided roughly into three areas: other Java code, other project files, and everything else.

Other Java Code

Modern Android code gets littered with annotations. Some are from standard Java (e.g., @Override). Some are from Android libraries (e.g., @NotNull). Some are from third-party libraries, like greenrobot’s EventBus (e.g., @Subscribe).

Sometimes, these annotations are for validation at compile time. @Override tells Java compilers that we think that we are overriding a method from a superclass or are implementing an interface method, and so the compile should fail if our method signature does not match anything that we could be overriding. @NotNull indicates that a parameter should not be null, allowing static code analysis to help point out places where we might accidentally pass null in as a value.

Sometimes, these annotations are used by third-party libraries at runtime. greenrobot’s EventBus, for example, will look for @Subscribe annotations to determine how to deliver events to registered event-handling objects.

Sometimes, these runtime annotations generate something resembling code at runtime. Retrofit, for example, creates an instance of our service interface at runtime, with an implementation that will make the Web service requests that we desire. However, this Java bytecode is generated on devices, not as ordinary Java source files that are included in the build.

But, sometimes, these annotations get used by compile-time annotation processors. These add-ons to the build process read in Java code, analyze it (annotations in particular), and code generate Java code in support of our existing Java code. Google’s AutoValue processor, for example, looks for @AutoValue-annotated abstract classes and code-generates a concrete implementation of a “value class” (one with immutable members), complete with hashCode(), equals(), and related methods.

Other Project Files

A code generator might generate Java code from other types of input that are in the project:

Here, the inputs are resources or other files, not annotated Java code.

Other Data Sources

There is nothing stopping you from generating code based on inputs that come from outside of the project. For example, whereas SQLDelight works off of SQL scripts in your project tree, you can imagine a similar code generator that retrieved schemas from a live database using database I/O.

The big advantage of limiting code generation to files in the project is having reproducible builds based on version control. Ideally, years from now, you should be able to check out a branch or tag from a version control system and be able to build the project the same way then as you do on the day you committed that code to version control. That works best when the entire project specification is included in version control. A server is not, and the server response in a few years might differ from what the server response is today.

One workaround for this is to have two tools. One retrieves the data from the server and writes that data out into files that go into your project (e.g., JSON files). You would run this code from time to time, when you specifically need to get the latest configuration from the server. However, the second tool performs the code generation, working off of the project files, not from the live server data. That way, you can perform that code generation again in the future in a reproducible fashion.

Java as Poetry

The preview of this section was the victim of a MITM ('Martian in the middle') attack.

Writing a Code Generation Plugin

The preview of this section is in an invisible, microscopic font.

Using the Generated Code

The preview of this section was the victim of a MITM ('Martian in the middle') attack.