com.google.auto.value.extension.serializable.userguide.serializer-extension.md Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of auto-value Show documentation
Show all versions of auto-value Show documentation
Immutable value-type code generation for Java 1.6+.
# 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