All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.auto.value.extension.serializable.g3doc.serializer-extension.md Maven / Gradle / Ivy

There is a newer version: 1.11.0
Show newest version
# Serializer Extensions


[`SerializableAutoValueExtension`] can be extended to support additional
un-serializable types. Extensions are created by implementing
[`SerializerExtension`].

[TOC]

## Using SerializerExtensions

To use an extension that's not bundled with [`SerializableAutoValueExtension`],
include it in your `AutoValue` class's dependencies.

## Writing a SerializerExtension

To add serialization support to a new type, write a class that extends
[`SerializerExtension`]. Put that class on the `processorpath` along with
`SerializerExtension`.

See [`AutoService`] for Java extension loading information.

### How SerializerExtension Works

`SerializableAutoValueExtension` iterates through each property of the
`@AutoValue` class and tries to look up a `SerializerExtension` for the
property's type. If a `SerializerExtension` is available, a [`Serializer`] is
returned. The `Serializer` does three things:

1.  From the original property's type, determine what a suitable serializable
    type is.
2.  Generate an expression that maps the original property's value to the proxy
    value.
3.  Generate an expression that maps a proxy value back to the original
    property's value.

### Example

We have a `Foo` AutoValue class that we would like to serialize:

```java
@SerializableAutoValue
@AutoValue
public abstract class Foo implements Serializable {

  public static Foo create(Bar bar) {
    return new AutoValue_Foo(bar);
  }

  // Bar is unserializable.
  abstract Bar bar();
}

// An un-serializable class.
public final class Bar {

  public int x;

  public Bar(int x) {
    this.x = x;
  }
}
```

Unfortunately, `Bar` is un-serializable, which makes the entire class
un-serializable. We can extend `SerializableAutoValue` to support `Bar` by
implementing a `SerializerExtension` for `Bar`.

```java
// Use AutoService to make BarSerializerExtension discoverable by java.util.ServiceLoader.
@AutoService(SerializerExtension.class)
public final class BarSerializerExtension implements SerializerExtension {

  // Service providers must have a public constructor with no formal parameters.
  public BarSerializerExtension() {}

  // This method is called for each property in an AutoValue.
  // When this extension can handle a type (i.e. Bar), we return a Serializer.
  called on all SerializerExtensions.
  @Override
  public Optional getSerializer(
      TypeMirror type,
      SerializerFactory factory,
      ProcessingEnvironment env) {
    // BarSerializerExtension only handles Bar types.
    if (!isBar(type)) {
      return Optional.empty();
    }

    return Optional.of(new BarSerializer(env));
  }

  // Our implementation of how Bar should be serialized + de-serialized.
  private static class BarSerializer implements Serializer {

    private final ProcessingEnvironment env;

    BarSerializer(ProcessingEnvironment env) {
      this.env = env;
    }

    // One way to serialize Bar is to just serialize Bar.x.
    // We can do that by mapping Bar to an int.
    @Override
    public TypeMirror proxyFieldType() {
      return Types.getPrimitiveType(TypeKind.INT);
    }

    // We need to map Bar to the type we declared in {@link #proxyFieldType}.
    // Note that {@code expression} is a variable of type Bar.
    @Override
    public CodeBlock toProxy(CodeBlock expression) {
      return CodeBlock.of("$L.x", expression);
    }

    // We need to map the integer back to a Bar.
    @Override
    public CodeBlock fromProxy(CodeBlock expression) {
      return CodeBlock.of("new $T($L)", Bar.class, expression);
    }
  }
}
```

`BarSerializerExtension` enables AutoValue classes with `Bar` properties to be
serialized. For our example class `Foo`, it would help `SerializableAutoValue`
generate the following code:

```java
@Generated("SerializableAutoValueExtension")
final class AutoValue_Foo extends $AutoValue_Foo {

  Object writeReplace() throws ObjectStreamException {
    return new Proxy$(this.x);
  }

  static class Proxy$ implements Serializable {

    // The type is generated by {@code BarSerializer#proxyFieldType}.
    private int bar;

    Proxy$(Bar bar) {
      // The assignment expression is generated by {@code BarSerializer#toProxy}.
      this.bar = bar.x;
    }

    Object readResolve() throws ObjectStreamException {
      // The reverse mapping expression is generated by {@code BarSerializer#fromProxy}.
      return new AutoValue_Foo(new Bar(bar));
    }
  }
}
```

### Type Parameters

Objects with type parameters are also supported by `SerializerExtension`.

For example:

```java
// A potentially un-serializable class, depending on the actual type of T.
public final class Baz implements Serializable {

  public T x;

  public Baz(int x) {
    this.x = x;
  }
}
```

`Baz`'s type argument `T` may not be serializable, but we could create a
`SerializerExtension` that supports `Baz` by asking for a `SerializerExtension`
for `T`.

```java
@AutoService(SerializerExtension.class)
public final class BazSerializerExtension implements SerializerExtension {

  public BazSerializerExtension() {}

  @Override
  public Optional getSerializer(
        TypeMirror type,
        SerializerFactory factory,
        ProcessingEnvironment env) {
    if (!isBaz(type)) {
      return Optional.empty();
    }

    // Extract the T of Baz.
    TypeMirror containedType = getContainedType(type);

    // Look up a serializer for the contained type T.
    Serializer containedTypeSerializer = factory.getSerializer(containedType);

    // If the serializer for the contained type T is an identity function, it
    // means the contained type is either serializable or unsupported.
    // Either way, nothing needs to be done. Baz can be serialized as is.
    if (containedTypeSerializer.isIdentity()) {
      return Optional.empty();
    }

    // Make Baz serializable by using the contained type T serializer.
    return Optional.of(new BazSerializer(containedTypeSerializer));
  }

  private static class BazSerializer implements Serializer {

    private Serializer serializer;

    BazSerializer(Serializer serialize) {
      this.serializer = serializer;
    }

    @Override
    public TypeMirror proxyFieldType() {
      // Since the contained type "T" is Baz's only field, we map Baz to "T"'s
      // proxy type.
      return serializer.proxyFieldType();
    }

    @Override
    public CodeBlock toProxy(CodeBlock expression) {
      return serializer.toProxy(expression);
    }

    @Override
    public CodeBlock fromProxy(CodeBlock expression) {
      return serializer.fromProxy(expression);
    }
  }
}
```

This implementation uses `SerializerFactory` to find a `Serializer` for `T`. If
a `Serializer` is available, we use it to map `Baz` to a serializable type. If
no `Serializer` is available, we can do nothing and let `Baz` be serialized
as-is.

[AutoService]: https://github.com/google/auto/tree/master/service
[`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
[`SerializerExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java
[`Serializer`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java




© 2015 - 2024 Weber Informatics LLC | Privacy Policy