Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.

Understanding AutoValue and its extensions

Hello there! My name is Rein Raudjärv and I am one of the engineers working in the JRebel for Android team here at ZeroTurnaround.

Some time ago, I started working on an AutoValue extension for reading and writing Java properties files. Here’s a rough plan of what I thought it should do:

  • Have a simple and reliable implementation.
  • Be similar or reuse the existing AutoValue extensions as much as possible.
  • Easily replace the custom code in our projects for reading/writing the .properties files.
  • Support obfuscated builds out of the box.

I didn’t get it to the stage where the code is ready to become public yet. However, we are open to that idea. I learned quite a bit about AutoValue and about how its extensions work. In this post, I want to share that knowledge with you.

We will look at what AutoValue is and how it can help you keep your code cleaner and less verbose. We’ll also explore how the AutoValue extension mechanism and the AutoValue Moshi extension works.

AutoValue

AutoValue is an annotation processor from Google for generating immutable Java classes in Java. A developer only needs to write a minimalistic abstract version of a data class:

import com.google.auto.value.AutoValue;

@AutoValue
abstract class Animal {
  static Animal create(String name, int numberOfLegs) {
    // See "How do I...?" below for nested classes.
    return new AutoValue_Animal(name, numberOfLegs);
  }

  abstract String name();
  abstract int numberOfLegs();
}

The actual implementation is a subclass that is generated at compilation time. It includes the fields corresponding to the abstract methods, a constructor, hashCode(), equals() and toString() implementations. The implementation class is generated at compile time, so no Reflection API is used at runtime. Even after obfuscating, the code toString() still includes the original field names in the output.

By default, all fields are not-nullable. The constructor throws a NullPointerException if any of the values are null. To mark a field as nullable, you can use any annotation with the name Nullable.

AutoValue is a great tool for making your code concise and clean.

Builders

To create an instance of the value class, you can use the static create() method. Still, sometimes invoking the create() method with multiple arguments gets inconvenient. In these cases, a fluent Builder class becomes handy. AutoValue also generates them automatically:

import com.google.auto.value.AutoValue;

@AutoValue
abstract class Animal {
  abstract String name();
  abstract int numberOfLegs();

  static Builder builder() {
    return new AutoValue_Animal.Builder();
  }

  @AutoValue.Builder
  abstract static class Builder {
    abstract Builder setName(String value);
    abstract Builder setNumberOfLegs(int value);
    abstract Animal build();
  }
}

The build() method also checks for nulls and can throw an IllegalStateException with a list of fields that have no value. This list also includes the primitive fields for which the set methods were not called.

IDE support

IntelliJ IDEA and Android Studio users also have an AutoValue plugin that generates or updates the create() method or the Builder class automatically.

My experience with IntelliJ (and Android Studio) regarding annotation processors is pretty bad:

  • When adding a new @AutoValue class, I need to manually compile the classes so it would generate the sub classes.
  • When renaming an @AutoValue class, I need to clean the project so it wouldn’t complain about the old class which does not compile anymore (this is solved by using JRebel for Android with its incremental compiler).

I hope the development experience gets improved in the future IDE versions.

Extensions

AutoValue has a nice way of extending the behavior of the generated classes: AutoValue extensions. Ryan Harter has provided a great overview of that, including a list of available extensions. He is also the author of multiple extensions: auto-value-parcel, auto-value-gson and auto-value-moshi.

The main idea behind the extensions is to supply the generated classes with the integrations with the libraries and frameworks you might want to use with the data classes, like serialization libraries for example.

In short, the extensions work by generating a subclass of the generated AutoValue class. When several extensions need to work on a single class, they generate a chain of subclasses, implementing the interface you marked with @AutoValue.

Here is an illustration of that, taken from Ryan’s article I mentioned above:

You use the most specific implementation and get all the functionality added by each extension through the Java inheritance mechanism.

The extension API is good, but at the moment it lacks the support for Builder classes. This is required for correctly handling custom default values.

Moshi

Before we talk about the auto-value-moshi extension, it’s good to know what it is supposed to do. Moshi is a modern JSON library from Square (for both Android and Java platforms). It uses the same streaming and binding mechanisms as Gson, but it is faster and simpler, as well as more robust.

The core class in Moshi for mapping a Java object to JSON and vice versa is JsonAdapter:

public abstract class JsonAdapter<T> {
  public abstract T fromJson(JsonReader reader)
  public abstract void toJson(JsonWriter writer, T value);
  ...
  public interface Factory {
    /**
     * Attempts to create an adapter for {@code type} annotated with {@code annotations}. This
     * returns the adapter if one was created, or null if this factory isn't capable of creating
     * such an adapter.
     *
     * <p>Implementations may use to {@link Moshi#adapter} to compose adapters of other types, or
     * {@link Moshi#nextAdapter} to delegate to the underlying adapter of the same type.
     */
    JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi);
  }
}

The create() methods accept a Type instead of a Class to also support generics. This is a must for collections as the adapters are selected only by types, not the actual runtime values. A set of annotations are passed to add qualifiers to the types, for example we could create @IgnoreCase for enums to parse them, ignoring their case. The Moshi object here is for looking up other adapters. It’s required for any composite types: collections and Java classes to find the corresponding adapters for their members.

AutoValue Moshi extension

Moshi works with POJOs out of the box via the Reflection API. So it might be a bit slower in terms of performance. Also, the field names cannot be obfuscated. Developers could write custom adapters for their Java types to fix this, but it’s a boilerplate and a waste of time. That’s what the AutoValue Moshi extension does for us. It generates an adapter for each AutoValue class that uses plain Java without the reflection (with some exceptions). We only need to add one static method for each @AutoValue class:

@AutoValue public abstract class Foo {
  abstract String bar();
  @Json(name="Baz") abstract String baz();

  public static JsonAdapter<Foo> jsonAdapter(Moshi moshi) {
    return new AutoValue_Foo.MoshiJsonAdapter(moshi);
  }
}

We could add those adapters to a Moshi object one by one, but it’s easy to forget some. There is a more convenient option to let it also generate a factory for us that works for any number of AutoValue classes we have in the current module:

@MoshiAdapterFactory
public abstract class MyAdapterFactory implements JsonAdapter.Factory {
  // Static factory method to access the package 
  // private generated implementation
  public static JsonAdapter.Factory create() {
    return new AutoValueMoshi_MyAdapterFactory();
  }
}

So the extension actually has two compile time processors:

Both see the class structures via javax.lang.model APIs and write the generated source code using JavaPoet, another library from Square for writing Java source files. It’s a successor to a former JavaWriter library. If you feel like you need more information about the annotation processors, here is a wonderful article about annotation processors written by Hannes Dorfmann.

A design draft for converting the .properties files

Now let’s take a look at what one needs to do to create an extension for AutoValue. Remember, initially I wanted a way to instantiate AutoValue classes based on the property files.

Every AutoValue class would need to have an adapter to read and write property files:

interface PropsAdapter<T> {
  void write(PropsWriter out, T value);
  T read(PropsReader in);
}

interface PropsWriter {
  <T> void setProperty(String key, Class<T> type, T value);
}

interface PropsReader {
  <T> T getProperty(String key, Class<T> type);
}

It sounds quite easy to implement. Just go over the AutoValue classes, pick the ones that have a static method for the PropsAdapter declared and generate the code for serializing the instances, field by field, into a properties file.

public static PropsAdapter<Foo> propsAdapter() {
  return new AutoValue_Foo.PropsAdapter();
}  

In fact, some problems exist which we perhaps explore in the second part of this blog post, when the extension code is publicly available to talk about.

The first problem you will encounter is the type safety: you want the code to be type-safe about the properties and handle generic collections well. Collections are hard anyway because properties files don’t have the built-in syntax for the collections. Meaning that not only do you need to agree on that, the extension should also somehow make it configurable.

Then you’ll encounter another complication with Java primitives. AutoValue’s approach is that the primitive values are always required. However, during deserialization, Moshi and Gson do not require them. They simply skip setting those fields, leaving them with the default values like 0 or false when they are not found in the JSON input. Since AutoValue doesn’t let you skip setting any required fields, the corresponding extensions explicitly set the fields as 0 or false in that case. This is somewhat in violation of AutoValue’s own logic.

Not requiring additional ProGuard rules would be amazing. I mentioned that AutoValue generates code at compile time, so no Reflection API is used at runtime. auto-value-moshi actually breaks this rule when annotating a property with an annotation that is a @JsonQualifier. It looks up those annotations at runtime by an original method name, stored as a String so ProGuard doesn’t replace that. In that case, users still need custom obfuscation rules.

All in all, it’s not an easy problem. In the next blog post I will try to concentrate on how I handled these questions and with which design choices.

Summary

I wanted to create an AutoValue extension for converting .properties files based on the AutoValue Moshi extension.

During this exercise I learned a bit about AutoValue, its extensions, generating Java source code with JavaPoet, and pondered about issues with serialization.

In this post, we looked at the base knowledge required to understand and implement an AutoValue extension. Soon enough, we can hopefully also look at the source code for the AutoValue properties extension!