aboutsummaryrefslogtreecommitdiff
path: root/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md
blob: da026a783d42f606c3d551f5d5b62f949573df44 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# 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<Serializer> 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<T> 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<Serializer> getSerializer(
        TypeMirror type,
        SerializerFactory factory,
        ProcessingEnvironment env) {
    if (!isBaz(type)) {
      return Optional.empty();
    }

    // Extract the T of Baz<T>.
    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/main/service
[`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
[`SerializerExtension`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java
[`Serializer`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java