aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralvinlao <alvinlao@google.com>2020-04-01 10:21:31 -0700
committerChris Povirk <beigetangerine@gmail.com>2020-04-10 11:30:09 -0400
commitf91d2fef64ffb10c0bf394c56e72b9c55754cf6d (patch)
treef67105c14371275e275a420559c2a8d3bd69e26e
parente4ab0e7a0d4a1505b8b83104231f35f5e45ccd64 (diff)
downloadauto-f91d2fef64ffb10c0bf394c56e72b9c55754cf6d.tar.gz
Release the SerializableAutoValue extension.
RELNOTES=Release the SerializableAutoValue extension. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=304211473
-rw-r--r--value/annotations/pom.xml1
-rw-r--r--value/pom.xml2
-rw-r--r--value/processor/pom.xml8
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java30
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/processor/ClassNames.java24
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/processor/PropertyMirror.java56
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java310
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java60
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactory.java60
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java120
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java166
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtension.java106
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java48
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java40
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java51
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java29
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/serializer/runtime/FunctionWithExceptions.java41
-rw-r--r--value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java4
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java387
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoaderTest.java39
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactoryTest.java70
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtensionTest.java122
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtensionTest.java140
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtensionTest.java98
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImplTest.java53
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java82
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java75
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/TestStringSerializerFactory.java77
28 files changed, 2296 insertions, 3 deletions
diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml
index c284f2f0..d0d4a6bb 100644
--- a/value/annotations/pom.xml
+++ b/value/annotations/pom.xml
@@ -53,6 +53,7 @@
<includes>
<include>com/google/auto/value/*</include>
<include>com/google/auto/value/extension/memoized/*</include>
+ <include>com/google/auto/value/extension/serializable/*</include>
</includes>
</configuration>
</plugin>
diff --git a/value/pom.xml b/value/pom.xml
index 8c36feec..ffcd9d74 100644
--- a/value/pom.xml
+++ b/value/pom.xml
@@ -122,7 +122,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.12</version>
+ <version>4.13</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
diff --git a/value/processor/pom.xml b/value/processor/pom.xml
index da1c52a5..0aa9ec8e 100644
--- a/value/processor/pom.xml
+++ b/value/processor/pom.xml
@@ -119,6 +119,12 @@
<artifactId>compile-testing</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>3.1.0</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -141,6 +147,8 @@
<includes>
<include>com/google/auto/value/processor/**/*.java</include>
<include>com/google/auto/value/extension/memoized/processor/**/*.java</include>
+ <include>com/google/auto/value/extension/serializable/processor/**/*.java</include>
+ <include>com/google/auto/value/extension/serializable/serializer/**/*.java</include>
</includes>
</configuration>
</plugin>
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java b/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java
new file mode 100644
index 00000000..b100e793
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates {@link com.google.auto.value.AutoValue @AutoValue} classes that implement {@link
+ * java.io.Serializable}. A serializable subclass is generated for classes with normally
+ * un-serializable fields like {@link java.util.Optional}.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface SerializableAutoValue {}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/processor/ClassNames.java b/value/src/main/java/com/google/auto/value/extension/serializable/processor/ClassNames.java
new file mode 100644
index 00000000..96a95762
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/ClassNames.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.processor;
+
+/** Names of classes that are referenced in /processor. */
+final class ClassNames {
+ static final String SERIALIZABLE_AUTO_VALUE_NAME =
+ "com.google.auto.value.extension.serializable.SerializableAutoValue";
+
+ private ClassNames() {}
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/processor/PropertyMirror.java b/value/src/main/java/com/google/auto/value/extension/serializable/processor/PropertyMirror.java
new file mode 100644
index 00000000..966ce44a
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/PropertyMirror.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.processor;
+
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A POJO containing information about an AutoValue's property.
+ *
+ * <p>For example, given this AutoValue property: <code>abstract T getX();</code>
+ *
+ * <ol>
+ * <li>The type would be T.
+ * <li>The name would be x.
+ * <li>The method would be getX.
+ */
+final class PropertyMirror {
+
+ private final TypeMirror type;
+ private final String name;
+ private final String method;
+
+ PropertyMirror(TypeMirror type, String name, String method) {
+ this.type = type;
+ this.name = name;
+ this.method = method;
+ }
+
+ /** Gets the AutoValue property's type. */
+ TypeMirror getType() {
+ return type;
+ }
+
+ /** Gets the AutoValue property's name. */
+ String getName() {
+ return name;
+ }
+
+ /** Gets the AutoValue property accessor method. */
+ String getMethod() {
+ return method;
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java
new file mode 100644
index 00000000..e8cfc270
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.processor;
+
+import static com.google.auto.value.extension.serializable.processor.ClassNames.SERIALIZABLE_AUTO_VALUE_NAME;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static java.util.stream.Collectors.joining;
+
+import com.google.auto.common.GeneratedAnnotationSpecs;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.auto.value.extension.AutoValueExtension.Context;
+import com.google.auto.value.extension.serializable.serializer.SerializerFactoryLoader;
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.TypeVariableName;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Optional;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * An AutoValue extension that enables classes with unserializable fields to be serializable.
+ *
+ * <p>For this extension to work:
+ *
+ * <ul>
+ * <li>The AutoValue class must implement {@link Serializable}.
+ * <li>Unserializable fields in the AutoValue class must be supported by a {@link
+ * com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension}.
+ */
+@AutoService(AutoValueExtension.class)
+public final class SerializableAutoValueExtension extends AutoValueExtension {
+
+ @Override
+ public boolean applicable(Context context) {
+ return hasSerializableInterface(context) && hasSerializableAutoValueAnnotation(context);
+ }
+
+ @Override
+ public String generateClass(
+ Context context, String className, String classToExtend, boolean isFinal) {
+ return new Generator(context, className, classToExtend, isFinal).generate();
+ }
+
+ private static final class Generator {
+ private final Context context;
+ private final String className;
+ private final String classToExtend;
+ private final boolean isFinal;
+ private final ImmutableList<PropertyMirror> propertyMirrors;
+ private final ImmutableList<TypeVariableName> typeVariableNames;
+ private final ProxyGenerator proxyGenerator;
+
+ Generator(Context context, String className, String classToExtend, boolean isFinal) {
+ this.context = context;
+ this.className = className;
+ this.classToExtend = classToExtend;
+ this.isFinal = isFinal;
+
+ this.propertyMirrors =
+ context.propertyTypes().entrySet().stream()
+ .map(
+ entry ->
+ new PropertyMirror(
+ /* type= */ entry.getValue(),
+ /* name= */ entry.getKey(),
+ /* method= */ context
+ .properties()
+ .get(entry.getKey())
+ .getSimpleName()
+ .toString()))
+ .collect(toImmutableList());
+ this.typeVariableNames =
+ context.autoValueClass().getTypeParameters().stream()
+ .map(TypeVariableName::get)
+ .collect(toImmutableList());
+
+ TypeName classTypeName =
+ getClassTypeName(ClassName.get(context.packageName(), className), typeVariableNames);
+ this.proxyGenerator =
+ new ProxyGenerator(
+ classTypeName, typeVariableNames, propertyMirrors, buildSerializersMap());
+ }
+
+ private String generate() {
+ ClassName superclass = ClassName.get(context.packageName(), classToExtend);
+ Optional<AnnotationSpec> generatedAnnotationSpec =
+ GeneratedAnnotationSpecs.generatedAnnotationSpec(
+ context.processingEnvironment().getElementUtils(),
+ context.processingEnvironment().getSourceVersion(),
+ SerializableAutoValueExtension.class);
+
+ TypeSpec.Builder subclass =
+ TypeSpec.classBuilder(className)
+ .superclass(getClassTypeName(superclass, typeVariableNames))
+ .addTypeVariables(typeVariableNames)
+ .addModifiers(isFinal ? Modifier.FINAL : Modifier.ABSTRACT)
+ .addMethod(constructor())
+ .addMethod(writeReplace())
+ .addType(proxyGenerator.generate());
+ generatedAnnotationSpec.ifPresent(subclass::addAnnotation);
+
+ return JavaFile.builder(context.packageName(), subclass.build()).build().toString();
+ }
+
+ /** Creates a constructor that calls super with all the AutoValue fields. */
+ private MethodSpec constructor() {
+ MethodSpec.Builder constructor =
+ MethodSpec.constructorBuilder()
+ .addStatement(
+ "super($L)",
+ propertyMirrors.stream().map(PropertyMirror::getName).collect(joining(", ")));
+
+ for (PropertyMirror propertyMirror : propertyMirrors) {
+ constructor.addParameter(TypeName.get(propertyMirror.getType()), propertyMirror.getName());
+ }
+
+ return constructor.build();
+ }
+
+ /**
+ * Creates an implementation of writeReplace that delegates serialization to its inner Proxy
+ * class.
+ */
+ private MethodSpec writeReplace() {
+ ImmutableList<CodeBlock> properties =
+ propertyMirrors.stream()
+ .map(propertyMirror -> CodeBlock.of("$L()", propertyMirror.getMethod()))
+ .collect(toImmutableList());
+
+ return MethodSpec.methodBuilder("writeReplace")
+ .returns(Object.class)
+ .addStatement(
+ "return new $T($L)",
+ getClassTypeName(
+ ClassName.get(
+ context.packageName(),
+ className,
+ SerializableAutoValueExtension.ProxyGenerator.PROXY_CLASS_NAME),
+ typeVariableNames),
+ CodeBlock.join(properties, ", "))
+ .build();
+ }
+
+ private ImmutableMap<TypeMirror, Serializer> buildSerializersMap() {
+ SerializerFactory factory =
+ SerializerFactoryLoader.getFactory(context.processingEnvironment());
+ return propertyMirrors.stream()
+ .collect(
+ toImmutableMap(
+ PropertyMirror::getType,
+ propertyMirror -> factory.getSerializer(propertyMirror.getType())));
+ }
+
+ /** Adds type parameters to the given {@link ClassName}, if available. */
+ private static TypeName getClassTypeName(
+ ClassName className, List<TypeVariableName> typeVariableNames) {
+ return typeVariableNames.isEmpty()
+ ? className
+ : ParameterizedTypeName.get(className, typeVariableNames.toArray(new TypeName[] {}));
+ }
+ }
+
+ /** A generator of nested serializable Proxy classes. */
+ private static final class ProxyGenerator {
+ private static final String PROXY_CLASS_NAME = "Proxy$";
+
+ private final TypeName outerClassTypeName;
+ private final ImmutableList<TypeVariableName> typeVariableNames;
+ private final ImmutableList<PropertyMirror> propertyMirrors;
+ private final ImmutableMap<TypeMirror, Serializer> serializersMap;
+
+ ProxyGenerator(
+ TypeName outerClassTypeName,
+ ImmutableList<TypeVariableName> typeVariableNames,
+ ImmutableList<PropertyMirror> propertyMirrors,
+ ImmutableMap<TypeMirror, Serializer> serializersMap) {
+ this.outerClassTypeName = outerClassTypeName;
+ this.typeVariableNames = typeVariableNames;
+ this.propertyMirrors = propertyMirrors;
+ this.serializersMap = serializersMap;
+ }
+
+ private TypeSpec generate() {
+ TypeSpec.Builder proxy =
+ TypeSpec.classBuilder(PROXY_CLASS_NAME)
+ .addModifiers(Modifier.STATIC)
+ .addTypeVariables(typeVariableNames)
+ .addSuperinterface(Serializable.class)
+ .addField(serialVersionUid())
+ .addFields(properties())
+ .addMethod(constructor())
+ .addMethod(readResolve());
+
+ return proxy.build();
+ }
+
+ private static FieldSpec serialVersionUid() {
+ return FieldSpec.builder(
+ long.class, "serialVersionUID", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+ .initializer("0")
+ .build();
+ }
+
+ /** Maps each AutoValue property to a serializable type. */
+ private List<FieldSpec> properties() {
+ return propertyMirrors.stream()
+ .map(
+ propertyMirror ->
+ FieldSpec.builder(
+ TypeName.get(
+ serializersMap.get(propertyMirror.getType()).proxyFieldType()),
+ propertyMirror.getName(),
+ Modifier.PRIVATE)
+ .build())
+ .collect(toImmutableList());
+ }
+
+ /** Creates a constructor that converts the AutoValue's properties to serializable values. */
+ private MethodSpec constructor() {
+ MethodSpec.Builder constructor = MethodSpec.constructorBuilder();
+
+ for (PropertyMirror propertyMirror : propertyMirrors) {
+ Serializer serializer = serializersMap.get(propertyMirror.getType());
+ String name = propertyMirror.getName();
+
+ constructor.addParameter(TypeName.get(propertyMirror.getType()), name);
+ constructor.addStatement(
+ CodeBlock.of("this.$L = $L", name, serializer.toProxy(CodeBlock.of(name))));
+ }
+
+ return constructor.build();
+ }
+
+ /**
+ * Creates an implementation of {@code readResolve} that returns the serializable values in the
+ * Proxy object back to their original types.
+ */
+ private MethodSpec readResolve() {
+ return MethodSpec.methodBuilder("readResolve")
+ .returns(Object.class)
+ .addException(Exception.class)
+ .addStatement(
+ "return new $T($L)",
+ outerClassTypeName,
+ CodeBlock.join(
+ propertyMirrors.stream().map(this::resolve).collect(toImmutableList()), ", "))
+ .build();
+ }
+
+ /** Maps a serializable type back to its original AutoValue property. */
+ private CodeBlock resolve(PropertyMirror propertyMirror) {
+ return serializersMap
+ .get(propertyMirror.getType())
+ .fromProxy(CodeBlock.of(propertyMirror.getName()));
+ }
+ }
+
+ private static boolean hasSerializableInterface(Context context) {
+ final TypeMirror serializableTypeMirror =
+ context
+ .processingEnvironment()
+ .getElementUtils()
+ .getTypeElement(Serializable.class.getCanonicalName())
+ .asType();
+
+ return context
+ .processingEnvironment()
+ .getTypeUtils()
+ .isAssignable(context.autoValueClass().asType(), serializableTypeMirror);
+ }
+
+ private static boolean hasSerializableAutoValueAnnotation(Context context) {
+ return context.autoValueClass().getAnnotationMirrors().stream()
+ .map(AnnotationMirror::getAnnotationType)
+ .map(MoreTypes::asTypeElement)
+ .map(TypeElement::getQualifiedName)
+ .anyMatch(name -> name.contentEquals(SERIALIZABLE_AUTO_VALUE_NAME));
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java
new file mode 100644
index 00000000..12984b05
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer;
+
+import com.google.auto.value.extension.serializable.serializer.impl.SerializerFactoryImpl;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
+import com.google.auto.value.processor.SimpleServiceLoader;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.tools.Diagnostic;
+
+/**
+ * Builds a {@link SerializerFactory} populated with discovered {@link SerializerExtension}
+ * instances.
+ */
+public final class SerializerFactoryLoader {
+
+ /**
+ * Returns a {@link SerializerFactory} with {@link SerializerExtension} instances provided by the
+ * {@link java.util.ServiceLoader}.
+ */
+ public static SerializerFactory getFactory(ProcessingEnvironment processingEnv) {
+ return new SerializerFactoryImpl(loadExtensions(processingEnv), processingEnv);
+ }
+
+ private static ImmutableList<SerializerExtension> loadExtensions(
+ ProcessingEnvironment processingEnv) {
+ try {
+ return ImmutableList.copyOf(
+ SimpleServiceLoader.load(
+ SerializerExtension.class, SerializerFactoryLoader.class.getClassLoader()));
+ } catch (Throwable t) {
+ processingEnv
+ .getMessager()
+ .printMessage(
+ Diagnostic.Kind.ERROR,
+ "An exception occurred while looking for SerializerExtensions. No extensions will"
+ + " function.\n"
+ + Throwables.getStackTraceAsString(t));
+ return ImmutableList.of();
+ }
+ }
+
+ private SerializerFactoryLoader() {}
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactory.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactory.java
new file mode 100644
index 00000000..83b86341
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.squareup.javapoet.CodeBlock;
+import javax.lang.model.type.TypeMirror;
+
+/** Creates identity {@link Serializer} instances. */
+public final class IdentitySerializerFactory {
+
+ /** Returns a {@link Serializer} that leaves the type as is. */
+ public static Serializer getSerializer(TypeMirror typeMirror) {
+ return new IdentitySerializer(typeMirror);
+ }
+
+ private static class IdentitySerializer implements Serializer {
+
+ private final TypeMirror typeMirror;
+
+ IdentitySerializer(TypeMirror typeMirror) {
+ this.typeMirror = typeMirror;
+ }
+
+ @Override
+ public TypeMirror proxyFieldType() {
+ return typeMirror;
+ }
+
+ @Override
+ public CodeBlock toProxy(CodeBlock expression) {
+ return expression;
+ }
+
+ @Override
+ public CodeBlock fromProxy(CodeBlock expression) {
+ return expression;
+ }
+
+ @Override
+ public boolean isIdentity() {
+ return true;
+ }
+ }
+
+ private IdentitySerializerFactory() {}
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java
new file mode 100644
index 00000000..7ff4f19d
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
+import com.google.auto.value.extension.serializable.serializer.runtime.FunctionWithExceptions;
+import com.google.common.collect.ImmutableList;
+import com.squareup.javapoet.CodeBlock;
+import java.util.Optional;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A {@link SerializerExtension} that deserializes objects inside an {@link ImmutableList}.
+ *
+ * <p>Enables unserializable objects inside an ImmutableList to be serializable.
+ */
+@AutoService(SerializerExtension.class)
+public final class ImmutableListSerializerExtension implements SerializerExtension {
+
+ public ImmutableListSerializerExtension() {}
+
+ @Override
+ public Optional<Serializer> getSerializer(
+ TypeMirror typeMirror, SerializerFactory factory, ProcessingEnvironment processingEnv) {
+ if (!isImmutableList(typeMirror)) {
+ return Optional.empty();
+ }
+
+ // Extract the T of ImmutableList<T>.
+ TypeMirror containedType = getContainedType(typeMirror);
+ Serializer containedTypeSerializer = factory.getSerializer(containedType);
+
+ // We don't need this serializer if the T of ImmutableList<T> is serializable.
+ if (containedTypeSerializer.isIdentity()) {
+ return Optional.empty();
+ }
+
+ return Optional.of(new ImmutableListSerializer(containedTypeSerializer, processingEnv));
+ }
+
+ private static class ImmutableListSerializer implements Serializer {
+
+ private final Serializer containedTypeSerializer;
+ private final ProcessingEnvironment processingEnv;
+
+ ImmutableListSerializer(
+ Serializer containedTypeSerializer, ProcessingEnvironment processingEnv) {
+ this.containedTypeSerializer = containedTypeSerializer;
+ this.processingEnv = processingEnv;
+ }
+
+ @Override
+ public TypeMirror proxyFieldType() {
+ TypeElement immutableListTypeElement =
+ processingEnv.getElementUtils().getTypeElement(ImmutableList.class.getCanonicalName());
+ TypeMirror containedProxyType = containedTypeSerializer.proxyFieldType();
+ return processingEnv
+ .getTypeUtils()
+ .getDeclaredType(immutableListTypeElement, containedProxyType);
+ }
+
+ @Override
+ public CodeBlock toProxy(CodeBlock expression) {
+ CodeBlock element = CodeBlock.of("value$$");
+ return CodeBlock.of(
+ "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())",
+ expression,
+ FunctionWithExceptions.class,
+ element,
+ containedTypeSerializer.toProxy(element),
+ ImmutableList.class);
+ }
+
+ @Override
+ public CodeBlock fromProxy(CodeBlock expression) {
+ CodeBlock element = CodeBlock.of("value$$");
+ return CodeBlock.of(
+ "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())",
+ expression,
+ FunctionWithExceptions.class,
+ element,
+ containedTypeSerializer.fromProxy(element),
+ ImmutableList.class);
+ }
+ }
+
+ private static boolean isImmutableList(TypeMirror type) {
+ if (type.getKind() != TypeKind.DECLARED) {
+ return false;
+ }
+
+ return MoreTypes.asTypeElement(type)
+ .getQualifiedName()
+ .contentEquals("com.google.common.collect.ImmutableList");
+ }
+
+ private static TypeMirror getContainedType(TypeMirror type) {
+ return MoreTypes.asDeclared(type).getTypeArguments().get(0);
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java
new file mode 100644
index 00000000..9d571e3b
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
+import com.google.auto.value.extension.serializable.serializer.runtime.FunctionWithExceptions;
+import com.google.common.collect.ImmutableMap;
+import com.squareup.javapoet.CodeBlock;
+import java.util.Optional;
+import java.util.function.Function;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A {@link SerializerExtension} that deserializes objects inside an {@link ImmutableMap}.
+ *
+ * <p>Enables unserializable objects inside an ImmutableMap to be serializable.
+ */
+@AutoService(SerializerExtension.class)
+public final class ImmutableMapSerializerExtension implements SerializerExtension {
+
+ public ImmutableMapSerializerExtension() {}
+
+ @Override
+ public Optional<Serializer> getSerializer(
+ TypeMirror typeMirror, SerializerFactory factory, ProcessingEnvironment processingEnv) {
+ if (!isImmutableMap(typeMirror)) {
+ return Optional.empty();
+ }
+
+ // Extract the K, V of ImmutableMap<K, V>.
+ TypeMirror keyType = getKeyType(typeMirror);
+ TypeMirror valueType = getValueType(typeMirror);
+ Serializer keyTypeSerializer = factory.getSerializer(keyType);
+ Serializer valueTypeSerializer = factory.getSerializer(valueType);
+
+ // We don't need this serializer if the K and V of ImmutableMap<K, V> are serializable.
+ if (keyTypeSerializer.isIdentity() && valueTypeSerializer.isIdentity()) {
+ return Optional.empty();
+ }
+
+ return Optional.of(
+ new ImmutableMapSerializer(
+ keyType, valueType, keyTypeSerializer, valueTypeSerializer, processingEnv));
+ }
+
+ private static class ImmutableMapSerializer implements Serializer {
+
+ private final TypeMirror keyType;
+ private final TypeMirror valueType;
+ private final TypeMirror keyProxyType;
+ private final TypeMirror valueProxyType;
+ private final Serializer keyTypeSerializer;
+ private final Serializer valueTypeSerializer;
+ private final ProcessingEnvironment processingEnv;
+
+ ImmutableMapSerializer(
+ TypeMirror keyType,
+ TypeMirror valueType,
+ Serializer keyTypeSerializer,
+ Serializer valueTypeSerializer,
+ ProcessingEnvironment processingEnv) {
+ this.keyType = keyType;
+ this.valueType = valueType;
+ this.keyProxyType = keyTypeSerializer.proxyFieldType();
+ this.valueProxyType = valueTypeSerializer.proxyFieldType();
+ this.keyTypeSerializer = keyTypeSerializer;
+ this.valueTypeSerializer = valueTypeSerializer;
+ this.processingEnv = processingEnv;
+ }
+
+ @Override
+ public TypeMirror proxyFieldType() {
+ TypeElement immutableMapTypeElement =
+ processingEnv.getElementUtils().getTypeElement(ImmutableMap.class.getCanonicalName());
+ return processingEnv
+ .getTypeUtils()
+ .getDeclaredType(immutableMapTypeElement, keyProxyType, valueProxyType);
+ }
+
+ @Override
+ public CodeBlock toProxy(CodeBlock expression) {
+ return CodeBlock.of(
+ "$L.entrySet().stream().collect($T.toImmutableMap($L, $L))",
+ expression,
+ ImmutableMap.class,
+ generateKeyMapFunction(keyType, keyProxyType, keyTypeSerializer::toProxy),
+ generateValueMapFunction(valueType, valueProxyType, valueTypeSerializer::toProxy));
+ }
+
+ @Override
+ public CodeBlock fromProxy(CodeBlock expression) {
+ return CodeBlock.of(
+ "$L.entrySet().stream().collect($T.toImmutableMap($L, $L))",
+ expression,
+ ImmutableMap.class,
+ generateKeyMapFunction(keyProxyType, keyType, keyTypeSerializer::fromProxy),
+ generateValueMapFunction(valueProxyType, valueType, valueTypeSerializer::fromProxy));
+ }
+
+ private static CodeBlock generateKeyMapFunction(
+ TypeMirror originalType,
+ TypeMirror transformedType,
+ Function<CodeBlock, CodeBlock> proxyMap) {
+ CodeBlock element = CodeBlock.of("element$$");
+ return CodeBlock.of(
+ "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getKey())",
+ FunctionWithExceptions.class,
+ originalType,
+ transformedType,
+ element,
+ proxyMap.apply(element));
+ }
+
+ private static CodeBlock generateValueMapFunction(
+ TypeMirror originalType,
+ TypeMirror transformedType,
+ Function<CodeBlock, CodeBlock> proxyMap) {
+ CodeBlock element = CodeBlock.of("element$$");
+ return CodeBlock.of(
+ "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getValue())",
+ FunctionWithExceptions.class,
+ originalType,
+ transformedType,
+ element,
+ proxyMap.apply(element));
+ }
+ }
+
+ private static boolean isImmutableMap(TypeMirror type) {
+ if (type.getKind() != TypeKind.DECLARED) {
+ return false;
+ }
+
+ return MoreTypes.asTypeElement(type)
+ .getQualifiedName()
+ .contentEquals("com.google.common.collect.ImmutableMap");
+ }
+
+ private static TypeMirror getKeyType(TypeMirror type) {
+ return MoreTypes.asDeclared(type).getTypeArguments().get(0);
+ }
+
+ private static TypeMirror getValueType(TypeMirror type) {
+ return MoreTypes.asDeclared(type).getTypeArguments().get(1);
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtension.java
new file mode 100644
index 00000000..a5025a8f
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtension.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
+import com.squareup.javapoet.CodeBlock;
+import java.util.Optional;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A {@link SerializerExtension} that enables {@link Optional} types to be serialized.
+ *
+ * <p>The type argument {@code T} of {@code Optional<T>} is queried against the {@link
+ * SerializerFactory}.
+ */
+@AutoService(SerializerExtension.class)
+public final class OptionalSerializerExtension implements SerializerExtension {
+
+ public OptionalSerializerExtension() {}
+
+ /** Creates a {@link Serializer} that supports {@link Optional} types. */
+ @Override
+ public Optional<Serializer> getSerializer(
+ TypeMirror typeMirror, SerializerFactory factory, ProcessingEnvironment processingEnv) {
+ if (!isOptional(typeMirror)) {
+ return Optional.empty();
+ }
+
+ // Extract the T of Optional<T>.
+ TypeMirror containedType = getContainedType(typeMirror);
+ Serializer containedTypeSerializer = factory.getSerializer(containedType);
+
+ return Optional.of(new OptionalSerializer(containedTypeSerializer));
+ }
+
+ private static class OptionalSerializer implements Serializer {
+
+ private final Serializer containedTypeSerializer;
+
+ OptionalSerializer(Serializer containedTypeSerializer) {
+ this.containedTypeSerializer = containedTypeSerializer;
+ }
+
+ @Override
+ public TypeMirror proxyFieldType() {
+ // If this is an Optional<String> then the proxy field type is String.
+ // If this is an Optional<Foo>, and the proxy field type for Foo is Bar, then the proxy field
+ // type for Optional<Foo> is Bar.
+ return containedTypeSerializer.proxyFieldType();
+ }
+
+ @Override
+ public CodeBlock toProxy(CodeBlock expression) {
+ return CodeBlock.of(
+ "$L.isPresent() ? $L : null",
+ expression,
+ containedTypeSerializer.toProxy(CodeBlock.of("$L.get()", expression)));
+ }
+
+ @Override
+ public CodeBlock fromProxy(CodeBlock expression) {
+ return CodeBlock.of(
+ "$T.ofNullable($L == null ? null : $L)",
+ Optional.class,
+ expression,
+ containedTypeSerializer.fromProxy(expression));
+ }
+ }
+
+ /** Checks if the given type is an {@link Optional}. */
+ private static boolean isOptional(TypeMirror type) {
+ if (type.getKind() != TypeKind.DECLARED) {
+ return false;
+ }
+
+ return MoreTypes.asTypeElement(type).getQualifiedName().contentEquals("java.util.Optional");
+ }
+
+ /**
+ * Gets the given type's first type argument.
+ *
+ * <p>Returns the {@code T} in {@code Optional<T>}.
+ */
+ private static TypeMirror getContainedType(TypeMirror type) {
+ return MoreTypes.asDeclared(type).getTypeArguments().get(0);
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java
new file mode 100644
index 00000000..57741f91
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
+import com.google.common.collect.ImmutableList;
+import java.util.Optional;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.type.TypeMirror;
+
+/** A concrete implementation of {@link SerializerFactory}. */
+public final class SerializerFactoryImpl implements SerializerFactory {
+
+ private final ImmutableList<SerializerExtension> extensions;
+ private final ProcessingEnvironment env;
+
+ public SerializerFactoryImpl(
+ ImmutableList<SerializerExtension> extensions, ProcessingEnvironment env) {
+ this.extensions = extensions;
+ this.env = env;
+ }
+
+ @Override
+ public Serializer getSerializer(TypeMirror typeMirror) {
+ for (SerializerExtension extension : extensions) {
+ Optional<Serializer> serializer = extension.getSerializer(typeMirror, this, env);
+ if (serializer.isPresent()) {
+ return serializer.get();
+ }
+ }
+ return IdentitySerializerFactory.getSerializer(typeMirror);
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java
new file mode 100644
index 00000000..96b9308c
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.interfaces;
+
+import com.squareup.javapoet.CodeBlock;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A Serializer, at compile time, generates code to map an unserializable type to a serializable
+ * type. It also generates the reverse code to re-create the original type.
+ */
+public interface Serializer {
+
+ /** The proxy type the original unserializable type will be mapped to. */
+ TypeMirror proxyFieldType();
+
+ /** Creates an expression that converts the original type to the proxy type. */
+ CodeBlock toProxy(CodeBlock expression);
+
+ /** Creates an expression that converts the proxy type back to the original type. */
+ CodeBlock fromProxy(CodeBlock expression);
+
+ /** Returns true if this is an identity {@link Serializer}. */
+ default boolean isIdentity() {
+ return false;
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java
new file mode 100644
index 00000000..6e38b9b7
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.interfaces;
+
+import java.util.Optional;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A SerializerExtension allows unserializable types to be serialized by SerializableAutoValue.
+ *
+ * <p>Extensions are discovered at compile time using the {@link java.util.ServiceLoader} APIs,
+ * allowing them to run without any additional annotations. To be found by {@code ServiceLoader}, an
+ * extension class must be public with a public no-arg constructor, and its fully-qualified name
+ * must appear in a file called {@code
+ * META-INF/services/com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension}
+ * in a jar that is on the compiler's {@code -classpath} or {@code -processorpath}.
+ *
+ * <p>When SerializableAutoValue maps each field in an AutoValue to a serializable proxy object, it
+ * asks each SerializerExtension whether it can generate code to make the given type serializable. A
+ * SerializerExtension replies that it can by returning a non-empty {@link Serializer}.
+ *
+ * <p>A SerializerExtension is also provided with a SerializerFactory, which it can use to query
+ * nested types.
+ */
+public interface SerializerExtension {
+
+ /**
+ * Returns a {@link Serializer} if this {@link SerializerExtension} applies to the given {@code
+ * type}. Otherwise, {@code Optional.empty} is returned.
+ *
+ * @param type the type being serialized
+ * @param factory a {@link SerializerFactory} that can be used to serialize nested types
+ * @param processingEnv the processing environment provided by the annotation processing framework
+ */
+ Optional<Serializer> getSerializer(
+ TypeMirror type, SerializerFactory factory, ProcessingEnvironment processingEnv);
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java
new file mode 100644
index 00000000..d05c88b2
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.interfaces;
+
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A factory that returns a {@link Serializer} for any given {@link TypeMirror}.
+ *
+ * <p>Defaults to an identity serializer if no SerializerExtensions are suitable.
+ */
+public interface SerializerFactory {
+
+ /** Returns a {@link Serializer} for the given {@link TypeMirror}. */
+ Serializer getSerializer(TypeMirror type);
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/runtime/FunctionWithExceptions.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/runtime/FunctionWithExceptions.java
new file mode 100644
index 00000000..93aa6009
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/runtime/FunctionWithExceptions.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.runtime;
+
+import java.util.function.Function;
+
+/** A utility for lambdas that throw exceptions. */
+public final class FunctionWithExceptions {
+
+ /** Creates a wrapper for lambdas that converts checked exceptions to runtime exceptions. */
+ public static <I, O> Function<I, O> wrapper(FunctionWithException<I, O> fe) {
+ return arg -> {
+ try {
+ return fe.apply(arg);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ };
+ }
+
+ /** A function that can throw an exception. */
+ @FunctionalInterface
+ public interface FunctionWithException<I, O> {
+ O apply(I i) throws Exception;
+ }
+
+ private FunctionWithExceptions() {}
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java b/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java
index 5f2e47b2..55f9ae4e 100644
--- a/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java
+++ b/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java
@@ -39,10 +39,10 @@ import java.util.ServiceConfigurationError;
* @see <a href="https://github.com/google/auto/issues/718">Issue #718</a>
* @see <a href="https://bugs.openjdk.java.net/browse/JDK-8156014">JDK-8156014</a>
*/
-final class SimpleServiceLoader {
+public final class SimpleServiceLoader {
private SimpleServiceLoader() {}
- static <T> ImmutableList<T> load(Class<? extends T> service, ClassLoader loader) {
+ public static <T> ImmutableList<T> load(Class<? extends T> service, ClassLoader loader) {
String resourceName = "META-INF/services/" + service.getName();
List<URL> resourceUrls;
try {
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java
new file mode 100644
index 00000000..9974d512
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.serializable.SerializableAutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.SerializableTester;
+import java.io.ByteArrayOutputStream;
+import java.io.NotSerializableException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class SerializableAutoValueExtensionTest {
+ private static final String A = "a";
+ private static final int B = 1;
+ private static final String C = "c";
+ private static final int D = 2;
+
+ @SerializableAutoValue
+ @AutoValue
+ abstract static class DummySerializableAutoValue implements Serializable {
+ // Primitive fields
+ abstract String a();
+
+ abstract int b();
+
+ // Optional fields
+ abstract Optional<String> optionalC();
+
+ abstract Optional<Integer> optionalD();
+
+ static DummySerializableAutoValue.Builder builder() {
+ return new AutoValue_SerializableAutoValueExtensionTest_DummySerializableAutoValue.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract DummySerializableAutoValue.Builder setA(String value);
+
+ abstract DummySerializableAutoValue.Builder setB(int value);
+
+ abstract DummySerializableAutoValue.Builder setOptionalC(String value);
+
+ abstract DummySerializableAutoValue.Builder setOptionalD(int value);
+
+ abstract DummySerializableAutoValue build();
+ }
+ }
+
+ @Test
+ public void allFieldsAreSet_noEmpty() {
+ DummySerializableAutoValue autoValue =
+ DummySerializableAutoValue.builder()
+ .setA(A)
+ .setB(B)
+ .setOptionalC(C)
+ .setOptionalD(D)
+ .build();
+
+ assertThat(autoValue.a()).isEqualTo(A);
+ assertThat(autoValue.b()).isEqualTo(B);
+ assertThat(autoValue.optionalC()).hasValue(C);
+ assertThat(autoValue.optionalD()).hasValue(D);
+ }
+
+ @Test
+ public void allFieldsAreSet_withMixedEmpty() {
+ DummySerializableAutoValue autoValue =
+ DummySerializableAutoValue.builder().setA(A).setB(B).setOptionalC(C).build();
+
+ assertThat(autoValue.a()).isEqualTo(A);
+ assertThat(autoValue.b()).isEqualTo(B);
+ assertThat(autoValue.optionalC()).hasValue(C);
+ assertThat(autoValue.optionalD()).isEmpty();
+ }
+
+ @Test
+ public void allFieldsAreSet_withEmpty() {
+ DummySerializableAutoValue autoValue =
+ DummySerializableAutoValue.builder().setA(A).setB(B).build();
+
+ assertThat(autoValue.a()).isEqualTo(A);
+ assertThat(autoValue.b()).isEqualTo(B);
+ assertThat(autoValue.optionalC()).isEmpty();
+ assertThat(autoValue.optionalD()).isEmpty();
+ }
+
+ @Test
+ public void allFieldsAreSerialized_noEmpty() {
+ DummySerializableAutoValue autoValue =
+ DummySerializableAutoValue.builder()
+ .setA(A)
+ .setB(B)
+ .setOptionalC(C)
+ .setOptionalD(D)
+ .build();
+
+ DummySerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @Test
+ public void allFieldsAreSerialized_withEmpty() {
+ DummySerializableAutoValue autoValue =
+ DummySerializableAutoValue.builder().setA(A).setB(B).build();
+
+ DummySerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @Test
+ public void allFieldsAreSerialized_withMixedEmpty() {
+ DummySerializableAutoValue autoValue =
+ DummySerializableAutoValue.builder().setA(A).setB(B).setOptionalC(C).build();
+
+ DummySerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @SerializableAutoValue
+ @AutoValue
+ abstract static class PrefixSerializableAutoValue implements Serializable {
+ // Primitive fields
+ abstract String getA();
+
+ abstract boolean isB();
+
+ // Optional fields
+ abstract Optional<String> getC();
+
+ abstract Optional<Boolean> getD();
+
+ static PrefixSerializableAutoValue.Builder builder() {
+ return new AutoValue_SerializableAutoValueExtensionTest_PrefixSerializableAutoValue.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract PrefixSerializableAutoValue.Builder a(String value);
+
+ abstract PrefixSerializableAutoValue.Builder b(boolean value);
+
+ abstract PrefixSerializableAutoValue.Builder c(String value);
+
+ abstract PrefixSerializableAutoValue.Builder d(boolean value);
+
+ abstract PrefixSerializableAutoValue build();
+ }
+ }
+
+ @Test
+ public void allPrefixFieldsAreSerialized_noEmpty() {
+ PrefixSerializableAutoValue autoValue =
+ PrefixSerializableAutoValue.builder().a("A").b(true).c("C").d(false).build();
+
+ PrefixSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @Test
+ public void allPrefixFieldsAreSerialized_WithEmpty() {
+ PrefixSerializableAutoValue autoValue =
+ PrefixSerializableAutoValue.builder().a("A").b(true).build();
+
+ PrefixSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @SerializableAutoValue
+ @AutoValue
+ abstract static class NotSerializable {
+ static NotSerializable create() {
+ return new AutoValue_SerializableAutoValueExtensionTest_NotSerializable(Optional.of("A"));
+ }
+
+ abstract Optional<String> optionalA();
+ }
+
+ @Test
+ public void missingImplementsSerializableThrowsException() throws Exception {
+ NotSerializable autoValue = NotSerializable.create();
+ ByteArrayOutputStream bo = new ByteArrayOutputStream();
+ ObjectOutputStream so = new ObjectOutputStream(bo);
+
+ assertThrows(NotSerializableException.class, () -> so.writeObject(autoValue));
+ }
+
+ @AutoValue
+ abstract static class NotSerializableNoAnnotation implements Serializable {
+ static NotSerializableNoAnnotation create() {
+ return new AutoValue_SerializableAutoValueExtensionTest_NotSerializableNoAnnotation(
+ Optional.of("A"));
+ }
+
+ abstract Optional<String> optionalA();
+ }
+
+ @Test
+ public void missingSerializableAutoValueAnnotationThrowsException() throws Exception {
+ NotSerializableNoAnnotation autoValue = NotSerializableNoAnnotation.create();
+ ByteArrayOutputStream bo = new ByteArrayOutputStream();
+ ObjectOutputStream so = new ObjectOutputStream(bo);
+
+ assertThrows(NotSerializableException.class, () -> so.writeObject(autoValue));
+ }
+
+ @SerializableAutoValue
+ @AutoValue
+ // Technically all type parameters should extend serializable, but for the purposes of testing,
+ // only one type parameter is bounded.
+ abstract static class HasTypeParameters<T extends Serializable, S> implements Serializable {
+ abstract T a();
+
+ abstract Optional<S> optionalB();
+
+ static <T extends Serializable, S> Builder<T, S> builder() {
+ return new AutoValue_SerializableAutoValueExtensionTest_HasTypeParameters.Builder<>();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder<T extends Serializable, S> {
+ abstract Builder<T, S> setA(T value);
+
+ abstract Builder<T, S> setOptionalB(S value);
+
+ abstract HasTypeParameters<T, S> build();
+ }
+ }
+
+ @Test
+ public void typeParameterizedFieldsAreSet_noEmpty() {
+ HasTypeParameters<String, Integer> autoValue =
+ HasTypeParameters.<String, Integer>builder().setA(A).setOptionalB(B).build();
+
+ assertThat(autoValue.a()).isEqualTo(A);
+ assertThat(autoValue.optionalB()).hasValue(B);
+ }
+
+ @Test
+ public void typeParameterizedFieldsAreSet_withEmpty() {
+ HasTypeParameters<String, Integer> autoValue =
+ HasTypeParameters.<String, Integer>builder().setA(A).build();
+
+ assertThat(autoValue.a()).isEqualTo(A);
+ assertThat(autoValue.optionalB()).isEmpty();
+ }
+
+ @Test
+ public void typeParameterizedFieldsAreSerializable_noEmpty() {
+ HasTypeParameters<String, Integer> autoValue =
+ HasTypeParameters.<String, Integer>builder().setA(A).setOptionalB(B).build();
+
+ HasTypeParameters<String, Integer> actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @Test
+ public void typeParameterizedFieldsAreSerializable_withEmpty() {
+ HasTypeParameters<String, Integer> autoValue =
+ HasTypeParameters.<String, Integer>builder().setA(A).build();
+
+ HasTypeParameters<String, Integer> actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @SerializableAutoValue
+ @AutoValue
+ abstract static class ImmutableListSerializableAutoValue implements Serializable {
+ abstract ImmutableList<Optional<String>> payload();
+
+ static ImmutableListSerializableAutoValue.Builder builder() {
+ return new AutoValue_SerializableAutoValueExtensionTest_ImmutableListSerializableAutoValue
+ .Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract ImmutableListSerializableAutoValue.Builder setPayload(
+ ImmutableList<Optional<String>> payload);
+
+ abstract ImmutableListSerializableAutoValue build();
+ }
+ }
+
+ @Test
+ public void immutableList_emptyListSerialized() {
+ ImmutableListSerializableAutoValue autoValue =
+ ImmutableListSerializableAutoValue.builder().setPayload(ImmutableList.of()).build();
+
+ ImmutableListSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @Test
+ public void immutableList_allFieldsSetAndSerialized() {
+ ImmutableListSerializableAutoValue autoValue =
+ ImmutableListSerializableAutoValue.builder()
+ .setPayload(ImmutableList.of(Optional.of("a1"), Optional.of("a2")))
+ .build();
+
+ ImmutableListSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @SerializableAutoValue
+ @AutoValue
+ abstract static class ImmutableMapSerializableAutoValue implements Serializable {
+ abstract ImmutableMap<Optional<String>, String> a();
+
+ abstract ImmutableMap<String, Optional<String>> b();
+
+ static ImmutableMapSerializableAutoValue.Builder builder() {
+ return new AutoValue_SerializableAutoValueExtensionTest_ImmutableMapSerializableAutoValue
+ .Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract ImmutableMapSerializableAutoValue.Builder setA(
+ ImmutableMap<Optional<String>, String> a);
+
+ abstract ImmutableMapSerializableAutoValue.Builder setB(
+ ImmutableMap<String, Optional<String>> b);
+
+ abstract ImmutableMapSerializableAutoValue build();
+ }
+ }
+
+ @Test
+ public void immutableMap_emptyMapSerialized() {
+ ImmutableMapSerializableAutoValue autoValue =
+ ImmutableMapSerializableAutoValue.builder()
+ .setA(ImmutableMap.of())
+ .setB(ImmutableMap.of())
+ .build();
+
+ ImmutableMapSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+
+ @Test
+ public void immutableMap_allFieldsSetAndSerialized() {
+ ImmutableMapSerializableAutoValue autoValue =
+ ImmutableMapSerializableAutoValue.builder()
+ .setA(ImmutableMap.of(Optional.of("key"), "value"))
+ .setB(ImmutableMap.of("key", Optional.of("value")))
+ .build();
+
+ ImmutableMapSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue);
+
+ assertThat(actualAutoValue).isEqualTo(autoValue);
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoaderTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoaderTest.java
new file mode 100644
index 00000000..81410761
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoaderTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
+import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class SerializerFactoryLoaderTest extends CompilationAbstractTest {
+
+ @Test
+ public void getFactory_extensionsLoaded() throws Exception {
+ SerializerFactory factory = SerializerFactoryLoader.getFactory(mockProcessingEnvironment);
+
+ Serializer actualSerializer = factory.getSerializer(typeMirrorOf(String.class));
+
+ assertThat(actualSerializer.getClass().getName())
+ .contains("TestStringSerializerFactory$TestStringSerializer");
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactoryTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactoryTest.java
new file mode 100644
index 00000000..87800b3d
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactoryTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest;
+import com.squareup.javapoet.CodeBlock;
+import javax.lang.model.type.TypeMirror;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class IdentitySerializerFactoryTest extends CompilationAbstractTest {
+
+ @Test
+ public void proxyFieldType_isUnchanged() throws Exception {
+ TypeMirror typeMirror = typeMirrorOf(String.class);
+
+ TypeMirror actualTypeMirror =
+ IdentitySerializerFactory.getSerializer(typeMirror).proxyFieldType();
+
+ assertThat(actualTypeMirror).isSameInstanceAs(typeMirror);
+ }
+
+ @Test
+ public void toProxy_isUnchanged() throws Exception {
+ TypeMirror typeMirror = typeMirrorOf(String.class);
+ CodeBlock inputExpression = CodeBlock.of("x");
+
+ CodeBlock outputExpression =
+ IdentitySerializerFactory.getSerializer(typeMirror).toProxy(inputExpression);
+
+ assertThat(outputExpression).isSameInstanceAs(inputExpression);
+ }
+
+ @Test
+ public void fromProxy_isUnchanged() throws Exception {
+ TypeMirror typeMirror = typeMirrorOf(String.class);
+ CodeBlock inputExpression = CodeBlock.of("x");
+
+ CodeBlock outputExpression =
+ IdentitySerializerFactory.getSerializer(typeMirror).fromProxy(inputExpression);
+
+ assertThat(outputExpression).isSameInstanceAs(inputExpression);
+ }
+
+ @Test
+ public void isIdentity() throws Exception {
+ TypeMirror typeMirror = typeMirrorOf(String.class);
+
+ boolean actualIsIdentity = IdentitySerializerFactory.getSerializer(typeMirror).isIdentity();
+
+ assertThat(actualIsIdentity).isTrue();
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtensionTest.java
new file mode 100644
index 00000000..159059d3
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtensionTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest;
+import com.google.auto.value.extension.serializable.serializer.utils.FakeSerializerFactory;
+import com.google.common.collect.ImmutableList;
+import com.squareup.javapoet.CodeBlock;
+import java.util.Optional;
+import javax.lang.model.type.TypeMirror;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ImmutableListSerializerExtensionTest extends CompilationAbstractTest {
+
+ private static final String FUNCTION_WITH_EXCEPTIONS =
+ "com.google.auto.value.extension.serializable.serializer.runtime.FunctionWithExceptions";
+ private static final String IMMUTABLE_LIST = "com.google.common.collect.ImmutableList";
+
+ private ImmutableListSerializerExtension extension;
+ private FakeSerializerFactory fakeSerializerFactory;
+
+ @Before
+ public void setUpExtension() {
+ extension = new ImmutableListSerializerExtension();
+ fakeSerializerFactory = new FakeSerializerFactory();
+ fakeSerializerFactory.setReturnIdentitySerializer(false);
+ }
+
+ @Test
+ public void getSerializer_nonImmutableList_emptyReturned() {
+ TypeMirror typeMirror = typeMirrorOf(String.class);
+
+ Optional<Serializer> actualSerializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment);
+
+ assertThat(actualSerializer).isEmpty();
+ }
+
+ @Test
+ public void getSerializer_immutableListWithSerializableContainedType_emptyReturned() {
+ fakeSerializerFactory.setReturnIdentitySerializer(true);
+ TypeMirror typeMirror = typeMirrorOf(ImmutableList.class);
+
+ Optional<Serializer> actualSerializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment);
+
+ assertThat(actualSerializer).isEmpty();
+ }
+
+ @Test
+ public void getSerializer_immutableList_serializerReturned() {
+ TypeMirror typeMirror = typeMirrorOf(ImmutableList.class);
+
+ Serializer actualSerializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+
+ assertThat(actualSerializer.getClass().getName())
+ .contains("ImmutableListSerializerExtension$ImmutableListSerializer");
+ }
+
+ @Test
+ public void proxyFieldType() {
+ TypeMirror typeMirror = declaredTypeOf(ImmutableList.class, Integer.class);
+
+ Serializer serializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+ TypeMirror actualTypeMirror = serializer.proxyFieldType();
+
+ assertThat(typeUtils.isSameType(actualTypeMirror, typeMirror)).isTrue();
+ }
+
+ @Test
+ public void toProxy() {
+ TypeMirror typeMirror = declaredTypeOf(ImmutableList.class, Integer.class);
+
+ Serializer serializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+ CodeBlock actualCodeBlock = serializer.toProxy(CodeBlock.of("x"));
+
+ assertThat(actualCodeBlock.toString())
+ .isEqualTo(
+ String.format(
+ "x.stream().map(%s.wrapper(value$ -> value$)).collect(%s.toImmutableList())",
+ FUNCTION_WITH_EXCEPTIONS, IMMUTABLE_LIST));
+ }
+
+ @Test
+ public void fromProxy() {
+ TypeMirror typeMirror = declaredTypeOf(ImmutableList.class, Integer.class);
+
+ Serializer serializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+ CodeBlock actualCodeBlock = serializer.fromProxy(CodeBlock.of("x"));
+
+ assertThat(actualCodeBlock.toString())
+ .isEqualTo(
+ String.format(
+ "x.stream().map(%s.wrapper(value$ -> value$)).collect(%s.toImmutableList())",
+ FUNCTION_WITH_EXCEPTIONS, IMMUTABLE_LIST));
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtensionTest.java
new file mode 100644
index 00000000..a7006f54
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtensionTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest;
+import com.google.auto.value.extension.serializable.serializer.utils.FakeSerializerFactory;
+import com.google.common.collect.ImmutableMap;
+import com.squareup.javapoet.CodeBlock;
+import java.util.Optional;
+import javax.lang.model.type.TypeMirror;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ImmutableMapSerializerExtensionTest extends CompilationAbstractTest {
+
+ private static final String FUNCTION_WITH_EXCEPTIONS =
+ "com.google.auto.value.extension.serializable.serializer.runtime.FunctionWithExceptions";
+ private static final String IMMUTABLE_MAP = "com.google.common.collect.ImmutableMap";
+ private static final String INTEGER = "java.lang.Integer";
+ private static final String STRING = "java.lang.String";
+
+ private ImmutableMapSerializerExtension extension;
+ private FakeSerializerFactory fakeSerializerFactory;
+
+ @Before
+ public void setUpExtension() {
+ extension = new ImmutableMapSerializerExtension();
+ fakeSerializerFactory = new FakeSerializerFactory();
+ fakeSerializerFactory.setReturnIdentitySerializer(false);
+ }
+
+ @Test
+ public void getSerializer_nonImmutableMap_emptyReturned() {
+ TypeMirror typeMirror = typeMirrorOf(String.class);
+
+ Optional<Serializer> actualSerializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment);
+
+ assertThat(actualSerializer).isEmpty();
+ }
+
+ @Test
+ public void getSerializer_immutableMapWithSerializableContainedTypes_emptyReturned() {
+ fakeSerializerFactory.setReturnIdentitySerializer(true);
+ TypeMirror typeMirror = typeMirrorOf(ImmutableMap.class);
+
+ Optional<Serializer> actualSerializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment);
+
+ assertThat(actualSerializer).isEmpty();
+ }
+
+ @Test
+ public void getSerializer_immutableMap_serializerReturned() {
+ TypeMirror typeMirror = typeMirrorOf(ImmutableMap.class);
+
+ Serializer actualSerializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+
+ assertThat(actualSerializer.getClass().getName())
+ .contains("ImmutableMapSerializerExtension$ImmutableMapSerializer");
+ }
+
+ @Test
+ public void proxyFieldType() {
+ TypeMirror typeMirror = declaredTypeOf(ImmutableMap.class, Integer.class, String.class);
+
+ Serializer serializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+ TypeMirror actualTypeMirror = serializer.proxyFieldType();
+
+ assertThat(typeUtils.isSameType(actualTypeMirror, typeMirror)).isTrue();
+ }
+
+ @Test
+ public void toProxy() {
+ TypeMirror typeMirror = declaredTypeOf(ImmutableMap.class, Integer.class, String.class);
+
+ Serializer serializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+ CodeBlock actualCodeBlock = serializer.toProxy(CodeBlock.of("x"));
+
+ assertThat(actualCodeBlock.toString())
+ .isEqualTo(
+ String.format(
+ "x.entrySet().stream().collect(%s.toImmutableMap(value$ -> %s.<%s,"
+ + " %s>wrapper(element$ -> element$).apply(value$.getKey()), value$ -> %s.<%s,"
+ + " %s>wrapper(element$ -> element$).apply(value$.getValue())))",
+ IMMUTABLE_MAP,
+ FUNCTION_WITH_EXCEPTIONS,
+ INTEGER,
+ INTEGER,
+ FUNCTION_WITH_EXCEPTIONS,
+ STRING,
+ STRING));
+ }
+
+ @Test
+ public void fromProxy() {
+ TypeMirror typeMirror = declaredTypeOf(ImmutableMap.class, Integer.class, String.class);
+
+ Serializer serializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+ CodeBlock actualCodeBlock = serializer.fromProxy(CodeBlock.of("x"));
+
+ assertThat(actualCodeBlock.toString())
+ .isEqualTo(
+ String.format(
+ "x.entrySet().stream().collect(%s.toImmutableMap(value$ -> %s.<%s,"
+ + " %s>wrapper(element$ -> element$).apply(value$.getKey()), value$ -> %s.<%s,"
+ + " %s>wrapper(element$ -> element$).apply(value$.getValue())))",
+ IMMUTABLE_MAP,
+ FUNCTION_WITH_EXCEPTIONS,
+ INTEGER,
+ INTEGER,
+ FUNCTION_WITH_EXCEPTIONS,
+ STRING,
+ STRING));
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtensionTest.java
new file mode 100644
index 00000000..e817911e
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtensionTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest;
+import com.google.auto.value.extension.serializable.serializer.utils.FakeSerializerFactory;
+import com.squareup.javapoet.CodeBlock;
+import java.util.Optional;
+import javax.lang.model.type.TypeMirror;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class OptionalSerializerExtensionTest extends CompilationAbstractTest {
+
+ private OptionalSerializerExtension extension;
+ private FakeSerializerFactory fakeSerializerFactory;
+
+ @Before
+ public void setUpExtension() {
+ extension = new OptionalSerializerExtension();
+ fakeSerializerFactory = new FakeSerializerFactory();
+ }
+
+ @Test
+ public void getSerializer_nonOptional_emptyReturned() {
+ TypeMirror typeMirror = typeMirrorOf(String.class);
+
+ Optional<Serializer> actualSerializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment);
+
+ assertThat(actualSerializer).isEmpty();
+ }
+
+ @Test
+ public void getSerializer_optional_serializerReturned() {
+ TypeMirror typeMirror = typeMirrorOf(Optional.class);
+
+ Serializer actualSerializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+
+ assertThat(actualSerializer.getClass().getName())
+ .contains("OptionalSerializerExtension$OptionalSerializer");
+ }
+
+ @Test
+ public void proxyFieldType() {
+ TypeMirror typeMirror = declaredTypeOf(Optional.class, Integer.class);
+
+ Serializer serializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+ TypeMirror actualTypeMirror = serializer.proxyFieldType();
+
+ assertThat(actualTypeMirror).isEqualTo(typeMirrorOf(Integer.class));
+ }
+
+ @Test
+ public void toProxy() {
+ TypeMirror typeMirror = declaredTypeOf(Optional.class, Integer.class);
+
+ Serializer serializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+ CodeBlock actualCodeBlock = serializer.toProxy(CodeBlock.of("x"));
+
+ assertThat(actualCodeBlock.toString()).isEqualTo("x.isPresent() ? x.get() : null");
+ }
+
+ @Test
+ public void fromProxy() {
+ TypeMirror typeMirror = declaredTypeOf(Optional.class, Integer.class);
+
+ Serializer serializer =
+ extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get();
+ CodeBlock actualCodeBlock = serializer.fromProxy(CodeBlock.of("x"));
+
+ assertThat(actualCodeBlock.toString())
+ .isEqualTo("java.util.Optional.ofNullable(x == null ? null : x)");
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImplTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImplTest.java
new file mode 100644
index 00000000..e5150f90
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImplTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest;
+import com.google.auto.value.extension.serializable.serializer.utils.TestStringSerializerFactory;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class SerializerFactoryImplTest extends CompilationAbstractTest {
+
+ @Test
+ public void getSerializer_emptyFactories_identitySerializerReturned() throws Exception {
+ SerializerFactoryImpl factory =
+ new SerializerFactoryImpl(ImmutableList.of(), mockProcessingEnvironment);
+
+ Serializer actualSerializer = factory.getSerializer(typeMirrorOf(String.class));
+
+ assertThat(actualSerializer.getClass().getName())
+ .contains("IdentitySerializerFactory$IdentitySerializer");
+ }
+
+ @Test
+ public void getSerializer_factoriesProvided_factoryReturned() throws Exception {
+ SerializerFactoryImpl factory =
+ new SerializerFactoryImpl(
+ ImmutableList.of(new TestStringSerializerFactory()), mockProcessingEnvironment);
+
+ Serializer actualSerializer = factory.getSerializer(typeMirrorOf(String.class));
+
+ assertThat(actualSerializer.getClass().getName())
+ .contains("TestStringSerializerFactory$TestStringSerializer");
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java
new file mode 100644
index 00000000..0eb634a6
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.utils;
+
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.testing.compile.CompilationRule;
+import java.util.Arrays;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(JUnit4.class)
+public abstract class CompilationAbstractTest {
+
+ @Rule public final CompilationRule compilationRule = new CompilationRule();
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock protected ProcessingEnvironment mockProcessingEnvironment;
+ @Mock protected Messager mockMessager;
+
+ protected Types typeUtils;
+ protected Elements elementUtils;
+
+ @Before
+ public final void setUp() {
+ typeUtils = compilationRule.getTypes();
+ elementUtils = compilationRule.getElements();
+
+ when(mockProcessingEnvironment.getTypeUtils()).thenReturn(typeUtils);
+ when(mockProcessingEnvironment.getElementUtils()).thenReturn(elementUtils);
+ when(mockProcessingEnvironment.getMessager()).thenReturn(mockMessager);
+ }
+
+ protected TypeElement typeElementOf(Class<?> c) {
+ return elementUtils.getTypeElement(c.getCanonicalName());
+ }
+
+ protected TypeMirror typeMirrorOf(Class<?> c) {
+ return typeElementOf(c).asType();
+ }
+
+ protected DeclaredType declaredTypeOf(Class<?> enclosingClass, Class<?> containedClass) {
+ return typeUtils.getDeclaredType(typeElementOf(enclosingClass), typeMirrorOf(containedClass));
+ }
+
+ protected DeclaredType declaredTypeOf(Class<?> enclosingClass, Class<?>... classArgs) {
+ return typeUtils.getDeclaredType(
+ typeElementOf(enclosingClass),
+ Iterables.toArray(
+ Arrays.stream(classArgs)
+ .map(this::typeMirrorOf)
+ .collect(ImmutableList.toImmutableList()),
+ TypeMirror.class));
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java
new file mode 100644
index 00000000..388977fb
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.utils;
+
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
+import com.squareup.javapoet.CodeBlock;
+import javax.lang.model.type.TypeMirror;
+
+/** A fake {@link SerializerFactory} that returns an identity serializer used for tests. */
+public final class FakeSerializerFactory implements SerializerFactory {
+
+ private boolean isIdentity = true;
+
+ public FakeSerializerFactory() {}
+
+ /**
+ * Set if this factory should return a serializer that is considered an identity serializer.
+ *
+ * <p>The underlying fake serializer implementation will always be an identity serializer. This
+ * only changes the {@link Serializer#isIdentity} return value.
+ */
+ public void setReturnIdentitySerializer(boolean isIdentity) {
+ this.isIdentity = isIdentity;
+ }
+
+ @Override
+ public Serializer getSerializer(TypeMirror type) {
+ return new FakeIdentitySerializer(type, isIdentity);
+ }
+
+ private static class FakeIdentitySerializer implements Serializer {
+
+ private final TypeMirror typeMirror;
+ private final boolean isIdentity;
+
+ FakeIdentitySerializer(TypeMirror typeMirror, boolean isIdentity) {
+ this.typeMirror = typeMirror;
+ this.isIdentity = isIdentity;
+ }
+
+ @Override
+ public TypeMirror proxyFieldType() {
+ return typeMirror;
+ }
+
+ @Override
+ public CodeBlock toProxy(CodeBlock expression) {
+ return expression;
+ }
+
+ @Override
+ public CodeBlock fromProxy(CodeBlock expression) {
+ return expression;
+ }
+
+ @Override
+ public boolean isIdentity() {
+ return isIdentity;
+ }
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/TestStringSerializerFactory.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/TestStringSerializerFactory.java
new file mode 100644
index 00000000..7c0f2047
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/TestStringSerializerFactory.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.extension.serializable.serializer.utils;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension;
+import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
+import com.squareup.javapoet.CodeBlock;
+import java.util.Optional;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+/** A test implementation of {@link SerializerExtension} that overwrites a string field's value. */
+@AutoService(SerializerExtension.class)
+public final class TestStringSerializerFactory implements SerializerExtension {
+
+ public TestStringSerializerFactory() {}
+
+ @Override
+ public Optional<Serializer> getSerializer(
+ TypeMirror typeMirror, SerializerFactory factory, ProcessingEnvironment processingEnv) {
+ if (typeMirror.getKind() != TypeKind.DECLARED) {
+ return Optional.empty();
+ }
+
+ DeclaredType declaredType = MoreTypes.asDeclared(typeMirror);
+ TypeElement typeElement = MoreElements.asType(declaredType.asElement());
+ if (typeElement.getQualifiedName().contentEquals("java.lang.String")) {
+ return Optional.of(new TestStringSerializer(typeMirror));
+ }
+
+ return Optional.empty();
+ }
+
+ private static class TestStringSerializer implements Serializer {
+
+ private final TypeMirror typeMirror;
+
+ TestStringSerializer(TypeMirror typeMirror) {
+ this.typeMirror = typeMirror;
+ }
+
+ @Override
+ public TypeMirror proxyFieldType() {
+ return typeMirror;
+ }
+
+ @Override
+ public CodeBlock toProxy(CodeBlock expression) {
+ return CodeBlock.of("$S", "test");
+ }
+
+ @Override
+ public CodeBlock fromProxy(CodeBlock expression) {
+ return CodeBlock.of("$S", "test");
+ }
+ }
+}