Release the SerializableAutoValue extension.

RELNOTES=Release the SerializableAutoValue extension.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=304211473
diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml
index c284f2f..d0d4a6b 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 8c36fee..ffcd9d7 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 da1c52a..0aa9ec8 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 0000000..b100e79
--- /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 0000000..96a9576
--- /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 0000000..966ce44
--- /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 0000000..e8cfc27
--- /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 0000000..12984b0
--- /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 0000000..83b8634
--- /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 0000000..7ff4f19
--- /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 0000000..9d571e3
--- /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 0000000..a5025a8
--- /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 0000000..57741f9
--- /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 0000000..96b9308
--- /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 0000000..6e38b9b
--- /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 0000000..d05c88b
--- /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 0000000..93aa600
--- /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 5f2e47b..55f9ae4 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 @@
  * @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 0000000..9974d51
--- /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 0000000..8141076
--- /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 0000000..87800b3
--- /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 0000000..159059d
--- /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 0000000..a7006f5
--- /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 0000000..e817911
--- /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 0000000..e5150f9
--- /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 0000000..0eb634a
--- /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 0000000..388977f
--- /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 0000000..7c0f204
--- /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");
+    }
+  }
+}