Merge tag auto-value-1.7.3^ into master am: b6571d386f am: 17516cb506 am: 5b568db45f

Original change: https://android-review.googlesource.com/c/platform/external/auto/+/1341934

Change-Id: I3b48264a05b8d7ec2a6d4799d4e759d183662fb5
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7480ade
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+.classpath
+.factorypath
+.project
+.settings
+eclipsebin
+
+bin
+gen
+build
+out
+lib
+
+target
+*.class
+pom.xml.*
+release.properties
+
+.idea
+*.iml
+classes
+
+obj
+
+.DS_Store
+*~
+dependency-reduced-pom.xml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b0be6fa
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,28 @@
+sudo: false
+
+language: java
+
+install:
+  - mvn -B -U -f build-pom.xml dependency:go-offline test clean --quiet --fail-never -DskipTests=true
+
+script:
+  - mvn -B -U -f build-pom.xml verify --fail-at-end -Dsource.skip=true -Dmaven.javadoc.skip=true
+
+jdk:
+  - openjdk9
+  - openjdk8
+
+env:
+  global:
+  - secure: "bWNSSMURwYC0oZWIMZRd7dy5+JdoyZ060d427TAqFRJmOkYtlR+dBbBggjeJmM0PEkQDzeBrWwln/Vq3lnRPv8czA7hSg/R33r3GzTyi1GZhjCYN2mPW8qp4qgqlloh78aaOODUNSJsOtQqPDJPmhLLfD6UCY0eq9zHhweIjYdw="
+  - secure: "s5V9d8MKl7ZHqCxuYLljLSD4sp9KLtYkk9hVxEPqCLAi4zA70WkX9h+GZI1gAOpcavomfrWcgSDT2ZReiuNpwx7OtczdS4zB+s6mo4F598iRs4bhSLiPT+Hzvx6BSwf1ZKZTYEhrUPGmKOp2T29AxMV7D0Q+P7n574ubvpUuZmA="
+  - secure: "T24JAd60zthkeLBmenvZn6+qI43uvfuLwVb70Ljhbc19XDYEZV4Zm/kaafsisP5+F6kV4GjFaT+NCq2sJlwvPSMMRvU1JJgmNVh8TmtswkC/PHKonkMkOsj2KmFP0RRSPdvQv2NrSguZUq8mg+2pvnPO0qoPg4VeIODPGtAxNb8="
+
+after_success:
+  - util/generate-latest-docs.sh
+  - util/publish-snapshot-on-commit.sh
+
+branches:
+  only:
+    - master
+    - /^release.*$/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..b5622f6
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,34 @@
+Contributing
+============
+
+If you would like to contribute code to Auto you can do so through GitHub
+by forking the repository and sending a pull request.
+
+When submitting code, please make every effort to follow existing conventions
+and style in order to keep the code as readable as possible.
+
+Where appropriate, please provide unit tests or integration tests. Unit tests
+should be JUnit based tests and can use either standard JUnit assertions or
+Truth assertions and be added to `<project>/src/test/java`.  Changes to
+code generation or other build-time behaviour should go into small maven
+projects using the `maven-invoker-plugin`.  Examples of this are in
+`generator/src/it` and can include bean-shell verification scripts and other
+facilities provided by `maven-invoker-plugin`.
+
+Please make sure your code compiles by running `mvn clean verify` which will
+execute both unit and integration test phases.  Additionally, consider using
+http://travis-ci.org to validate your branches before you even put them into
+pull requests.  All pull requests will be validated by Travis-ci in any case
+and must pass before being merged.
+
+If you are adding or modifying files you may add your own copyright line, but
+please ensure that the form is consistent with the existing files, and please
+note that a Google, Inc. copyright line must appear in every copyright notice.
+All files are released with the Apache 2.0 license and any new files may only
+be accepted under the terms of that license.
+
+Before your code can be accepted into the project you must sign the
+[Individual Contributor License Agreement (CLA)][1].
+
+
+ [1]: https://developers.google.com/open-source/cla/individual
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..366f940
--- /dev/null
+++ b/README.md
@@ -0,0 +1,59 @@
+# Auto
+
+[![Build Status](https://travis-ci.org/google/auto.svg?branch=master)](https://travis-ci.org/google/auto)
+
+A collection of source code generators for [Java][java].
+
+## Auto‽
+
+[Java][java] is full of code that is mechanical, repetitive, typically untested
+and sometimes the source of subtle bugs. _Sounds like a job for robots!_
+
+The Auto subprojects are a collection of code generators that automate those
+types of tasks. They create the code you would have written, but without
+the bugs.
+
+Save time.  Save code.  Save sanity.
+
+## Subprojects
+
+  * [AutoFactory] - JSR-330-compatible factories
+
+    [![Maven Central](https://img.shields.io/maven-central/v/com.google.auto.factory/auto-factory.svg)](https://mvnrepository.com/artifact/com.google.auto.factory/auto-factory)
+
+  * [AutoService] - Provider-configuration files for [`ServiceLoader`]
+
+    [![Maven Central](https://img.shields.io/maven-central/v/com.google.auto.service/auto-service.svg)](https://mvnrepository.com/artifact/com.google.auto.service/auto-service)
+
+  * [AutoValue] - Immutable [value-type] code generation for Java 7+.
+
+    [![Maven Central](https://img.shields.io/maven-central/v/com.google.auto.value/auto-value.svg)](https://mvnrepository.com/artifact/com.google.auto.value/auto-value)
+
+  * [Common] - Helper utilities for writing annotation processors.
+
+    [![Maven Central](https://img.shields.io/maven-central/v/com.google.auto/auto-common.svg)](https://mvnrepository.com/artifact/com.google.auto/auto-common)
+
+## License
+
+    Copyright 2013 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.
+
+[AutoFactory]: https://github.com/google/auto/tree/master/factory
+[AutoService]: https://github.com/google/auto/tree/master/service
+[AutoValue]: https://github.com/google/auto/tree/master/value
+[Common]: https://github.com/google/auto/tree/master/common
+
+[java]: https://en.wikipedia.org/wiki/Java_(programming_language)
+[value-type]: http://en.wikipedia.org/wiki/Value_object
+[`ServiceLoader`]: http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html
diff --git a/build-pom.xml b/build-pom.xml
new file mode 100644
index 0000000..45a3113
--- /dev/null
+++ b/build-pom.xml
@@ -0,0 +1,26 @@
+<!-- A pure convenience for local builds and travis-ci. -->
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>build-only</groupId>
+  <artifactId>build-only</artifactId>
+  <version>NO_VERSION</version>
+  <packaging>pom</packaging>
+  <modules>
+    <module>common</module>
+    <module>factory</module>
+    <module>service</module>
+    <module>value</module>
+  </modules>
+  <distributionManagement>
+    <repository>
+      <id>sonatype-nexus-staging</id>
+      <name>Nexus Release Repository</name>
+      <url>file:///tmp/auto_project_maven_fake_repo/</url>
+    </repository>
+    <snapshotRepository>
+      <id>sonatype-nexus-snapshots</id>
+      <name>Sonatype Nexus Snapshots</name>
+      <url>file:///tmp/auto_project_maven_fake_repo/</url>
+    </snapshotRepository>
+  </distributionManagement>
+</project>
diff --git a/common/README.md b/common/README.md
new file mode 100644
index 0000000..990aa31
--- /dev/null
+++ b/common/README.md
@@ -0,0 +1,79 @@
+Auto Common Utilities
+========
+
+## Overview
+
+The Auto project has a set of common utilities to help ease use of the annotation processing
+environment.
+
+## Utility classes of note
+
+  * MoreTypes - utilities and Equivalence wrappers for TypeMirror and related subtypes
+  * MoreElements - utilities for Element and related subtypes
+  * SuperficialValidation - very simple scanner to ensure an Element is valid and free from
+    distortion from upstream compilation errors
+  * Visibility - utilities for working with Elements' visibility levels (public, protected, etc.)
+  * BasicAnnotationProcessor/ProcessingStep - simple types that
+    - implement a validating annotation processor
+    - defer invalid elements until later
+    - break processor actions into multiple steps (which may each handle different annotations)
+
+## Usage/Setup
+
+Auto common utilities have a standard [Maven](http://maven.apache.org) setup which can also be
+used from Gradle, Ivy, Ant, or other systems which consume binary artifacts from the central Maven
+binary artifact repositories.
+
+```xml
+<dependency>
+  <groupId>com.google.auto</groupId>
+  <artifactId>auto-common</artifactId>
+  <version>1.0-SNAPSHOT</version> <!-- or use a known release version -->
+</dependency>
+```
+
+## Processor Resilience
+
+Auto Common Utilities is used by a variety of annotation processors in Google and new versions
+may have breaking changes.  Users of auto-common are urged to use
+[shade](https://maven.apache.org/plugins/maven-shade-plugin/) or
+[jarjar](https://code.google.com/p/jarjar/) (or something similar) in packaging their processors
+so that conflicting versions of this library do not adversely interact with each other.
+
+For example, in a Maven build you can repackage `com.google.auto.common` into
+`your.processor.shaded.auto.common` like this:
+
+```xml
+<project>
+  <!-- your other config -->
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-shade-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <artifactSet>
+                <excludes>
+                  <!-- exclude dependencies you don't want to bundle in your processor -->
+                </excludes>
+              </artifactSet>
+              <relocations>
+                <relocation>
+                  <pattern>com.google.auto.common</pattern>
+                  <shadedPattern>your.processor.shaded.auto.common</shadedPattern>
+                </relocation>
+              </relocations>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
+```
+
diff --git a/common/pom.xml b/common/pom.xml
new file mode 100644
index 0000000..7d1c877
--- /dev/null
+++ b/common/pom.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2014 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+
+  <groupId>com.google.auto</groupId>
+  <artifactId>auto-common</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>Auto Common Libraries</name>
+  <description>
+    Common utilities for creating annotation processors.
+  </description>
+  <url>https://github.com/google/auto/tree/master/common</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <java.version>1.8</java.version>
+    <guava.version>27.0.1-jre</guava.version>
+    <truth.version>1.0.1</truth.version>
+  </properties>
+
+  <scm>
+    <url>http://github.com/google/auto</url>
+    <connection>scm:git:git://github.com/google/auto.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/google/auto.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <issueManagement>
+    <system>GitHub Issues</system>
+    <url>http://github.com/google/auto/issues</url>
+  </issueManagement>
+
+  <licenses>
+    <license>
+      <name>Apache 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+    </license>
+  </licenses>
+
+  <organization>
+    <name>Google LLC</name>
+    <url>http://www.google.com</url>
+  </organization>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <version>${guava.version}</version>
+    </dependency>
+    <dependency>
+      <!-- Used only by GeneratedAnnotationSpecs.
+           If you use JavaPoet, you can use GeneratedAnnotationSpecs. -->
+      <groupId>com.squareup</groupId>
+      <artifactId>javapoet</artifactId>
+      <version>1.9.0</version>
+      <optional>true</optional>
+    </dependency>
+
+    <!-- test dependencies -->
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava-testlib</artifactId>
+      <version>${guava.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.testing.compile</groupId>
+      <artifactId>compile-testing</artifactId>
+      <version>0.18</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <version>${truth.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jdt</groupId>
+      <artifactId>ecj</artifactId>
+      <version>3.20.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.7.0</version>
+        <configuration>
+          <source>${java.version}</source>
+          <target>${java.version}</target>
+          <compilerArgument>-Xlint:all</compilerArgument>
+          <showWarnings>true</showWarnings>
+          <showDeprecation>true</showDeprecation>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-java</artifactId>
+            <version>0.9.4</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>3.0.2</version>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
new file mode 100644
index 0000000..62e5834
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Equivalence;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+
+/**
+ * A utility class for working with {@link AnnotationMirror} instances.
+ *
+ * @author Gregory Kick
+ */
+public final class AnnotationMirrors {
+  private static final Equivalence<AnnotationMirror> ANNOTATION_MIRROR_EQUIVALENCE =
+      new Equivalence<AnnotationMirror>() {
+        @Override
+        protected boolean doEquivalent(AnnotationMirror left, AnnotationMirror right) {
+          return MoreTypes.equivalence().equivalent(left.getAnnotationType(),
+              right.getAnnotationType()) && AnnotationValues.equivalence().pairwise().equivalent(
+              getAnnotationValuesWithDefaults(left).values(),
+              getAnnotationValuesWithDefaults(right).values());
+        }
+        @Override
+        protected int doHash(AnnotationMirror annotation) {
+          DeclaredType type = annotation.getAnnotationType();
+          Iterable<AnnotationValue> annotationValues =
+              getAnnotationValuesWithDefaults(annotation).values();
+          return Arrays.hashCode(new int[] {MoreTypes.equivalence().hash(type),
+              AnnotationValues.equivalence().pairwise().hash(annotationValues)});
+        }
+    };
+
+  /**
+   * Returns an {@link Equivalence} for {@link AnnotationMirror} as some implementations
+   * delegate equality tests to {@link Object#equals} whereas the documentation explicitly
+   * states that instance/reference equality is not the proper test.
+   */
+  public static Equivalence<AnnotationMirror> equivalence() {
+    return ANNOTATION_MIRROR_EQUIVALENCE;
+  }
+
+  /**
+   * Returns the {@link AnnotationMirror}'s map of {@link AnnotationValue} indexed by {@link
+   * ExecutableElement}, supplying default values from the annotation if the annotation property has
+   * not been set. This is equivalent to {@link
+   * Elements#getElementValuesWithDefaults(AnnotationMirror)} but can be called statically without
+   * an {@link Elements} instance.
+   *
+   * <p>The iteration order of elements of the returned map will be the order in which the {@link
+   * ExecutableElement}s are defined in {@code annotation}'s {@linkplain
+   * AnnotationMirror#getAnnotationType() type}.
+   */
+  public static ImmutableMap<ExecutableElement, AnnotationValue> getAnnotationValuesWithDefaults(
+      AnnotationMirror annotation) {
+    ImmutableMap.Builder<ExecutableElement, AnnotationValue> values = ImmutableMap.builder();
+    Map<? extends ExecutableElement, ? extends AnnotationValue> declaredValues =
+        annotation.getElementValues();
+    for (ExecutableElement method :
+        ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) {
+      // Must iterate and put in this order, to ensure consistency in generated code.
+      if (declaredValues.containsKey(method)) {
+        values.put(method, declaredValues.get(method));
+      } else if (method.getDefaultValue() != null) {
+        values.put(method, method.getDefaultValue());
+      } else {
+        throw new IllegalStateException(
+            "Unset annotation value without default should never happen: "
+            + MoreElements.asType(method.getEnclosingElement()).getQualifiedName()
+            + '.' + method.getSimpleName() + "()");
+      }
+    }
+    return values.build();
+  }
+
+  /**
+   * Returns an {@link AnnotationValue} for the named element if such an element was
+   * either declared in the usage represented by the provided {@link AnnotationMirror}, or if
+   * such an element was defined with a default.
+   *
+   * @throws IllegalArgumentException if no element is defined with the given elementName.
+   */
+  public static AnnotationValue getAnnotationValue(
+      AnnotationMirror annotationMirror, String elementName) {
+    return getAnnotationElementAndValue(annotationMirror, elementName).getValue();
+  }
+
+  /**
+   * Returns a {@link ExecutableElement} and its associated {@link AnnotationValue} if such
+   * an element was either declared in the usage represented by the provided
+   * {@link AnnotationMirror}, or if such an element was defined with a default.
+   *
+   * @throws IllegalArgumentException if no element is defined with the given elementName.
+   */
+  public static Map.Entry<ExecutableElement, AnnotationValue> getAnnotationElementAndValue(
+      AnnotationMirror annotationMirror, final String elementName) {
+    checkNotNull(annotationMirror);
+    checkNotNull(elementName);
+    for (Map.Entry<ExecutableElement, AnnotationValue> entry :
+        getAnnotationValuesWithDefaults(annotationMirror).entrySet()) {
+      if (entry.getKey().getSimpleName().contentEquals(elementName)) {
+        return entry;
+      }
+    }
+    throw new IllegalArgumentException(String.format("@%s does not define an element %s()",
+        MoreElements.asType(annotationMirror.getAnnotationType().asElement()).getQualifiedName(),
+        elementName));
+  }
+
+  /**
+   * Returns all {@linkplain AnnotationMirror annotations} that are present on the given
+   * {@link Element} which are themselves annotated with {@code annotationType}.
+   */
+  public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations(Element element,
+      final Class<? extends Annotation> annotationType) {
+    List<? extends AnnotationMirror> annotations = element.getAnnotationMirrors();
+    return FluentIterable.from(annotations)
+        .filter(new Predicate<AnnotationMirror>() {
+          @Override public boolean apply(AnnotationMirror input) {
+            return isAnnotationPresent(input.getAnnotationType().asElement(), annotationType);
+          }
+        })
+        .toSet();
+  }
+
+  private AnnotationMirrors() {}
+}
diff --git a/common/src/main/java/com/google/auto/common/AnnotationValues.java b/common/src/main/java/com/google/auto/common/AnnotationValues.java
new file mode 100644
index 0000000..e233901
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/AnnotationValues.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.base.Equivalence;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.function.Function;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+
+/**
+ * A utility class for working with {@link AnnotationValue} instances.
+ *
+ * @author Christian Gruber
+ */
+public final class AnnotationValues {
+  private static final Equivalence<AnnotationValue> ANNOTATION_VALUE_EQUIVALENCE =
+      new Equivalence<AnnotationValue>() {
+        @Override protected boolean doEquivalent(AnnotationValue left, AnnotationValue right) {
+          return left.accept(new SimpleAnnotationValueVisitor8<Boolean, AnnotationValue>() {
+            // LHS is not an annotation or array of annotation values, so just test equality.
+            @Override protected Boolean defaultAction(Object left, AnnotationValue right) {
+              return left.equals(right.accept(
+                  new SimpleAnnotationValueVisitor8<Object, Void>() {
+                    @Override protected Object defaultAction(Object object, Void unused) {
+                      return object;
+                    }
+                  }, null));
+            }
+
+            // LHS is an annotation mirror so test equivalence for RHS annotation mirrors
+            // and false for other types.
+            @Override public Boolean visitAnnotation(AnnotationMirror left, AnnotationValue right) {
+              return right.accept(
+                  new SimpleAnnotationValueVisitor8<Boolean, AnnotationMirror>() {
+                    @Override protected Boolean defaultAction(Object right, AnnotationMirror left) {
+                      return false; // Not an annotation mirror, so can't be equal to such.
+                    }
+                    @Override
+                    public Boolean visitAnnotation(AnnotationMirror right, AnnotationMirror left) {
+                      return AnnotationMirrors.equivalence().equivalent(left, right);
+                    }
+                  }, left);
+            }
+
+            // LHS is a list of annotation values have to collect-test equivalences, or false
+            // for any other types.
+            @Override
+            public Boolean visitArray(List<? extends AnnotationValue> left, AnnotationValue right) {
+              return right.accept(
+                  new SimpleAnnotationValueVisitor8<Boolean, List<? extends AnnotationValue>>() {
+                    @Override protected Boolean defaultAction(
+                        Object ignored, List<? extends AnnotationValue> alsoIgnored) {
+                      return false; // Not an array, so can't be equal to such.
+                    }
+
+                    @SuppressWarnings("unchecked") // safe covariant cast
+                    @Override public Boolean visitArray(
+                        List<? extends AnnotationValue> right ,
+                        List<? extends AnnotationValue> left) {
+                      return AnnotationValues.equivalence().pairwise().equivalent(
+                          (List<AnnotationValue>) left, (List<AnnotationValue>) right);
+                    }
+                  }, left);
+            }
+
+            @Override
+            public Boolean visitType(TypeMirror left, AnnotationValue right) {
+              return right.accept(
+                  new SimpleAnnotationValueVisitor8<Boolean, TypeMirror>() {
+                    @Override protected Boolean defaultAction(
+                        Object ignored, TypeMirror alsoIgnored) {
+                      return false; // Not an annotation mirror, so can't be equal to such.
+                    }
+
+                    @Override public Boolean visitType(TypeMirror right, TypeMirror left) {
+                      return MoreTypes.equivalence().equivalent(left, right);
+                    }
+                  }, left);
+            }
+          }, right);
+        }
+
+        @Override protected int doHash(AnnotationValue value) {
+          return value.accept(new SimpleAnnotationValueVisitor8<Integer, Void>() {
+            @Override public Integer visitAnnotation(AnnotationMirror value, Void ignore) {
+              return AnnotationMirrors.equivalence().hash(value);
+            }
+
+            @SuppressWarnings("unchecked") // safe covariant cast
+            @Override public Integer visitArray(
+                List<? extends AnnotationValue> values, Void ignore) {
+              return AnnotationValues.equivalence().pairwise().hash((List<AnnotationValue>) values);
+            }
+
+            @Override public Integer visitType(TypeMirror value, Void ignore) {
+              return MoreTypes.equivalence().hash(value);
+            }
+
+            @Override protected Integer defaultAction(Object value, Void ignored) {
+              return value.hashCode();
+            }
+          }, null);
+        }
+      };
+
+  /**
+   * Returns an {@link Equivalence} for {@link AnnotationValue} as annotation values may
+   * contain {@link AnnotationMirror} instances some of whose implementations delegate
+   * equality tests to {@link Object#equals} whereas the documentation explicitly states
+   * that instance/reference equality is not the proper test.
+   *
+   * @see AnnotationMirrors#equivalence()
+   */
+  public static Equivalence<AnnotationValue> equivalence() {
+    return ANNOTATION_VALUE_EQUIVALENCE;
+  }
+
+  private static class DefaultVisitor<T> extends SimpleAnnotationValueVisitor8<T, Void> {
+    final Class<T> clazz;
+
+    DefaultVisitor(Class<T> clazz) {
+      this.clazz = checkNotNull(clazz);
+    }
+
+    @Override
+    public T defaultAction(Object o, Void unused) {
+      throw new IllegalArgumentException(
+          "Expected a " + clazz.getSimpleName() + ", got instead: " + o);
+    }
+  }
+
+  private static final class TypeMirrorVisitor extends DefaultVisitor<DeclaredType> {
+    static final TypeMirrorVisitor INSTANCE = new TypeMirrorVisitor();
+
+    TypeMirrorVisitor() {
+      super(DeclaredType.class);
+    }
+
+    @Override
+    public DeclaredType visitType(TypeMirror value, Void unused) {
+      return MoreTypes.asDeclared(value);
+    }
+  }
+  ;
+
+  /**
+   * Returns the value as a class.
+   *
+   * @throws IllegalArgumentException if the value is not a class.
+   */
+  public static DeclaredType getTypeMirror(AnnotationValue value) {
+    return TypeMirrorVisitor.INSTANCE.visit(value);
+  }
+
+  private static final class AnnotationMirrorVisitor extends DefaultVisitor<AnnotationMirror> {
+    static final AnnotationMirrorVisitor INSTANCE = new AnnotationMirrorVisitor();
+
+    AnnotationMirrorVisitor() {
+      super(AnnotationMirror.class);
+    }
+
+    @Override
+    public AnnotationMirror visitAnnotation(AnnotationMirror value, Void unused) {
+      return value;
+    }
+  }
+  ;
+
+  /**
+   * Returns the value as an AnnotationMirror.
+   *
+   * @throws IllegalArgumentException if the value is not an annotation.
+   */
+  public static AnnotationMirror getAnnotationMirror(AnnotationValue value) {
+    return AnnotationMirrorVisitor.INSTANCE.visit(value);
+  }
+
+  private static final class EnumVisitor extends DefaultVisitor<VariableElement> {
+    static final EnumVisitor INSTANCE = new EnumVisitor();
+
+    EnumVisitor() {
+      super(VariableElement.class);
+    }
+
+    @Override
+    public VariableElement visitEnumConstant(VariableElement value, Void unused) {
+      return value;
+    }
+  }
+  ;
+
+  /**
+   * Returns the value as a VariableElement.
+   *
+   * @throws IllegalArgumentException if the value is not an enum.
+   */
+  public static VariableElement getEnum(AnnotationValue value) {
+    return EnumVisitor.INSTANCE.visit(value);
+  }
+
+  private static <T> T valueOfType(AnnotationValue annotationValue, Class<T> type) {
+    Object value = annotationValue.getValue();
+    if (!type.isInstance(value)) {
+      throw new IllegalArgumentException(
+          "Expected " + type.getSimpleName() + ", got instead: " + value);
+    }
+    return type.cast(value);
+  }
+
+  /**
+   * Returns the value as a string.
+   *
+   * @throws IllegalArgumentException if the value is not a string.
+   */
+  public static String getString(AnnotationValue value) {
+    return valueOfType(value, String.class);
+  }
+
+  /**
+   * Returns the value as an int.
+   *
+   * @throws IllegalArgumentException if the value is not an int.
+   */
+  public static int getInt(AnnotationValue value) {
+    return valueOfType(value, Integer.class);
+  }
+
+  /**
+   * Returns the value as a long.
+   *
+   * @throws IllegalArgumentException if the value is not a long.
+   */
+  public static long getLong(AnnotationValue value) {
+    return valueOfType(value, Long.class);
+  }
+
+  /**
+   * Returns the value as a byte.
+   *
+   * @throws IllegalArgumentException if the value is not a byte.
+   */
+  public static byte getByte(AnnotationValue value) {
+    return valueOfType(value, Byte.class);
+  }
+
+  /**
+   * Returns the value as a short.
+   *
+   * @throws IllegalArgumentException if the value is not a short.
+   */
+  public static short getShort(AnnotationValue value) {
+    return valueOfType(value, Short.class);
+  }
+
+  /**
+   * Returns the value as a float.
+   *
+   * @throws IllegalArgumentException if the value is not a float.
+   */
+  public static float getFloat(AnnotationValue value) {
+    return valueOfType(value, Float.class);
+  }
+
+  /**
+   * Returns the value as a double.
+   *
+   * @throws IllegalArgumentException if the value is not a double.
+   */
+  public static double getDouble(AnnotationValue value) {
+    return valueOfType(value, Double.class);
+  }
+
+  /**
+   * Returns the value as a boolean.
+   *
+   * @throws IllegalArgumentException if the value is not a boolean.
+   */
+  public static boolean getBoolean(AnnotationValue value) {
+    return valueOfType(value, Boolean.class);
+  }
+
+  /**
+   * Returns the value as a char.
+   *
+   * @throws IllegalArgumentException if the value is not a char.
+   */
+  public static char getChar(AnnotationValue value) {
+    return valueOfType(value, Character.class);
+  }
+
+  private static final class ArrayVisitor<T>
+      extends SimpleAnnotationValueVisitor8<ImmutableList<T>, Void> {
+    final Function<AnnotationValue, T> visitT;
+
+    ArrayVisitor(Function<AnnotationValue, T> visitT) {
+      this.visitT = checkNotNull(visitT);
+    }
+
+    @Override
+    public ImmutableList<T> defaultAction(Object o, Void unused) {
+      throw new IllegalStateException("Expected an array, got instead: " + o);
+    }
+
+    @Override
+    public ImmutableList<T> visitArray(List<? extends AnnotationValue> values, Void unused) {
+      return values.stream().map(visitT).collect(toImmutableList());
+    }
+  }
+
+  private static final ArrayVisitor<DeclaredType> TYPE_MIRRORS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getTypeMirror);
+
+  /**
+   * Returns the value as a list of classes.
+   *
+   * @throws IllegalArgumentException if the value is not an array of classes.
+   */
+  public static ImmutableList<DeclaredType> getTypeMirrors(AnnotationValue value) {
+    return TYPE_MIRRORS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<AnnotationMirror> ANNOTATION_MIRRORS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getAnnotationMirror);
+
+  /**
+   * Returns the value as a list of annotations.
+   *
+   * @throws IllegalArgumentException if the value if not an array of annotations.
+   */
+  public static ImmutableList<AnnotationMirror> getAnnotationMirrors(AnnotationValue value) {
+    return ANNOTATION_MIRRORS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<VariableElement> ENUMS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getEnum);
+
+  /**
+   * Returns the value as a list of enums.
+   *
+   * @throws IllegalArgumentException if the value is not an array of enums.
+   */
+  public static ImmutableList<VariableElement> getEnums(AnnotationValue value) {
+    return ENUMS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<String> STRINGS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getString);
+
+  /**
+   * Returns the value as a list of strings.
+   *
+   * @throws IllegalArgumentException if the value is not an array of strings.
+   */
+  public static ImmutableList<String> getStrings(AnnotationValue value) {
+    return STRINGS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<Integer> INTS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getInt);
+
+  /**
+   * Returns the value as a list of integers.
+   *
+   * @throws IllegalArgumentException if the value is not an array of ints.
+   */
+  public static ImmutableList<Integer> getInts(AnnotationValue value) {
+    return INTS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<Long> LONGS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getLong);
+
+  /**
+   * Returns the value as a list of longs.
+   *
+   * @throws IllegalArgumentException if the value is not an array of longs.
+   */
+  public static ImmutableList<Long> getLongs(AnnotationValue value) {
+    return LONGS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<Byte> BYTES_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getByte);
+
+  /**
+   * Returns the value as a list of bytes.
+   *
+   * @throws IllegalArgumentException if the value is not an array of bytes.
+   */
+  public static ImmutableList<Byte> getBytes(AnnotationValue value) {
+    return BYTES_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<Short> SHORTS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getShort);
+  /**
+   * Returns the value as a list of shorts.
+   *
+   * @throws IllegalArgumentException if the value is not an array of shorts.
+   */
+  public static ImmutableList<Short> getShorts(AnnotationValue value) {
+    return SHORTS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<Float> FLOATS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getFloat);
+
+  /**
+   * Returns the value as a list of floats.
+   *
+   * @throws IllegalArgumentException if the value is not an array of floats.
+   */
+  public static ImmutableList<Float> getFloats(AnnotationValue value) {
+    return FLOATS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<Double> DOUBLES_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getDouble);
+
+  /**
+   * Returns the value as a list of doubles.
+   *
+   * @throws IllegalArgumentException if the value is not an array of doubles.
+   */
+  public static ImmutableList<Double> getDoubles(AnnotationValue value) {
+    return DOUBLES_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<Boolean> BOOLEANS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getBoolean);
+
+  /**
+   * Returns the value as a list of booleans.
+   *
+   * @throws IllegalArgumentException if the value is not an array of booleans.
+   */
+  public static ImmutableList<Boolean> getBooleans(AnnotationValue value) {
+    return BOOLEANS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<Character> CHARS_VISITOR =
+      new ArrayVisitor<>(AnnotationValues::getChar);
+
+  /**
+   * Returns the value as a list of characters.
+   *
+   * @throws IllegalArgumentException if the value is not an array of chars.
+   */
+  public static ImmutableList<Character> getChars(AnnotationValue value) {
+    return CHARS_VISITOR.visit(value);
+  }
+
+  private static final ArrayVisitor<AnnotationValue> ANNOTATION_VALUES_VISITOR =
+      new ArrayVisitor<>(x -> x);
+
+  /**
+   * Returns the value as a list of {@link AnnotationValue}s.
+   *
+   * @throws IllegalArgumentException if the value is not an array.
+   */
+  public static ImmutableList<AnnotationValue> getAnnotationValues(AnnotationValue value) {
+    return ANNOTATION_VALUES_VISITOR.visit(value);
+  }
+
+  private AnnotationValues() {}
+}
+
diff --git a/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java
new file mode 100644
index 0000000..d3f67aa
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.auto.common.MoreElements.asExecutable;
+import static com.google.auto.common.MoreElements.asPackage;
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.auto.common.SuperficialValidation.validateElement;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Multimaps.filterKeys;
+import static javax.lang.model.element.ElementKind.PACKAGE;
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import com.google.common.base.Ascii;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import java.lang.annotation.Annotation;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleElementVisitor8;
+
+/**
+ * An abstract {@link Processor} implementation that defers processing of {@link Element}s to later
+ * rounds if they cannot be processed.
+ *
+ * <p>Subclasses put their processing logic in {@link ProcessingStep} implementations. The steps are
+ * passed to the processor by returning them in the {@link #initSteps()} method, and can access the
+ * {@link ProcessingEnvironment} using {@link #processingEnv}.
+ *
+ * <p>Any logic that needs to happen once per round can be specified by overriding {@link
+ * #postRound(RoundEnvironment)}.
+ *
+ * <h3>Ill-formed elements are deferred</h3>
+ *
+ * Any annotated element whose nearest enclosing type is not well-formed is deferred, and not passed
+ * to any {@code ProcessingStep}. This helps processors to avoid many common pitfalls, such as
+ * {@link ErrorType} instances, {@link ClassCastException}s and badly coerced types.
+ *
+ * <p>A non-package element is considered well-formed if its type, type parameters, parameters,
+ * default values, supertypes, annotations, and enclosed elements are. Package elements are treated
+ * similarly, except that their enclosed elements are not validated. See {@link
+ * SuperficialValidation#validateElement(Element)} for details.
+ *
+ * <p>The primary disadvantage to this validation is that any element that forms a circular
+ * dependency with a type generated by another {@code BasicAnnotationProcessor} will never compile
+ * because the element will never be fully complete. All such compilations will fail with an error
+ * message on the offending type that describes the issue.
+ *
+ * <h3>Each {@code ProcessingStep} can defer elements</h3>
+ *
+ * <p>Each {@code ProcessingStep} can defer elements by including them in the set returned by {@link
+ * ProcessingStep#process(SetMultimap)}; elements deferred by a step will be passed back to that
+ * step in a later round of processing.
+ *
+ * <p>This feature is useful when one processor may depend on code generated by another, independent
+ * processor, in a way that isn't caught by the well-formedness check described above. For example,
+ * if an element {@code A} cannot be processed because processing it depends on the existence of
+ * some class {@code B}, then {@code A} should be deferred until a later round of processing, when
+ * {@code B} will have been generated by another processor.
+ *
+ * <p>If {@code A} directly references {@code B}, then the well-formedness check will correctly
+ * defer processing of {@code A} until {@code B} has been generated.
+ *
+ * <p>However, if {@code A} references {@code B} only indirectly (for example, from within a method
+ * body), then the well-formedness check will not defer processing {@code A}, but a processing step
+ * can reject {@code A}.
+ */
+public abstract class BasicAnnotationProcessor extends AbstractProcessor {
+
+  private final Set<ElementName> deferredElementNames = new LinkedHashSet<>();
+  private final SetMultimap<ProcessingStep, ElementName> elementsDeferredBySteps =
+      LinkedHashMultimap.create();
+
+  private Elements elements;
+  private Messager messager;
+  private ImmutableList<? extends ProcessingStep> steps;
+
+  @Override
+  public final synchronized void init(ProcessingEnvironment processingEnv) {
+    super.init(processingEnv);
+    this.elements = processingEnv.getElementUtils();
+    this.messager = processingEnv.getMessager();
+    this.steps = ImmutableList.copyOf(initSteps());
+  }
+
+  /**
+   * Creates {@linkplain ProcessingStep processing steps} for this processor. {@link #processingEnv}
+   * is guaranteed to be set when this method is invoked.
+   */
+  protected abstract Iterable<? extends ProcessingStep> initSteps();
+
+  /**
+   * An optional hook for logic to be executed at the end of each round.
+   *
+   * @deprecated use {@link #postRound(RoundEnvironment)} instead
+   */
+  @Deprecated
+  protected void postProcess() {}
+
+  /** An optional hook for logic to be executed at the end of each round. */
+  protected void postRound(RoundEnvironment roundEnv) {
+    if (!roundEnv.processingOver()) {
+      postProcess();
+    }
+  }
+
+  private ImmutableSet<? extends Class<? extends Annotation>> getSupportedAnnotationClasses() {
+    checkState(steps != null);
+    ImmutableSet.Builder<Class<? extends Annotation>> builder = ImmutableSet.builder();
+    for (ProcessingStep step : steps) {
+      builder.addAll(step.annotations());
+    }
+    return builder.build();
+  }
+
+  /**
+   * Returns the set of supported annotation types as a collected from registered {@linkplain
+   * ProcessingStep processing steps}.
+   */
+  @Override
+  public final ImmutableSet<String> getSupportedAnnotationTypes() {
+    ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+    for (Class<? extends Annotation> annotationClass : getSupportedAnnotationClasses()) {
+      builder.add(annotationClass.getCanonicalName());
+    }
+    return builder.build();
+  }
+
+  @Override
+  public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    checkState(elements != null);
+    checkState(messager != null);
+    checkState(steps != null);
+
+    // If this is the last round, report all of the missing elements if there
+    // were no errors raised in the round; otherwise reporting the missing
+    // elements just adds noise the output.
+    if (roundEnv.processingOver()) {
+      postRound(roundEnv);
+      if (!roundEnv.errorRaised()) {
+        reportMissingElements(
+            ImmutableSet.<ElementName>builder()
+                .addAll(deferredElementNames)
+                .addAll(elementsDeferredBySteps.values())
+                .build());
+      }
+      return false;
+    }
+
+    process(validElements(roundEnv));
+
+    postRound(roundEnv);
+
+    return false;
+  }
+
+  /** Processes the valid elements, including those previously deferred by each step. */
+  private void process(ImmutableSetMultimap<Class<? extends Annotation>, Element> validElements) {
+    for (ProcessingStep step : steps) {
+      ImmutableSetMultimap<Class<? extends Annotation>, Element> stepElements =
+          new ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element>()
+              .putAll(indexByAnnotation(elementsDeferredBySteps.get(step), step.annotations()))
+              .putAll(filterKeys(validElements, Predicates.<Object>in(step.annotations())))
+              .build();
+      if (stepElements.isEmpty()) {
+        elementsDeferredBySteps.removeAll(step);
+      } else {
+        Set<? extends Element> rejectedElements = step.process(stepElements);
+        elementsDeferredBySteps.replaceValues(
+            step, transform(rejectedElements, ElementName::forAnnotatedElement));
+      }
+    }
+  }
+
+  private void reportMissingElements(Set<ElementName> missingElementNames) {
+    for (ElementName missingElementName : missingElementNames) {
+      Optional<? extends Element> missingElement = missingElementName.getElement(elements);
+      if (missingElement.isPresent()) {
+        messager.printMessage(
+            ERROR,
+            processingErrorMessage(
+                "this " + Ascii.toLowerCase(missingElement.get().getKind().name())),
+            missingElement.get());
+      } else {
+        messager.printMessage(ERROR, processingErrorMessage(missingElementName.name()));
+      }
+    }
+  }
+
+  private String processingErrorMessage(String target) {
+    return String.format(
+        "[%s:MiscError] %s was unable to process %s because not all of its dependencies could be "
+            + "resolved. Check for compilation errors or a circular dependency with generated "
+            + "code.",
+        getClass().getSimpleName(), getClass().getCanonicalName(), target);
+  }
+
+  /**
+   * Returns the valid annotated elements contained in all of the deferred elements. If none are
+   * found for a deferred element, defers it again.
+   */
+  private ImmutableSetMultimap<Class<? extends Annotation>, Element> validElements(
+      RoundEnvironment roundEnv) {
+    ImmutableSet<ElementName> prevDeferredElementNames = ImmutableSet.copyOf(deferredElementNames);
+    deferredElementNames.clear();
+
+    ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element>
+        deferredElementsByAnnotationBuilder = ImmutableSetMultimap.builder();
+    for (ElementName deferredElementName : prevDeferredElementNames) {
+      Optional<? extends Element> deferredElement = deferredElementName.getElement(elements);
+      if (deferredElement.isPresent()) {
+        findAnnotatedElements(
+            deferredElement.get(),
+            getSupportedAnnotationClasses(),
+            deferredElementsByAnnotationBuilder);
+      } else {
+        deferredElementNames.add(deferredElementName);
+      }
+    }
+
+    ImmutableSetMultimap<Class<? extends Annotation>, Element> deferredElementsByAnnotation =
+        deferredElementsByAnnotationBuilder.build();
+
+    ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> validElements =
+        ImmutableSetMultimap.builder();
+
+    Set<ElementName> validElementNames = new LinkedHashSet<>();
+
+    // Look at the elements we've found and the new elements from this round and validate them.
+    for (Class<? extends Annotation> annotationClass : getSupportedAnnotationClasses()) {
+      // This should just call roundEnv.getElementsAnnotatedWith(Class) directly, but there is a bug
+      // in some versions of eclipse that cause that method to crash.
+      TypeElement annotationType = elements.getTypeElement(annotationClass.getCanonicalName());
+      Set<? extends Element> roundElements =
+          (annotationType == null)
+              ? ImmutableSet.<Element>of()
+              : roundEnv.getElementsAnnotatedWith(annotationType);
+      ImmutableSet<Element> prevRoundElements = deferredElementsByAnnotation.get(annotationClass);
+      for (Element element : Sets.union(roundElements, prevRoundElements)) {
+        ElementName elementName = ElementName.forAnnotatedElement(element);
+        boolean isValidElement =
+            validElementNames.contains(elementName)
+                || (!deferredElementNames.contains(elementName)
+                    && validateElement(
+                        element.getKind().equals(PACKAGE) ? element : getEnclosingType(element)));
+        if (isValidElement) {
+          validElements.put(annotationClass, element);
+          validElementNames.add(elementName);
+        } else {
+          deferredElementNames.add(elementName);
+        }
+      }
+    }
+
+    return validElements.build();
+  }
+
+  private ImmutableSetMultimap<Class<? extends Annotation>, Element> indexByAnnotation(
+      Set<ElementName> annotatedElements,
+      Set<? extends Class<? extends Annotation>> annotationClasses) {
+    ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> deferredElements =
+        ImmutableSetMultimap.builder();
+    for (ElementName elementName : annotatedElements) {
+      Optional<? extends Element> element = elementName.getElement(elements);
+      if (element.isPresent()) {
+        findAnnotatedElements(element.get(), annotationClasses, deferredElements);
+      }
+    }
+    return deferredElements.build();
+  }
+
+  /**
+   * Adds {@code element} and its enclosed elements to {@code annotatedElements} if they are
+   * annotated with any annotations in {@code annotationClasses}. Does not traverse to member types
+   * of {@code element}, so that if {@code Outer} is passed in the example below, looking for
+   * {@code @X}, then {@code Outer}, {@code Outer.foo}, and {@code Outer.foo()} will be added to the
+   * multimap, but neither {@code Inner} nor its members will.
+   *
+   * <pre><code>
+   *   {@literal @}X class Outer {
+   *     {@literal @}X Object foo;
+   *     {@literal @}X void foo() {}
+   *     {@literal @}X static class Inner {
+   *       {@literal @}X Object bar;
+   *       {@literal @}X void bar() {}
+   *     }
+   *   }
+   * </code></pre>
+   */
+  private static void findAnnotatedElements(
+      Element element,
+      Set<? extends Class<? extends Annotation>> annotationClasses,
+      ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> annotatedElements) {
+    for (Element enclosedElement : element.getEnclosedElements()) {
+      if (!enclosedElement.getKind().isClass() && !enclosedElement.getKind().isInterface()) {
+        findAnnotatedElements(enclosedElement, annotationClasses, annotatedElements);
+      }
+    }
+
+    // element.getEnclosedElements() does NOT return parameter elements
+    if (element instanceof ExecutableElement) {
+      for (Element parameterElement : asExecutable(element).getParameters()) {
+        findAnnotatedElements(parameterElement, annotationClasses, annotatedElements);
+      }
+    }
+    for (Class<? extends Annotation> annotationClass : annotationClasses) {
+      if (isAnnotationPresent(element, annotationClass)) {
+        annotatedElements.put(annotationClass, element);
+      }
+    }
+  }
+
+  /**
+   * Returns the nearest enclosing {@link TypeElement} to the current element, throwing an {@link
+   * IllegalArgumentException} if the provided {@link Element} is a {@link PackageElement} or is
+   * otherwise not enclosed by a type.
+   */
+  // TODO(cgruber) move to MoreElements and make public.
+  private static TypeElement getEnclosingType(Element element) {
+    return element.accept(
+        new SimpleElementVisitor8<TypeElement, Void>() {
+          @Override
+          protected TypeElement defaultAction(Element e, Void p) {
+            return e.getEnclosingElement().accept(this, p);
+          }
+
+          @Override
+          public TypeElement visitType(TypeElement e, Void p) {
+            return e;
+          }
+
+          @Override
+          public TypeElement visitPackage(PackageElement e, Void p) {
+            throw new IllegalArgumentException();
+          }
+        },
+        null);
+  }
+
+  /**
+   * The unit of processing logic that runs under the guarantee that all elements are complete and
+   * well-formed. A step may reject elements that are not ready for processing but may be at a later
+   * round.
+   */
+  public interface ProcessingStep {
+    /** The set of annotation types processed by this step. */
+    Set<? extends Class<? extends Annotation>> annotations();
+
+    /**
+     * The implementation of processing logic for the step. It is guaranteed that the keys in {@code
+     * elementsByAnnotation} will be a subset of the set returned by {@link #annotations()}.
+     *
+     * @return the elements (a subset of the values of {@code elementsByAnnotation}) that this step
+     *     is unable to process, possibly until a later processing round. These elements will be
+     *     passed back to this step at the next round of processing.
+     */
+    Set<? extends Element> process(
+        SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation);
+  }
+
+  /**
+   * A package or type name.
+   *
+   * <p>It's unfortunate that we have to track types and packages separately, but since there are
+   * two different methods to look them up in {@link Elements}, we end up with a lot of parallel
+   * logic. :(
+   *
+   * <p>Packages declared (and annotated) in {@code package-info.java} are tracked as deferred
+   * packages, type elements are tracked directly, and all other elements are tracked via their
+   * nearest enclosing type.
+   */
+  private static final class ElementName {
+    private enum Kind {
+      PACKAGE_NAME,
+      TYPE_NAME,
+    }
+
+    private final Kind kind;
+    private final String name;
+
+    private ElementName(Kind kind, Name name) {
+      this.kind = checkNotNull(kind);
+      this.name = name.toString();
+    }
+
+    /**
+     * An {@link ElementName} for an annotated element. If {@code element} is a package, uses the
+     * fully qualified name of the package. If it's a type, uses its fully qualified name.
+     * Otherwise, uses the fully-qualified name of the nearest enclosing type.
+     */
+    static ElementName forAnnotatedElement(Element element) {
+      return element.getKind() == PACKAGE
+          ? new ElementName(Kind.PACKAGE_NAME, asPackage(element).getQualifiedName())
+          : new ElementName(Kind.TYPE_NAME, getEnclosingType(element).getQualifiedName());
+    }
+
+    /** The fully-qualified name of the element. */
+    String name() {
+      return name;
+    }
+
+    /**
+     * The {@link Element} whose fully-qualified name is {@link #name()}. Absent if the relevant
+     * method on {@link Elements} returns {@code null}.
+     */
+    Optional<? extends Element> getElement(Elements elements) {
+      return Optional.fromNullable(
+          kind == Kind.PACKAGE_NAME
+              ? elements.getPackageElement(name)
+              : elements.getTypeElement(name));
+    }
+
+    @Override
+    public boolean equals(Object object) {
+      if (!(object instanceof ElementName)) {
+        return false;
+      }
+
+      ElementName that = (ElementName) object;
+      return this.kind == that.kind && this.name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(kind, name);
+    }
+  }
+}
diff --git a/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java b/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java
new file mode 100644
index 0000000..bb35e22
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017 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.common;
+
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import java.util.Optional;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.util.Elements;
+
+/** Utility methods for writing {@code @Generated} annotations using JavaPoet. */
+public final class GeneratedAnnotationSpecs {
+
+  private GeneratedAnnotationSpecs() {}
+
+  /**
+   * Returns {@code @Generated("processorClass"} if either {@code
+   * javax.annotation.processing.Generated} or {@code javax.annotation.Generated} is {@linkplain
+   * GeneratedAnnotations#generatedAnnotation(Elements) available at compile time}.
+   *
+   * @deprecated prefer {@link #generatedAnnotationSpec(Elements, SourceVersion, Class)}
+   */
+  @Deprecated
+  public static Optional<AnnotationSpec> generatedAnnotationSpec(
+      Elements elements, Class<?> processorClass) {
+    return generatedAnnotationSpecBuilder(elements, processorClass)
+        .map(AnnotationSpec.Builder::build);
+  }
+
+  /**
+   * Returns {@code @Generated(value = "processorClass", comments = "comments"} if either {@code
+   * javax.annotation.processing.Generated} or {@code javax.annotation.Generated} is {@linkplain
+   * GeneratedAnnotations#generatedAnnotation(Elements) available at compile time}.
+   *
+   * @deprecated prefer {@link #generatedAnnotationSpec(Elements, SourceVersion, Class, String)}
+   */
+  @Deprecated
+  public static Optional<AnnotationSpec> generatedAnnotationSpec(
+      Elements elements, Class<?> processorClass, String comments) {
+    return generatedAnnotationSpecBuilder(elements, processorClass)
+        .map(annotation -> annotation.addMember("comments", "$S", comments).build());
+  }
+
+  /**
+   * Returns {@code @Generated("processorClass"} for the target {@code SourceVersion}.
+   *
+   * <p>Returns {@code javax.annotation.processing.Generated} for JDK 9 and newer, {@code
+   * javax.annotation.Generated} for earlier releases, and Optional#empty()} if the annotation is
+   * not available.
+   */
+  public static Optional<AnnotationSpec> generatedAnnotationSpec(
+      Elements elements, SourceVersion sourceVersion, Class<?> processorClass) {
+    return generatedAnnotationSpecBuilder(elements, sourceVersion, processorClass)
+        .map(AnnotationSpec.Builder::build);
+  }
+
+  /**
+   * Returns {@code @Generated(value = "processorClass", comments = "comments"} for the target
+   * {@code SourceVersion}.
+   *
+   * <p>Returns {@code javax.annotation.processing.Generated} for JDK 9 and newer, {@code
+   * javax.annotation.Generated} for earlier releases, and Optional#empty()} if the annotation is
+   * not available.
+   */
+  public static Optional<AnnotationSpec> generatedAnnotationSpec(
+      Elements elements, SourceVersion sourceVersion, Class<?> processorClass, String comments) {
+    return generatedAnnotationSpecBuilder(elements, sourceVersion, processorClass)
+        .map(annotation -> annotation.addMember("comments", "$S", comments).build());
+  }
+
+  private static Optional<AnnotationSpec.Builder> generatedAnnotationSpecBuilder(
+      Elements elements, Class<?> processorClass) {
+    return GeneratedAnnotations.generatedAnnotation(elements)
+        .map(
+            generated ->
+                AnnotationSpec.builder(ClassName.get(generated))
+                    .addMember("value", "$S", processorClass.getCanonicalName()));
+  }
+
+  private static Optional<AnnotationSpec.Builder> generatedAnnotationSpecBuilder(
+      Elements elements, SourceVersion sourceVersion, Class<?> processorClass) {
+    return GeneratedAnnotations.generatedAnnotation(elements, sourceVersion)
+        .map(
+            generated ->
+                AnnotationSpec.builder(ClassName.get(generated))
+                    .addMember("value", "$S", processorClass.getCanonicalName()));
+  }
+}
diff --git a/common/src/main/java/com/google/auto/common/GeneratedAnnotations.java b/common/src/main/java/com/google/auto/common/GeneratedAnnotations.java
new file mode 100644
index 0000000..b4616d2
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/GeneratedAnnotations.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 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.common;
+
+import java.util.Optional;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+/** Utility methods for writing {@code @Generated} annotations. */
+public final class GeneratedAnnotations {
+  private GeneratedAnnotations() {}
+
+  /**
+   * Returns the element corresponding to the version of the {@code @Generated} annotation present
+   * in the compile-time class- or module-path.
+   *
+   * <p>First looks for {@code javax.annotation.processing.Generated}, and then {@code
+   * javax.annotation.Generated}. Returns whichever is in the classpath (or modulepath), or {@link
+   * Optional#empty()} if neither is.
+   *
+   * @deprecated prefer {@link #generatedAnnotation(Elements, SourceVersion)}
+   */
+  @Deprecated
+  public static Optional<TypeElement> generatedAnnotation(Elements elements) {
+    TypeElement jdk9Generated = elements.getTypeElement("javax.annotation.processing.Generated");
+    if (jdk9Generated != null) {
+      return Optional.of(jdk9Generated);
+    }
+    return Optional.ofNullable(elements.getTypeElement("javax.annotation.Generated"));
+  }
+
+  /**
+   * Returns the element corresponding to the {@code @Generated} annotation present at the target
+   * {@code SourceVersion}.
+   *
+   * <p>Returns {@code javax.annotation.processing.Generated} for JDK 9 and newer, {@code
+   * javax.annotation.Generated} for earlier releases, and Optional#empty()} if the annotation is
+   * not available.
+   */
+  public static Optional<TypeElement> generatedAnnotation(
+      Elements elements, SourceVersion sourceVersion) {
+    return Optional.ofNullable(
+        elements.getTypeElement(
+            sourceVersion.compareTo(SourceVersion.RELEASE_8) > 0
+                ? "javax.annotation.processing.Generated"
+                : "javax.annotation.Generated"));
+  }
+}
diff --git a/common/src/main/java/com/google/auto/common/MoreElements.java b/common/src/main/java/com/google/auto/common/MoreElements.java
new file mode 100644
index 0000000..5e8e354
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/MoreElements.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright 2013 Google LLC
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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.common;
+
+import static javax.lang.model.element.ElementKind.PACKAGE;
+import static javax.lang.model.element.Modifier.STATIC;
+
+import com.google.auto.common.Overrides.ExplicitOverrides;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.SetMultimap;
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleElementVisitor8;
+import javax.lang.model.util.Types;
+
+/**
+ * Static utility methods pertaining to {@link Element} instances.
+ *
+ * @author Gregory Kick
+ */
+@Beta
+public final class MoreElements {
+  /**
+   * An alternate implementation of {@link Elements#getPackageOf} that does not require an
+   * {@link Elements} instance.
+   *
+   * @throws NullPointerException if {@code element} is {@code null}
+   */
+  public static PackageElement getPackage(Element element) {
+    while (element.getKind() != PACKAGE) {
+      element = element.getEnclosingElement();
+    }
+    return (PackageElement) element;
+  }
+
+  private static final class PackageElementVisitor extends CastingElementVisitor<PackageElement> {
+    private static final PackageElementVisitor INSTANCE = new PackageElementVisitor();
+
+    PackageElementVisitor() {
+      super("package element");
+    }
+
+    @Override
+    public PackageElement visitPackage(PackageElement e, Void ignore) {
+      return e;
+    }
+  }
+
+  /**
+   * Returns the given {@link Element} instance as {@link PackageElement}.
+   *
+   * <p>This method is functionally equivalent to an {@code instanceof} check and a cast, but should
+   * always be used over that idiom as instructed in the documentation for {@link Element}.
+   *
+   * @throws NullPointerException if {@code element} is {@code null}
+   * @throws IllegalArgumentException if {@code element} isn't a {@link PackageElement}.
+   */
+  public static PackageElement asPackage(Element element) {
+    return element.accept(PackageElementVisitor.INSTANCE, null);
+  }
+
+  private static final class TypeElementVisitor extends CastingElementVisitor<TypeElement> {
+    private static final TypeElementVisitor INSTANCE = new TypeElementVisitor();
+
+    TypeElementVisitor() {
+      super("type element");
+    }
+
+    @Override
+    public TypeElement visitType(TypeElement e, Void ignore) {
+      return e;
+    }
+  }
+
+  /**
+   * Returns true if the given {@link Element} instance is a {@link TypeElement}.
+   *
+   * <p>This method is functionally equivalent to an {@code instanceof} check, but should
+   * always be used over that idiom as instructed in the documentation for {@link Element}.
+   *
+   * @throws NullPointerException if {@code element} is {@code null}
+   */
+  public static boolean isType(Element element) {
+    return element.getKind().isClass() || element.getKind().isInterface();
+  }
+
+  /**
+   * Returns the given {@link Element} instance as {@link TypeElement}.
+   *
+   * <p>This method is functionally equivalent to an {@code instanceof} check and a cast, but should
+   * always be used over that idiom as instructed in the documentation for {@link Element}.
+   *
+   * @throws NullPointerException if {@code element} is {@code null}
+   * @throws IllegalArgumentException if {@code element} isn't a {@link TypeElement}.
+   */
+  public static TypeElement asType(Element element) {
+    return element.accept(TypeElementVisitor.INSTANCE, null);
+  }
+
+  /**
+   * Returns the given {@link Element} instance as {@link TypeParameterElement}.
+   *
+   * <p>This method is functionally equivalent to an {@code instanceof} check and a cast, but should
+   * always be used over that idiom as instructed in the documentation for {@link Element}.
+   *
+   * @throws NullPointerException if {@code element} is {@code null}
+   * @throws IllegalArgumentException if {@code element} isn't a {@link TypeParameterElement}.
+   */
+  public static TypeParameterElement asTypeParameter(Element element) {
+    return element.accept(TypeParameterElementVisitor.INSTANCE, null);
+  }
+
+  private static final class TypeParameterElementVisitor
+      extends CastingElementVisitor<TypeParameterElement> {
+    private static final TypeParameterElementVisitor INSTANCE = new TypeParameterElementVisitor();
+
+    TypeParameterElementVisitor() {
+      super("type parameter element");
+    }
+
+    @Override
+    public TypeParameterElement visitTypeParameter(TypeParameterElement e, Void ignore) {
+      return e;
+    }
+  }
+
+  private static final class VariableElementVisitor extends CastingElementVisitor<VariableElement> {
+    private static final VariableElementVisitor INSTANCE = new VariableElementVisitor();
+
+    VariableElementVisitor() {
+      super("variable element");
+    }
+
+    @Override
+    public VariableElement visitVariable(VariableElement e, Void ignore) {
+      return e;
+    }
+  }
+
+  /**
+   * Returns the given {@link Element} instance as {@link VariableElement}.
+   *
+   * <p>This method is functionally equivalent to an {@code instanceof} check and a cast, but should
+   * always be used over that idiom as instructed in the documentation for {@link Element}.
+   *
+   * @throws NullPointerException if {@code element} is {@code null}
+   * @throws IllegalArgumentException if {@code element} isn't a {@link VariableElement}.
+   */
+  public static VariableElement asVariable(Element element) {
+    return element.accept(VariableElementVisitor.INSTANCE, null);
+  }
+
+  private static final class ExecutableElementVisitor
+      extends CastingElementVisitor<ExecutableElement> {
+    private static final ExecutableElementVisitor INSTANCE = new ExecutableElementVisitor();
+
+    ExecutableElementVisitor() {
+      super("executable element");
+    }
+
+    @Override
+    public ExecutableElement visitExecutable(ExecutableElement e, Void label) {
+      return e;
+    }
+  }
+
+  /**
+   * Returns the given {@link Element} instance as {@link ExecutableElement}.
+   *
+   * <p>This method is functionally equivalent to an {@code instanceof} check and a cast, but should
+   * always be used over that idiom as instructed in the documentation for {@link Element}.
+   *
+   * @throws NullPointerException if {@code element} is {@code null}
+   * @throws IllegalArgumentException if {@code element} isn't a {@link ExecutableElement}.
+   */
+  public static ExecutableElement asExecutable(Element element) {
+    return element.accept(ExecutableElementVisitor.INSTANCE, null);
+  }
+
+  /**
+   * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose
+   * {@linkplain AnnotationMirror#getAnnotationType() annotation type} has the same canonical name
+   * as that of {@code annotationClass}. This method is a safer alternative to calling
+   * {@link Element#getAnnotation} and checking for {@code null} as it avoids any interaction with
+   * annotation proxies.
+   */
+  public static boolean isAnnotationPresent(Element element,
+      Class<? extends Annotation> annotationClass) {
+    return getAnnotationMirror(element, annotationClass).isPresent();
+  }
+
+  /**
+   * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on
+   * {@code element}, or {@link Optional#absent()} if no such annotation exists. This method is a
+   * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with
+   * annotation proxies.
+   */
+  public static Optional<AnnotationMirror> getAnnotationMirror(Element element,
+      Class<? extends Annotation> annotationClass) {
+    String annotationClassName = annotationClass.getCanonicalName();
+    for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
+      TypeElement annotationTypeElement = asType(annotationMirror.getAnnotationType().asElement());
+      if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) {
+        return Optional.of(annotationMirror);
+      }
+    }
+    return Optional.absent();
+  }
+
+  /**
+   * Returns a {@link Predicate} that can be used to filter elements by {@link Modifier}.
+   * The predicate returns {@code true} if the input {@link Element} has all of the given
+   * {@code modifiers}, perhaps in addition to others.
+   *
+   * <p>Here is an example how one could get a List of static methods of a class:
+   * <pre>{@code
+   * FluentIterable.from(ElementFilter.methodsIn(clazzElement.getEnclosedElements()))
+   *     .filter(MoreElements.hasModifiers(Modifier.STATIC).toList();
+   * }</pre>
+   */
+  public static <T extends Element> Predicate<T> hasModifiers(Modifier... modifiers) {
+    return hasModifiers(ImmutableSet.copyOf(modifiers));
+  }
+
+  /**
+   * Returns a {@link Predicate} that can be used to filter elements by {@link Modifier}.
+   * The predicate returns {@code true} if the input {@link Element} has all of the given
+   * {@code modifiers}, perhaps in addition to others.
+   *
+   * <p>Here is an example how one could get a List of methods with certain modifiers of a class:
+   * <pre>{@code
+   * Set<Modifier> modifiers = ...;
+   * FluentIterable.from(ElementFilter.methodsIn(clazzElement.getEnclosedElements()))
+   *     .filter(MoreElements.hasModifiers(modifiers).toList();}
+   * </pre>
+   */
+  public static <T extends Element> Predicate<T> hasModifiers(final Set<Modifier> modifiers) {
+    return new Predicate<T>() {
+      @Override
+      public boolean apply(T input) {
+        return input.getModifiers().containsAll(modifiers);
+      }
+    };
+  }
+
+  /**
+   * Returns the set of all non-private, non-static methods from {@code type}, including methods
+   * that it inherits from its ancestors. Inherited methods that are overridden are not included in
+   * the result. So if {@code type} defines {@code public String toString()}, the returned set will
+   * contain that method, but not the {@code toString()} method defined by {@code Object}.
+   *
+   * <p>The returned set may contain more than one method with the same signature, if
+   * {@code type} inherits those methods from different ancestors. For example, if it
+   * inherits from unrelated interfaces {@code One} and {@code Two} which each define
+   * {@code void foo();}, and if it does not itself override the {@code foo()} method,
+   * then both {@code One.foo()} and {@code Two.foo()} will be in the returned set.
+   *
+   * <p>The order of the returned set is deterministic: within a class or interface, methods are in
+   * the order they appear in the source code; methods in ancestors come before methods in
+   * descendants; methods in interfaces come before methods in classes; and in a class or interface
+   * that has more than one superinterface, the interfaces are in the order of their appearance in
+   * {@code implements} or {@code extends}.
+   *
+   * @param type the type whose own and inherited methods are to be returned
+   * @param elementUtils an {@link Elements} object, typically returned by
+   *     {@link javax.annotation.processing.AbstractProcessor#processingEnv processingEnv}<!--
+   *     -->.{@link javax.annotation.processing.ProcessingEnvironment#getElementUtils
+   *     getElementUtils()}
+   *
+   * @deprecated The method {@link #getLocalAndInheritedMethods(TypeElement, Types, Elements)}
+   *     has better consistency between Java compilers.
+   */
+  @Deprecated
+  public static ImmutableSet<ExecutableElement> getLocalAndInheritedMethods(
+      TypeElement type, Elements elementUtils) {
+    Overrides overrides = new Overrides.NativeOverrides(elementUtils);
+    return getLocalAndInheritedMethods(type, overrides);
+  }
+
+  /**
+   * Returns the set of all non-private, non-static methods from {@code type}, including methods
+   * that it inherits from its ancestors. Inherited methods that are overridden are not included in
+   * the result. So if {@code type} defines {@code public String toString()}, the returned set will
+   * contain that method, but not the {@code toString()} method defined by {@code Object}.
+   *
+   * <p>The returned set may contain more than one method with the same signature, if
+   * {@code type} inherits those methods from different ancestors. For example, if it
+   * inherits from unrelated interfaces {@code One} and {@code Two} which each define
+   * {@code void foo();}, and if it does not itself override the {@code foo()} method,
+   * then both {@code One.foo()} and {@code Two.foo()} will be in the returned set.
+   *
+   * <p>The order of the returned set is deterministic: within a class or interface, methods are in
+   * the order they appear in the source code; methods in ancestors come before methods in
+   * descendants; methods in interfaces come before methods in classes; and in a class or interface
+   * that has more than one superinterface, the interfaces are in the order of their appearance in
+   * {@code implements} or {@code extends}.
+   *
+   * @param type the type whose own and inherited methods are to be returned
+   * @param typeUtils a {@link Types} object, typically returned by
+   *     {@link javax.annotation.processing.AbstractProcessor#processingEnv processingEnv}<!--
+   *     -->.{@link javax.annotation.processing.ProcessingEnvironment#getTypeUtils
+   *     getTypeUtils()}
+   * @param elementUtils an {@link Elements} object, typically returned by
+   *     {@link javax.annotation.processing.AbstractProcessor#processingEnv processingEnv}<!--
+   *     -->.{@link javax.annotation.processing.ProcessingEnvironment#getElementUtils
+   *     getElementUtils()}
+   */
+  public static ImmutableSet<ExecutableElement> getLocalAndInheritedMethods(
+      TypeElement type, Types typeUtils, Elements elementUtils) {
+    return getLocalAndInheritedMethods(type, new ExplicitOverrides(typeUtils));
+  }
+
+  private static ImmutableSet<ExecutableElement> getLocalAndInheritedMethods(
+      TypeElement type, Overrides overrides) {
+    PackageElement pkg = getPackage(type);
+
+    ImmutableSet.Builder<ExecutableElement> methods = ImmutableSet.builder();
+    for (ExecutableElement method : getAllMethods(type, overrides)) {
+      // Filter out all static and non-visible methods.
+      if (!method.getModifiers().contains(STATIC) && methodVisibleFromPackage(method, pkg)) {
+        methods.add(method);
+      }
+    }
+    return methods.build();
+  }
+
+  /**
+   * Tests whether one method, as a member of a given type, overrides another method.
+   *
+   * <p>This method does the same thing as {@link Elements#overrides(ExecutableElement,
+   * ExecutableElement, TypeElement)}, but in a way that is more consistent between compilers, in
+   * particular between javac and ecj (the Eclipse compiler).
+   *
+   * @param overrider the first method, possible overrider
+   * @param overridden the second method, possibly being overridden
+   * @param type the type of which the first method is a member
+   * @return {@code true} if and only if the first method overrides the second
+   */
+  public static boolean overrides(
+      ExecutableElement overrider,
+      ExecutableElement overridden,
+      TypeElement type,
+      Types typeUtils) {
+    return new ExplicitOverrides(typeUtils).overrides(overrider, overridden, type);
+  }
+
+  /**
+   * Returns the set of all methods from {@code type}, including methods that it inherits
+   * from its ancestors. Inherited methods that are overridden are not included in the
+   * result. So if {@code type} defines {@code public String toString()}, the returned set
+   * will contain that method, but not the {@code toString()} method defined by {@code Object}.
+   *
+   * <p>The returned set may contain more than one method with the same signature, if
+   * {@code type} inherits those methods from different ancestors. For example, if it
+   * inherits from unrelated interfaces {@code One} and {@code Two} which each define
+   * {@code void foo();}, and if it does not itself override the {@code foo()} method,
+   * then both {@code One.foo()} and {@code Two.foo()} will be in the returned set.
+   *
+   * <p>The order of the returned set is deterministic: within a class or interface, methods are in
+   * the order they appear in the source code; methods in ancestors come before methods in
+   * descendants; methods in interfaces come before methods in classes; and in a class or interface
+   * that has more than one superinterface, the interfaces are in the order of their appearance in
+   * {@code implements} or {@code extends}.
+   *
+   * @param type the type whose own and inherited methods are to be returned
+   * @param typeUtils a {@link Types} object, typically returned by
+   *     {@link javax.annotation.processing.AbstractProcessor#processingEnv processingEnv}<!--
+   *     -->.{@link javax.annotation.processing.ProcessingEnvironment#getTypeUtils
+   *     getTypeUtils()}
+   * @param elementUtils an {@link Elements} object, typically returned by
+   *     {@link javax.annotation.processing.AbstractProcessor#processingEnv processingEnv}<!--
+   *     -->.{@link javax.annotation.processing.ProcessingEnvironment#getElementUtils
+   *     getElementUtils()}
+   */
+  public static ImmutableSet<ExecutableElement> getAllMethods(
+      TypeElement type, Types typeUtils, Elements elementUtils) {
+    return getAllMethods(type, new ExplicitOverrides(typeUtils));
+  }
+
+  private static ImmutableSet<ExecutableElement> getAllMethods(
+      TypeElement type, Overrides overrides) {
+    SetMultimap<String, ExecutableElement> methodMap = LinkedHashMultimap.create();
+    getAllMethods(type, methodMap);
+    // Find methods that are overridden. We do this using `Elements.overrides`, which means
+    // that it is inherently a quadratic operation, since we have to compare every method against
+    // every other method. We reduce the performance impact by (a) grouping methods by name, since
+    // a method cannot override another method with a different name, and (b) making sure that
+    // methods in ancestor types precede those in descendant types, which means we only have to
+    // check a method against the ones that follow it in that order.
+    Set<ExecutableElement> overridden = new LinkedHashSet<ExecutableElement>();
+    for (Collection<ExecutableElement> methods : methodMap.asMap().values()) {
+      List<ExecutableElement> methodList = ImmutableList.copyOf(methods);
+      for (int i = 0; i < methodList.size(); i++) {
+        ExecutableElement methodI = methodList.get(i);
+        for (int j = i + 1; j < methodList.size(); j++) {
+          ExecutableElement methodJ = methodList.get(j);
+          if (overrides.overrides(methodJ, methodI, type)) {
+            overridden.add(methodI);
+            break;
+          }
+        }
+      }
+    }
+    Set<ExecutableElement> methods = new LinkedHashSet<ExecutableElement>(methodMap.values());
+    methods.removeAll(overridden);
+    return ImmutableSet.copyOf(methods);
+  }
+
+  // Add to `methods` the static and instance methods from `type`. This means all methods from
+  // `type` itself and all methods it inherits from its ancestors. This method does not take
+  // overriding into account, so it will add both an ancestor method and a descendant method that
+  // overrides it. `methods` is a multimap from a method name to all of the methods with that name,
+  // including methods that override or overload one another. Within those methods, those in
+  // ancestor types always precede those in descendant types.
+  private static void getAllMethods(
+      TypeElement type, SetMultimap<String, ExecutableElement> methods) {
+    for (TypeMirror superInterface : type.getInterfaces()) {
+      getAllMethods(MoreTypes.asTypeElement(superInterface), methods);
+    }
+    if (type.getSuperclass().getKind() != TypeKind.NONE) {
+      // Visit the superclass after superinterfaces so we will always see the implementation of a
+      // method after any interfaces that declared it.
+      getAllMethods(MoreTypes.asTypeElement(type.getSuperclass()), methods);
+    }
+    for (ExecutableElement method : ElementFilter.methodsIn(type.getEnclosedElements())) {
+      methods.put(method.getSimpleName().toString(), method);
+    }
+  }
+
+  static boolean methodVisibleFromPackage(ExecutableElement method, PackageElement pkg) {
+    // We use Visibility.ofElement rather than .effectiveVisibilityOfElement because it doesn't
+    // really matter whether the containing class is visible. If you inherit a public method
+    // then you have a public method, regardless of whether you inherit it from a public class.
+    Visibility visibility = Visibility.ofElement(method);
+    switch (visibility) {
+      case PRIVATE:
+        return false;
+      case DEFAULT:
+        return getPackage(method).equals(pkg);
+      default:
+        return true;
+    }
+  }
+
+  private abstract static class CastingElementVisitor<T> extends SimpleElementVisitor8<T, Void> {
+    private final String label;
+
+    CastingElementVisitor(String label) {
+      this.label = label;
+    }
+
+    @Override
+    protected final T defaultAction(Element e, Void ignore) {
+      throw new IllegalArgumentException(e + " does not represent a " + label);
+    }
+  }
+
+  private MoreElements() {}
+}
diff --git a/common/src/main/java/com/google/auto/common/MoreTypes.java b/common/src/main/java/com/google/auto/common/MoreTypes.java
new file mode 100644
index 0000000..e09680b
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/MoreTypes.java
@@ -0,0 +1,1019 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static javax.lang.model.type.TypeKind.ARRAY;
+import static javax.lang.model.type.TypeKind.DECLARED;
+import static javax.lang.model.type.TypeKind.EXECUTABLE;
+import static javax.lang.model.type.TypeKind.INTERSECTION;
+import static javax.lang.model.type.TypeKind.TYPEVAR;
+import static javax.lang.model.type.TypeKind.WILDCARD;
+
+import com.google.common.base.Equivalence;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.NullType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleTypeVisitor8;
+import javax.lang.model.util.Types;
+
+/**
+ * Utilities related to {@link TypeMirror} instances.
+ *
+ * @author Gregory Kick
+ * @since 2.0
+ */
+public final class MoreTypes {
+  private static final class TypeEquivalence extends Equivalence<TypeMirror> {
+    private static final TypeEquivalence INSTANCE = new TypeEquivalence();
+
+    @Override
+    protected boolean doEquivalent(TypeMirror a, TypeMirror b) {
+      return MoreTypes.equal(a, b, ImmutableSet.<ComparedElements>of());
+    }
+
+    @Override
+    protected int doHash(TypeMirror t) {
+      return MoreTypes.hash(t, ImmutableSet.<Element>of());
+    }
+  }
+
+  /**
+   * Returns an {@link Equivalence} that can be used to compare types. The standard way to compare
+   * types is {@link javax.lang.model.util.Types#isSameType Types.isSameType}, but this alternative
+   * may be preferred in a number of cases:
+   *
+   * <ul>
+   * <li>If you don't have an instance of {@code Types}.
+   * <li>If you want a reliable {@code hashCode()} for the types, for example to construct a set
+   *     of types using {@link java.util.HashSet} with {@link Equivalence#wrap(Object)}.
+   * <li>If you want distinct type variables to be considered equal if they have the same names
+   *     and bounds.
+   * <li>If you want wildcard types to compare equal if they have the same bounds. {@code
+   *     Types.isSameType} never considers wildcards equal, even when comparing a type to itself.
+   * </ul>
+   */
+  public static Equivalence<TypeMirror> equivalence() {
+    return TypeEquivalence.INSTANCE;
+  }
+
+  // So EQUAL_VISITOR can be a singleton, we maintain visiting state, in particular which types
+  // have been seen already, in this object.
+  // The logic for handling recursive types like Comparable<T extends Comparable<T>> is very tricky.
+  // If we're not careful we'll end up with an infinite recursion. So we record the types that
+  // we've already seen during the recursion, and if we see the same pair of types again we just
+  // return true provisionally. But "the same pair of types" is itself poorly-defined. We can't
+  // just say that it is an equal pair of TypeMirrors, because of course if we knew how to
+  // determine that then we wouldn't need the complicated type visitor at all. On the other hand,
+  // we can't say that it is an identical pair of TypeMirrors either, because there's no
+  // guarantee that the TypeMirrors for the two Ts in Comparable<T extends Comparable<T>> will be
+  // represented by the same object, and indeed with the Eclipse compiler they aren't. We could
+  // compare the corresponding Elements, since equality is well-defined there, but that's not enough
+  // either, because the Element for Set<Object> is the same as the one for Set<String>. So we
+  // approximate by comparing the Elements and, if there are any type arguments, requiring them to
+  // be identical. This may not be foolproof either but it is sufficient for all the cases we've
+  // encountered so far.
+  private static final class EqualVisitorParam {
+    TypeMirror type;
+    Set<ComparedElements> visiting;
+  }
+
+  private static class ComparedElements {
+    final Element a;
+    final ImmutableList<TypeMirror> aArguments;
+    final Element b;
+    final ImmutableList<TypeMirror> bArguments;
+
+    ComparedElements(
+        Element a,
+        ImmutableList<TypeMirror> aArguments,
+        Element b,
+        ImmutableList<TypeMirror> bArguments) {
+      this.a = a;
+      this.aArguments = aArguments;
+      this.b = b;
+      this.bArguments = bArguments;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof ComparedElements) {
+        ComparedElements that = (ComparedElements) o;
+        int nArguments = aArguments.size();
+        if (!this.a.equals(that.a)
+            || !this.b.equals(that.b)
+            || nArguments != bArguments.size()) {
+          // The arguments must be the same size, but we check anyway.
+          return false;
+        }
+        for (int i = 0; i < nArguments; i++) {
+          if (aArguments.get(i) != bArguments.get(i)) {
+            return false;
+          }
+        }
+        return true;
+      } else {
+        return false;
+      }
+    }
+
+    @Override
+    public int hashCode() {
+      return a.hashCode() * 31 + b.hashCode();
+    }
+  }
+
+  private static final class EqualVisitor extends SimpleTypeVisitor8<Boolean, EqualVisitorParam> {
+    private static final EqualVisitor INSTANCE = new EqualVisitor();
+
+    @Override
+    protected Boolean defaultAction(TypeMirror a, EqualVisitorParam p) {
+      return a.getKind().equals(p.type.getKind());
+    }
+
+    @Override
+    public Boolean visitArray(ArrayType a, EqualVisitorParam p) {
+      if (p.type.getKind().equals(ARRAY)) {
+        ArrayType b = (ArrayType) p.type;
+        return equal(a.getComponentType(), b.getComponentType(), p.visiting);
+      }
+      return false;
+    }
+
+    @Override
+    public Boolean visitDeclared(DeclaredType a, EqualVisitorParam p) {
+      if (p.type.getKind().equals(DECLARED)) {
+        DeclaredType b = (DeclaredType) p.type;
+        Element aElement = a.asElement();
+        Element bElement = b.asElement();
+        Set<ComparedElements> newVisiting =
+            visitingSetPlus(
+                p.visiting, aElement, a.getTypeArguments(), bElement, b.getTypeArguments());
+        if (newVisiting.equals(p.visiting)) {
+          // We're already visiting this pair of elements.
+          // This can happen for example with Enum in Enum<E extends Enum<E>>. Return a
+          // provisional true value since if the Elements are not in fact equal the original
+          // visitor of Enum will discover that. We have to check both Elements being compared
+          // though to avoid missing the fact that one of the types being compared
+          // differs at exactly this point.
+          return true;
+        }
+        return aElement.equals(bElement)
+            && equal(enclosingType(a), enclosingType(b), newVisiting)
+            && equalLists(a.getTypeArguments(), b.getTypeArguments(), newVisiting);
+      }
+      return false;
+    }
+
+    @Override
+    @SuppressWarnings("TypeEquals")
+    public Boolean visitError(ErrorType a, EqualVisitorParam p) {
+      return a.equals(p.type);
+    }
+
+    @Override
+    public Boolean visitExecutable(ExecutableType a, EqualVisitorParam p) {
+      if (p.type.getKind().equals(EXECUTABLE)) {
+        ExecutableType b = (ExecutableType) p.type;
+        return equalLists(a.getParameterTypes(), b.getParameterTypes(), p.visiting)
+            && equal(a.getReturnType(), b.getReturnType(), p.visiting)
+            && equalLists(a.getThrownTypes(), b.getThrownTypes(), p.visiting)
+            && equalLists(a.getTypeVariables(), b.getTypeVariables(), p.visiting);
+      }
+      return false;
+    }
+
+    @Override
+    public Boolean visitIntersection(IntersectionType a, EqualVisitorParam p) {
+      if (p.type.getKind().equals(INTERSECTION)) {
+        IntersectionType b = (IntersectionType) p.type;
+        return equalLists(a.getBounds(), b.getBounds(), p.visiting);
+      }
+      return false;
+    }
+
+    @Override
+    public Boolean visitTypeVariable(TypeVariable a, EqualVisitorParam p) {
+      if (p.type.getKind().equals(TYPEVAR)) {
+        TypeVariable b = (TypeVariable) p.type;
+        TypeParameterElement aElement = (TypeParameterElement) a.asElement();
+        TypeParameterElement bElement = (TypeParameterElement) b.asElement();
+        Set<ComparedElements> newVisiting = visitingSetPlus(p.visiting, aElement, bElement);
+        if (newVisiting.equals(p.visiting)) {
+          // We're already visiting this pair of elements.
+          // This can happen with our friend Eclipse when looking at <T extends Comparable<T>>.
+          // It incorrectly reports the upper bound of T as T itself.
+          return true;
+        }
+        // We use aElement.getBounds() instead of a.getUpperBound() to avoid having to deal with
+        // the different way intersection types (like <T extends Number & Comparable<T>>) are
+        // represented before and after Java 8. We do have an issue that this code may consider
+        // that <T extends Foo & Bar> is different from <T extends Bar & Foo>, but it's very
+        // hard to avoid that, and not likely to be much of a problem in practice.
+        return equalLists(aElement.getBounds(), bElement.getBounds(), newVisiting)
+            && equal(a.getLowerBound(), b.getLowerBound(), newVisiting)
+            && a.asElement().getSimpleName().equals(b.asElement().getSimpleName());
+      }
+      return false;
+    }
+
+    @Override
+    public Boolean visitWildcard(WildcardType a, EqualVisitorParam p) {
+      if (p.type.getKind().equals(WILDCARD)) {
+        WildcardType b = (WildcardType) p.type;
+        return equal(a.getExtendsBound(), b.getExtendsBound(), p.visiting)
+            && equal(a.getSuperBound(), b.getSuperBound(), p.visiting);
+      }
+      return false;
+    }
+
+    @Override
+    public Boolean visitUnknown(TypeMirror a, EqualVisitorParam p) {
+      throw new UnsupportedOperationException();
+    }
+
+    private Set<ComparedElements> visitingSetPlus(
+        Set<ComparedElements> visiting, Element a, Element b) {
+      ImmutableList<TypeMirror> noArguments = ImmutableList.of();
+      return visitingSetPlus(visiting, a, noArguments, b, noArguments);
+    }
+
+    private Set<ComparedElements> visitingSetPlus(
+        Set<ComparedElements> visiting,
+        Element a,
+        List<? extends TypeMirror> aArguments,
+        Element b,
+        List<? extends TypeMirror> bArguments) {
+      ComparedElements comparedElements =
+          new ComparedElements(
+              a, ImmutableList.<TypeMirror>copyOf(aArguments),
+              b, ImmutableList.<TypeMirror>copyOf(bArguments));
+      Set<ComparedElements> newVisiting = new HashSet<ComparedElements>(visiting);
+      newVisiting.add(comparedElements);
+      return newVisiting;
+    }
+  }
+
+  @SuppressWarnings("TypeEquals")
+  private static boolean equal(TypeMirror a, TypeMirror b, Set<ComparedElements> visiting) {
+    // TypeMirror.equals is not guaranteed to return true for types that are equal, but we can
+    // assume that if it does return true then the types are equal. This check also avoids getting
+    // stuck in infinite recursion when Eclipse decrees that the upper bound of the second K in
+    // <K extends Comparable<K>> is a distinct but equal K.
+    // The javac implementation of ExecutableType, at least in some versions, does not take thrown
+    // exceptions into account in its equals implementation, so avoid this optimization for
+    // ExecutableType.
+    if (Objects.equal(a, b) && !(a instanceof ExecutableType)) {
+      return true;
+    }
+    EqualVisitorParam p = new EqualVisitorParam();
+    p.type = b;
+    p.visiting = visiting;
+    return (a == b) || (a != null && b != null && a.accept(EqualVisitor.INSTANCE, p));
+  }
+
+  /**
+   * Returns the type of the innermost enclosing instance, or null if there is none. This is the
+   * same as {@link DeclaredType#getEnclosingType()} except that it returns null rather than
+   * NoType for a static type. We need this because of
+   * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=508222">this bug</a> whereby
+   * the Eclipse compiler returns a value for static classes that is not NoType.
+   */
+  private static TypeMirror enclosingType(DeclaredType t) {
+    TypeMirror enclosing = t.getEnclosingType();
+    if (enclosing.getKind().equals(TypeKind.NONE)
+        || t.asElement().getModifiers().contains(Modifier.STATIC)) {
+      return null;
+    }
+    return enclosing;
+  }
+
+  private static boolean equalLists(
+      List<? extends TypeMirror> a, List<? extends TypeMirror> b, Set<ComparedElements> visiting) {
+    int size = a.size();
+    if (size != b.size()) {
+      return false;
+    }
+    // Use iterators in case the Lists aren't RandomAccess
+    Iterator<? extends TypeMirror> aIterator = a.iterator();
+    Iterator<? extends TypeMirror> bIterator = b.iterator();
+    while (aIterator.hasNext()) {
+      if (!bIterator.hasNext()) {
+        return false;
+      }
+      TypeMirror nextMirrorA = aIterator.next();
+      TypeMirror nextMirrorB = bIterator.next();
+      if (!equal(nextMirrorA, nextMirrorB, visiting)) {
+        return false;
+      }
+    }
+    return !aIterator.hasNext();
+  }
+
+  private static final int HASH_SEED = 17;
+  private static final int HASH_MULTIPLIER = 31;
+
+  private static final class HashVisitor extends SimpleTypeVisitor8<Integer, Set<Element>> {
+    private static final HashVisitor INSTANCE = new HashVisitor();
+
+    int hashKind(int seed, TypeMirror t) {
+      int result = seed * HASH_MULTIPLIER;
+      result += t.getKind().hashCode();
+      return result;
+    }
+
+    @Override
+    protected Integer defaultAction(TypeMirror e, Set<Element> visiting) {
+      return hashKind(HASH_SEED, e);
+    }
+
+    @Override
+    public Integer visitArray(ArrayType t, Set<Element> visiting) {
+      int result = hashKind(HASH_SEED, t);
+      result *= HASH_MULTIPLIER;
+      result += t.getComponentType().accept(this, visiting);
+      return result;
+    }
+
+    @Override
+    public Integer visitDeclared(DeclaredType t, Set<Element> visiting) {
+      Element element = t.asElement();
+      if (visiting.contains(element)) {
+        return 0;
+      }
+      Set<Element> newVisiting = new HashSet<Element>(visiting);
+      newVisiting.add(element);
+      int result = hashKind(HASH_SEED, t);
+      result *= HASH_MULTIPLIER;
+      result += t.asElement().hashCode();
+      result *= HASH_MULTIPLIER;
+      result += t.getEnclosingType().accept(this, newVisiting);
+      result *= HASH_MULTIPLIER;
+      result += hashList(t.getTypeArguments(), newVisiting);
+      return result;
+    }
+
+    @Override
+    public Integer visitExecutable(ExecutableType t, Set<Element> visiting) {
+      int result = hashKind(HASH_SEED, t);
+      result *= HASH_MULTIPLIER;
+      result += hashList(t.getParameterTypes(), visiting);
+      result *= HASH_MULTIPLIER;
+      result += t.getReturnType().accept(this, visiting);
+      result *= HASH_MULTIPLIER;
+      result += hashList(t.getThrownTypes(), visiting);
+      result *= HASH_MULTIPLIER;
+      result += hashList(t.getTypeVariables(), visiting);
+      return result;
+    }
+
+    @Override
+    public Integer visitTypeVariable(TypeVariable t, Set<Element> visiting) {
+      int result = hashKind(HASH_SEED, t);
+      result *= HASH_MULTIPLIER;
+      result += t.getLowerBound().accept(this, visiting);
+      TypeParameterElement element = (TypeParameterElement) t.asElement();
+      for (TypeMirror bound : element.getBounds()) {
+        result *= HASH_MULTIPLIER;
+        result += bound.accept(this, visiting);
+      }
+      return result;
+    }
+
+    @Override
+    public Integer visitWildcard(WildcardType t, Set<Element> visiting) {
+      int result = hashKind(HASH_SEED, t);
+      result *= HASH_MULTIPLIER;
+      result += (t.getExtendsBound() == null) ? 0 : t.getExtendsBound().accept(this, visiting);
+      result *= HASH_MULTIPLIER;
+      result += (t.getSuperBound() == null) ? 0 : t.getSuperBound().accept(this, visiting);
+      return result;
+    }
+
+    @Override
+    public Integer visitUnknown(TypeMirror t, Set<Element> visiting) {
+      throw new UnsupportedOperationException();
+    }
+  };
+
+  private static int hashList(List<? extends TypeMirror> mirrors, Set<Element> visiting) {
+    int result = HASH_SEED;
+    for (TypeMirror mirror : mirrors) {
+      result *= HASH_MULTIPLIER;
+      result += hash(mirror, visiting);
+    }
+    return result;
+  }
+
+  private static int hash(TypeMirror mirror, Set<Element> visiting) {
+    return mirror == null ? 0 : mirror.accept(HashVisitor.INSTANCE, visiting);
+  }
+
+  /**
+   * Returns the set of {@linkplain TypeElement types} that are referenced by the given {@link
+   * TypeMirror}.
+   */
+  public static ImmutableSet<TypeElement> referencedTypes(TypeMirror type) {
+    checkNotNull(type);
+    ImmutableSet.Builder<TypeElement> elements = ImmutableSet.builder();
+    type.accept(ReferencedTypes.INSTANCE, elements);
+    return elements.build();
+  }
+
+  private static final class ReferencedTypes
+      extends SimpleTypeVisitor8<Void, ImmutableSet.Builder<TypeElement>> {
+    private static final ReferencedTypes INSTANCE = new ReferencedTypes();
+
+    @Override
+    public Void visitArray(ArrayType t, ImmutableSet.Builder<TypeElement> p) {
+      t.getComponentType().accept(this, p);
+      return null;
+    }
+
+    @Override
+    public Void visitDeclared(DeclaredType t, ImmutableSet.Builder<TypeElement> p) {
+      p.add(MoreElements.asType(t.asElement()));
+      for (TypeMirror typeArgument : t.getTypeArguments()) {
+        typeArgument.accept(this, p);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder<TypeElement> p) {
+      t.getLowerBound().accept(this, p);
+      t.getUpperBound().accept(this, p);
+      return null;
+    }
+
+    @Override
+    public Void visitWildcard(WildcardType t, ImmutableSet.Builder<TypeElement> p) {
+      TypeMirror extendsBound = t.getExtendsBound();
+      if (extendsBound != null) {
+        extendsBound.accept(this, p);
+      }
+      TypeMirror superBound = t.getSuperBound();
+      if (superBound != null) {
+        superBound.accept(this, p);
+      }
+      return null;
+    }
+  }
+
+  /**
+   * An alternate implementation of {@link Types#asElement} that does not require a {@link Types}
+   * instance with the notable difference that it will throw {@link IllegalArgumentException}
+   * instead of returning null if the {@link TypeMirror} can not be converted to an {@link Element}.
+   *
+   * @throws NullPointerException if {@code typeMirror} is {@code null}
+   * @throws IllegalArgumentException if {@code typeMirror} cannot be converted to an {@link
+   *     Element}
+   */
+  public static Element asElement(TypeMirror typeMirror) {
+    return typeMirror.accept(AsElementVisitor.INSTANCE, null);
+  }
+
+  private static final class AsElementVisitor extends SimpleTypeVisitor8<Element, Void> {
+    private static final AsElementVisitor INSTANCE = new AsElementVisitor();
+
+    @Override
+    protected Element defaultAction(TypeMirror e, Void p) {
+      throw new IllegalArgumentException(e + " cannot be converted to an Element");
+    }
+
+    @Override
+    public Element visitDeclared(DeclaredType t, Void p) {
+      return t.asElement();
+    }
+
+    @Override
+    public Element visitError(ErrorType t, Void p) {
+      return t.asElement();
+    }
+
+    @Override
+    public Element visitTypeVariable(TypeVariable t, Void p) {
+      return t.asElement();
+    }
+  };
+
+  // TODO(gak): consider removing these two methods as they're pretty trivial now
+  public static TypeElement asTypeElement(TypeMirror mirror) {
+    return MoreElements.asType(asElement(mirror));
+  }
+
+  public static ImmutableSet<TypeElement> asTypeElements(Iterable<? extends TypeMirror> mirrors) {
+    checkNotNull(mirrors);
+    ImmutableSet.Builder<TypeElement> builder = ImmutableSet.builder();
+    for (TypeMirror mirror : mirrors) {
+      builder.add(asTypeElement(mirror));
+    }
+    return builder.build();
+  }
+
+  /**
+   * Returns a {@link ArrayType} if the {@link TypeMirror} represents an array or throws an {@link
+   * IllegalArgumentException}.
+   */
+  public static ArrayType asArray(TypeMirror maybeArrayType) {
+    return maybeArrayType.accept(ArrayTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class ArrayTypeVisitor extends CastingTypeVisitor<ArrayType> {
+    private static final ArrayTypeVisitor INSTANCE = new ArrayTypeVisitor();
+
+    ArrayTypeVisitor() {
+      super("array");
+    }
+
+    @Override
+    public ArrayType visitArray(ArrayType type, Void ignore) {
+      return type;
+    }
+  }
+
+  /**
+   * Returns a {@link DeclaredType} if the {@link TypeMirror} represents a declared type such as a
+   * class, interface, union/compound, or enum or throws an {@link IllegalArgumentException}.
+   */
+  public static DeclaredType asDeclared(TypeMirror maybeDeclaredType) {
+    return maybeDeclaredType.accept(DeclaredTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class DeclaredTypeVisitor extends CastingTypeVisitor<DeclaredType> {
+    private static final DeclaredTypeVisitor INSTANCE = new DeclaredTypeVisitor();
+
+    DeclaredTypeVisitor() {
+      super("declared type");
+    }
+
+    @Override
+    public DeclaredType visitDeclared(DeclaredType type, Void ignore) {
+      return type;
+    }
+  }
+
+  /**
+   * Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such
+   * as may result from missing code, or bad compiles or throws an {@link IllegalArgumentException}.
+   */
+  public static ErrorType asError(TypeMirror maybeErrorType) {
+    return maybeErrorType.accept(ErrorTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class ErrorTypeVisitor extends CastingTypeVisitor<ErrorType> {
+    private static final ErrorTypeVisitor INSTANCE = new ErrorTypeVisitor();
+
+    ErrorTypeVisitor() {
+      super("error type");
+    }
+
+    @Override
+    public ErrorType visitError(ErrorType type, Void ignore) {
+      return type;
+    }
+  }
+
+  /**
+   * Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such
+   * as a method, constructor, or initializer or throws an {@link IllegalArgumentException}.
+   */
+  public static ExecutableType asExecutable(TypeMirror maybeExecutableType) {
+    return maybeExecutableType.accept(ExecutableTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class ExecutableTypeVisitor extends CastingTypeVisitor<ExecutableType> {
+    private static final ExecutableTypeVisitor INSTANCE = new ExecutableTypeVisitor();
+
+    ExecutableTypeVisitor() {
+      super("executable type");
+    }
+
+    @Override
+    public ExecutableType visitExecutable(ExecutableType type, Void ignore) {
+      return type;
+    }
+  }
+
+  /**
+   * Returns an {@link IntersectionType} if the {@link TypeMirror} represents an intersection-type
+   * or throws an {@link IllegalArgumentException}.
+   */
+  public static IntersectionType asIntersection(TypeMirror maybeIntersectionType) {
+    return maybeIntersectionType.accept(IntersectionTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class IntersectionTypeVisitor extends CastingTypeVisitor<IntersectionType> {
+    private static final IntersectionTypeVisitor INSTANCE = new IntersectionTypeVisitor();
+
+    IntersectionTypeVisitor() {
+      super("intersection type");
+    }
+
+    @Override
+    public IntersectionType visitIntersection(IntersectionType type, Void ignore) {
+      return type;
+    }
+  }
+
+  /**
+   * Returns a {@link NoType} if the {@link TypeMirror} represents an non-type such as void, or
+   * package, etc. or throws an {@link IllegalArgumentException}.
+   */
+  public static NoType asNoType(TypeMirror maybeNoType) {
+    return maybeNoType.accept(NoTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class NoTypeVisitor extends CastingTypeVisitor<NoType> {
+    private static final NoTypeVisitor INSTANCE = new NoTypeVisitor();
+
+    NoTypeVisitor() {
+      super("non-type");
+    }
+
+    @Override
+    public NoType visitNoType(NoType type, Void ignore) {
+      return type;
+    }
+  }
+
+  /**
+   * Returns a {@link NullType} if the {@link TypeMirror} represents the null type or throws an
+   * {@link IllegalArgumentException}.
+   */
+  public static NullType asNullType(TypeMirror maybeNullType) {
+    return maybeNullType.accept(NullTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class NullTypeVisitor extends CastingTypeVisitor<NullType> {
+    private static final NullTypeVisitor INSTANCE = new NullTypeVisitor();
+
+    NullTypeVisitor() {
+      super("null");
+    }
+
+    @Override
+    public NullType visitNull(NullType type, Void ignore) {
+      return type;
+    }
+  }
+
+  /**
+   * Returns a {@link PrimitiveType} if the {@link TypeMirror} represents a primitive type or throws
+   * an {@link IllegalArgumentException}.
+   */
+  public static PrimitiveType asPrimitiveType(TypeMirror maybePrimitiveType) {
+    return maybePrimitiveType.accept(PrimitiveTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class PrimitiveTypeVisitor extends CastingTypeVisitor<PrimitiveType> {
+    private static final PrimitiveTypeVisitor INSTANCE = new PrimitiveTypeVisitor();
+
+    PrimitiveTypeVisitor() {
+      super("primitive type");
+    }
+
+    @Override
+    public PrimitiveType visitPrimitive(PrimitiveType type, Void ignore) {
+      return type;
+    }
+  }
+
+  //
+  // visitUnionType would go here, but isn't relevant for annotation processors
+  //
+
+  /**
+   * Returns a {@link TypeVariable} if the {@link TypeMirror} represents a type variable or throws
+   * an {@link IllegalArgumentException}.
+   */
+  public static TypeVariable asTypeVariable(TypeMirror maybeTypeVariable) {
+    return maybeTypeVariable.accept(TypeVariableVisitor.INSTANCE, null);
+  }
+
+  private static final class TypeVariableVisitor extends CastingTypeVisitor<TypeVariable> {
+    private static final TypeVariableVisitor INSTANCE = new TypeVariableVisitor();
+
+    TypeVariableVisitor() {
+      super("type variable");
+    }
+
+    @Override
+    public TypeVariable visitTypeVariable(TypeVariable type, Void ignore) {
+      return type;
+    }
+  }
+
+  /**
+   * Returns a {@link WildcardType} if the {@link TypeMirror} represents a wildcard type or throws
+   * an {@link IllegalArgumentException}.
+   */
+  public static WildcardType asWildcard(TypeMirror maybeWildcardType) {
+    return maybeWildcardType.accept(WildcardTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class WildcardTypeVisitor extends CastingTypeVisitor<WildcardType> {
+    private static final WildcardTypeVisitor INSTANCE = new WildcardTypeVisitor();
+
+    WildcardTypeVisitor() {
+      super("wildcard type");
+    }
+
+    @Override
+    public WildcardType visitWildcard(WildcardType type, Void ignore) {
+      return type;
+    }
+  }
+
+  /**
+   * Returns true if the raw type underlying the given {@link TypeMirror} represents a type that can
+   * be referenced by a {@link Class}. If this returns true, then {@link #isTypeOf} is guaranteed to
+   * not throw.
+   */
+  public static boolean isType(TypeMirror type) {
+    return type.accept(IsTypeVisitor.INSTANCE, null);
+  }
+
+  private static final class IsTypeVisitor extends SimpleTypeVisitor8<Boolean, Void> {
+    private static final IsTypeVisitor INSTANCE = new IsTypeVisitor();
+
+    @Override
+    protected Boolean defaultAction(TypeMirror type, Void ignored) {
+      return false;
+    }
+
+    @Override
+    public Boolean visitNoType(NoType noType, Void p) {
+      return noType.getKind().equals(TypeKind.VOID);
+    }
+
+    @Override
+    public Boolean visitPrimitive(PrimitiveType type, Void p) {
+      return true;
+    }
+
+    @Override
+    public Boolean visitArray(ArrayType array, Void p) {
+      return true;
+    }
+
+    @Override
+    public Boolean visitDeclared(DeclaredType type, Void ignored) {
+      return MoreElements.isType(type.asElement());
+    }
+  }
+
+  /**
+   * Returns true if the raw type underlying the given {@link TypeMirror} represents the same raw
+   * type as the given {@link Class} and throws an IllegalArgumentException if the {@link
+   * TypeMirror} does not represent a type that can be referenced by a {@link Class}
+   */
+  public static boolean isTypeOf(final Class<?> clazz, TypeMirror type) {
+    checkNotNull(clazz);
+    return type.accept(new IsTypeOf(clazz), null);
+  }
+
+  private static final class IsTypeOf extends SimpleTypeVisitor8<Boolean, Void> {
+    private final Class<?> clazz;
+
+    IsTypeOf(Class<?> clazz) {
+      this.clazz = clazz;
+    }
+
+    @Override
+    protected Boolean defaultAction(TypeMirror type, Void ignored) {
+      throw new IllegalArgumentException(type + " cannot be represented as a Class<?>.");
+    }
+
+    @Override
+    public Boolean visitNoType(NoType noType, Void p) {
+      if (noType.getKind().equals(TypeKind.VOID)) {
+        return clazz.equals(Void.TYPE);
+      }
+      throw new IllegalArgumentException(noType + " cannot be represented as a Class<?>.");
+    }
+
+    @Override
+    public Boolean visitPrimitive(PrimitiveType type, Void p) {
+      switch (type.getKind()) {
+        case BOOLEAN:
+          return clazz.equals(Boolean.TYPE);
+        case BYTE:
+          return clazz.equals(Byte.TYPE);
+        case CHAR:
+          return clazz.equals(Character.TYPE);
+        case DOUBLE:
+          return clazz.equals(Double.TYPE);
+        case FLOAT:
+          return clazz.equals(Float.TYPE);
+        case INT:
+          return clazz.equals(Integer.TYPE);
+        case LONG:
+          return clazz.equals(Long.TYPE);
+        case SHORT:
+          return clazz.equals(Short.TYPE);
+        default:
+          throw new IllegalArgumentException(type + " cannot be represented as a Class<?>.");
+      }
+    }
+
+    @Override
+    public Boolean visitArray(ArrayType array, Void p) {
+      return clazz.isArray() && isTypeOf(clazz.getComponentType(), array.getComponentType());
+    }
+
+    @Override
+    public Boolean visitDeclared(DeclaredType type, Void ignored) {
+      TypeElement typeElement = MoreElements.asType(type.asElement());
+      return typeElement.getQualifiedName().contentEquals(clazz.getCanonicalName());
+    }
+  }
+
+  /**
+   * Returns the superclass of {@code type}, with any type parameters bound by {@code type}, or
+   * {@link Optional#absent()} if {@code type} is an interface or {@link Object} or its superclass
+   * is {@link Object}.
+   */
+  // TODO(user): Remove unused parameter Elements?
+  public static Optional<DeclaredType> nonObjectSuperclass(Types types, Elements elements,
+      DeclaredType type) {
+    checkNotNull(types);
+    checkNotNull(elements);  // This is no longer used, but here to avoid changing the API.
+    checkNotNull(type);
+
+    TypeMirror superclassType = asTypeElement(type).getSuperclass();
+    if (!isType(superclassType)) { // type is Object or an interface
+      return Optional.absent();
+    }
+
+    DeclaredType superclass =  asDeclared(superclassType);
+    if (isObjectType(superclass)) {
+      return Optional.absent();
+    }
+
+    if (superclass.getTypeArguments().isEmpty()) {
+      return Optional.of(superclass);
+    }
+
+    // In the case where the super class has type parameters, TypeElement#getSuperclass gives
+    // SuperClass<T> rather than SuperClass<Foo>, so use Types#directSupertypes instead. The javadoc
+    // for Types#directSupertypes guarantees that a super class, if it exists, comes before any
+    // interfaces. Thus, we can just get the first element in the list.
+    return Optional.of(asDeclared(types.directSupertypes(type).get(0)));
+  }
+
+  private static boolean isObjectType(DeclaredType type) {
+    return asTypeElement(type).getQualifiedName().contentEquals("java.lang.Object");
+  }
+
+  /**
+   * Resolves a {@link VariableElement} parameter to a method or constructor based on the given
+   * container, or a member of a class. For parameters to a method or constructor, the variable's
+   * enclosing element must be a supertype of the container type. For example, given a
+   * {@code container} of type {@code Set<String>}, and a variable corresponding to the {@code E e}
+   * parameter in the {@code Set.add(E e)} method, this will return a TypeMirror for {@code String}.
+   */
+  public static TypeMirror asMemberOf(Types types, DeclaredType container,
+      VariableElement variable) {
+    if (variable.getKind().equals(ElementKind.PARAMETER)) {
+      ExecutableElement methodOrConstructor =
+          MoreElements.asExecutable(variable.getEnclosingElement());
+      ExecutableType resolvedMethodOrConstructor =
+          MoreTypes.asExecutable(types.asMemberOf(container, methodOrConstructor));
+      List<? extends VariableElement> parameters = methodOrConstructor.getParameters();
+      List<? extends TypeMirror> parameterTypes = resolvedMethodOrConstructor.getParameterTypes();
+      checkState(parameters.size() == parameterTypes.size());
+      for (int i = 0; i < parameters.size(); i++) {
+        // We need to capture the parameter type of the variable we're concerned about,
+        // for later printing.  This is the only way to do it since we can't use
+        // types.asMemberOf on variables of methods.
+        if (parameters.get(i).equals(variable)) {
+          return parameterTypes.get(i);
+        }
+      }
+      throw new IllegalStateException("Could not find variable: " + variable);
+    } else {
+      return types.asMemberOf(container, variable);
+    }
+  }
+
+  private abstract static class CastingTypeVisitor<T> extends SimpleTypeVisitor8<T, Void> {
+    private final String label;
+
+    CastingTypeVisitor(String label) {
+      this.label = label;
+    }
+
+    @Override
+    protected T defaultAction(TypeMirror e, Void v) {
+      throw new IllegalArgumentException(e + " does not represent a " + label);
+    }
+  }
+
+  /**
+   * Returns true if casting {@code Object} to the given type will elicit an unchecked warning from
+   * the compiler. Only type variables and parameterized types such as {@code List<String>} produce
+   * such warnings. There will be no warning if the type's only type parameters are simple
+   * wildcards, as in {@code Map<?, ?>}.
+   */
+  public static boolean isConversionFromObjectUnchecked(TypeMirror type) {
+    return new CastingUncheckedVisitor().visit(type, null);
+  }
+
+  /**
+   * Visitor that tells whether a type is erased, in the sense of {@link #castIsUnchecked}. Each
+   * visitX method returns true if its input parameter is true or if the type being visited is
+   * erased.
+   */
+  private static class CastingUncheckedVisitor extends SimpleTypeVisitor8<Boolean, Void> {
+    CastingUncheckedVisitor() {
+      super(false);
+    }
+
+    @Override
+    public Boolean visitUnknown(TypeMirror t, Void p) {
+      // We don't know whether casting is unchecked for this mysterious type but assume it is,
+      // so we will insert a possibly unnecessary @SuppressWarnings("unchecked").
+      return true;
+    }
+
+    @Override
+    public Boolean visitArray(ArrayType t, Void p) {
+      return visit(t.getComponentType(), p);
+    }
+
+    @Override
+    public Boolean visitDeclared(DeclaredType t, Void p) {
+      return t.getTypeArguments().stream().anyMatch(CastingUncheckedVisitor::uncheckedTypeArgument);
+    }
+
+    @Override
+    public Boolean visitTypeVariable(TypeVariable t, Void p) {
+      return true;
+    }
+
+    // If a type has a type argument, then casting to the type is unchecked, except if the argument
+    // is <?> or <? extends Object>. The same applies to all type arguments, so casting to Map<?, ?>
+    // does not produce an unchecked warning for example.
+    private static boolean uncheckedTypeArgument(TypeMirror arg) {
+      if (arg.getKind().equals(TypeKind.WILDCARD)) {
+        WildcardType wildcard = asWildcard(arg);
+        if (wildcard.getExtendsBound() == null || isJavaLangObject(wildcard.getExtendsBound())) {
+          // This is <?>, unless there's a super bound, in which case it is <? super Foo> and
+          // is erased.
+          return (wildcard.getSuperBound() != null);
+        }
+      }
+      return true;
+    }
+
+    private static boolean isJavaLangObject(TypeMirror type) {
+      if (type.getKind() != TypeKind.DECLARED) {
+        return false;
+      }
+      TypeElement typeElement = asTypeElement(type);
+      return typeElement.getQualifiedName().contentEquals("java.lang.Object");
+    }
+  }
+
+  private MoreTypes() {}
+}
diff --git a/common/src/main/java/com/google/auto/common/Overrides.java b/common/src/main/java/com/google/auto/common/Overrides.java
new file mode 100644
index 0000000..19a4586
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/Overrides.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2016 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.common;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleTypeVisitor8;
+import javax.lang.model.util.Types;
+
+/**
+ * Determines if one method overrides another. This class defines two ways of doing that:
+ * {@code NativeOverrides} uses the method
+ * {@link Elements#overrides(ExecutableElement, ExecutableElement, TypeElement)} while
+ * {@code ExplicitOverrides} reimplements that method in a way that is more consistent between
+ * compilers, in particular between javac and ecj (the Eclipse compiler).
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+abstract class Overrides {
+  abstract boolean overrides(
+      ExecutableElement overrider, ExecutableElement overridden, TypeElement in);
+
+  static class NativeOverrides extends Overrides {
+    private final Elements elementUtils;
+
+    NativeOverrides(Elements elementUtils) {
+      this.elementUtils = elementUtils;
+    }
+
+    @Override
+    boolean overrides(ExecutableElement overrider, ExecutableElement overridden, TypeElement in) {
+      return elementUtils.overrides(overrider, overridden, in);
+    }
+  }
+
+  static class ExplicitOverrides extends Overrides {
+    private final Types typeUtils;
+
+    ExplicitOverrides(Types typeUtils) {
+      this.typeUtils = typeUtils;
+    }
+
+    @Override
+    public boolean overrides(
+        ExecutableElement overrider, ExecutableElement overridden, TypeElement in) {
+      if (!overrider.getSimpleName().equals(overridden.getSimpleName())) {
+        // They must have the same name.
+        return false;
+      }
+      // We should just be able to write overrider.equals(overridden) here, but that runs afoul
+      // of a problem with Eclipse. If for example you look at the method Stream<E> stream() in
+      // Collection<E>, as obtained by collectionTypeElement.getEnclosedElements(), it will not
+      // compare equal to the method Stream<E> stream() as obtained by
+      // elementUtils.getAllMembers(listTypeElement), even though List<E> inherits the method
+      // from Collection<E>. The reason is that, in ecj, getAllMembers does type substitution,
+      // so the return type of stream() is Stream<E'>, where E' is the E from List<E> rather than
+      // the one from Collection<E>. Instead we compare the enclosing element, which will be
+      // Collection<E> no matter how we got the method. If two methods are in the same type
+      // then it's impossible for one to override the other, regardless of whether they are the
+      // same method.
+      if (overrider.getEnclosingElement().equals(overridden.getEnclosingElement())) {
+        return false;
+      }
+      if (overridden.getModifiers().contains(Modifier.STATIC)) {
+        // Static methods can't be overridden (though they can be hidden by other static methods).
+        return false;
+      }
+      Visibility overriddenVisibility = Visibility.ofElement(overridden);
+      Visibility overriderVisibility = Visibility.ofElement(overrider);
+      if (overriddenVisibility.equals(Visibility.PRIVATE)
+          || overriderVisibility.compareTo(overriddenVisibility) < 0) {
+        // Private methods can't be overridden, and methods can't be overridden by less-visible
+        // methods. The latter condition is enforced by the compiler so in theory we might report
+        // an "incorrect" result here for code that javac would not have allowed.
+        return false;
+      }
+      if (!isSubsignature(overrider, overridden, in)) {
+        return false;
+      }
+      if (!MoreElements.methodVisibleFromPackage(overridden, MoreElements.getPackage(overrider))) {
+        // If the overridden method is a package-private method in a different package then it
+        // can't be overridden.
+        return false;
+      }
+      TypeElement overriddenType;
+      if (!(overridden.getEnclosingElement() instanceof TypeElement)) {
+        return false;
+        // We don't know how this could happen but we avoid blowing up if it does.
+      }
+      overriddenType = MoreElements.asType(overridden.getEnclosingElement());
+      // We erase the types before checking subtypes, because the TypeMirror we get for List<E> is
+      // not a subtype of the one we get for Collection<E> since the two E instances are not the
+      // same. For the purposes of overriding, type parameters in the containing type should not
+      // matter because if the code compiles at all then they must be consistent.
+      if (!typeUtils.isSubtype(
+          typeUtils.erasure(in.asType()), typeUtils.erasure(overriddenType.asType()))) {
+        return false;
+      }
+      if (in.getKind().isClass()) {
+        // Method mC in or inherited by class C (JLS 8.4.8.1)...
+        if (overriddenType.getKind().isClass()) {
+          // ...overrides from C another method mA declared in class A. The only condition we
+          // haven't checked is that C does not inherit mA. Ideally we could just write this:
+          //    return !elementUtils.getAllMembers(in).contains(overridden);
+          // But that doesn't work in Eclipse. For example, getAllMembers(AbstractList)
+          // contains List.isEmpty() where you might reasonably expect it to contain
+          // AbstractCollection.isEmpty(). So we need to visit superclasses until we reach
+          // one that declares the same method, and check that we haven't reached mA. We compare
+          // the enclosing elements rather than the methods themselves for the reason described
+          // at the start of the method.
+          ExecutableElement inherited = methodFromSuperclasses(in, overridden);
+          return !overridden.getEnclosingElement().equals(inherited.getEnclosingElement());
+        } else if (overriddenType.getKind().isInterface()) {
+          // ...overrides from C another method mI declared in interface I. We've already checked
+          // the conditions (assuming that the only alternative to mI being abstract or default is
+          // mI being static, which we eliminated above). However, it appears that the logic here
+          // is necessary in order to be compatible with javac's `overrides` method. An inherited
+          // abstract method does not override another method. (But, if it is not inherited,
+          // it does, including if `in` inherits a concrete method of the same name from its
+          // superclass.) Here again we can use getAllMembers with javac but not with ecj. javac
+          // says that getAllMembers(AbstractList) contains both AbstractCollection.size() and
+          // List.size(), but ecj doesn't have the latter. The spec is not particularly clear so
+          // either seems justifiable. So we need to look up the interface path that goes from `in`
+          // to `overriddenType` (or the several paths if there are several) and apply similar logic
+          // to methodFromSuperclasses above.
+          if (overrider.getModifiers().contains(Modifier.ABSTRACT)) {
+            ExecutableElement inherited = methodFromSuperinterfaces(in, overridden);
+            return !overridden.getEnclosingElement().equals(inherited.getEnclosingElement());
+          } else {
+            return true;
+          }
+        } else {
+          // We don't know what this is so say no.
+          return false;
+        }
+      } else {
+        return in.getKind().isInterface();
+        // Method mI in or inherited by interface I (JLS 9.4.1.1). We've already checked everything.
+        // If this is not an interface then we don't know what it is so we say no.
+      }
+    }
+
+    private boolean isSubsignature(
+        ExecutableElement overrider, ExecutableElement overridden, TypeElement in) {
+      DeclaredType inType = MoreTypes.asDeclared(in.asType());
+      try {
+        ExecutableType overriderExecutable =
+            MoreTypes.asExecutable(typeUtils.asMemberOf(inType, overrider));
+        ExecutableType overriddenExecutable =
+            MoreTypes.asExecutable(typeUtils.asMemberOf(inType, overridden));
+        return typeUtils.isSubsignature(overriderExecutable, overriddenExecutable);
+      } catch (IllegalArgumentException e) {
+        // This might mean that at least one of the methods is not in fact declared in or inherited
+        // by `in` (in which case we should indeed return false); or it might mean that we are
+        // tickling an Eclipse bug such as https://bugs.eclipse.org/bugs/show_bug.cgi?id=499026
+        // (in which case we fall back on explicit code to find the parameters).
+        int nParams = overrider.getParameters().size();
+        if (overridden.getParameters().size() != nParams) {
+          return false;
+        }
+        List<TypeMirror> overriderParams = erasedParameterTypes(overrider, in);
+        List<TypeMirror> overriddenParams = erasedParameterTypes(overridden, in);
+        if (overriderParams == null || overriddenParams == null) {
+          // This probably means that one or other of the methods is not in `in`.
+          return false;
+        }
+        for (int i = 0; i < nParams; i++) {
+          if (!typeUtils.isSameType(overriderParams.get(i), overriddenParams.get(i))) {
+            // If the erasures of the parameters don't correspond, return false. We erase so we
+            // don't get any confusion about different type variables not comparing equal.
+            return false;
+          }
+        }
+        return true;
+      }
+    }
+
+    /**
+     * Returns the list of erased parameter types of the given method as they appear in the given
+     * type. For example, if the method is {@code add(E)} from {@code List<E>} and we ask how it
+     * appears in {@code class NumberList implements List<Number>}, the answer will be
+     * {@code Number}. That will also be the answer for {@code class NumberList<E extends Number>
+     * implements List<E>}. The parameter types are erased since the purpose of this method is to
+     * determine whether two methods are candidates for one to override the other.
+     */
+    ImmutableList<TypeMirror> erasedParameterTypes(ExecutableElement method, TypeElement in) {
+      if (method.getParameters().isEmpty()) {
+        return ImmutableList.of();
+      }
+      return new TypeSubstVisitor().erasedParameterTypes(method, in);
+    }
+
+    /**
+     * Visitor that replaces type variables with their values in the types it sees. If we know
+     * that {@code E} is {@code String}, then we can return {@code String} for {@code E},
+     * {@code List<String>} for {@code List<E>}, {@code String[]} for {@code E[]}, etc. We don't
+     * have to cover all types here because (1) the type is going to end up being erased, and
+     * (2) wildcards can't appear in direct supertypes. So for example it is illegal to write
+     * {@code class MyList implements List<? extends Number>}. It's legal to write
+     * {@code class MyList implements List<Set<? extends Number>>} but that doesn't matter
+     * because the {@code E} of the {@code List} is going to be erased to raw {@code Set}.
+     */
+    private class TypeSubstVisitor extends SimpleTypeVisitor8<TypeMirror, Void> {
+      /**
+       * The bindings of type variables. We can put them all in one map because E in {@code List<E>}
+       * is not the same as E in {@code Collection<E>}. As we ascend the type hierarchy we'll add
+       * mappings for all the variables we see. We could equivalently create a new map for each type
+       * we visit, but this is slightly simpler and probably about as performant.
+       */
+      private final Map<TypeParameterElement, TypeMirror> typeBindings = Maps.newLinkedHashMap();
+
+      ImmutableList<TypeMirror> erasedParameterTypes(ExecutableElement method, TypeElement in) {
+        if (method.getEnclosingElement().equals(in)) {
+          ImmutableList.Builder<TypeMirror> params = ImmutableList.builder();
+          for (VariableElement param : method.getParameters()) {
+            params.add(typeUtils.erasure(visit(param.asType())));
+          }
+          return params.build();
+        }
+        // Make a list of supertypes we are going to visit recursively: the superclass, if there
+        // is one, plus the superinterfaces.
+        List<TypeMirror> supers = Lists.newArrayList();
+        if (in.getSuperclass().getKind() == TypeKind.DECLARED) {
+          supers.add(in.getSuperclass());
+        }
+        supers.addAll(in.getInterfaces());
+        for (TypeMirror supertype : supers) {
+          DeclaredType declared = MoreTypes.asDeclared(supertype);
+          TypeElement element = MoreElements.asType(declared.asElement());
+          List<? extends TypeMirror> actuals = declared.getTypeArguments();
+          List<? extends TypeParameterElement> formals = element.getTypeParameters();
+          Verify.verify(actuals.size() == formals.size());
+          for (int i = 0; i < actuals.size(); i++) {
+            typeBindings.put(formals.get(i), actuals.get(i));
+          }
+          ImmutableList<TypeMirror> params = erasedParameterTypes(method, element);
+          if (params != null) {
+            return params;
+          }
+        }
+        return null;
+      }
+
+      @Override
+      protected TypeMirror defaultAction(TypeMirror e, Void p) {
+        return e;
+      }
+
+      @Override
+      public TypeMirror visitTypeVariable(TypeVariable t, Void p) {
+        Element element = typeUtils.asElement(t);
+        if (element instanceof TypeParameterElement) {
+          TypeParameterElement e = (TypeParameterElement) element;
+          if (typeBindings.containsKey(e)) {
+            return visit(typeBindings.get(e));
+          }
+        }
+        // We erase the upper bound to avoid infinite recursion. We can get away with erasure for
+        // the reasons described above.
+        return visit(typeUtils.erasure(t.getUpperBound()));
+      }
+
+      @Override
+      public TypeMirror visitDeclared(DeclaredType t, Void p) {
+        if (t.getTypeArguments().isEmpty()) {
+          return t;
+        }
+        List<TypeMirror> newArgs = Lists.newArrayList();
+        for (TypeMirror arg : t.getTypeArguments()) {
+          newArgs.add(visit(arg));
+        }
+        return typeUtils.getDeclaredType(asTypeElement(t), newArgs.toArray(new TypeMirror[0]));
+      }
+
+      @Override
+      public TypeMirror visitArray(ArrayType t, Void p) {
+        return typeUtils.getArrayType(visit(t.getComponentType()));
+      }
+    }
+
+    /**
+     * Returns the given method as it appears in the given type. This is the method itself,
+     * or the nearest override in a superclass of the given type, or null if the method is not
+     * found in the given type or any of its superclasses.
+     */
+    ExecutableElement methodFromSuperclasses(TypeElement in, ExecutableElement method) {
+      for (TypeElement t = in; t != null; t = superclass(t)) {
+        ExecutableElement tMethod = methodInType(t, method);
+        if (tMethod != null) {
+          return tMethod;
+        }
+      }
+      return null;
+    }
+
+    /**
+     * Returns the given interface method as it appears in the given type. This is the method
+     * itself, or the nearest override in a superinterface of the given type, or null if the method
+     * is not found in the given type or any of its transitive superinterfaces.
+     */
+    ExecutableElement methodFromSuperinterfaces(TypeElement in, ExecutableElement method) {
+      TypeElement methodContainer = MoreElements.asType(method.getEnclosingElement());
+      Preconditions.checkArgument(methodContainer.getKind().isInterface());
+      TypeMirror methodContainerType = typeUtils.erasure(methodContainer.asType());
+      ImmutableList<TypeElement> types = ImmutableList.of(in);
+      // On the first pass through this loop, `types` is the type we're starting from,
+      // which might be a class or an interface. On later passes it is a list of direct
+      // superinterfaces we saw in the previous pass, but only the ones that were assignable
+      // to the interface that `method` appears in.
+      while (!types.isEmpty()) {
+        ImmutableList.Builder<TypeElement> newTypes = ImmutableList.builder();
+        for (TypeElement t : types) {
+          TypeMirror candidateType = typeUtils.erasure(t.asType());
+          if (typeUtils.isAssignable(candidateType, methodContainerType)) {
+            ExecutableElement tMethod = methodInType(t, method);
+            if (tMethod != null) {
+              return tMethod;
+            }
+            newTypes.addAll(superinterfaces(t));
+          }
+          if (t.getKind().isClass()) {
+            TypeElement sup = superclass(t);
+            if (sup != null) {
+              newTypes.add(sup);
+            }
+          }
+        }
+        types = newTypes.build();
+      }
+      return null;
+    }
+
+    /**
+     * Returns the method from within the given type that has the same erased signature as the given
+     * method, or null if there is no such method.
+     */
+    private ExecutableElement methodInType(TypeElement type, ExecutableElement method) {
+      int nParams = method.getParameters().size();
+      List<TypeMirror> params = erasedParameterTypes(method, type);
+      if (params == null) {
+        return null;
+      }
+      methods:
+      for (ExecutableElement tMethod : ElementFilter.methodsIn(type.getEnclosedElements())) {
+        if (tMethod.getSimpleName().equals(method.getSimpleName())
+            && tMethod.getParameters().size() == nParams) {
+          for (int i = 0; i < nParams; i++) {
+            TypeMirror tParamType = typeUtils.erasure(tMethod.getParameters().get(i).asType());
+            if (!typeUtils.isSameType(params.get(i), tParamType)) {
+              continue methods;
+            }
+          }
+          return tMethod;
+        }
+      }
+      return null;
+    }
+
+    private TypeElement superclass(TypeElement type) {
+      TypeMirror sup = type.getSuperclass();
+      if (sup.getKind() == TypeKind.DECLARED) {
+        return MoreElements.asType(typeUtils.asElement(sup));
+      } else {
+        return null;
+      }
+    }
+
+    private ImmutableList<TypeElement> superinterfaces(TypeElement type) {
+      ImmutableList.Builder<TypeElement> types = ImmutableList.builder();
+      for (TypeMirror sup : type.getInterfaces()) {
+        types.add(MoreElements.asType(typeUtils.asElement(sup)));
+      }
+      return types.build();
+    }
+
+    private TypeElement asTypeElement(TypeMirror typeMirror) {
+      DeclaredType declaredType = MoreTypes.asDeclared(typeMirror);
+      Element element = declaredType.asElement();
+      return MoreElements.asType(element);
+    }
+  }
+}
diff --git a/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java b/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java
new file mode 100644
index 0000000..7d508e3
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2017 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.common;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+
+/**
+ * A simple implementation of the {@link AnnotationMirror} interface.
+ *
+ * <p>This type implements {@link #equals(Object)} and {@link #hashCode()} using {@link
+ * AnnotationMirrors#equivalence} in accordance with the {@link AnnotationMirror} spec. Some {@link
+ * AnnotationMirror}s, however, do not correctly implement equals, you should always compare them
+ * using {@link AnnotationMirrors#equivalence} anyway.
+ */
+public final class SimpleAnnotationMirror implements AnnotationMirror {
+  private final TypeElement annotationType;
+  private final ImmutableMap<String, ? extends AnnotationValue> namedValues;
+  private final ImmutableMap<ExecutableElement, ? extends AnnotationValue> elementValues;
+
+  private SimpleAnnotationMirror(
+      TypeElement annotationType, Map<String, ? extends AnnotationValue> namedValues) {
+    checkArgument(
+        annotationType.getKind().equals(ElementKind.ANNOTATION_TYPE),
+        "annotationType must be an annotation: %s",
+        annotationType);
+    Map<String, AnnotationValue> values = new LinkedHashMap<>();
+    Map<String, AnnotationValue> unusedValues = new LinkedHashMap<>(namedValues);
+    List<String> missingMembers = new ArrayList<>();
+    for (ExecutableElement method : methodsIn(annotationType.getEnclosedElements())) {
+      String memberName = method.getSimpleName().toString();
+      if (unusedValues.containsKey(memberName)) {
+        values.put(memberName, unusedValues.remove(memberName));
+      } else if (method.getDefaultValue() != null) {
+        values.put(memberName, method.getDefaultValue());
+      } else {
+        missingMembers.add(memberName);
+      }
+    }
+    
+    checkArgument(
+        unusedValues.isEmpty(),
+        "namedValues has entries for members that are not in %s: %s",
+        annotationType,
+        unusedValues);
+    checkArgument(
+        missingMembers.isEmpty(), "namedValues is missing entries for: %s", missingMembers);
+
+    this.annotationType = annotationType;
+    this.namedValues = ImmutableMap.copyOf(namedValues);
+    this.elementValues =
+        methodsIn(annotationType.getEnclosedElements())
+            .stream()
+            .collect(toImmutableMap(e -> e, e -> values.get(e.getSimpleName().toString())));
+  }
+
+  /**
+   * An object representing an {@linkplain ElementKind#ANNOTATION_TYPE annotation} instance. If
+   * {@code annotationType} has any annotation members, they must have default values.
+   */
+  public static AnnotationMirror of(TypeElement annotationType) {
+    return of(annotationType, ImmutableMap.of());
+  }
+
+  /**
+   * An object representing an {@linkplain ElementKind#ANNOTATION_TYPE annotation} instance. If
+   * {@code annotationType} has any annotation members, they must either be present in {@code
+   * namedValues} or have default values.
+   */
+  public static AnnotationMirror of(
+      TypeElement annotationType, Map<String, ? extends AnnotationValue> namedValues) {
+    return new SimpleAnnotationMirror(annotationType, namedValues);
+  }
+
+  @Override
+  public DeclaredType getAnnotationType() {
+    return MoreTypes.asDeclared(annotationType.asType());
+  }
+
+  @Override
+  public Map<ExecutableElement, ? extends AnnotationValue> getElementValues() {
+    return elementValues;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder("@").append(annotationType.getQualifiedName());
+    if (!namedValues.isEmpty()) {
+      builder
+          .append('(')
+          .append(Joiner.on(", ").withKeyValueSeparator(" = ").join(namedValues))
+          .append(')');
+    }
+    return builder.toString();
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    return other instanceof AnnotationMirror
+        && AnnotationMirrors.equivalence().equivalent(this, (AnnotationMirror) other);
+  }
+
+  @Override
+  public int hashCode() {
+    return AnnotationMirrors.equivalence().hash(this);
+  }
+}
diff --git a/common/src/main/java/com/google/auto/common/SimpleTypeAnnotationValue.java b/common/src/main/java/com/google/auto/common/SimpleTypeAnnotationValue.java
new file mode 100644
index 0000000..a281011
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/SimpleTypeAnnotationValue.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 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.common;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static javax.lang.model.type.TypeKind.ARRAY;
+import static javax.lang.model.type.TypeKind.DECLARED;
+
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A simple implementation of the {@link AnnotationValue} interface for a class literal, e.g. an
+ * annotation member of type {@code Class<?>} or {@code Class<? extends Foo>}.
+ */
+public final class SimpleTypeAnnotationValue implements AnnotationValue {
+  private final TypeMirror value;
+
+  private SimpleTypeAnnotationValue(TypeMirror value) {
+    checkArgument(
+        value.getKind().isPrimitive()
+            || value.getKind().equals(DECLARED)
+            || value.getKind().equals(ARRAY),
+        "value must be a primitive, array, or declared type, but was %s (%s)",
+        value.getKind(),
+        value);
+    if (value.getKind().equals(DECLARED)) {
+      checkArgument(
+          MoreTypes.asDeclared(value).getTypeArguments().isEmpty(),
+          "value must not be a parameterized type: %s",
+          value);
+    }
+    this.value = value;
+  }
+
+  /**
+   * An object representing an annotation value instance.
+   *
+   * @param value a primitive, array, or non-parameterized declared type
+   */
+  public static AnnotationValue of(TypeMirror value) {
+    return new SimpleTypeAnnotationValue(value);
+  }
+
+  @Override
+  public TypeMirror getValue() {
+    return value;
+  }
+
+  @Override
+  public String toString() {
+    return value + ".class";
+  }
+
+  @Override
+  public <R, P> R accept(AnnotationValueVisitor<R, P> visitor, P parameter) {
+    return visitor.visitType(getValue(), parameter);
+  }
+}
diff --git a/common/src/main/java/com/google/auto/common/SuperficialValidation.java b/common/src/main/java/com/google/auto/common/SuperficialValidation.java
new file mode 100644
index 0000000..dddb1be
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/SuperficialValidation.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2014 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.common;
+
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementVisitor;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.AbstractElementVisitor8;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+import javax.lang.model.util.SimpleTypeVisitor8;
+
+/**
+ * A utility class that traverses {@link Element} instances and ensures that all type information
+ * is present and resolvable.
+ *
+ * @author Gregory Kick
+ */
+public final class SuperficialValidation {
+  public static boolean validateElements(Iterable<? extends Element> elements) {
+    for (Element element : elements) {
+      if (!validateElement(element)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private static final ElementVisitor<Boolean, Void> ELEMENT_VALIDATING_VISITOR =
+      new AbstractElementVisitor8<Boolean, Void>() {
+        @Override public Boolean visitPackage(PackageElement e, Void p) {
+          // don't validate enclosed elements because it will return types in the package
+          return validateAnnotations(e.getAnnotationMirrors());
+        }
+
+        @Override public Boolean visitType(TypeElement e, Void p) {
+          return isValidBaseElement(e)
+              && validateElements(e.getTypeParameters())
+              && validateTypes(e.getInterfaces())
+              && validateType(e.getSuperclass());
+        }
+
+        @Override public Boolean visitVariable(VariableElement e, Void p) {
+          return isValidBaseElement(e);
+        }
+
+        @Override public Boolean visitExecutable(ExecutableElement e, Void p) {
+          AnnotationValue defaultValue = e.getDefaultValue();
+          return isValidBaseElement(e)
+              && (defaultValue == null || validateAnnotationValue(defaultValue, e.getReturnType()))
+              && validateType(e.getReturnType())
+              && validateTypes(e.getThrownTypes())
+              && validateElements(e.getTypeParameters())
+              && validateElements(e.getParameters());
+        }
+
+        @Override public Boolean visitTypeParameter(TypeParameterElement e, Void p) {
+          return isValidBaseElement(e)
+              && validateTypes(e.getBounds());
+        }
+
+        @Override public Boolean visitUnknown(Element e, Void p) {
+          // just assume that unknown elements are OK
+          return true;
+        }
+      };
+
+  public static boolean validateElement(Element element) {
+    return element.accept(ELEMENT_VALIDATING_VISITOR, null);
+  }
+
+  private static boolean isValidBaseElement(Element e) {
+    return validateType(e.asType())
+        && validateAnnotations(e.getAnnotationMirrors())
+        && validateElements(e.getEnclosedElements());
+  }
+
+  private static boolean validateTypes(Iterable<? extends TypeMirror> types) {
+    for (TypeMirror type : types) {
+      if (!validateType(type)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /*
+   * This visitor does not test type variables specifically, but it seems that that is not actually
+   * an issue.  Javac turns the whole type parameter into an error type if it can't figure out the
+   * bounds.
+   */
+  private static final TypeVisitor<Boolean, Void> TYPE_VALIDATING_VISITOR =
+      new SimpleTypeVisitor8<Boolean, Void>() {
+        @Override
+        protected Boolean defaultAction(TypeMirror t, Void p) {
+          return true;
+        }
+
+        @Override
+        public Boolean visitArray(ArrayType t, Void p) {
+          return validateType(t.getComponentType());
+        }
+
+        @Override
+        public Boolean visitDeclared(DeclaredType t, Void p) {
+          return validateTypes(t.getTypeArguments());
+        }
+
+        @Override
+        public Boolean visitError(ErrorType t, Void p) {
+          return false;
+        }
+
+        @Override
+        public Boolean visitUnknown(TypeMirror t, Void p) {
+          // just make the default choice for unknown types
+          return defaultAction(t, p);
+        }
+
+        @Override
+        public Boolean visitWildcard(WildcardType t, Void p) {
+          TypeMirror extendsBound = t.getExtendsBound();
+          TypeMirror superBound = t.getSuperBound();
+          return (extendsBound == null || validateType(extendsBound))
+              && (superBound == null || validateType(superBound));
+        }
+
+        @Override
+        public Boolean visitExecutable(ExecutableType t, Void p) {
+          return validateTypes(t.getParameterTypes())
+              && validateType(t.getReturnType())
+              && validateTypes(t.getThrownTypes())
+              && validateTypes(t.getTypeVariables());
+        }
+      };
+
+  private static boolean validateType(TypeMirror type) {
+    return type.accept(TYPE_VALIDATING_VISITOR, null);
+  }
+
+  private static boolean validateAnnotations(
+      Iterable<? extends AnnotationMirror> annotationMirrors) {
+    for (AnnotationMirror annotationMirror : annotationMirrors) {
+      if (!validateAnnotation(annotationMirror)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private static boolean validateAnnotation(AnnotationMirror annotationMirror) {
+    return validateType(annotationMirror.getAnnotationType())
+        && validateAnnotationValues(annotationMirror.getElementValues());
+  }
+
+  @SuppressWarnings("unused")
+  private static boolean validateAnnotationValues(
+      Map<? extends ExecutableElement, ? extends AnnotationValue> valueMap) {
+    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> valueEntry :
+        valueMap.entrySet()) {
+      TypeMirror expectedType = valueEntry.getKey().getReturnType();
+      if (!validateAnnotationValue(valueEntry.getValue(), expectedType)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private static final AnnotationValueVisitor<Boolean, TypeMirror> VALUE_VALIDATING_VISITOR =
+      new SimpleAnnotationValueVisitor8<Boolean, TypeMirror>() {
+        @Override protected Boolean defaultAction(Object o, TypeMirror expectedType) {
+          return MoreTypes.isTypeOf(o.getClass(), expectedType);
+        }
+
+        @Override public Boolean visitUnknown(AnnotationValue av, TypeMirror expectedType) {
+          // just take the default action for the unknown
+          return defaultAction(av, expectedType);
+        }
+
+        @Override public Boolean visitAnnotation(AnnotationMirror a, TypeMirror expectedType) {
+          return MoreTypes.equivalence().equivalent(a.getAnnotationType(), expectedType)
+              && validateAnnotation(a);
+        }
+
+        @Override
+        public Boolean visitArray(List<? extends AnnotationValue> values, TypeMirror expectedType) {
+          if (!expectedType.getKind().equals(TypeKind.ARRAY)) {
+            return false;
+          }
+          try {
+            expectedType = MoreTypes.asArray(expectedType).getComponentType();
+          } catch (IllegalArgumentException e) {
+            return false; // Not an array expected, ergo invalid.
+          }
+          for (AnnotationValue value : values) {
+            if (!value.accept(this, expectedType)) {
+              return false;
+            }
+          }
+          return true;
+        }
+
+        @Override
+        public Boolean visitEnumConstant(VariableElement enumConstant, TypeMirror expectedType) {
+          return MoreTypes.equivalence().equivalent(enumConstant.asType(), expectedType)
+              && validateElement(enumConstant);
+        }
+
+        @Override public Boolean visitType(TypeMirror type, TypeMirror ignored) {
+          // We could check assignability here, but would require a Types instance. Since this
+          // isn't really the sort of thing that shows up in a bad AST from upstream compilation
+          // we ignore the expected type and just validate the type.  It might be wrong, but
+          // it's valid.
+          return validateType(type);
+        }
+
+        @Override public Boolean visitBoolean(boolean b, TypeMirror expectedType) {
+          return MoreTypes.isTypeOf(Boolean.TYPE, expectedType);
+        }
+
+        @Override public Boolean visitByte(byte b, TypeMirror expectedType) {
+          return MoreTypes.isTypeOf(Byte.TYPE, expectedType);
+        }
+
+        @Override public Boolean visitChar(char c, TypeMirror expectedType) {
+          return MoreTypes.isTypeOf(Character.TYPE, expectedType);
+        }
+
+        @Override public Boolean visitDouble(double d, TypeMirror expectedType) {
+          return MoreTypes.isTypeOf(Double.TYPE, expectedType);
+        }
+
+        @Override public Boolean visitFloat(float f, TypeMirror expectedType) {
+          return MoreTypes.isTypeOf(Float.TYPE, expectedType);
+        }
+
+        @Override public Boolean visitInt(int i, TypeMirror expectedType) {
+          return MoreTypes.isTypeOf(Integer.TYPE, expectedType);
+        }
+
+        @Override public Boolean visitLong(long l, TypeMirror expectedType) {
+          return MoreTypes.isTypeOf(Long.TYPE, expectedType);
+        }
+
+        @Override public Boolean visitShort(short s, TypeMirror expectedType) {
+          return MoreTypes.isTypeOf(Short.TYPE, expectedType);
+        }
+      };
+
+  private static boolean validateAnnotationValue(
+      AnnotationValue annotationValue, TypeMirror expectedType) {
+    return annotationValue.accept(VALUE_VALIDATING_VISITOR, expectedType);
+  }
+}
diff --git a/common/src/main/java/com/google/auto/common/Visibility.java b/common/src/main/java/com/google/auto/common/Visibility.java
new file mode 100644
index 0000000..f82fdd5
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/Visibility.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static javax.lang.model.element.ElementKind.PACKAGE;
+
+import com.google.common.base.Enums;
+import com.google.common.collect.Ordering;
+import java.util.Set;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.Modifier;
+
+/**
+ * Represents the visibility of a given {@link Element}: {@code public}, {@code protected},
+ * {@code private} or default/package-private.
+ *
+ * <p>The constants for this enum are ordered according by increasing visibility.
+ *
+ * @author Gregory Kick
+ */
+public enum Visibility {
+  PRIVATE,
+  DEFAULT,
+  PROTECTED,
+  PUBLIC;
+
+  // TODO(ronshapiro): remove this and reference ElementKind.MODULE directly once we start building
+  // with -source 9
+  private static final ElementKind MODULE =
+      Enums.getIfPresent(ElementKind.class, "MODULE").orNull();
+
+  /**
+   * Returns the visibility of the given {@link Element}. While package and module elements don't
+   * technically have a visibility associated with them, this method returns {@link #PUBLIC} for
+   * them.
+   */
+  public static Visibility ofElement(Element element) {
+    checkNotNull(element);
+    // packages and module don't have modifiers, but they're obviously "public"
+    if (element.getKind().equals(PACKAGE) || element.getKind().equals(MODULE)) {
+      return PUBLIC;
+    }
+    Set<Modifier> modifiers = element.getModifiers();
+    if (modifiers.contains(Modifier.PRIVATE)) {
+      return PRIVATE;
+    } else if (modifiers.contains(Modifier.PROTECTED)) {
+      return PROTECTED;
+    } else if (modifiers.contains(Modifier.PUBLIC)) {
+      return PUBLIC;
+    } else {
+      return DEFAULT;
+    }
+  }
+
+  /**
+   * Returns effective visibility of the given element meaning that it takes into account the
+   * visibility of its enclosing elements.
+   */
+  public static Visibility effectiveVisibilityOfElement(Element element) {
+    checkNotNull(element);
+    Visibility effectiveVisibility = PUBLIC;
+    Element currentElement = element;
+    while (currentElement != null) {
+      effectiveVisibility =
+          Ordering.natural().min(effectiveVisibility, ofElement(currentElement));
+      currentElement = currentElement.getEnclosingElement();
+    }
+    return effectiveVisibility;
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java b/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java
new file mode 100644
index 0000000..dfc043a
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.BLAH;
+import static com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.FOO;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.testing.EquivalenceTester;
+import com.google.testing.compile.CompilationRule;
+import java.util.Map;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleAnnotationValueVisitor6;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests {@link AnnotationMirrors}.
+ */
+@RunWith(JUnit4.class)
+public class AnnotationMirrorsTest {
+  @Rule public CompilationRule compilationRule = new CompilationRule();
+
+  private Elements elements;
+
+  @Before public void setUp() {
+    this.elements = compilationRule.getElements();
+  }
+
+  @interface SimpleAnnotation {}
+
+  @SimpleAnnotation class SimplyAnnotated {}
+  @SimpleAnnotation class AlsoSimplyAnnotated {}
+
+  enum SimpleEnum {
+    BLAH, FOO
+  }
+
+  @interface Outer {
+    SimpleEnum value();
+  }
+
+  @Outer(BLAH) static class TestClassBlah {}
+  @Outer(BLAH) static class TestClassBlah2 {}
+  @Outer(FOO) static class TestClassFoo {}
+
+  @interface DefaultingOuter {
+    SimpleEnum value() default SimpleEnum.BLAH;
+  }
+
+  @DefaultingOuter class TestWithDefaultingOuterDefault {}
+  @DefaultingOuter(BLAH) class TestWithDefaultingOuterBlah {}
+  @DefaultingOuter(FOO) class TestWithDefaultingOuterFoo {}
+
+  @interface AnnotatedOuter {
+    DefaultingOuter value();
+  }
+
+  @AnnotatedOuter(@DefaultingOuter) class TestDefaultNestedAnnotated {}
+  @AnnotatedOuter(@DefaultingOuter(BLAH)) class TestBlahNestedAnnotated {}
+  @AnnotatedOuter(@DefaultingOuter(FOO)) class TestFooNestedAnnotated {}
+
+  @interface OuterWithValueArray {
+    DefaultingOuter[] value() default {};
+  }
+
+  @OuterWithValueArray class TestValueArrayWithDefault {}
+  @OuterWithValueArray({}) class TestValueArrayWithEmpty {}
+
+  @OuterWithValueArray({@DefaultingOuter}) class TestValueArrayWithOneDefault {}
+  @OuterWithValueArray(@DefaultingOuter(BLAH)) class TestValueArrayWithOneBlah {}
+  @OuterWithValueArray(@DefaultingOuter(FOO)) class TestValueArrayWithOneFoo {}
+
+  @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter})
+  class TestValueArrayWithFooAndDefaultBlah {}
+  @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter(BLAH)})
+  class TestValueArrayWithFooBlah {}
+  @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter(BLAH)})
+  class TestValueArrayWithFooBlah2 {} // Different instances than on TestValueArrayWithFooBlah.
+  @OuterWithValueArray({@DefaultingOuter(BLAH), @DefaultingOuter(FOO)})
+  class TestValueArrayWithBlahFoo {}
+
+  @Test public void testEquivalences() {
+    EquivalenceTester<AnnotationMirror> tester =
+        EquivalenceTester.of(AnnotationMirrors.equivalence());
+
+    tester.addEquivalenceGroup(
+        annotationOn(SimplyAnnotated.class),
+        annotationOn(AlsoSimplyAnnotated.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestClassBlah.class),
+        annotationOn(TestClassBlah2.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestClassFoo.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestWithDefaultingOuterDefault.class),
+        annotationOn(TestWithDefaultingOuterBlah.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestWithDefaultingOuterFoo.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestDefaultNestedAnnotated.class),
+        annotationOn(TestBlahNestedAnnotated.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestFooNestedAnnotated.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestValueArrayWithDefault.class),
+        annotationOn(TestValueArrayWithEmpty.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestValueArrayWithOneDefault.class),
+        annotationOn(TestValueArrayWithOneBlah.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestValueArrayWithOneFoo.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestValueArrayWithFooAndDefaultBlah.class),
+        annotationOn(TestValueArrayWithFooBlah.class),
+        annotationOn(TestValueArrayWithFooBlah2.class));
+
+    tester.addEquivalenceGroup(
+        annotationOn(TestValueArrayWithBlahFoo.class));
+
+    tester.test();
+  }
+
+  @interface Stringy {
+    String value() default "default";
+  }
+
+  @Stringy class StringyUnset {}
+  @Stringy("foo") class StringySet {}
+
+  @Test public void testGetDefaultValuesUnset() {
+    assertThat(annotationOn(StringyUnset.class).getElementValues()).isEmpty();
+    Iterable<AnnotationValue> values = AnnotationMirrors.getAnnotationValuesWithDefaults(
+        annotationOn(StringyUnset.class)).values();
+    String value = getOnlyElement(values).accept(new SimpleAnnotationValueVisitor6<String, Void>() {
+          @Override public String visitString(String value, Void ignored) {
+            return value;
+          }
+        }, null);
+    assertThat(value).isEqualTo("default");
+  }
+
+  @Test public void testGetDefaultValuesSet() {
+    Iterable<AnnotationValue> values = AnnotationMirrors.getAnnotationValuesWithDefaults(
+        annotationOn(StringySet.class)).values();
+    String value = getOnlyElement(values).accept(new SimpleAnnotationValueVisitor6<String, Void>() {
+          @Override public String visitString(String value, Void ignored) {
+            return value;
+          }
+        }, null);
+    assertThat(value).isEqualTo("foo");
+  }
+
+  @Test public void testGetValueEntry() {
+    Map.Entry<ExecutableElement, AnnotationValue> elementValue =
+        AnnotationMirrors.getAnnotationElementAndValue(
+            annotationOn(TestClassBlah.class), "value");
+    assertThat(elementValue.getKey().getSimpleName().toString()).isEqualTo("value");
+    assertThat(elementValue.getValue().getValue()).isInstanceOf(VariableElement.class);
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(
+        annotationOn(TestClassBlah.class), "value");
+    assertThat(value.getValue()).isInstanceOf(VariableElement.class);
+  }
+
+  @Test public void testGetValueEntryFailure() {
+    try {
+      AnnotationMirrors.getAnnotationValue(annotationOn(TestClassBlah.class), "a");
+    } catch (IllegalArgumentException e) {
+      assertThat(e)
+          .hasMessageThat()
+          .isEqualTo(
+              "@com.google.auto.common.AnnotationMirrorsTest.Outer does not define an element a()");
+      return;
+    }
+    fail("Should have thrown.");
+  }
+
+  private AnnotationMirror annotationOn(Class<?> clazz) {
+    return getOnlyElement(elements.getTypeElement(clazz.getCanonicalName()).getAnnotationMirrors());
+  }
+
+}
diff --git a/common/src/test/java/com/google/auto/common/AnnotationValuesTest.java b/common/src/test/java/com/google/auto/common/AnnotationValuesTest.java
new file mode 100644
index 0000000..825c85a
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/AnnotationValuesTest.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2019 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.common;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.Correspondence;
+import com.google.testing.compile.CompilationRule;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+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.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests {@link AnnotationValues}. */
+@RunWith(JUnit4.class)
+public final class AnnotationValuesTest {
+
+  private @interface InsideAnnotation {
+    int value();
+  }
+
+  private static class GenericClass<T> {}
+
+  private static class InsideClassA {}
+
+  private static class InsideClassB {}
+
+  private @interface MultiValueAnnotation {
+    Class<InsideClassA> classValue();
+
+    Class<?>[] classValues();
+
+    Class<?> genericClassValue();
+
+    InsideAnnotation insideAnnotationValue();
+
+    InsideAnnotation[] insideAnnotationValues();
+
+    String stringValue();
+
+    String[] stringValues();
+
+    Foo enumValue();
+
+    Foo[] enumValues();
+
+    int intValue();
+
+    int[] intValues();
+
+    long longValue();
+
+    long[] longValues();
+
+    byte byteValue();
+
+    byte[] byteValues();
+
+    short shortValue();
+
+    short[] shortValues();
+
+    float floatValue();
+
+    float[] floatValues();
+
+    double doubleValue();
+
+    double[] doubleValues();
+
+    boolean booleanValue();
+
+    boolean[] booleanValues();
+
+    char charValue();
+
+    char[] charValues();
+  }
+
+  private enum Foo {
+    BAR,
+    BAZ,
+    BAH;
+  }
+
+  @MultiValueAnnotation(
+      classValue = InsideClassA.class,
+      classValues = {InsideClassA.class, InsideClassB.class},
+      genericClassValue = GenericClass.class,
+      insideAnnotationValue = @InsideAnnotation(19),
+      insideAnnotationValues = {@InsideAnnotation(20), @InsideAnnotation(21)},
+      stringValue = "hello",
+      stringValues = {"it's", "me"},
+      enumValue = Foo.BAR,
+      enumValues = {Foo.BAZ, Foo.BAH},
+      intValue = 5,
+      intValues = {1, 2},
+      longValue = 6L,
+      longValues = {3L, 4L},
+      byteValue = (byte) 7,
+      byteValues = {(byte) 8, (byte) 9},
+      shortValue = (short) 10,
+      shortValues = {(short) 11, (short) 12},
+      floatValue = 13F,
+      floatValues = {14F, 15F},
+      doubleValue = 16D,
+      doubleValues = {17D, 18D},
+      booleanValue = true,
+      booleanValues = {true, false},
+      charValue = 'a',
+      charValues = {'b', 'c'})
+  private static class AnnotatedClass {}
+
+  @Rule public final CompilationRule compilation = new CompilationRule();
+
+  private Elements elements;
+  private Types types;
+  private AnnotationMirror annotationMirror;
+
+  @Before
+  public void setUp() {
+    elements = compilation.getElements();
+    types = compilation.getTypes();
+    TypeElement annotatedClass = getTypeElement(AnnotatedClass.class);
+    annotationMirror =
+        MoreElements.getAnnotationMirror(annotatedClass, MultiValueAnnotation.class).get();
+  }
+
+  @Test
+  public void getTypeMirror() {
+    TypeElement insideClassA = getTypeElement(InsideClassA.class);
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "classValue");
+    assertThat(AnnotationValues.getTypeMirror(value).asElement()).isEqualTo(insideClassA);
+  }
+
+  @Test
+  public void getTypeMirrorGenericClass() {
+    TypeElement genericClass = getTypeElement(GenericClass.class);
+    AnnotationValue gvalue =
+        AnnotationMirrors.getAnnotationValue(annotationMirror, "genericClassValue");
+    assertThat(AnnotationValues.getTypeMirror(gvalue).asElement()).isEqualTo(genericClass);
+  }
+
+  @Test
+  public void getTypeMirrors() {
+    TypeMirror insideClassA = getTypeElement(InsideClassA.class).asType();
+    TypeMirror insideClassB = getTypeElement(InsideClassB.class).asType();
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "classValues");
+    ImmutableList<DeclaredType> valueElements = AnnotationValues.getTypeMirrors(value);
+    assertThat(valueElements)
+        .comparingElementsUsing(Correspondence.from(types::isSameType, "has Same Type"))
+        .containsExactly(insideClassA, insideClassB)
+        .inOrder();
+  }
+
+  @Test
+  public void getAnnotationMirror() {
+    TypeElement insideAnnotation = getTypeElement(InsideAnnotation.class);
+    AnnotationValue value =
+        AnnotationMirrors.getAnnotationValue(annotationMirror, "insideAnnotationValue");
+    AnnotationMirror annotationMirror = AnnotationValues.getAnnotationMirror(value);
+    assertThat(annotationMirror.getAnnotationType().asElement()).isEqualTo(insideAnnotation);
+    assertThat(AnnotationMirrors.getAnnotationValue(annotationMirror, "value").getValue())
+        .isEqualTo(19);
+  }
+
+  @Test
+  public void getAnnotationMirrors() {
+    TypeElement insideAnnotation = getTypeElement(InsideAnnotation.class);
+    AnnotationValue value =
+        AnnotationMirrors.getAnnotationValue(annotationMirror, "insideAnnotationValues");
+    ImmutableList<AnnotationMirror> annotationMirrors =
+        AnnotationValues.getAnnotationMirrors(value);
+    ImmutableList<Element> valueElements =
+        annotationMirrors.stream()
+            .map(AnnotationMirror::getAnnotationType)
+            .map(DeclaredType::asElement)
+            .collect(toImmutableList());
+    assertThat(valueElements).containsExactly(insideAnnotation, insideAnnotation);
+    ImmutableList<Object> valuesStoredInAnnotation =
+        annotationMirrors.stream()
+            .map(
+                annotationMirror ->
+                    AnnotationMirrors.getAnnotationValue(annotationMirror, "value").getValue())
+            .collect(toImmutableList());
+    assertThat(valuesStoredInAnnotation).containsExactly(20, 21).inOrder();
+  }
+
+  @Test
+  public void getString() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "stringValue");
+    assertThat(AnnotationValues.getString(value)).isEqualTo("hello");
+  }
+
+  @Test
+  public void getStrings() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "stringValues");
+    assertThat(AnnotationValues.getStrings(value)).containsExactly("it's", "me").inOrder();
+  }
+
+  @Test
+  public void getEnum() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "enumValue");
+    assertThat(AnnotationValues.getEnum(value)).isEqualTo(value.getValue());
+  }
+
+  @Test
+  public void getEnums() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "enumValues");
+    assertThat(getEnumNames(AnnotationValues.getEnums(value)))
+        .containsExactly(Foo.BAZ.name(), Foo.BAH.name())
+        .inOrder();
+  }
+
+  @Test
+  public void getAnnotationValues() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "intValues");
+    ImmutableList<AnnotationValue> values = AnnotationValues.getAnnotationValues(value);
+    assertThat(values)
+        .comparingElementsUsing(Correspondence.transforming(AnnotationValue::getValue, "has value"))
+        .containsExactly(1, 2)
+        .inOrder();
+  }
+
+  @Test
+  public void getInt() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "intValue");
+    assertThat(AnnotationValues.getInt(value)).isEqualTo(5);
+  }
+
+  @Test
+  public void getInts() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "intValues");
+    assertThat(AnnotationValues.getInts(value)).containsExactly(1, 2).inOrder();
+  }
+
+  @Test
+  public void getLong() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "longValue");
+    assertThat(AnnotationValues.getLong(value)).isEqualTo(6L);
+  }
+
+  @Test
+  public void getLongs() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "longValues");
+    assertThat(AnnotationValues.getLongs(value)).containsExactly(3L, 4L).inOrder();
+  }
+
+  @Test
+  public void getByte() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "byteValue");
+    assertThat(AnnotationValues.getByte(value)).isEqualTo((byte) 7);
+  }
+
+  @Test
+  public void getBytes() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "byteValues");
+    assertThat(AnnotationValues.getBytes(value)).containsExactly((byte) 8, (byte) 9).inOrder();
+  }
+
+  @Test
+  public void getShort() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "shortValue");
+    assertThat(AnnotationValues.getShort(value)).isEqualTo((short) 10);
+  }
+
+  @Test
+  public void getShorts() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "shortValues");
+    assertThat(AnnotationValues.getShorts(value)).containsExactly((short) 11, (short) 12).inOrder();
+  }
+
+  @Test
+  public void getFloat() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "floatValue");
+    assertThat(AnnotationValues.getFloat(value)).isEqualTo(13F);
+  }
+
+  @Test
+  public void getFloats() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "floatValues");
+    assertThat(AnnotationValues.getFloats(value)).containsExactly(14F, 15F).inOrder();
+  }
+
+  @Test
+  public void getDouble() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "doubleValue");
+    assertThat(AnnotationValues.getDouble(value)).isEqualTo(16D);
+  }
+
+  @Test
+  public void getDoubles() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "doubleValues");
+    assertThat(AnnotationValues.getDoubles(value)).containsExactly(17D, 18D).inOrder();
+  }
+
+  @Test
+  public void getBoolean() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "booleanValue");
+    assertThat(AnnotationValues.getBoolean(value)).isTrue();
+  }
+
+  @Test
+  public void getBooleans() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "booleanValues");
+    assertThat(AnnotationValues.getBooleans(value)).containsExactly(true, false).inOrder();
+  }
+
+  @Test
+  public void getChar() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "charValue");
+    assertThat(AnnotationValues.getChar(value)).isEqualTo('a');
+  }
+
+  @Test
+  public void getChars() {
+    AnnotationValue value = AnnotationMirrors.getAnnotationValue(annotationMirror, "charValues");
+    assertThat(AnnotationValues.getChars(value)).containsExactly('b', 'c').inOrder();
+  }
+
+  private TypeElement getTypeElement(Class<?> clazz) {
+    return elements.getTypeElement(clazz.getCanonicalName());
+  }
+
+  private static ImmutableList<String> getEnumNames(ImmutableList<VariableElement> values) {
+    return values.stream()
+        .map(VariableElement::getSimpleName)
+        .map(Name::toString)
+        .collect(toImmutableList());
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java
new file mode 100644
index 0000000..59a135c
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.common.collect.Multimaps.transformValues;
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
+import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
+import static javax.tools.Diagnostic.Kind.ERROR;
+import static javax.tools.StandardLocation.SOURCE_OUTPUT;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.truth.Correspondence;
+import com.google.testing.compile.JavaFileObjects;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+import javax.annotation.processing.Filer;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class BasicAnnotationProcessorTest {
+
+  @Retention(RetentionPolicy.SOURCE)
+  public @interface RequiresGeneratedCode {}
+
+  /**
+   * Rejects elements unless the class generated by {@link GeneratesCode}'s processor is present.
+   */
+  private static final class RequiresGeneratedCodeProcessor extends BasicAnnotationProcessor {
+
+    int rejectedRounds;
+    final ImmutableList.Builder<ImmutableSetMultimap<Class<? extends Annotation>, Element>>
+        processArguments = ImmutableList.builder();
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+
+    @Override
+    protected Iterable<? extends ProcessingStep> initSteps() {
+      return ImmutableSet.of(
+          new ProcessingStep() {
+            @Override
+            public Set<Element> process(
+                SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
+              processArguments.add(ImmutableSetMultimap.copyOf(elementsByAnnotation));
+              TypeElement requiredClass =
+                  processingEnv.getElementUtils().getTypeElement("test.SomeGeneratedClass");
+              if (requiredClass == null) {
+                rejectedRounds++;
+                return ImmutableSet.copyOf(elementsByAnnotation.values());
+              }
+              generateClass(processingEnv.getFiler(), "GeneratedByRequiresGeneratedCodeProcessor");
+              return ImmutableSet.of();
+            }
+
+            @Override
+            public Set<? extends Class<? extends Annotation>> annotations() {
+              return ImmutableSet.of(RequiresGeneratedCode.class);
+            }
+          },
+          new ProcessingStep() {
+            @Override
+            public Set<? extends Class<? extends Annotation>> annotations() {
+              return ImmutableSet.of(AnAnnotation.class);
+            }
+
+            @Override
+            public Set<? extends Element> process(
+                SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
+              return ImmutableSet.of();
+            }
+          });
+    }
+
+    ImmutableList<ImmutableSetMultimap<Class<? extends Annotation>, Element>> processArguments() {
+      return processArguments.build();
+    }
+  }
+
+  @Retention(RetentionPolicy.SOURCE)
+  public @interface GeneratesCode {}
+
+  /** Generates a class called {@code test.SomeGeneratedClass}. */
+  public class GeneratesCodeProcessor extends BasicAnnotationProcessor {
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+
+    @Override
+    protected Iterable<? extends ProcessingStep> initSteps() {
+      return ImmutableSet.of(
+          new ProcessingStep() {
+            @Override
+            public Set<Element> process(
+                SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
+              generateClass(processingEnv.getFiler(), "SomeGeneratedClass");
+              return ImmutableSet.of();
+            }
+
+            @Override
+            public Set<? extends Class<? extends Annotation>> annotations() {
+              return ImmutableSet.of(GeneratesCode.class);
+            }
+          });
+    }
+  }
+
+  public @interface AnAnnotation {}
+
+  /** When annotating a type {@code Foo}, generates a class called {@code FooXYZ}. */
+  public class AnAnnotationProcessor extends BasicAnnotationProcessor {
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+
+    @Override
+    protected Iterable<? extends ProcessingStep> initSteps() {
+      return ImmutableSet.of(
+          new ProcessingStep() {
+            @Override
+            public Set<Element> process(
+                SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
+              for (Element element : elementsByAnnotation.values()) {
+                generateClass(processingEnv.getFiler(), element.getSimpleName() + "XYZ");
+              }
+              return ImmutableSet.of();
+            }
+
+            @Override
+            public Set<? extends Class<? extends Annotation>> annotations() {
+              return ImmutableSet.of(AnAnnotation.class);
+            }
+          });
+    }
+  }
+
+  /** An annotation which causes an annotation processing error. */
+  public @interface CauseError {}
+
+  /** Report an error for any class annotated. */
+  public static class CauseErrorProcessor extends BasicAnnotationProcessor {
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+
+    @Override
+    protected Iterable<? extends ProcessingStep> initSteps() {
+      return ImmutableSet.of(
+          new ProcessingStep() {
+            @Override
+            public Set<Element> process(
+                SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
+              for (Element e : elementsByAnnotation.values()) {
+                processingEnv.getMessager().printMessage(ERROR, "purposeful error", e);
+              }
+              return ImmutableSet.copyOf(elementsByAnnotation.values());
+            }
+
+            @Override
+            public Set<? extends Class<? extends Annotation>> annotations() {
+              return ImmutableSet.of(CauseError.class);
+            }
+          });
+    }
+  }
+
+  @Test public void properlyDefersProcessing_typeElement() {
+    JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA",
+        "package test;",
+        "",
+        "@" + RequiresGeneratedCode.class.getCanonicalName(),
+        "public class ClassA {",
+        "  SomeGeneratedClass sgc;",
+        "}");
+    JavaFileObject classBFileObject = JavaFileObjects.forSourceLines("test.ClassB",
+        "package test;",
+        "",
+        "@" + GeneratesCode.class.getCanonicalName(),
+        "public class ClassB {}");
+    RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor =
+        new RequiresGeneratedCodeProcessor();
+    assertAbout(javaSources())
+        .that(ImmutableList.of(classAFileObject, classBFileObject))
+        .processedWith(requiresGeneratedCodeProcessor, new GeneratesCodeProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesFileNamed(
+            SOURCE_OUTPUT, "test", "GeneratedByRequiresGeneratedCodeProcessor.java");
+    assertThat(requiresGeneratedCodeProcessor.rejectedRounds).isEqualTo(0);
+  }
+
+  @Test
+  public void properlyDefersProcessing_nestedTypeValidBeforeOuterType() {
+    JavaFileObject source =
+        JavaFileObjects.forSourceLines(
+            "test.ValidInRound2",
+            "package test;",
+            "",
+            "@" + AnAnnotation.class.getCanonicalName(),
+            "public class ValidInRound2 {",
+            "  ValidInRound1XYZ vir1xyz;",
+            "  @" + AnAnnotation.class.getCanonicalName(),
+            "  static class ValidInRound1 {}",
+            "}");
+    assertAbout(javaSource())
+        .that(source)
+        .processedWith(new AnAnnotationProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesFileNamed(SOURCE_OUTPUT, "test", "ValidInRound2XYZ.java");
+  }
+
+  @Retention(RetentionPolicy.SOURCE)
+  public @interface ReferencesAClass {
+    Class<?> value();
+  }
+
+  @Test public void properlyDefersProcessing_packageElement() {
+    JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA",
+        "package test;",
+        "",
+        "@" + GeneratesCode.class.getCanonicalName(),
+        "public class ClassA {",
+        "}");
+    JavaFileObject packageFileObject = JavaFileObjects.forSourceLines("test.package-info",
+        "@" + RequiresGeneratedCode.class.getCanonicalName(),
+        "@" + ReferencesAClass.class.getCanonicalName() + "(SomeGeneratedClass.class)",
+        "package test;");
+    RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor =
+        new RequiresGeneratedCodeProcessor();
+    assertAbout(javaSources())
+        .that(ImmutableList.of(classAFileObject, packageFileObject))
+        .processedWith(requiresGeneratedCodeProcessor, new GeneratesCodeProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesFileNamed(
+            SOURCE_OUTPUT, "test", "GeneratedByRequiresGeneratedCodeProcessor.java");
+    assertThat(requiresGeneratedCodeProcessor.rejectedRounds).isEqualTo(0);
+  }
+
+  @Test public void properlyDefersProcessing_argumentElement() {
+    JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA",
+        "package test;",
+        "",
+        "public class ClassA {",
+        "  SomeGeneratedClass sgc;",
+        "  public void myMethod(@" + RequiresGeneratedCode.class.getCanonicalName() + " int myInt)",
+        "  {}",
+        "}");
+    JavaFileObject classBFileObject = JavaFileObjects.forSourceLines("test.ClassB",
+        "package test;",
+        "",
+        "public class ClassB {",
+        "  public void myMethod(@" + GeneratesCode.class.getCanonicalName() + " int myInt) {}",
+        "}");
+    RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor =
+        new RequiresGeneratedCodeProcessor();
+    assertAbout(javaSources())
+        .that(ImmutableList.of(classAFileObject, classBFileObject))
+        .processedWith(requiresGeneratedCodeProcessor, new GeneratesCodeProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesFileNamed(
+            SOURCE_OUTPUT, "test", "GeneratedByRequiresGeneratedCodeProcessor.java");
+    assertThat(requiresGeneratedCodeProcessor.rejectedRounds).isEqualTo(0);
+  }
+
+  @Test
+  public void properlyDefersProcessing_rejectsElement() {
+    JavaFileObject classAFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.ClassA",
+            "package test;",
+            "",
+            "@" + RequiresGeneratedCode.class.getCanonicalName(),
+            "public class ClassA {",
+            "  @" + AnAnnotation.class.getCanonicalName(),
+            "  public void method() {}",
+            "}");
+    JavaFileObject classBFileObject = JavaFileObjects.forSourceLines("test.ClassB",
+        "package test;",
+        "",
+        "@" + GeneratesCode.class.getCanonicalName(),
+        "public class ClassB {}");
+    RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor =
+        new RequiresGeneratedCodeProcessor();
+    assertAbout(javaSources())
+        .that(ImmutableList.of(classAFileObject, classBFileObject))
+        .processedWith(requiresGeneratedCodeProcessor, new GeneratesCodeProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesFileNamed(
+            SOURCE_OUTPUT, "test", "GeneratedByRequiresGeneratedCodeProcessor.java");
+    assertThat(requiresGeneratedCodeProcessor.rejectedRounds).isEqualTo(1);
+
+    // Re b/118372780: Assert that the right deferred elements are passed back, and not any enclosed
+    // elements annotated with annotations from a different step.
+    assertThat(requiresGeneratedCodeProcessor.processArguments())
+        .comparingElementsUsing(setMultimapValuesByString())
+        .containsExactly(
+            ImmutableSetMultimap.of(RequiresGeneratedCode.class, "test.ClassA"),
+            ImmutableSetMultimap.of(RequiresGeneratedCode.class, "test.ClassA"))
+        .inOrder();
+  }
+
+  private static <K, V>
+      Correspondence<SetMultimap<K, V>, SetMultimap<K, String>> setMultimapValuesByString() {
+    return Correspondence.from(
+        (actual, expected) ->
+            ImmutableSetMultimap.copyOf(transformValues(actual, Object::toString)).equals(expected),
+        "is equivalent comparing multimap values by `toString()` to");
+  }
+
+  @Test public void reportsMissingType() {
+    JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA",
+        "package test;",
+        "",
+        "@" + RequiresGeneratedCode.class.getCanonicalName(),
+        "public class ClassA {",
+        "  SomeGeneratedClass bar;",
+        "}");
+    assertAbout(javaSources())
+        .that(ImmutableList.of(classAFileObject))
+        .processedWith(new RequiresGeneratedCodeProcessor())
+        .failsToCompile()
+        .withErrorContaining(RequiresGeneratedCodeProcessor.class.getCanonicalName())
+        .in(classAFileObject).onLine(4);
+  }
+
+  @Test
+  public void reportsMissingTypeSuppressedWhenOtherErrors() {
+    JavaFileObject classAFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.ClassA",
+            "package test;",
+            "",
+            "@" + CauseError.class.getCanonicalName(),
+            "public class ClassA {}");
+    assertAbout(javaSources())
+        .that(ImmutableList.of(classAFileObject))
+        .processedWith(new CauseErrorProcessor())
+        .failsToCompile()
+        .withErrorCount(1)
+        .withErrorContaining("purposeful");
+  }
+
+  private static void generateClass(Filer filer, String generatedClassName) {
+    PrintWriter writer = null;
+    try {
+      writer = new PrintWriter(filer.createSourceFile("test." + generatedClassName).openWriter());
+      writer.println("package test;");
+      writer.println("public class " + generatedClassName + " {}");
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    } finally {
+      if (writer != null) {
+        writer.close();
+      }
+    }
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java b/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java
new file mode 100644
index 0000000..1c816c1
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2018 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assume.assumeTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.Reflection;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.TypeSpec;
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.nio.file.Files;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** {@link GeneratedAnnotations}Test */
+@RunWith(JUnit4.class)
+public class GeneratedAnnotationsTest {
+
+  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  private static final String JAVAX_ANNOTATION_PROCESSING_GENERATED =
+      "javax.annotation.processing.Generated";
+  private static final String JAVAX_ANNOTATION_GENERATED = "javax.annotation.Generated";
+
+  /**
+   * A toy annotation processor for testing. Matches on all annotations, and unconditionally
+   * generates a source that uses {@code @Generated}.
+   */
+  @SupportedAnnotationTypes("*")
+  public static class TestProcessor extends AbstractProcessor {
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+
+    private boolean first = true;
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      if (first) {
+        TypeSpec.Builder type = TypeSpec.classBuilder("G");
+        GeneratedAnnotationSpecs.generatedAnnotationSpec(
+                processingEnv.getElementUtils(),
+                processingEnv.getSourceVersion(),
+                TestProcessor.class)
+            .ifPresent(type::addAnnotation);
+        JavaFile javaFile = JavaFile.builder("", type.build()).build();
+        try {
+          javaFile.writeTo(processingEnv.getFiler());
+        } catch (IOException e) {
+          throw new UncheckedIOException(e);
+        }
+        first = false;
+      }
+      return false;
+    }
+  }
+
+  /**
+   * Run {@link TestProcessor} in a compilation with the given {@code options}, and prevent the
+   * compilation from accessing classes with the qualified names in {@code maskFromClasspath}.
+   */
+  private String runProcessor(ImmutableList<String> options, String packageToMask)
+      throws IOException {
+    File tempDir = temporaryFolder.newFolder();
+    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+    StandardJavaFileManager standardFileManager =
+        compiler.getStandardFileManager(/* diagnostics= */ null, /* locale= */ null, UTF_8);
+    standardFileManager.setLocation(StandardLocation.CLASS_OUTPUT, ImmutableList.of(tempDir));
+    StandardJavaFileManager proxyFileManager =
+        Reflection.newProxy(
+            StandardJavaFileManager.class,
+            new FileManagerInvocationHandler(standardFileManager, packageToMask));
+    CompilationTask task =
+        compiler.getTask(
+            /* out= */ null,
+            proxyFileManager,
+            /* diagnosticListener= */ null,
+            options,
+            /* classes= */ null,
+            ImmutableList.of(
+                new SimpleJavaFileObject(URI.create("test"), Kind.SOURCE) {
+                  @Override
+                  public CharSequence getCharContent(boolean ignoreEncodingErrors)
+                      throws IOException {
+                    return "class Test {}";
+                  }
+                }));
+    task.setProcessors(ImmutableList.of(new TestProcessor()));
+    assertThat(task.call()).isTrue();
+    return new String(Files.readAllBytes(tempDir.toPath().resolve("G.java")), UTF_8);
+  }
+
+  /**
+   * Used to produce a {@link StandardJavaFileManager} where a certain package appears to have
+   * no classes. The results are exactly those from the proxied {@code StandardJavaFileManager}
+   * except for the {@link StandardJavaFileManager#list list} method when its {@code packageName}
+   * argument is the given package, in which case the result is an empty list.
+   *
+   * <p>We can't use {@link javax.tools.ForwardingJavaFileManager} because at least some JDK
+   * versions require the file manager to be a {@code StandardJavaFileManager} when the
+   * {@code --release} flag is given.
+   */
+  private static class FileManagerInvocationHandler implements InvocationHandler {
+    private final StandardJavaFileManager fileManager;
+    private final String packageToMask;
+
+    FileManagerInvocationHandler(StandardJavaFileManager fileManager, String packageToMask) {
+      this.fileManager = fileManager;
+      this.packageToMask = packageToMask;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+      if (method.getName().equals("list")) {
+        String packageName = (String) args[1];
+        if (packageName.equals(packageToMask)) {
+          return ImmutableList.of();
+        }
+      }
+      return method.invoke(fileManager, args);
+    }
+  }
+
+  @Test
+  public void source8() throws Exception {
+    // Post-JDK8, we need to use --release 8 in order to be able to see the javax.annotation
+    // package, which was deleted from the JDK in that release. On JDK8, there is no --release
+    // option, so we have to use -source 8 -target 8.
+    ImmutableList<String> options =
+        isJdk9OrLater()
+            ? ImmutableList.of("--release", "8")
+            : ImmutableList.of("-source", "8", "-target", "8");
+    String generated = runProcessor(options, null);
+    assertThat(generated).contains(JAVAX_ANNOTATION_GENERATED);
+    assertThat(generated).doesNotContain(JAVAX_ANNOTATION_PROCESSING_GENERATED);
+  }
+
+  @Test
+  public void source8_masked() throws Exception {
+    // It appears that the StandardJavaFileManager hack that removes a package does not work in
+    // conjunction with --release. This is probably a bug. What we find is that
+    // Elements.getTypeElement returns a value for javax.annotation.Generated even though
+    // javax.annotation is being masked. It doesn't seem to go through the StandardJavaFileManager
+    // interface to get it. So, we continue using the -source 8 -target 8 options. Those don't
+    // actually get the JDK8 API when running post-JDK8, so we end up testing what we want.
+    //
+    // An alternative would be to delete this test method. JDK8 always has
+    // javax.annotation.Generated so it isn't really meaningful to test it without.
+    ImmutableList<String> options = ImmutableList.of("-source", "8", "-target", "8");
+    String generated =
+        runProcessor(options, "javax.annotation");
+    assertThat(generated).doesNotContain(JAVAX_ANNOTATION_GENERATED);
+    assertThat(generated).doesNotContain(JAVAX_ANNOTATION_PROCESSING_GENERATED);
+  }
+
+  @Test
+  public void source9() throws Exception {
+    assumeTrue(isJdk9OrLater());
+    String generated = runProcessor(ImmutableList.of("-source", "9", "-target", "9"), null);
+    assertThat(generated).doesNotContain(JAVAX_ANNOTATION_GENERATED);
+    assertThat(generated).contains(JAVAX_ANNOTATION_PROCESSING_GENERATED);
+  }
+
+  @Test
+  public void source9_masked() throws Exception {
+    assumeTrue(isJdk9OrLater());
+    String generated =
+        runProcessor(
+            ImmutableList.of("-source", "9", "-target", "9"), "javax.annotation.processing");
+    assertThat(generated).doesNotContain(JAVAX_ANNOTATION_GENERATED);
+    assertThat(generated).doesNotContain(JAVAX_ANNOTATION_PROCESSING_GENERATED);
+  }
+
+  private static boolean isJdk9OrLater() {
+    return SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0;
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/MoreElementsTest.java b/common/src/test/java/com/google/auto/common/MoreElementsTest.java
new file mode 100644
index 0000000..95043cf
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/MoreElementsTest.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.truth.Expect;
+import com.google.testing.compile.CompilationRule;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.AbstractList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MoreElementsTest {
+  @Rule public CompilationRule compilation = new CompilationRule();
+  @Rule public Expect expect = Expect.create();
+
+  private PackageElement javaLangPackageElement;
+  private TypeElement objectElement;
+  private TypeElement stringElement;
+
+  @Before
+  public void initializeTestElements() {
+    Elements elements = compilation.getElements();
+    this.javaLangPackageElement = elements.getPackageElement("java.lang");
+    this.objectElement = elements.getTypeElement(Object.class.getCanonicalName());
+    this.stringElement = elements.getTypeElement(String.class.getCanonicalName());
+  }
+
+  @Test
+  public void getPackage() {
+    assertThat(MoreElements.getPackage(stringElement)).isEqualTo(javaLangPackageElement);
+    for (Element childElement : stringElement.getEnclosedElements()) {
+      assertThat(MoreElements.getPackage(childElement)).isEqualTo(javaLangPackageElement);
+    }
+  }
+
+  @Test
+  public void asPackage() {
+    assertThat(MoreElements.asPackage(javaLangPackageElement))
+        .isEqualTo(javaLangPackageElement);
+  }
+
+  @Test
+  public void asPackage_illegalArgument() {
+    try {
+      MoreElements.asPackage(stringElement);
+      fail();
+    } catch (IllegalArgumentException expected) {}
+  }
+
+  @Test public void asTypeElement() {
+    Element typeElement =
+        compilation.getElements().getTypeElement(String.class.getCanonicalName());
+    assertTrue(MoreElements.isType(typeElement));
+    assertThat(MoreElements.asType(typeElement)).isEqualTo(typeElement);
+  }
+
+  @Test public void asTypeElement_notATypeElement() {
+    TypeElement typeElement =
+        compilation.getElements().getTypeElement(String.class.getCanonicalName());
+    for (ExecutableElement e : ElementFilter.methodsIn(typeElement.getEnclosedElements())) {
+      assertFalse(MoreElements.isType(e));
+      try {
+        MoreElements.asType(e);
+        fail();
+      } catch (IllegalArgumentException expected) {
+      }
+    }
+  }
+
+  @Test
+  public void asTypeParameterElement() {
+    Element typeParameterElement =
+        getOnlyElement(
+            compilation
+                .getElements()
+                .getTypeElement(List.class.getCanonicalName())
+                .getTypeParameters());
+    assertThat(MoreElements.asTypeParameter(typeParameterElement)).isEqualTo(typeParameterElement);
+  }
+
+  @Test
+  public void asTypeParameterElement_illegalArgument() {
+    try {
+      MoreElements.asTypeParameter(javaLangPackageElement);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test
+  public void asType() {
+    assertThat(MoreElements.asType(stringElement)).isEqualTo(stringElement);
+  }
+
+  @Test
+  public void asType_illegalArgument() {
+    assertFalse(MoreElements.isType(javaLangPackageElement));
+    try {
+      MoreElements.asType(javaLangPackageElement);
+      fail();
+    } catch (IllegalArgumentException expected) {}
+  }
+
+  @Test
+  public void asVariable() {
+    for (Element variableElement : ElementFilter.fieldsIn(stringElement.getEnclosedElements())) {
+      assertThat(MoreElements.asVariable(variableElement)).isEqualTo(variableElement);
+    }
+  }
+
+  @Test
+  public void asVariable_illegalArgument() {
+    try {
+      MoreElements.asVariable(javaLangPackageElement);
+      fail();
+    } catch (IllegalArgumentException expected) {}
+  }
+
+  @Test
+  public void asExecutable() {
+    for (Element methodElement : ElementFilter.methodsIn(stringElement.getEnclosedElements())) {
+      assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement);
+    }
+    for (Element methodElement
+        : ElementFilter.constructorsIn(stringElement.getEnclosedElements())) {
+      assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement);
+    }
+  }
+
+  @Test
+  public void asExecutable_illegalArgument() {
+    try {
+      MoreElements.asExecutable(javaLangPackageElement);
+      fail();
+    } catch (IllegalArgumentException expected) {}
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  private @interface InnerAnnotation {}
+
+  @Documented
+  @InnerAnnotation
+  private @interface AnnotatedAnnotation {}
+
+  @Test
+  public void isAnnotationPresent() {
+    TypeElement annotatedAnnotationElement =
+        compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
+    assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, Documented.class))
+        .isTrue();
+    assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, InnerAnnotation.class))
+        .isTrue();
+    assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, SuppressWarnings.class))
+        .isFalse();
+  }
+
+  @Test
+  public void getAnnotationMirror() {
+    TypeElement element =
+        compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
+
+    Optional<AnnotationMirror> documented =
+        MoreElements.getAnnotationMirror(element, Documented.class);
+    Optional<AnnotationMirror> innerAnnotation =
+        MoreElements.getAnnotationMirror(element, InnerAnnotation.class);
+    Optional<AnnotationMirror> suppressWarnings =
+        MoreElements.getAnnotationMirror(element, SuppressWarnings.class);
+
+    expect.that(documented).isPresent();
+    expect.that(innerAnnotation).isPresent();
+    expect.that(suppressWarnings).isAbsent();
+
+    Element annotationElement = documented.get().getAnnotationType().asElement();
+    expect.that(MoreElements.isType(annotationElement)).isTrue();
+    expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString())
+        .isEqualTo(Documented.class.getCanonicalName());
+
+    annotationElement = innerAnnotation.get().getAnnotationType().asElement();
+    expect.that(MoreElements.isType(annotationElement)).isTrue();
+    expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString())
+        .isEqualTo(InnerAnnotation.class.getCanonicalName());
+  }
+
+  private abstract static class ParentClass {
+    static void staticMethod() {}
+
+    abstract String foo();
+
+    private void privateMethod() {}
+  }
+
+  private interface ParentInterface {
+    static void staticMethod() {}
+
+    abstract int bar();
+
+    abstract int bar(long x);
+  }
+
+  private abstract static class Child extends ParentClass implements ParentInterface {
+    static void staticMethod() {}
+
+    @Override
+    public int bar() {
+      return 0;
+    }
+
+    abstract void baz();
+
+    void buh(int x) {}
+
+    void buh(int x, int y) {}
+  }
+
+  @Test
+  public void getLocalAndInheritedMethods_Old() {
+    Elements elements = compilation.getElements();
+    Types types = compilation.getTypes();
+    TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
+    TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
+    TypeElement childType = elements.getTypeElement(Child.class.getCanonicalName());
+    @SuppressWarnings("deprecation")
+    Set<ExecutableElement> childTypeMethods =
+        MoreElements.getLocalAndInheritedMethods(childType, elements);
+    Set<ExecutableElement> objectMethods = visibleMethodsFromObject();
+    assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods);
+    Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods);
+    assertThat(nonObjectMethods).containsExactly(
+            getMethod(ParentInterface.class, "bar", longMirror),
+            getMethod(ParentClass.class, "foo"),
+            getMethod(Child.class, "bar"),
+            getMethod(Child.class, "baz"),
+            getMethod(Child.class, "buh", intMirror),
+            getMethod(Child.class, "buh", intMirror, intMirror))
+        .inOrder();;
+  }
+
+  @Test
+  public void getLocalAndInheritedMethods() {
+    Elements elements = compilation.getElements();
+    Types types = compilation.getTypes();
+    TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
+    TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
+    TypeElement childType = elements.getTypeElement(Child.class.getCanonicalName());
+    @SuppressWarnings("deprecation")
+    Set<ExecutableElement> childTypeMethods =
+        MoreElements.getLocalAndInheritedMethods(childType, types, elements);
+    Set<ExecutableElement> objectMethods = visibleMethodsFromObject();
+    assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods);
+    Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods);
+    assertThat(nonObjectMethods).containsExactly(
+            getMethod(ParentInterface.class, "bar", longMirror),
+            getMethod(ParentClass.class, "foo"),
+            getMethod(Child.class, "bar"),
+            getMethod(Child.class, "baz"),
+            getMethod(Child.class, "buh", intMirror),
+            getMethod(Child.class, "buh", intMirror, intMirror))
+        .inOrder();
+  }
+
+  @Test
+  public void getAllMethods() {
+    Elements elements = compilation.getElements();
+    Types types = compilation.getTypes();
+    TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
+    TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
+    TypeElement childType = elements.getTypeElement(Child.class.getCanonicalName());
+    @SuppressWarnings("deprecation")
+    Set<ExecutableElement> childTypeMethods =
+        MoreElements.getAllMethods(childType, types, elements);
+    Set<ExecutableElement> objectMethods = allMethodsFromObject();
+    assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods);
+    Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods);
+    assertThat(nonObjectMethods).containsExactly(
+            getMethod(ParentInterface.class, "staticMethod"),
+            getMethod(ParentInterface.class, "bar", longMirror),
+            getMethod(ParentClass.class, "staticMethod"),
+            getMethod(ParentClass.class, "foo"),
+            getMethod(ParentClass.class, "privateMethod"),
+            getMethod(Child.class, "staticMethod"),
+            getMethod(Child.class, "bar"),
+            getMethod(Child.class, "baz"),
+            getMethod(Child.class, "buh", intMirror),
+            getMethod(Child.class, "buh", intMirror, intMirror))
+        .inOrder();
+  }
+
+  static class Injectable {}
+
+  public static class MenuManager {
+    public interface ParentComponent extends MenuItemA.ParentComponent, MenuItemB.ParentComponent {}
+  }
+
+  public static class MenuItemA {
+    public interface ParentComponent {
+      Injectable injectable();
+    }
+  }
+
+  public static class MenuItemB {
+    public interface ParentComponent {
+      Injectable injectable();
+    }
+  }
+
+  public static class Main {
+    public interface ParentComponent extends MenuManager.ParentComponent {}
+  }
+
+  // Example from https://github.com/williamlian/daggerbug
+  @Test
+  public void getLocalAndInheritedMethods_DaggerBug() {
+    Elements elementUtils = compilation.getElements();
+    TypeElement main = elementUtils.getTypeElement(Main.ParentComponent.class.getCanonicalName());
+    Set<ExecutableElement> methods = MoreElements.getLocalAndInheritedMethods(
+        main, compilation.getTypes(), elementUtils);
+    assertThat(methods).hasSize(1);
+    ExecutableElement method = methods.iterator().next();
+    assertThat(method.getSimpleName().toString()).isEqualTo("injectable");
+    assertThat(method.getParameters()).isEmpty();
+  }
+
+  private Set<ExecutableElement> visibleMethodsFromObject() {
+    Types types = compilation.getTypes();
+    TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
+    TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
+    Set<ExecutableElement> methods = new HashSet<ExecutableElement>();
+    for (ExecutableElement method : ElementFilter.methodsIn(objectElement.getEnclosedElements())) {
+      if (method.getModifiers().contains(Modifier.PUBLIC)
+          || method.getModifiers().contains(Modifier.PROTECTED)) {
+        methods.add(method);
+      }
+    }
+    assertThat(methods)
+        .containsAtLeast(
+            getMethod(Object.class, "clone"),
+            getMethod(Object.class, "finalize"),
+            getMethod(Object.class, "wait"),
+            getMethod(Object.class, "wait", longMirror),
+            getMethod(Object.class, "wait", longMirror, intMirror));
+    return methods;
+  }
+
+  private Set<ExecutableElement> allMethodsFromObject() {
+    Types types = compilation.getTypes();
+    TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
+    TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
+    Set<ExecutableElement> methods = new HashSet<>();
+    methods.addAll(ElementFilter.methodsIn(objectElement.getEnclosedElements()));
+    assertThat(methods)
+        .containsAtLeast(
+            getMethod(Object.class, "clone"),
+            getMethod(Object.class, "registerNatives"),
+            getMethod(Object.class, "finalize"),
+            getMethod(Object.class, "wait"),
+            getMethod(Object.class, "wait", longMirror),
+            getMethod(Object.class, "wait", longMirror, intMirror));
+    return methods;
+  }
+
+  private ExecutableElement getMethod(Class<?> c, String methodName, TypeMirror... parameterTypes) {
+    TypeElement type = compilation.getElements().getTypeElement(c.getCanonicalName());
+    Types types = compilation.getTypes();
+    ExecutableElement found = null;
+    for (ExecutableElement method : ElementFilter.methodsIn(type.getEnclosedElements())) {
+      if (method.getSimpleName().contentEquals(methodName)
+          && method.getParameters().size() == parameterTypes.length) {
+        boolean match = true;
+        for (int i = 0; i < parameterTypes.length; i++) {
+          TypeMirror expectedType = parameterTypes[i];
+          TypeMirror actualType = method.getParameters().get(i).asType();
+          match &= types.isSameType(expectedType, actualType);
+        }
+        if (match) {
+          assertThat(found).isNull();
+          found = method;
+        }
+      }
+    }
+    assertWithMessage(methodName + Arrays.toString(parameterTypes)).that(found).isNotNull();
+    return found;
+  }
+
+  private abstract static class AbstractAbstractList extends AbstractList<String> {}
+
+  private static class ConcreteAbstractList<T> extends AbstractList<T> {
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public T get(int index) {
+      throw new NoSuchElementException();
+    }
+  }
+
+  private Set<String> abstractMethodNamesFrom(Set<ExecutableElement> methods) {
+    ImmutableSet.Builder<String> abstractMethodNames = ImmutableSet.builder();
+    for (ExecutableElement method : methods) {
+      if (method.getModifiers().contains(Modifier.ABSTRACT)) {
+        abstractMethodNames.add(method.getSimpleName().toString());
+      }
+    }
+    return abstractMethodNames.build();
+  }
+
+  // Test that getLocalAndInheritedMethods does the right thing with AbstractList. That class
+  // inherits from Collection along two paths, via its parent AbstractCollection (which implements
+  // Collection) and via its parent List (which extends Collection). Bugs in the past have meant
+  // that the multiple paths might lead the code into thinking that all the abstract methods of
+  // Collection were still abstract in the AbstractList subclasses here, even though most of them
+  // are implemented in AbstractList.
+  @Test
+  public void getLocalAndInheritedMethods_AbstractList() {
+    Elements elements = compilation.getElements();
+
+    TypeElement abstractType =
+        elements.getTypeElement(AbstractAbstractList.class.getCanonicalName());
+    Set<ExecutableElement> abstractTypeMethods =
+        MoreElements.getLocalAndInheritedMethods(abstractType, elements);
+    assertThat(abstractMethodNamesFrom(abstractTypeMethods)).containsExactly("get", "size");
+
+    TypeElement concreteType =
+        elements.getTypeElement(ConcreteAbstractList.class.getCanonicalName());
+    Set<ExecutableElement> concreteTypeMethods =
+        MoreElements.getLocalAndInheritedMethods(concreteType, elements);
+    assertThat(abstractMethodNamesFrom(concreteTypeMethods)).isEmpty();
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
new file mode 100644
index 0000000..05a0a11
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Iterables;
+import com.google.testing.compile.CompilationRule;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests {@link MoreTypes#isTypeOf}.
+ */
+@RunWith(JUnit4.class)
+public class MoreTypesIsTypeOfTest {
+
+  @Rule public CompilationRule compilationRule = new CompilationRule();
+
+  private Elements elements;
+
+  @Before public void setUp() {
+    this.elements = compilationRule.getElements();
+  }
+
+  private interface TestType {}
+
+  @Test public void isTypeOf_DeclaredType() {
+    assertTrue(MoreTypes.isType(typeElementFor(TestType.class).asType()));
+    assertWithMessage("mirror represents the TestType")
+        .that(MoreTypes.isTypeOf(TestType.class, typeElementFor(TestType.class).asType()))
+        .isTrue();
+    assertWithMessage("mirror does not represent a String")
+        .that(MoreTypes.isTypeOf(String.class, typeElementFor(TestType.class).asType()))
+        .isFalse();
+  }
+
+  private interface ArrayType {
+    String[] array();
+  }
+
+  @Test public void isTypeOf_ArrayType() {
+    assertTrue(MoreTypes.isType(typeElementFor(ArrayType.class).asType()));
+    TypeMirror type = extractReturnTypeFromHolder(typeElementFor(ArrayType.class));
+    assertWithMessage("array mirror represents an array Class object")
+        .that(MoreTypes.isTypeOf(String[].class, type))
+        .isTrue();
+  }
+
+  private interface PrimitiveBoolean {
+    boolean method();
+  }
+
+  @Test public void isTypeOf_PrimitiveBoolean() {
+    assertTrue(MoreTypes.isType(typeElementFor(PrimitiveBoolean.class).asType()));
+    TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveBoolean.class));
+    assertWithMessage("mirror of a boolean").that(MoreTypes.isTypeOf(Boolean.TYPE, type)).isTrue();
+  }
+
+  private interface PrimitiveByte {
+    byte method();
+  }
+
+  @Test public void isTypeOf_PrimitiveByte() {
+    assertTrue(MoreTypes.isType(typeElementFor(PrimitiveByte.class).asType()));
+    TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveByte.class));
+    assertWithMessage("mirror of a byte").that(MoreTypes.isTypeOf(Byte.TYPE, type)).isTrue();
+  }
+
+  private interface PrimitiveChar {
+    char method();
+  }
+
+  @Test public void isTypeOf_PrimitiveChar() {
+    assertTrue(MoreTypes.isType(typeElementFor(PrimitiveChar.class).asType()));
+    TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveChar.class));
+    assertWithMessage("mirror of a char").that(MoreTypes.isTypeOf(Character.TYPE, type)).isTrue();
+  }
+
+  private interface PrimitiveDouble {
+    double method();
+  }
+
+  @Test public void isTypeOf_PrimitiveDouble() {
+    assertTrue(MoreTypes.isType(typeElementFor(PrimitiveDouble.class).asType()));
+    TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveDouble.class));
+    assertWithMessage("mirror of a double").that(MoreTypes.isTypeOf(Double.TYPE, type)).isTrue();
+  }
+
+  private interface PrimitiveFloat {
+    float method();
+  }
+
+  @Test public void isTypeOf_PrimitiveFloat() {
+    assertTrue(MoreTypes.isType(typeElementFor(PrimitiveFloat.class).asType()));
+    TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveFloat.class));
+    assertWithMessage("mirror of a float").that(MoreTypes.isTypeOf(Float.TYPE, type)).isTrue();
+  }
+
+  private interface PrimitiveInt {
+    int method();
+  }
+
+  @Test public void isTypeOf_PrimitiveInt() {
+    assertTrue(MoreTypes.isType(typeElementFor(PrimitiveInt.class).asType()));
+    TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveInt.class));
+    assertWithMessage("mirror of a int").that(MoreTypes.isTypeOf(Integer.TYPE, type)).isTrue();
+  }
+
+  private interface PrimitiveLong {
+    long method();
+  }
+
+  @Test public void isTypeOf_PrimitiveLong() {
+    assertTrue(MoreTypes.isType(typeElementFor(PrimitiveLong.class).asType()));
+    TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveLong.class));
+    assertWithMessage("mirror of a long").that(MoreTypes.isTypeOf(Long.TYPE, type)).isTrue();
+  }
+
+  private interface PrimitiveShort {
+    short method();
+  }
+
+  @Test public void isTypeOf_PrimitiveShort() {
+    assertTrue(MoreTypes.isType(typeElementFor(PrimitiveShort.class).asType()));
+    TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveShort.class));
+    assertWithMessage("mirror of a short").that(MoreTypes.isTypeOf(Short.TYPE, type)).isTrue();
+  }
+
+  private interface PrimitiveVoid {
+    void method();
+  }
+
+  @Test public void isTypeOf_void() {
+    assertTrue(MoreTypes.isType(typeElementFor(PrimitiveVoid.class).asType()));
+    TypeMirror primitive = extractReturnTypeFromHolder(typeElementFor(PrimitiveVoid.class));
+    assertWithMessage("mirror of a void").that(MoreTypes.isTypeOf(Void.TYPE, primitive)).isTrue();
+  }
+
+  private interface DeclaredVoid {
+    Void method();
+  }
+
+  @Test public void isTypeOf_Void() {
+    assertTrue(MoreTypes.isType(typeElementFor(DeclaredVoid.class).asType()));
+    TypeMirror declared = extractReturnTypeFromHolder(typeElementFor(DeclaredVoid.class));
+    assertWithMessage("mirror of a void").that(MoreTypes.isTypeOf(Void.class, declared)).isTrue();
+  }
+
+  @Test public void isTypeOf_fail() {
+    assertFalse(MoreTypes.isType(
+        getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType()));
+    TypeMirror method =
+        getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType();
+    try {
+      MoreTypes.isTypeOf(String.class, method);
+      fail();
+    } catch (IllegalArgumentException expected) {}
+  }
+
+  // Utility methods for this test.
+
+  private TypeMirror extractReturnTypeFromHolder(TypeElement typeElement) {
+    Element element = Iterables.getOnlyElement(typeElement.getEnclosedElements());
+    TypeMirror arrayType = MoreElements.asExecutable(element).getReturnType();
+    return arrayType;
+  }
+
+  private TypeElement typeElementFor(Class<?> clazz) {
+    return elements.getTypeElement(clazz.getCanonicalName());
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/MoreTypesTest.java b/common/src/test/java/com/google/auto/common/MoreTypesTest.java
new file mode 100644
index 0000000..3cd360d
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/MoreTypesTest.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+import static javax.lang.model.type.TypeKind.NONE;
+import static javax.lang.model.type.TypeKind.VOID;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.testing.EquivalenceTester;
+import com.google.common.truth.Expect;
+import com.google.testing.compile.CompilationRule;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MoreTypesTest {
+  @Rule public final CompilationRule compilationRule = new CompilationRule();
+  @Rule public final Expect expect = Expect.create();
+
+  @Test
+  public void equivalence() {
+    Types types = compilationRule.getTypes();
+    Elements elements = compilationRule.getElements();
+    TypeMirror objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType();
+    TypeMirror stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
+    TypeElement mapElement = elements.getTypeElement(Map.class.getCanonicalName());
+    TypeElement setElement = elements.getTypeElement(Set.class.getCanonicalName());
+    TypeElement enumElement = elements.getTypeElement(Enum.class.getCanonicalName());
+    TypeElement container = elements.getTypeElement(Container.class.getCanonicalName());
+    TypeElement contained = elements.getTypeElement(Container.Contained.class.getCanonicalName());
+    TypeElement funkyBounds = elements.getTypeElement(FunkyBounds.class.getCanonicalName());
+    TypeElement funkyBounds2 = elements.getTypeElement(FunkyBounds2.class.getCanonicalName());
+    TypeElement funkierBounds = elements.getTypeElement(FunkierBounds.class.getCanonicalName());
+    TypeMirror funkyBoundsVar = ((DeclaredType) funkyBounds.asType()).getTypeArguments().get(0);
+    TypeMirror funkyBounds2Var = ((DeclaredType) funkyBounds2.asType()).getTypeArguments().get(0);
+    TypeMirror funkierBoundsVar = ((DeclaredType) funkierBounds.asType()).getTypeArguments().get(0);
+    DeclaredType mapOfObjectToObjectType =
+        types.getDeclaredType(mapElement, objectType, objectType);
+    TypeMirror mapType = mapElement.asType();
+    DeclaredType setOfSetOfObject =
+        types.getDeclaredType(setElement, types.getDeclaredType(setElement, objectType));
+    DeclaredType setOfSetOfString =
+        types.getDeclaredType(setElement, types.getDeclaredType(setElement, stringType));
+    DeclaredType setOfSetOfSetOfObject = types.getDeclaredType(setElement, setOfSetOfObject);
+    DeclaredType setOfSetOfSetOfString = types.getDeclaredType(setElement, setOfSetOfString);
+    WildcardType wildcard = types.getWildcardType(null, null);
+    DeclaredType containerOfObject = types.getDeclaredType(container, objectType);
+    DeclaredType containerOfString = types.getDeclaredType(container, stringType);
+    TypeMirror containedInObject = types.asMemberOf(containerOfObject, contained);
+    TypeMirror containedInString = types.asMemberOf(containerOfString, contained);
+    EquivalenceTester<TypeMirror> tester = EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence())
+        .addEquivalenceGroup(types.getNullType())
+        .addEquivalenceGroup(types.getNoType(NONE))
+        .addEquivalenceGroup(types.getNoType(VOID))
+        .addEquivalenceGroup(objectType)
+        .addEquivalenceGroup(stringType)
+        .addEquivalenceGroup(containedInObject)
+        .addEquivalenceGroup(containedInString)
+        .addEquivalenceGroup(funkyBounds.asType())
+        .addEquivalenceGroup(funkyBounds2.asType())
+        .addEquivalenceGroup(funkierBounds.asType())
+        .addEquivalenceGroup(funkyBoundsVar, funkyBounds2Var)
+        .addEquivalenceGroup(funkierBoundsVar)
+        // Enum<E extends Enum<E>>
+        .addEquivalenceGroup(enumElement.asType())
+        // Map<K, V>
+        .addEquivalenceGroup(mapType)
+        .addEquivalenceGroup(mapOfObjectToObjectType)
+        // Map<?, ?>
+        .addEquivalenceGroup(types.getDeclaredType(mapElement, wildcard, wildcard))
+        // Map
+        .addEquivalenceGroup(types.erasure(mapType), types.erasure(mapOfObjectToObjectType))
+        .addEquivalenceGroup(types.getDeclaredType(mapElement, objectType, stringType))
+        .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, objectType))
+        .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, stringType))
+        .addEquivalenceGroup(setOfSetOfObject)
+        .addEquivalenceGroup(setOfSetOfString)
+        .addEquivalenceGroup(setOfSetOfSetOfObject)
+        .addEquivalenceGroup(setOfSetOfSetOfString)
+        .addEquivalenceGroup(wildcard)
+        // ? extends Object
+        .addEquivalenceGroup(types.getWildcardType(objectType, null))
+        // ? extends String
+        .addEquivalenceGroup(types.getWildcardType(stringType, null))
+        // ? super String
+        .addEquivalenceGroup(types.getWildcardType(null, stringType))
+        // Map<String, Map<String, Set<Object>>>
+        .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType,
+            types.getDeclaredType(mapElement, stringType,
+                types.getDeclaredType(setElement, objectType))))
+        .addEquivalenceGroup(FAKE_ERROR_TYPE)
+        ;
+
+    for (TypeKind kind : TypeKind.values()) {
+      if (kind.isPrimitive()) {
+        PrimitiveType primitiveType = types.getPrimitiveType(kind);
+        TypeMirror boxedPrimitiveType = types.boxedClass(primitiveType).asType();
+        tester.addEquivalenceGroup(primitiveType, types.unboxedType(boxedPrimitiveType));
+        tester.addEquivalenceGroup(boxedPrimitiveType);
+        tester.addEquivalenceGroup(types.getArrayType(primitiveType));
+        tester.addEquivalenceGroup(types.getArrayType(boxedPrimitiveType));
+      }
+    }
+
+    ImmutableSet<Class<?>> testClasses = ImmutableSet.of(
+        ExecutableElementsGroupA.class,
+        ExecutableElementsGroupB.class,
+        ExecutableElementsGroupC.class,
+        ExecutableElementsGroupD.class,
+        ExecutableElementsGroupE.class);
+    for (Class<?> testClass : testClasses) {
+      ImmutableList<TypeMirror> equivalenceGroup = FluentIterable.from(
+          elements.getTypeElement(testClass.getCanonicalName()).getEnclosedElements())
+              .transform(new Function<Element, TypeMirror>() {
+                @Override public TypeMirror apply(Element input) {
+                  return input.asType();
+                }
+              })
+              .toList();
+      tester.addEquivalenceGroup(equivalenceGroup);
+    }
+
+    tester.test();
+  }
+
+  @SuppressWarnings("unused")
+  private static final class ExecutableElementsGroupA {
+    ExecutableElementsGroupA() {}
+    void a() {}
+    public static void b() {}
+  }
+
+  @SuppressWarnings("unused")
+  private static final class ExecutableElementsGroupB {
+    ExecutableElementsGroupB(String s) {}
+    void a(String s) {}
+    public static void b(String s) {}
+  }
+
+  @SuppressWarnings("unused")
+  private static final class ExecutableElementsGroupC {
+    ExecutableElementsGroupC() throws Exception {}
+    void a() throws Exception {}
+    public static void b() throws Exception {}
+  }
+
+  @SuppressWarnings("unused")
+  private static final class ExecutableElementsGroupD {
+    ExecutableElementsGroupD() throws RuntimeException {}
+    void a() throws RuntimeException {}
+    public static void b() throws RuntimeException {}
+  }
+
+  @SuppressWarnings("unused")
+  private static final class ExecutableElementsGroupE {
+    <T> ExecutableElementsGroupE() {}
+    <T> void a() {}
+    public static <T> void b() {}
+  }
+
+  @SuppressWarnings("unused")
+  private static final class Container<T> {
+    private final class Contained {}
+  }
+
+  @SuppressWarnings("unused")
+  private static final class FunkyBounds<T extends Number & Comparable<T>> {}
+
+  @SuppressWarnings("unused")
+  private static final class FunkyBounds2<T extends Number & Comparable<T>> {}
+
+  @SuppressWarnings("unused")
+  private static final class FunkierBounds<T extends Number & Comparable<T> & Cloneable> {}
+
+  @Test public void testReferencedTypes() {
+    Elements elements = compilationRule.getElements();
+    TypeElement testDataElement = elements
+        .getTypeElement(ReferencedTypesTestData.class.getCanonicalName());
+    ImmutableMap<String, VariableElement> fieldIndex =
+        FluentIterable.from(ElementFilter.fieldsIn(testDataElement.getEnclosedElements()))
+            .uniqueIndex(new Function<VariableElement, String>() {
+              @Override public String apply(VariableElement input) {
+                return input.getSimpleName().toString();
+              }
+            });
+
+    TypeElement objectElement =
+        elements.getTypeElement(Object.class.getCanonicalName());
+    TypeElement stringElement =
+        elements.getTypeElement(String.class.getCanonicalName());
+    TypeElement integerElement =
+        elements.getTypeElement(Integer.class.getCanonicalName());
+    TypeElement setElement =
+        elements.getTypeElement(Set.class.getCanonicalName());
+    TypeElement mapElement =
+        elements.getTypeElement(Map.class.getCanonicalName());
+    TypeElement charSequenceElement =
+        elements.getTypeElement(CharSequence.class.getCanonicalName());
+
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f1").asType()))
+        .containsExactly(objectElement);
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f2").asType()))
+        .containsExactly(setElement, stringElement);
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f3").asType()))
+        .containsExactly(mapElement, stringElement, objectElement);
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f4").asType()))
+        .containsExactly(integerElement);
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f5").asType()))
+        .containsExactly(setElement);
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f6").asType()))
+        .containsExactly(setElement, charSequenceElement);
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f7").asType()))
+        .containsExactly(mapElement, stringElement, setElement, charSequenceElement);
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f8").asType()))
+        .containsExactly(stringElement);
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f9").asType()))
+        .containsExactly(stringElement);
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f10").asType())).isEmpty();
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f11").asType())).isEmpty();
+    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f12").asType()))
+        .containsExactly(setElement, stringElement);
+  }
+
+  @SuppressWarnings("unused") // types used in compiler tests
+  private static final class ReferencedTypesTestData {
+    Object f1;
+    Set<String> f2;
+    Map<String, Object> f3;
+    Integer f4;
+    Set<?> f5;
+    Set<? extends CharSequence> f6;
+    Map<String, Set<? extends CharSequence>> f7;
+    String[] f8;
+    String[][] f9;
+    int f10;
+    int[] f11;
+    Set<? super String> f12;
+  }
+
+  private static class Parent<T> {}
+  private static class ChildA extends Parent<Number> {}
+  private static class ChildB extends Parent<String> {}
+  private static class GenericChild<T> extends Parent<T> {}
+  private interface InterfaceType {}
+
+  @Test
+  public void asElement_throws() {
+    TypeMirror javaDotLang =
+        compilationRule.getElements().getPackageElement("java.lang").asType();
+    try {
+      MoreTypes.asElement(javaDotLang);
+      fail();
+    } catch (IllegalArgumentException expected) {}
+
+  }
+
+  @Test
+  public void asElement() {
+    Elements elements = compilationRule.getElements();
+    TypeElement stringElement = elements.getTypeElement("java.lang.String");
+    assertThat(MoreTypes.asElement(stringElement.asType())).isEqualTo(stringElement);
+    TypeParameterElement setParameterElement = Iterables.getOnlyElement(
+        compilationRule.getElements().getTypeElement("java.util.Set").getTypeParameters());
+    assertThat(MoreTypes.asElement(setParameterElement.asType())).isEqualTo(setParameterElement);
+    // we don't test error types because those are very hard to get predictably
+  }
+
+  @Test
+  public void testNonObjectSuperclass() {
+    Types types = compilationRule.getTypes();
+    Elements elements = compilationRule.getElements();
+    TypeMirror numberType = elements.getTypeElement(Number.class.getCanonicalName()).asType();
+    TypeMirror stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
+    TypeMirror integerType = elements.getTypeElement(Integer.class.getCanonicalName()).asType();
+    TypeElement parent = elements.getTypeElement(Parent.class.getCanonicalName());
+    TypeElement childA = elements.getTypeElement(ChildA.class.getCanonicalName());
+    TypeElement childB = elements.getTypeElement(ChildB.class.getCanonicalName());
+    TypeElement genericChild = elements.getTypeElement(GenericChild.class.getCanonicalName());
+    TypeMirror genericChildOfNumber = types.getDeclaredType(genericChild, numberType);
+    TypeMirror genericChildOfInteger = types.getDeclaredType(genericChild, integerType);
+    TypeMirror objectType =
+        elements.getTypeElement(Object.class.getCanonicalName()).asType();
+    TypeMirror interfaceType =
+        elements.getTypeElement(InterfaceType.class.getCanonicalName()).asType();
+
+    assertThat(MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) objectType))
+        .isAbsent();
+    assertThat(MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) interfaceType))
+        .isAbsent();
+    assertThat(MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) parent.asType()))
+        .isAbsent();
+
+    Optional<DeclaredType> parentOfChildA =
+        MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) childA.asType());
+    Optional<DeclaredType> parentOfChildB =
+        MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) childB.asType());
+    Optional<DeclaredType> parentOfGenericChild =
+        MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) genericChild.asType());
+    Optional<DeclaredType> parentOfGenericChildOfNumber =
+        MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) genericChildOfNumber);
+    Optional<DeclaredType> parentOfGenericChildOfInteger =
+        MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) genericChildOfInteger);
+
+    EquivalenceTester<TypeMirror> tester = EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence())
+          .addEquivalenceGroup(parentOfChildA.get(),
+              types.getDeclaredType(parent, numberType),
+              parentOfGenericChildOfNumber.get())
+          .addEquivalenceGroup(parentOfChildB.get(), types.getDeclaredType(parent, stringType))
+          .addEquivalenceGroup(parentOfGenericChild.get(), parent.asType())
+          .addEquivalenceGroup(parentOfGenericChildOfInteger.get(),
+              types.getDeclaredType(parent, integerType));
+
+    tester.test();
+  }
+  
+  @Test
+  public void testAsMemberOf_variableElement() {
+    Types types = compilationRule.getTypes();
+    Elements elements = compilationRule.getElements();
+    TypeMirror numberType = elements.getTypeElement(Number.class.getCanonicalName()).asType();
+    TypeMirror stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
+    TypeMirror integerType = elements.getTypeElement(Integer.class.getCanonicalName()).asType();
+
+    TypeElement paramsElement = elements.getTypeElement(Params.class.getCanonicalName());
+    VariableElement tParam = Iterables.getOnlyElement(Iterables.getOnlyElement(
+        ElementFilter.methodsIn(paramsElement.getEnclosedElements())).getParameters());
+    VariableElement tField =
+        Iterables.getOnlyElement(ElementFilter.fieldsIn(paramsElement.getEnclosedElements())); 
+    
+    DeclaredType numberParams =
+        (DeclaredType) elements.getTypeElement(NumberParams.class.getCanonicalName()).asType();
+    DeclaredType stringParams =
+        (DeclaredType) elements.getTypeElement(StringParams.class.getCanonicalName()).asType();
+    TypeElement genericParams = elements.getTypeElement(GenericParams.class.getCanonicalName());
+    DeclaredType genericParamsOfNumber = types.getDeclaredType(genericParams, numberType);
+    DeclaredType genericParamsOfInteger = types.getDeclaredType(genericParams, integerType);
+    
+    TypeMirror fieldOfNumberParams = MoreTypes.asMemberOf(types, numberParams, tField);
+    TypeMirror paramOfNumberParams = MoreTypes.asMemberOf(types, numberParams, tParam);
+    TypeMirror fieldOfStringParams = MoreTypes.asMemberOf(types, stringParams, tField);
+    TypeMirror paramOfStringParams = MoreTypes.asMemberOf(types, stringParams, tParam);
+    TypeMirror fieldOfGenericOfNumber = MoreTypes.asMemberOf(types, genericParamsOfNumber, tField);
+    TypeMirror paramOfGenericOfNumber = MoreTypes.asMemberOf(types, genericParamsOfNumber, tParam);
+    TypeMirror fieldOfGenericOfInteger =
+        MoreTypes.asMemberOf(types, genericParamsOfInteger, tField);
+    TypeMirror paramOfGenericOfInteger =
+        MoreTypes.asMemberOf(types, genericParamsOfInteger, tParam);
+
+    EquivalenceTester<TypeMirror> tester = EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence())
+        .addEquivalenceGroup(fieldOfNumberParams, paramOfNumberParams, fieldOfGenericOfNumber,
+            paramOfGenericOfNumber, numberType)
+        .addEquivalenceGroup(fieldOfStringParams, paramOfStringParams, stringType)
+        .addEquivalenceGroup(fieldOfGenericOfInteger, paramOfGenericOfInteger, integerType);
+    tester.test();
+  }
+  
+  private static class Params<T> {
+    @SuppressWarnings("unused") T t;
+    @SuppressWarnings("unused") void add(T t) {}
+  }
+  private static class NumberParams extends Params<Number> {}
+  private static class StringParams extends Params<String> {}
+  private static class GenericParams<T> extends Params<T> {}
+
+  private static final ErrorType FAKE_ERROR_TYPE = new ErrorType() {
+    @Override
+    public TypeKind getKind() {
+      return TypeKind.ERROR;
+    }
+
+    @Override
+    public <R, P> R accept(TypeVisitor<R, P> v, P p) {
+      return v.visitError(this, p);
+    }
+
+    @Override
+    public List<? extends TypeMirror> getTypeArguments() {
+      return ImmutableList.of();
+    }
+
+    @Override
+    public TypeMirror getEnclosingType() {
+      return null;
+    }
+
+    @Override
+    public Element asElement() {
+      return null;
+    }
+
+    // JDK8 Compatibility:
+
+    public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationType) {
+      return null;
+    }
+
+    public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
+      return null;
+    }
+
+    public List<? extends AnnotationMirror> getAnnotationMirrors() {
+      return null;
+    }
+  };
+
+  @Test
+  public void testIsConversionFromObjectUnchecked_yes() {
+    Elements elements = compilationRule.getElements();
+    TypeElement unchecked = elements.getTypeElement(Unchecked.class.getCanonicalName());
+    for (VariableElement field : ElementFilter.fieldsIn(unchecked.getEnclosedElements())) {
+      TypeMirror type = field.asType();
+      expect
+          .withMessage("Casting to %s is unchecked", type)
+          .that(MoreTypes.isConversionFromObjectUnchecked(type))
+          .isTrue();
+    }
+  }
+
+  @Test
+  public void testIsConversionFromObjectUnchecked_no() {
+    Elements elements = compilationRule.getElements();
+    TypeElement notUnchecked = elements.getTypeElement(NotUnchecked.class.getCanonicalName());
+    for (VariableElement field : ElementFilter.fieldsIn(notUnchecked.getEnclosedElements())) {
+      TypeMirror type = field.asType();
+      expect
+          .withMessage("Casting to %s is not unchecked", type)
+          .that(MoreTypes.isConversionFromObjectUnchecked(type))
+          .isFalse();
+    }
+  }
+
+  // The type of every field here is such that casting to it provokes an "unchecked" warning.
+  @SuppressWarnings("unused")
+  private static class Unchecked<T> {
+    private List<String> listOfString;
+    private List<? extends CharSequence> listOfExtendsCharSequence;
+    private List<? super CharSequence> listOfSuperCharSequence;
+    private List<T> listOfT;
+    private List<T[]> listOfArrayOfT;
+    private T t;
+    private T[] arrayOfT;
+    private List<T>[] arrayOfListOfT;
+    private Map<?, String> mapWildcardToString;
+    private Map<String, ?> mapStringToWildcard;
+  }
+
+  // The type of every field here is such that casting to it doesn't provoke an "unchecked" warning.
+  @SuppressWarnings("unused")
+  private static class NotUnchecked {
+    private String string;
+    private int integer;
+    private String[] arrayOfString;
+    private int[] arrayOfInt;
+    private Thread.State threadStateEnum;
+    private List<?> listOfWildcard;
+    private List<? extends Object> listOfWildcardExtendsObject;
+    private Map<?, ?> mapWildcardToWildcard;
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/OverridesTest.java b/common/src/test/java/com/google/auto/common/OverridesTest.java
new file mode 100644
index 0000000..afb7976
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/OverridesTest.java
@@ -0,0 +1,666 @@
+/*
+ * Copyright 2016 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+
+import com.google.common.base.Converter;
+import com.google.common.base.Optional;
+import com.google.common.base.StandardSystemProperty;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Range;
+import com.google.common.io.Files;
+import com.google.common.truth.Expect;
+import com.google.testing.compile.CompilationRule;
+import java.io.File;
+import java.util.AbstractCollection;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleTypeVisitor6;
+import javax.lang.model.util.Types;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.model.Statement;
+
+/**
+ * Tests that the {@link Overrides} class has behaviour consistent with javac. We test this in
+ * two ways: once with {@link Overrides.ExplicitOverrides} using javac's own {@link Elements} and
+ * {@link Types}, and once with it using the version of those objects from the Eclipse compiler
+ * (ecj).
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(Parameterized.class)
+public class OverridesTest {
+  @Parameterized.Parameters(name = "{0}")
+  public static ImmutableList<CompilerType> data() {
+    return ImmutableList.of(CompilerType.JAVAC, CompilerType.ECJ);
+  }
+
+  @Rule public CompilationRule compilation = new CompilationRule();
+  @Rule public EcjCompilationRule ecjCompilation = new EcjCompilationRule();
+  @Rule public Expect expect = Expect.create();
+
+  public enum CompilerType {
+    JAVAC {
+      @Override
+      void initUtils(OverridesTest test) {
+        test.typeUtils = test.compilation.getTypes();
+        test.elementUtils = test.compilation.getElements();
+      }
+    },
+    ECJ {
+      @Override
+      void initUtils(OverridesTest test) {
+        test.typeUtils = test.ecjCompilation.types;
+        test.elementUtils = test.ecjCompilation.elements;
+      }
+    };
+
+    abstract void initUtils(OverridesTest test);
+  }
+  private final CompilerType compilerType;
+
+  private Types typeUtils;
+  private Elements elementUtils;
+  private Elements javacElementUtils;
+  private Overrides javacOverrides;
+  private Overrides.ExplicitOverrides explicitOverrides;
+
+  public OverridesTest(CompilerType compilerType) {
+    this.compilerType = compilerType;
+  }
+
+  @Before
+  public void initializeTestElements() {
+    javacElementUtils = compilation.getElements();
+    javacOverrides = new Overrides.NativeOverrides(javacElementUtils);
+    compilerType.initUtils(this);
+    explicitOverrides = new Overrides.ExplicitOverrides(typeUtils);
+  }
+
+  static class TypesForInheritance {
+    interface One {
+      void m();
+      void m(String x);
+      void n();
+    }
+
+    interface Two {
+      void m();
+      void m(int x);
+    }
+
+    static class Parent {
+      public void m() {}
+    }
+
+    static class ChildOfParent extends Parent {}
+
+    static class ChildOfOne implements One {
+      @Override public void m() {}
+      @Override public void m(String x) {}
+      @Override public void n() {}
+    }
+
+    static class ChildOfOneAndTwo implements One, Two {
+      @Override public void m() {}
+      @Override public void m(String x) {}
+      @Override public void m(int x) {}
+      @Override public void n() {}
+    }
+
+    static class ChildOfParentAndOne extends Parent implements One {
+      @Override public void m() {}
+      @Override public void m(String x) {}
+      @Override public void n() {}
+    }
+
+    static class ChildOfParentAndOneAndTwo extends Parent implements One, Two {
+      @Override public void m(String x) {}
+      @Override public void m(int x) {}
+      @Override public void n() {}
+    }
+
+    abstract static class AbstractChildOfOne implements One {}
+
+    abstract static class AbstractChildOfOneAndTwo implements One, Two {}
+
+    abstract static class AbstractChildOfParentAndOneAndTwo extends Parent implements One, Two {}
+  }
+
+  static class MoreTypesForInheritance {
+    interface Key {}
+
+    interface BindingType {}
+
+    interface ContributionType {}
+
+    interface HasKey {
+      Key key();
+    }
+
+    interface HasBindingType {
+      BindingType bindingType();
+    }
+
+    interface HasContributionType {
+      ContributionType contributionType();
+    }
+
+    abstract static class BindingDeclaration implements HasKey {
+      abstract Optional<Element> bindingElement();
+      abstract Optional<TypeElement> contributingModule();
+    }
+
+    abstract static class MultibindingDeclaration
+        extends BindingDeclaration implements HasBindingType, HasContributionType {
+      @Override public abstract Key key();
+      @Override public abstract ContributionType contributionType();
+      @Override public abstract BindingType bindingType();
+    }
+  }
+
+  static class TypesForVisibility {
+    public abstract static class PublicGrandparent {
+      public abstract String foo();
+    }
+
+    private static class PrivateParent extends PublicGrandparent {
+      @Override
+      public String foo() {
+        return "foo";
+      }
+    }
+
+    static class Child extends PrivateParent {}
+  }
+
+  static class TypesForGenerics {
+    interface XCollection<E> {
+      boolean add(E x);
+    }
+
+    interface XList<E> extends XCollection<E> {
+      @Override public boolean add(E x);
+    }
+
+    static class StringList implements XList<String> {
+      @Override public boolean add(String x) {
+        return false;
+      }
+    }
+  }
+
+  @SuppressWarnings("rawtypes")
+  static class TypesForRaw {
+    static class RawParent {
+      void frob(List x) {}
+    }
+
+    static class RawChildOfRaw extends RawParent {
+      @Override void frob(List x) {}
+    }
+
+    static class NonRawParent {
+      void frob(List<String> x) {}
+    }
+
+    static class RawChildOfNonRaw extends NonRawParent {
+      @Override void frob(List x) {}
+    }
+  }
+
+  @Test
+  public void overridesInheritance() {
+    checkOverridesInContainedClasses(TypesForInheritance.class);
+  }
+
+  @Test
+  public void overridesMoreInheritance() {
+    checkOverridesInContainedClasses(MoreTypesForInheritance.class);
+  }
+
+  @Test
+  public void overridesVisibility() {
+    checkOverridesInContainedClasses(TypesForVisibility.class);
+  }
+
+  @Test
+  public void overridesGenerics() {
+    checkOverridesInContainedClasses(TypesForGenerics.class);
+  }
+
+  @Test
+  public void overridesRaw() {
+    checkOverridesInContainedClasses(TypesForRaw.class);
+  }
+
+  // Test a tricky diamond inheritance hierarchy:
+  //               Collection
+  //              /          \
+  // AbstractCollection     List
+  //              \          /
+  //              AbstractList
+  // This also tests that we do the right thing with generics, since naively the TypeMirror
+  // that you get for List<E> will not appear to be a subtype of the one you get for Collection<E>
+  // since the two Es are not the same.
+  @Test
+  public void overridesDiamond() {
+    checkOverridesInSet(ImmutableSet.<Class<?>>of(
+        Collection.class, List.class, AbstractCollection.class, AbstractList.class));
+  }
+
+  private void checkOverridesInContainedClasses(Class<?> container) {
+    checkOverridesInSet(ImmutableSet.copyOf(container.getDeclaredClasses()));
+  }
+
+  private void checkOverridesInSet(ImmutableSet<Class<?>> testClasses) {
+    assertThat(testClasses).isNotEmpty();
+    ImmutableSet.Builder<TypeElement> testTypesBuilder = ImmutableSet.builder();
+    for (Class<?> testClass : testClasses) {
+      testTypesBuilder.add(getTypeElement(testClass));
+    }
+    ImmutableSet<TypeElement> testTypes = testTypesBuilder.build();
+    ImmutableSet.Builder<ExecutableElement> testMethodsBuilder = ImmutableSet.builder();
+    for (TypeElement testType : testTypes) {
+      testMethodsBuilder.addAll(methodsIn(testType.getEnclosedElements()));
+    }
+    ImmutableSet<ExecutableElement> testMethods = testMethodsBuilder.build();
+    for (TypeElement in : testTypes) {
+      TypeElement javacIn = javacType(in);
+      List<ExecutableElement> inMethods = methodsIn(elementUtils.getAllMembers(in));
+      for (ExecutableElement overrider : inMethods) {
+        ExecutableElement javacOverrider = javacMethod(overrider);
+        for (ExecutableElement overridden : testMethods) {
+          ExecutableElement javacOverridden = javacMethod(overridden);
+          boolean javacSays = javacOverrides.overrides(javacOverrider, javacOverridden, javacIn);
+          boolean weSay = explicitOverrides.overrides(overrider, overridden, in);
+          if (javacSays != weSay) {
+            expect
+                .withMessage(
+                    "%s.%s overrides %s.%s in %s: javac says %s, we say %s",
+                    overrider.getEnclosingElement(), overrider,
+                    overridden.getEnclosingElement(), overridden,
+                    in,
+                    javacSays, weSay)
+                .fail();
+          }
+        }
+      }
+    }
+  }
+
+  private TypeElement getTypeElement(Class<?> c) {
+    return elementUtils.getTypeElement(c.getCanonicalName());
+  }
+
+  private ExecutableElement getMethod(TypeElement in, String name, TypeKind... parameterTypeKinds) {
+    ExecutableElement found = null;
+    methods:
+    for (ExecutableElement method : methodsIn(in.getEnclosedElements())) {
+      if (method.getSimpleName().contentEquals(name)
+          && method.getParameters().size() == parameterTypeKinds.length) {
+        for (int i = 0; i < parameterTypeKinds.length; i++) {
+          if (method.getParameters().get(i).asType().getKind() != parameterTypeKinds[i]) {
+            continue methods;
+          }
+        }
+        assertThat(found).isNull();
+        found = method;
+      }
+    }
+    assertThat(found).isNotNull();
+    return found;
+  }
+
+  // These skeletal parallels to the real collection classes ensure that the test is independent
+  // of the details of those classes, for example whether List<E> redeclares add(E) even though
+  // it also inherits it from Collection<E>.
+
+  private interface XCollection<E> {
+    boolean add(E e);
+  }
+
+  private interface XList<E> extends XCollection<E> {}
+
+  private abstract static class XAbstractCollection<E> implements XCollection<E> {
+    @Override
+    public boolean add(E e) {
+      return false;
+    }
+  }
+
+  private abstract static class XAbstractList<E>
+      extends XAbstractCollection<E> implements XList<E> {
+    @Override
+    public boolean add(E e) {
+      return true;
+    }
+  }
+
+  private abstract static class XStringList extends XAbstractList<String> {}
+
+  private abstract static class XAbstractStringList implements XList<String> {}
+
+  private abstract static class XNumberList<E extends Number> extends XAbstractList<E> {}
+
+  // Parameter of add(E) in StringList is String.
+  // That means that we successfully recorded E[AbstractList] = String and E[List] = E[AbstractList]
+  // and String made it all the way through.
+  @Test
+  public void methodParameters_StringList() {
+    TypeElement xAbstractList = getTypeElement(XAbstractList.class);
+    TypeElement xStringList = getTypeElement(XStringList.class);
+    TypeElement string = getTypeElement(String.class);
+
+    ExecutableElement add = getMethod(xAbstractList, "add", TypeKind.TYPEVAR);
+    List<TypeMirror> params = explicitOverrides.erasedParameterTypes(add, xStringList);
+    List<TypeMirror> expectedParams = ImmutableList.of(string.asType());
+    assertTypeListsEqual(params, expectedParams);
+  }
+
+  // Parameter of add(E) in AbstractStringList is String.
+  // That means that we successfully recorded E[List] = String and E[Collection] = E[List].
+  @Test
+  public void methodParameters_AbstractStringList() {
+    TypeElement xCollection = getTypeElement(XCollection.class);
+    TypeElement xAbstractStringList = getTypeElement(XAbstractStringList.class);
+    TypeElement string = getTypeElement(String.class);
+
+    ExecutableElement add = getMethod(xCollection, "add", TypeKind.TYPEVAR);
+
+    List<TypeMirror> params = explicitOverrides.erasedParameterTypes(add, xAbstractStringList);
+    List<TypeMirror> expectedParams = ImmutableList.of(string.asType());
+    assertTypeListsEqual(params, expectedParams);
+  }
+
+  // Parameter of add(E) in NumberList is Number.
+  // That means that we successfully recorded E[AbstractList] = Number and on from
+  // there, with Number being used because it is the erasure of <E extends Number>.
+  @Test
+  public void methodParams_NumberList() {
+    TypeElement xCollection = getTypeElement(XCollection.class);
+    TypeElement xNumberList = getTypeElement(XNumberList.class);
+    TypeElement number = getTypeElement(Number.class);
+
+    ExecutableElement add = getMethod(xCollection, "add", TypeKind.TYPEVAR);
+
+    List<TypeMirror> params = explicitOverrides.erasedParameterTypes(add, xNumberList);
+    List<TypeMirror> expectedParams = ImmutableList.of(number.asType());
+    assertTypeListsEqual(params, expectedParams);
+  }
+
+  // This is derived from a class that provoked a StackOverflowError in an earlier version.
+  private abstract static class StringToRangeConverter<T extends Comparable<T>>
+      extends Converter<String, Range<T>> {
+    @Override
+    protected String doBackward(Range<T> b) {
+      return null;
+    }
+  }
+
+  @Test
+  public void methodParams_RecursiveBound() {
+    TypeElement stringToRangeConverter = getTypeElement(StringToRangeConverter.class);
+    TypeElement range = getTypeElement(Range.class);
+    ExecutableElement valueConverter =
+        getMethod(stringToRangeConverter, "doBackward", TypeKind.DECLARED);
+    List<TypeMirror> params =
+        explicitOverrides.erasedParameterTypes(valueConverter, stringToRangeConverter);
+    List<TypeMirror> expectedParams =
+        ImmutableList.<TypeMirror>of(typeUtils.erasure(range.asType()));
+    assertTypeListsEqual(params, expectedParams);
+  }
+
+  @Test
+  public void methodFromSuperclasses() {
+    TypeElement xAbstractCollection = getTypeElement(XAbstractCollection.class);
+    TypeElement xAbstractList = getTypeElement(XAbstractList.class);
+    TypeElement xAbstractStringList = getTypeElement(XAbstractStringList.class);
+    TypeElement xStringList = getTypeElement(XStringList.class);
+
+    ExecutableElement add = getMethod(xAbstractCollection, "add", TypeKind.TYPEVAR);
+
+    ExecutableElement addInAbstractStringList =
+        explicitOverrides.methodFromSuperclasses(xAbstractStringList, add);
+    assertThat(addInAbstractStringList).isNull();
+
+    ExecutableElement addInStringList =
+        explicitOverrides.methodFromSuperclasses(xStringList, add);
+    assertThat(addInStringList.getEnclosingElement()).isEqualTo(xAbstractList);
+  }
+
+  @Test
+  public void methodFromSuperinterfaces() {
+    TypeElement xCollection = getTypeElement(XCollection.class);
+    TypeElement xAbstractList = getTypeElement(XAbstractList.class);
+    TypeElement xAbstractStringList = getTypeElement(XAbstractStringList.class);
+    TypeElement xNumberList = getTypeElement(XNumberList.class);
+    TypeElement xList = getTypeElement(XList.class);
+
+    ExecutableElement add = getMethod(xCollection, "add", TypeKind.TYPEVAR);
+
+    ExecutableElement addInAbstractStringList =
+        explicitOverrides.methodFromSuperinterfaces(xAbstractStringList, add);
+    assertThat(addInAbstractStringList.getEnclosingElement()).isEqualTo(xCollection);
+
+    ExecutableElement addInNumberList =
+        explicitOverrides.methodFromSuperinterfaces(xNumberList, add);
+    assertThat(addInNumberList.getEnclosingElement()).isEqualTo(xAbstractList);
+
+    ExecutableElement addInList =
+        explicitOverrides.methodFromSuperinterfaces(xList, add);
+    assertThat(addInList.getEnclosingElement()).isEqualTo(xCollection);
+  }
+
+  private void assertTypeListsEqual(List<TypeMirror> actual, List<TypeMirror> expected) {
+    assertThat(actual.size()).isEqualTo(expected.size());
+    for (int i = 0; i < actual.size(); i++) {
+      assertThat(typeUtils.isSameType(actual.get(i), expected.get(i))).isTrue();
+    }
+  }
+
+  // TODO(emcmanus): replace this with something from compile-testing when that's available.
+  /**
+   * An equivalent to {@link CompilationRule} that uses ecj (the Eclipse compiler) instead of javac.
+   * If the parameterized test is not selecting ecj then this rule has no effect.
+   */
+  public class EcjCompilationRule implements TestRule {
+    Elements elements;
+    Types types;
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+      if (!compilerType.equals(CompilerType.ECJ)) {
+        return base;
+      }
+      return new EcjCompilationStatement(base);
+    }
+  }
+
+  private class EcjCompilationStatement extends Statement {
+    private final Statement statement;
+
+    EcjCompilationStatement(Statement base) {
+      this.statement = base;
+    }
+
+    @Override
+    public void evaluate() throws Throwable {
+      File tmpDir = File.createTempFile("OverridesTest", "dir");
+      tmpDir.delete();
+      tmpDir.mkdir();
+      File dummySourceFile = new File(tmpDir, "Dummy.java");
+      try {
+        Files.asCharSink(dummySourceFile, UTF_8).write("class Dummy {}");
+        evaluate(dummySourceFile);
+      } finally {
+        dummySourceFile.delete();
+        tmpDir.delete();
+      }
+    }
+
+    private void evaluate(File dummySourceFile) throws Throwable {
+      JavaCompiler compiler = new EclipseCompiler();
+      StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, UTF_8);
+      // This hack is only needed in a Google-internal Java 8 environment where symbolic links make
+      // it hard for ecj to find the boot class path. Elsewhere it is unnecessary but harmless.
+      File rtJar = new File(StandardSystemProperty.JAVA_HOME.value() + "/lib/rt.jar");
+      if (rtJar.exists()) {
+        List<File> bootClassPath = ImmutableList.<File>builder()
+            .add(rtJar)
+            .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH))
+            .build();
+        fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath);
+      }
+      Iterable<? extends JavaFileObject> sources = fileManager.getJavaFileObjects(dummySourceFile);
+      JavaCompiler.CompilationTask task =
+          compiler.getTask(null, fileManager, null, null, null, sources);
+      EcjTestProcessor processor = new EcjTestProcessor(statement);
+      task.setProcessors(ImmutableList.of(processor));
+      assertThat(task.call()).isTrue();
+      processor.maybeThrow();
+    }
+  }
+
+  @SupportedAnnotationTypes("*")
+  private class EcjTestProcessor extends AbstractProcessor {
+    private final Statement statement;
+    private Throwable thrown;
+
+    EcjTestProcessor(Statement statement) {
+      this.statement = statement;
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latest();
+    }
+
+    @Override
+    public boolean process(
+        Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      if (roundEnv.processingOver()) {
+        ecjCompilation.elements = processingEnv.getElementUtils();
+        ecjCompilation.types = processingEnv.getTypeUtils();
+        try {
+          statement.evaluate();
+        } catch (Throwable t) {
+          thrown = t;
+        }
+      }
+      return false;
+    }
+
+    void maybeThrow() throws Throwable {
+      if (thrown != null) {
+        throw thrown;
+      }
+    }
+  }
+
+  private TypeElement javacType(TypeElement type) {
+    return javacElementUtils.getTypeElement(type.getQualifiedName().toString());
+  }
+
+  private ExecutableElement javacMethod(ExecutableElement method) {
+    if (elementUtils == javacElementUtils) {
+      return method;
+    }
+    TypeElement containingType = MoreElements.asType(method.getEnclosingElement());
+    TypeElement javacContainingType = javacType(containingType);
+    List<ExecutableElement> candidates = new ArrayList<ExecutableElement>();
+    methods:
+    for (ExecutableElement javacMethod : methodsIn(javacContainingType.getEnclosedElements())) {
+      if (javacMethod.getSimpleName().contentEquals(method.getSimpleName())
+          && javacMethod.getParameters().size() == method.getParameters().size()) {
+        for (int i = 0; i < method.getParameters().size(); i++) {
+          VariableElement parameter = method.getParameters().get(i);
+          VariableElement javacParameter = javacMethod.getParameters().get(i);
+          if (!erasedToString(parameter.asType()).equals(erasedToString(javacParameter.asType()))) {
+            continue methods;
+          }
+        }
+        candidates.add(javacMethod);
+      }
+    }
+    if (candidates.size() == 1) {
+      return candidates.get(0);
+    } else {
+      throw new IllegalStateException(
+          "Expected one javac method matching " + method + " but found " + candidates);
+    }
+  }
+
+  private static String erasedToString(TypeMirror type) {
+    return ERASED_STRING_TYPE_VISITOR.visit(type);
+  }
+
+  private static final TypeVisitor<String, Void> ERASED_STRING_TYPE_VISITOR =
+      new SimpleTypeVisitor6<String, Void>() {
+    @Override
+    protected String defaultAction(TypeMirror e, Void p) {
+      return e.toString();
+    }
+
+    @Override
+    public String visitArray(ArrayType t, Void p) {
+      return visit(t.getComponentType()) + "[]";
+    }
+
+    @Override
+    public String visitDeclared(DeclaredType t, Void p) {
+      return MoreElements.asType(t.asElement()).getQualifiedName().toString();
+    }
+
+    @Override
+    public String visitTypeVariable(TypeVariable t, Void p) {
+      return visit(t.getUpperBound());
+    }
+  };
+}
diff --git a/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java b/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java
new file mode 100644
index 0000000..d73e1b6
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017 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.common;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.testing.compile.CompilationRule;
+import java.util.HashMap;
+import java.util.Map;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.element.TypeElement;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link SimpleAnnotationMirror}. */
+@RunWith(JUnit4.class)
+public class SimpleAnnotationMirrorTest {
+  @Rule public final CompilationRule compilation = new CompilationRule();
+
+  @interface EmptyAnnotation {}
+
+  @interface AnnotationWithDefault {
+    int value() default 3;
+  }
+
+  @interface MultipleValues {
+    int value1();
+    int value2();
+  }
+
+  @Test
+  public void emptyAnnotation() {
+    TypeElement emptyAnnotation = getTypeElement(EmptyAnnotation.class);
+    AnnotationMirror simpleAnnotation = SimpleAnnotationMirror.of(emptyAnnotation);
+    assertThat(simpleAnnotation.getAnnotationType()).isEqualTo(emptyAnnotation.asType());
+    assertThat(simpleAnnotation.getElementValues()).isEmpty();
+  }
+
+  @Test
+  public void multipleValues() {
+    TypeElement multipleValues = getTypeElement(MultipleValues.class);
+    Map<String, AnnotationValue> values = new HashMap<>();
+    values.put("value1", intValue(1));
+    values.put("value2", intValue(2));
+    assertThat(SimpleAnnotationMirror.of(multipleValues, values).getElementValues()).hasSize(2);
+  }
+
+  @Test
+  public void extraValues() {
+    TypeElement multipleValues = getTypeElement(MultipleValues.class);
+    Map<String, AnnotationValue> values = new HashMap<>();
+    values.put("value1", intValue(1));
+    values.put("value2", intValue(2));
+    values.put("value3", intValue(3));
+    expectThrows(() -> SimpleAnnotationMirror.of(multipleValues, values));
+  }
+
+  @Test
+  public void defaultValue() {
+    TypeElement withDefaults = getTypeElement(AnnotationWithDefault.class);
+    AnnotationMirror annotation = SimpleAnnotationMirror.of(withDefaults);
+    assertThat(annotation.getElementValues()).hasSize(1);
+    assertThat(getOnlyElement(annotation.getElementValues().values()).getValue()).isEqualTo(3);
+  }
+
+  @Test
+  public void overriddenDefaultValue() {
+    TypeElement withDefaults = getTypeElement(AnnotationWithDefault.class);
+    AnnotationMirror annotation =
+        SimpleAnnotationMirror.of(withDefaults, ImmutableMap.of("value", intValue(4)));
+    assertThat(annotation.getElementValues()).hasSize(1);
+    assertThat(getOnlyElement(annotation.getElementValues().values()).getValue()).isEqualTo(4);
+  }
+
+  @Test
+  public void missingValues() {
+    TypeElement multipleValues = getTypeElement(MultipleValues.class);
+    expectThrows(() -> SimpleAnnotationMirror.of(multipleValues));
+  }
+
+  @Test
+  public void notAnAnnotation() {
+    TypeElement stringElement = getTypeElement(String.class);
+    expectThrows(() -> SimpleAnnotationMirror.of(stringElement));
+  }
+
+  private TypeElement getTypeElement(Class<?> clazz) {
+    return compilation.getElements().getTypeElement(clazz.getCanonicalName());
+  }
+
+  private static void expectThrows(Runnable throwingRunnable) {
+    try {
+      throwingRunnable.run();
+      fail("Expected an IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  private static AnnotationValue intValue(int value) {
+    return new AnnotationValue() {
+      @Override
+      public Object getValue() {
+        return value;
+      }
+
+      @Override
+      public <R, P> R accept(AnnotationValueVisitor<R, P> annotationValueVisitor, P p) {
+        return annotationValueVisitor.visitInt(value, p);
+      }
+    };
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java b/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java
new file mode 100644
index 0000000..4fc61b5
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.testing.compile.CompilationRule;
+import java.util.List;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+import javax.lang.model.util.Types;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link SimpleTypeAnnotationValue}. */
+@RunWith(JUnit4.class)
+public class SimpleTypeAnnotationValueTest {
+  @Rule public final CompilationRule compilation = new CompilationRule();
+  private Types types;
+  private Elements elements;
+  private TypeMirror objectType;
+  private PrimitiveType primitiveType;
+
+  @Before
+  public void setUp() {
+    types = compilation.getTypes();
+    elements = compilation.getElements();
+    objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType();
+    primitiveType = types.getPrimitiveType(TypeKind.BOOLEAN);
+  }
+
+  @Test
+  public void primitiveClass() {
+    AnnotationValue annotationValue = SimpleTypeAnnotationValue.of(primitiveType);
+    assertThat(annotationValue.getValue()).isEqualTo(primitiveType);
+  }
+
+  @Test
+  public void arrays() {
+    SimpleTypeAnnotationValue.of(types.getArrayType(objectType));
+    SimpleTypeAnnotationValue.of(types.getArrayType(primitiveType));
+  }
+
+  @Test
+  public void declaredType() {
+    SimpleTypeAnnotationValue.of(objectType);
+  }
+
+  @Test
+  public void visitorMethod() {
+    SimpleTypeAnnotationValue.of(objectType).accept(new SimpleAnnotationValueVisitor8<Void, Void>(){
+      @Override
+      public Void visitType(TypeMirror typeMirror, Void aVoid) {
+        // do nothing, expected case
+        return null;
+      }
+
+      @Override
+      protected Void defaultAction(Object o, Void aVoid) {
+        throw new AssertionError();
+      }
+    }, null);
+  }
+
+  @Test
+  public void parameterizedType() {
+    try {
+      SimpleTypeAnnotationValue.of(
+          types.getDeclaredType(
+              elements.getTypeElement(List.class.getCanonicalName()), objectType));
+      fail("Expected an exception");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java b/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java
new file mode 100644
index 0000000..15e54ff
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.testing.compile.JavaFileObjects;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SuperficialValidationTest {
+  @Test
+  public void missingReturnType() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "abstract class TestClass {",
+        "  abstract MissingType blah();",
+        "}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+          }
+        })
+        .failsToCompile();
+  }
+
+  @Test
+  public void missingGenericReturnType() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "abstract class TestClass {",
+        "  abstract MissingType<?> blah();",
+        "}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+          }
+        })
+        .failsToCompile();
+  }
+
+  @Test
+  public void missingReturnTypeTypeParameter() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "import java.util.Map;",
+        "import java.util.Set;",
+        "",
+        "abstract class TestClass {",
+        "  abstract Map<Set<?>, MissingType<?>> blah();",
+        "}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+          }
+        })
+        .failsToCompile();
+  }
+
+  @Test
+  public void missingTypeParameter() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "class TestClass<T extends MissingType> {}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+          }
+        })
+        .failsToCompile();
+  }
+
+  @Test
+  public void missingParameterType() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "abstract class TestClass {",
+        "  abstract void foo(MissingType x);",
+        "}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+          }
+        })
+        .failsToCompile();
+  }
+
+  @Test
+  public void missingAnnotation() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "@MissingAnnotation",
+        "class TestClass {}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+          }
+        })
+        .failsToCompile();
+  }
+
+  @Test
+  public void handlesRecursiveTypeParams() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "class TestClass<T extends Comparable<T>> {}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
+          }
+        })
+        .compilesWithoutError();
+  }
+
+  @Test
+  public void handlesRecursiveType() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "abstract class TestClass {",
+        "  abstract TestClass foo(TestClass x);",
+        "}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
+          }
+        })
+        .compilesWithoutError();
+  }
+
+  @Test
+  public void missingWildcardBound() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "import java.util.Set;",
+        "",
+        "class TestClass {",
+        "  Set<? extends MissingType> extendsTest() {",
+        "    return null;",
+        "  }",
+        "",
+        "  Set<? super MissingType> superTest() {",
+        "    return null;",
+        "  }",
+        "}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+          }
+        })
+        .failsToCompile();
+  }
+
+  @Test
+  public void missingIntersection() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+        "test.TestClass",
+        "package test;",
+        "",
+        "class TestClass<T extends Number & Missing> {}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AssertingProcessor() {
+          @Override void runAssertions() {
+            TypeElement testClassElement =
+                processingEnv.getElementUtils().getTypeElement("test.TestClass");
+            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+          }
+        })
+        .failsToCompile();
+  }
+
+  @Test
+  public void invalidAnnotationValue() {
+    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines("test.Outer",
+        "package test;",
+        "",
+        "final class Outer {",
+        "  @interface TestAnnotation {",
+        "    Class[] classes();",
+        "  }",
+        "",
+        "  @TestAnnotation(classes = Foo)",
+        "  static class TestClass {}",
+        "}");
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.Outer.TestClass");
+                assertWithMessage("testClassElement is valid")
+                    .that(SuperficialValidation.validateElement(testClassElement))
+                    .isFalse();
+              }
+            })
+        .failsToCompile();
+  }
+
+  private abstract static class AssertingProcessor extends AbstractProcessor {
+    @Override
+    public Set<String> getSupportedAnnotationTypes() {
+      return ImmutableSet.of("*");
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      try {
+        runAssertions();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+      return false;
+    }
+
+    abstract void runAssertions() throws Exception;
+  }
+}
diff --git a/common/src/test/java/com/google/auto/common/VisibilityTest.java b/common/src/test/java/com/google/auto/common/VisibilityTest.java
new file mode 100644
index 0000000..6a80b7a
--- /dev/null
+++ b/common/src/test/java/com/google/auto/common/VisibilityTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2014 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.common;
+
+import static com.google.auto.common.Visibility.DEFAULT;
+import static com.google.auto.common.Visibility.PRIVATE;
+import static com.google.auto.common.Visibility.PROTECTED;
+import static com.google.auto.common.Visibility.PUBLIC;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.testing.compile.CompilationRule;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import javax.lang.model.element.Element;
+import javax.lang.model.util.Elements;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class VisibilityTest {
+  @Rule public CompilationRule compilation = new CompilationRule();
+
+  @Test
+  public void packageVisibility() {
+    assertThat(Visibility.ofElement(compilation.getElements().getPackageElement("java.lang")))
+        .isEqualTo(PUBLIC);
+    assertThat(Visibility.ofElement(
+        compilation.getElements().getPackageElement("com.google.auto.common")))
+            .isEqualTo(PUBLIC);
+  }
+
+  @Test
+  public void moduleVisibility() throws IllegalAccessException, InvocationTargetException {
+    Method getModuleElement;
+    try {
+      getModuleElement = Elements.class.getMethod("getModuleElement", CharSequence.class);
+    } catch (NoSuchMethodException e) {
+      // TODO(ronshapiro): rewrite this test without reflection once we're on Java 9
+      return;
+    }
+    Element moduleElement =
+        (Element) getModuleElement.invoke(compilation.getElements(), "java.base");
+    assertThat(Visibility.ofElement(moduleElement)).isEqualTo(PUBLIC);
+  }
+
+  @SuppressWarnings("unused")
+  public static class PublicClass {
+    public static class NestedPublicClass {}
+    protected static class NestedProtectedClass {}
+    static class NestedDefaultClass {}
+    private static class NestedPrivateClass {}
+  }
+
+  @SuppressWarnings("unused")
+  protected static class ProtectedClass {
+    public static class NestedPublicClass {}
+    protected static class NestedProtectedClass {}
+    static class NestedDefaultClass {}
+    private static class NestedPrivateClass {}
+  }
+
+  @SuppressWarnings("unused")
+  static class DefaultClass {
+    public static class NestedPublicClass {}
+    protected static class NestedProtectedClass {}
+    static class NestedDefaultClass {}
+    private static class NestedPrivateClass {}
+  }
+
+  @SuppressWarnings("unused")
+  private static class PrivateClass {
+    public static class NestedPublicClass {}
+    protected static class NestedProtectedClass {}
+    static class NestedDefaultClass {}
+    private static class NestedPrivateClass {}
+  }
+
+  @Test
+  public void classVisibility() {
+    assertThat(Visibility.ofElement(compilation.getElements().getTypeElement("java.util.Map")))
+        .isEqualTo(PUBLIC);
+    assertThat(Visibility.ofElement(
+        compilation.getElements().getTypeElement("java.util.Map.Entry")))
+            .isEqualTo(PUBLIC);
+    assertThat(Visibility.ofElement(
+        compilation.getElements().getTypeElement(PublicClass.class.getCanonicalName())))
+            .isEqualTo(PUBLIC);
+    assertThat(Visibility.ofElement(
+        compilation.getElements().getTypeElement(ProtectedClass.class.getCanonicalName())))
+            .isEqualTo(PROTECTED);
+    assertThat(Visibility.ofElement(
+        compilation.getElements().getTypeElement(DefaultClass.class.getCanonicalName())))
+            .isEqualTo(DEFAULT);
+    assertThat(Visibility.ofElement(
+        compilation.getElements().getTypeElement(PrivateClass.class.getCanonicalName())))
+            .isEqualTo(PRIVATE);
+  }
+
+  @Test
+  public void effectiveClassVisibility() {
+    assertThat(effectiveVisiblityOfClass(PublicClass.class)).isEqualTo(PUBLIC);
+    assertThat(effectiveVisiblityOfClass(ProtectedClass.class)).isEqualTo(PROTECTED);
+    assertThat(effectiveVisiblityOfClass(DefaultClass.class)).isEqualTo(DEFAULT);
+    assertThat(effectiveVisiblityOfClass(PrivateClass.class)).isEqualTo(PRIVATE);
+
+    assertThat(effectiveVisiblityOfClass(PublicClass.NestedPublicClass.class))
+        .isEqualTo(PUBLIC);
+    assertThat(effectiveVisiblityOfClass(PublicClass.NestedProtectedClass.class))
+        .isEqualTo(PROTECTED);
+    assertThat(effectiveVisiblityOfClass(PublicClass.NestedDefaultClass.class))
+        .isEqualTo(DEFAULT);
+    assertThat(effectiveVisiblityOfClass(PublicClass.NestedPrivateClass.class))
+        .isEqualTo(PRIVATE);
+
+    assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedPublicClass.class))
+        .isEqualTo(PROTECTED);
+    assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedProtectedClass.class))
+        .isEqualTo(PROTECTED);
+    assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedDefaultClass.class))
+        .isEqualTo(DEFAULT);
+    assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedPrivateClass.class))
+        .isEqualTo(PRIVATE);
+
+    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPublicClass.class))
+        .isEqualTo(DEFAULT);
+    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedProtectedClass.class))
+        .isEqualTo(DEFAULT);
+    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedDefaultClass.class))
+        .isEqualTo(DEFAULT);
+    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPrivateClass.class))
+        .isEqualTo(PRIVATE);
+
+    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPublicClass.class))
+        .isEqualTo(PRIVATE);
+    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedProtectedClass.class))
+        .isEqualTo(PRIVATE);
+    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedDefaultClass.class))
+        .isEqualTo(PRIVATE);
+    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPrivateClass.class))
+        .isEqualTo(PRIVATE);
+  }
+
+  private Visibility effectiveVisiblityOfClass(Class<?> clazz) {
+    return Visibility.effectiveVisibilityOfElement(
+        compilation.getElements().getTypeElement(clazz.getCanonicalName()));
+  }
+}
diff --git a/factory/README.md b/factory/README.md
new file mode 100644
index 0000000..f5874fe
--- /dev/null
+++ b/factory/README.md
@@ -0,0 +1,101 @@
+AutoFactory
+======
+
+A source code generator for JSR-330-compatible factories.
+
+AutoWhat‽
+-------------
+
+[Java][java] is full of [factories](http://en.wikipedia.org/wiki/Factory_method_pattern). They're mechanical, repetitive, typically untested and sometimes the source of subtle bugs. _Sounds like a job for robots!_
+
+AutoFactory generates factories that can be used on their own or with [JSR-330](http://jcp.org/en/jsr/detail?id=330)-compatible [dependency injectors](http://en.wikipedia.org/wiki/Dependency_injection) from a simple annotation. Any combination of parameters can either be passed through factory methods or provided to the factory at construction time. They can implement interfaces or extend abstract classes. They're what you would have written, but without the bugs.
+
+Save time.  Save code.  Save sanity.
+
+Example
+-------
+
+Say you have:
+
+```java
+@AutoFactory
+final class SomeClass {
+  private final String providedDepA;
+  private final String depB;
+
+  SomeClass(@Provided @AQualifier String providedDepA, String depB) {
+    this.providedDepA = providedDepA;
+    this.depB = depB;
+  }
+
+  // …
+}
+```
+
+AutoFactory will generate:
+
+```java
+import javax.annotation.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(value = "com.google.auto.factory.processor.AutoFactoryProcessor")
+final class SomeClassFactory {
+  private final Provider<String> providedDepAProvider;
+  
+  @Inject SomeClassFactory(
+      @AQualifier Provider<String> providedDepAProvider) {
+    this.providedDepAProvider = providedDepAProvider;
+  }
+  
+  SomeClass create(String depB) {
+    return new SomeClass(providedDepAProvider.get(), depB);
+  }
+}
+```
+
+> NOTE: AutoFactory only supports JSR-330 @Qualifier annotations. Older, 
+> framework-specific annotations from Guice, Spring, etc are not
+> supported (though these all support JSR-330)
+
+Download
+--------
+
+In order to activate code generation you will need to
+include `auto-factory-${version}.jar` in your build at 
+compile time.
+
+In a Maven project, one would include the `auto-factory` 
+artifact as an "optional" dependency:
+
+```xml
+<dependencies>
+  <dependency>
+    <groupId>com.google.auto.factory</groupId>
+    <artifactId>auto-factory</artifactId>
+    <version>${version}</version>
+    <optional>true</optional>
+  </dependency>
+</dependencies>
+```
+
+
+License
+-------
+
+    Copyright 2013 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.
+
+[java]: https://en.wikipedia.org/wiki/Java_(programming_language)
+
diff --git a/factory/pom.xml b/factory/pom.xml
new file mode 100644
index 0000000..48bcfef
--- /dev/null
+++ b/factory/pom.xml
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2012 Google LLC
+  Copyright (C) 2012 Square, Inc.
+
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+
+  <groupId>com.google.auto.factory</groupId>
+  <artifactId>auto-factory</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>AutoFactory</name>
+  <description>
+    JSR-330-compatible factories.
+  </description>
+  <url>https://github.com/google/auto/tree/master/factory</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <java.version>1.8</java.version>
+    <guava.version>28.2-jre</guava.version>
+    <truth.version>1.0.1</truth.version>
+  </properties>
+
+  <scm>
+    <url>http://github.com/google/auto</url>
+    <connection>scm:git:git://github.com/google/auto.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/google/auto.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <issueManagement>
+    <system>GitHub Issues</system>
+    <url>http://github.com/google/auto/issues</url>
+  </issueManagement>
+
+  <licenses>
+    <license>
+      <name>Apache 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+    </license>
+  </licenses>
+
+  <organization>
+    <name>Google LLC</name>
+    <url>http://www.google.com</url>
+  </organization>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.auto</groupId>
+      <artifactId>auto-common</artifactId>
+      <version>0.10</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto.value</groupId>
+      <artifactId>auto-value-annotations</artifactId>
+      <version>1.7</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto.value</groupId>
+      <artifactId>auto-value</artifactId>
+      <version>1.7</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto.service</groupId>
+      <artifactId>auto-service</artifactId>
+      <version>1.0-rc6</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>net.ltgt.gradle.incap</groupId>
+      <artifactId>incap</artifactId>
+      <version>0.2</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>net.ltgt.gradle.incap</groupId>
+      <artifactId>incap-processor</artifactId>
+      <version>0.2</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.googlejavaformat</groupId>
+      <artifactId>google-java-format</artifactId>
+      <version>1.7</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <version>${guava.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.squareup</groupId>
+      <artifactId>javapoet</artifactId>
+      <version>1.12.1</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.inject</groupId>
+      <artifactId>javax.inject</artifactId>
+      <version>1</version>
+    </dependency>
+    <!-- test dependencies -->
+    <dependency>
+      <groupId>com.google.testing.compile</groupId>
+      <artifactId>compile-testing</artifactId>
+      <version>0.18</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.13</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <version>${truth.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+      <version>3.0.2</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.checkerframework</groupId>
+      <artifactId>checker-compat-qual</artifactId>
+      <version>2.5.5</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.7.0</version>
+        <configuration>
+          <source>${java.version}</source>
+          <target>${java.version}</target>
+          <compilerArgument>-Xlint:all</compilerArgument>
+          <showWarnings>true</showWarnings>
+          <showDeprecation>true</showDeprecation>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-java</artifactId>
+            <version>0.9.4</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>3.0.2</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-invoker-plugin</artifactId>
+        <version>3.0.1</version>
+        <configuration>
+          <addTestClassPath>true</addTestClassPath>
+          <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
+          <pomIncludes>
+            <pomInclude>*/pom.xml</pomInclude>
+          </pomIncludes>
+          <streamLogs>true</streamLogs>
+        </configuration>
+        <executions>
+          <execution>
+            <id>integration-test</id>
+            <goals>
+              <goal>install</goal>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.immutables.tools</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>4</version>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/factory/src/it/functional/pom.xml b/factory/src/it/functional/pom.xml
new file mode 100644
index 0000000..56e8f6f
--- /dev/null
+++ b/factory/src/it/functional/pom.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2013 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.
+-->
+<!-- TODO(gak): see if we can manage these dependencies any better -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <!-- TODO(cpovirk): Remove parent entirely? -->
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.google.auto.value.it.functional</groupId>
+  <artifactId>functional</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>Auto-Value Functional Integration Test</name>
+  <dependencies>
+    <dependency>
+      <groupId>com.google.auto.factory</groupId>
+      <artifactId>auto-factory</artifactId>
+      <version>@project.version@</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+      <version>3.0.2</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <version>27.0.1-jre</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.inject</groupId>
+      <artifactId>guice</artifactId>
+      <version>4.1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.dagger</groupId>
+      <artifactId>dagger</artifactId>
+      <version>2.13</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.dagger</groupId>
+      <artifactId>dagger-compiler</artifactId>
+      <version>2.13</version>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <version>0.44</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>3.0.2</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.7.0</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+          <compilerArgument>-Xlint:all</compilerArgument>
+          <showWarnings>true</showWarnings>
+          <showDeprecation>true</showDeprecation>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-java</artifactId>
+            <version>0.9.4</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java
new file mode 100644
index 0000000..be9478f
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 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.factory;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+final class DaggerModule {
+  @Provides Dependency provideDependency(DependencyImpl impl) {
+    return impl;
+  }
+
+  @Provides
+  @Qualifier
+  Dependency provideQualifiedDependency(QualifiedDependencyImpl impl) {
+    return impl;
+  }
+
+  @Provides
+  int providePrimitive() {
+    return 1;
+  }
+
+  @Provides
+  @Qualifier
+  int provideQualifiedPrimitive() {
+    return 2;
+  }
+
+  @Provides
+  Number provideNumber() {
+    return 3;
+  }
+}
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/Dependency.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/Dependency.java
new file mode 100644
index 0000000..18d19eb
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/Dependency.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013 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.factory;
+
+public interface Dependency {}
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java
new file mode 100644
index 0000000..4c019ea
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 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.factory;
+
+import javax.inject.Inject;
+
+public class DependencyImpl implements Dependency {
+  @Inject DependencyImpl() {}
+}
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/FactoryComponent.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/FactoryComponent.java
new file mode 100644
index 0000000..7d0a162
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/FactoryComponent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015 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.factory;
+
+import dagger.Component;
+
+/** A component to materialize the factory using Dagger 2 */
+@Component(modules = DaggerModule.class)
+interface FactoryComponent {
+  FooFactory factory();
+
+  GenericFooFactory<Number> generatedFactory();
+}
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/FactoryInterface.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/FactoryInterface.java
new file mode 100644
index 0000000..5f9d415
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/FactoryInterface.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 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.factory;
+
+public interface FactoryInterface {
+  Foo generate(String name);
+}
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/Foo.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/Foo.java
new file mode 100644
index 0000000..3cc02d5
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/Foo.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 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.factory;
+
+import javax.inject.Provider;
+
+@AutoFactory(implementing = FactoryInterface.class)
+public final class Foo {
+  private final String name;
+  private final Dependency dependency;
+  private final Provider<Dependency> dependencyProvider;
+  private final int primitive;
+  private final int qualifiedPrimitive;
+
+  Foo(
+      String name,
+      @Provided Dependency dependency,
+      @Provided @Qualifier Provider<Dependency> dependencyProvider,
+      @Provided int primitive,
+      @Provided @Qualifier int qualifiedPrimitive) {
+    this.name = name;
+    this.dependency = dependency;
+    this.dependencyProvider = dependencyProvider;
+    this.primitive = primitive;
+    this.qualifiedPrimitive = qualifiedPrimitive;
+  }
+
+  // Generates second factory method with a different name for the Dependency dependency.
+  // Tests http://b/21632171.
+  Foo(
+      Object name,
+      @Provided Dependency dependency2,
+      @Provided @Qualifier Provider<Dependency> dependencyProvider,
+      @Provided int primitive,
+      @Provided @Qualifier int qualifiedPrimitive) {
+    this(name.toString(), dependency2, dependencyProvider, primitive, qualifiedPrimitive);
+  }
+
+  String name() {
+    return name;
+  }
+
+  Dependency dependency() {
+    return dependency;
+  }
+
+  Provider<Dependency> dependencyProvider() {
+    return dependencyProvider;
+  }
+
+  int primitive() {
+    return primitive;
+  }
+
+  int qualifiedPrimitive() {
+    return qualifiedPrimitive;
+  }
+}
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java
new file mode 100644
index 0000000..f7c13b4
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019 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.factory;
+
+import java.util.List;
+import javax.inject.Provider;
+
+@AutoFactory
+public class GenericFoo<A, B extends List<? extends A>, C, E extends Enum<E>> {
+
+  private final A depA;
+  private final B depB;
+  private final IntAccessor depDIntAccessor;
+  private final StringAccessor depDStringAccessor;
+  private final E depE;
+
+  <D extends IntAccessor & StringAccessor> GenericFoo(
+      @Provided Provider<A> depA,
+      B depB,
+      D depD,
+      E depE) {
+    this.depA = depA.get();
+    this.depB = depB;
+    this.depDIntAccessor = depD;
+    this.depDStringAccessor = depD;
+    this.depE = depE;
+  }
+
+  public A getDepA() {
+    return depA;
+  }
+
+  public B getDepB() {
+    return depB;
+  }
+
+  public C passThrough(C value) {
+    return value;
+  }
+
+  public IntAccessor getDepDIntAccessor() {
+    return depDIntAccessor;
+  }
+
+  public StringAccessor getDepDStringAccessor() {
+    return depDStringAccessor;
+  }
+
+  public E getDepE() {
+    return depE;
+  }
+
+  public interface IntAccessor {}
+
+  public interface StringAccessor {}
+
+  public interface IntAndStringAccessor extends IntAccessor, StringAccessor {}
+
+  public enum DepE {
+    VALUE_1,
+    VALUE_2
+  }
+}
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java
new file mode 100644
index 0000000..45d4d26
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 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.factory;
+
+import com.google.inject.AbstractModule;
+
+public class GuiceModule extends AbstractModule {
+  @Override protected void configure() {
+    bind(Dependency.class).to(DependencyImpl.class);
+    bind(Dependency.class).annotatedWith(Qualifier.class).to(QualifiedDependencyImpl.class);
+    bind(Integer.class).toInstance(1);
+    bind(Integer.class).annotatedWith(Qualifier.class).toInstance(2);
+    bind(Number.class).toInstance(3);
+  }
+}
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/QualifiedDependencyImpl.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/QualifiedDependencyImpl.java
new file mode 100644
index 0000000..ff36813
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/QualifiedDependencyImpl.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 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.factory;
+
+import javax.inject.Inject;
+
+public class QualifiedDependencyImpl implements Dependency {
+  @Inject
+  QualifiedDependencyImpl() {}
+}
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/Qualifier.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/Qualifier.java
new file mode 100644
index 0000000..a1780e0
--- /dev/null
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/Qualifier.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2015 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.factory;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@javax.inject.Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@interface Qualifier {}
diff --git a/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java
new file mode 100644
index 0000000..ebd8336
--- /dev/null
+++ b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java
@@ -0,0 +1,112 @@
+package com.google.auto.factory;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auto.factory.GenericFoo.DepE;
+import com.google.auto.factory.GenericFoo.IntAndStringAccessor;
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Guice;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+import java.util.ArrayList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DependencyInjectionIntegrationTest {
+
+  private static final IntAndStringAccessor INT_AND_STRING_ACCESSOR = new IntAndStringAccessor() {};
+
+  @Test public void daggerInjectedFactory() {
+    FooFactory fooFactory = DaggerFactoryComponent.create().factory();
+    Foo one = fooFactory.create("A");
+    Foo two = fooFactory.create("B");
+    assertThat(one.name()).isEqualTo("A");
+    assertThat(one.dependency()).isNotNull();
+    assertThat(one.dependencyProvider()).isNotNull();
+    assertThat(one.dependencyProvider().get()).isInstanceOf(QualifiedDependencyImpl.class);
+    assertThat(one.primitive()).isEqualTo(1);
+    assertThat(one.qualifiedPrimitive()).isEqualTo(2);
+    assertThat(two.name()).isEqualTo("B");
+    assertThat(two.dependency()).isNotNull();
+    assertThat(two.dependency()).isNotEqualTo(one.dependency());
+    assertThat(two.dependencyProvider()).isNotNull();
+    assertThat(two.dependencyProvider().get()).isInstanceOf(QualifiedDependencyImpl.class);
+    assertThat(two.primitive()).isEqualTo(1);
+    assertThat(two.qualifiedPrimitive()).isEqualTo(2);
+  }
+
+  @Test
+  public void daggerInjectedGenericFactory() {
+    GenericFooFactory<Number> genericFooFactory =
+        DaggerFactoryComponent.create().generatedFactory();
+    GenericFoo<Number, ImmutableList<Long>, String, DepE> three =
+        genericFooFactory.create(ImmutableList.of(3L), INT_AND_STRING_ACCESSOR, DepE.VALUE_1);
+    ArrayList<Double> intAndStringAccessorArrayList = new ArrayList<>();
+    intAndStringAccessorArrayList.add(4.0);
+    GenericFoo<Number, ArrayList<Double>, Long, DepE> four = genericFooFactory.create(
+        intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2);
+    assertThat(three.getDepA()).isEqualTo(3);
+    ImmutableList<Long> unusedLongList = three.getDepB();
+    assertThat(three.getDepB()).containsExactly(3L);
+    assertThat(three.getDepDIntAccessor()).isEqualTo(INT_AND_STRING_ACCESSOR);
+    assertThat(three.getDepDStringAccessor()).isEqualTo(INT_AND_STRING_ACCESSOR);
+    assertThat(three.passThrough("value")).isEqualTo("value");
+    assertThat(three.getDepE()).isEqualTo(DepE.VALUE_1);
+    assertThat(four.getDepA()).isEqualTo(3);
+    ArrayList<Double> unusedDoubleList = four.getDepB();
+    assertThat(four.getDepB()).isInstanceOf(ArrayList.class);
+    assertThat(four.getDepB()).containsExactly(4.0);
+    assertThat(four.getDepDIntAccessor()).isEqualTo(INT_AND_STRING_ACCESSOR);
+    assertThat(four.getDepDStringAccessor()).isEqualTo(INT_AND_STRING_ACCESSOR);
+    assertThat(four.passThrough(5L)).isEqualTo(5L);
+    assertThat(four.getDepE()).isEqualTo(DepE.VALUE_2);
+  }
+
+  @Test public void guiceInjectedFactory() {
+    FooFactory fooFactory = Guice.createInjector(new GuiceModule()).getInstance(FooFactory.class);
+    Foo one = fooFactory.create("A");
+    Foo two = fooFactory.create("B");
+    assertThat(one.name()).isEqualTo("A");
+    assertThat(one.dependency()).isNotNull();
+    assertThat(one.dependencyProvider()).isNotNull();
+    assertThat(one.dependencyProvider().get()).isInstanceOf(QualifiedDependencyImpl.class);
+    assertThat(one.primitive()).isEqualTo(1);
+    assertThat(one.qualifiedPrimitive()).isEqualTo(2);
+    assertThat(two.name()).isEqualTo("B");
+    assertThat(two.dependency()).isNotNull();
+    assertThat(two.dependency()).isNotEqualTo(one.dependency());
+    assertThat(two.dependencyProvider()).isNotNull();
+    assertThat(two.dependencyProvider().get()).isInstanceOf(QualifiedDependencyImpl.class);
+    assertThat(two.primitive()).isEqualTo(1);
+    assertThat(two.qualifiedPrimitive()).isEqualTo(2);
+  }
+
+  @Test
+  public void guiceInjectedGenericFactory() {
+    GenericFooFactory<Number> genericFooFactory =
+        Guice.createInjector(new GuiceModule())
+            .getInstance(Key.get(new TypeLiteral<GenericFooFactory<Number>>() {}));
+    GenericFoo<Number, ImmutableList<Long>, String, DepE> three =
+        genericFooFactory.create(ImmutableList.of(3L), INT_AND_STRING_ACCESSOR, DepE.VALUE_1);
+    ArrayList<Double> intAndStringAccessorArrayList = new ArrayList<>();
+    intAndStringAccessorArrayList.add(4.0);
+    GenericFoo<Number, ArrayList<Double>, Long, DepE> four = genericFooFactory.create(
+        intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2);
+    assertThat(three.getDepA()).isEqualTo(3);
+    ImmutableList<Long> unusedLongList = three.getDepB();
+    assertThat(three.getDepB()).containsExactly(3L);
+    assertThat(three.getDepDIntAccessor()).isEqualTo(INT_AND_STRING_ACCESSOR);
+    assertThat(three.getDepDStringAccessor()).isEqualTo(INT_AND_STRING_ACCESSOR);
+    assertThat(three.passThrough("value")).isEqualTo("value");
+    assertThat(three.getDepE()).isEqualTo(DepE.VALUE_1);
+    assertThat(four.getDepA()).isEqualTo(3);
+    ArrayList<Double> unusedDoubleList = four.getDepB();
+    assertThat(four.getDepB()).containsExactly(4.0);
+    assertThat(four.getDepDIntAccessor()).isEqualTo(INT_AND_STRING_ACCESSOR);
+    assertThat(four.getDepDStringAccessor()).isEqualTo(INT_AND_STRING_ACCESSOR);
+    assertThat(four.passThrough(5L)).isEqualTo(5L);
+    assertThat(four.getDepE()).isEqualTo(DepE.VALUE_2);
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/AutoFactory.java b/factory/src/main/java/com/google/auto/factory/AutoFactory.java
new file mode 100644
index 0000000..2ef84cc
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/AutoFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 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.factory;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Target;
+
+/**
+ * An annotation to be applied to elements for which a factory should be automatically generated.
+ *
+ * <h2>Visibility</h2>
+ * <p>The visibility of the generated factories will always be either {@code public} or default
+ * visibility. The visibility of any given factory method is determined by the visibility of the
+ * type being created. The generated factory is {@code public} if any of the factory methods are.
+ * Any method that implements an interface method is necessarily public and any method that
+ * overrides an abstract method has the same visibility as that method.
+ *
+ * @author Gregory Kick
+ */
+@Target({ TYPE, CONSTRUCTOR })
+public @interface AutoFactory {
+  /**
+   * The <i>simple</i> name of the generated factory; the factory is always generated in the same
+   * package as the annotated type.  The default value (the empty string) will result in a factory
+   * with the name of the type being created with {@code Factory} appended to the end. For example,
+   * the default name for a factory for {@code MyType} will be {@code MyTypeFactory}.
+   *
+   * <p>If the annotated type is nested, then the generated factory's name will start with the
+   * enclosing type names, separated by underscores. For example, the default name for a factory for
+   * {@code Outer.Inner.ReallyInner} is {@code Outer_Inner_ReallyInnerFactory}. If {@code className}
+   * is {@code Foo}, then the factory name is {@code Outer_Inner_Foo}.
+   */
+  String className() default "";
+
+  /**
+   * A list of interfaces that the generated factory is required to implement.
+   */
+  Class<?>[] implementing() default { };
+
+  /**
+   * The type that the generated factory is require to extend.
+   */
+  Class<?> extending() default Object.class;
+
+  /**
+   * Whether or not the generated factory should be final.
+   * Defaults to disallowing subclasses (generating the factory as final).
+   */
+  boolean allowSubclasses() default false;
+}
diff --git a/factory/src/main/java/com/google/auto/factory/Provided.java b/factory/src/main/java/com/google/auto/factory/Provided.java
new file mode 100644
index 0000000..e81e4aa
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/Provided.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 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.factory;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Target;
+
+/**
+ * An annotation to be applied to parameters that should be provided by an
+ * {@linkplain javax.inject.Inject injected} {@link javax.inject.Provider} in a generated factory.
+ *
+ * @author Gregory Kick
+ */
+@Target(PARAMETER)
+public @interface Provided { }
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java b/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java
new file mode 100644
index 0000000..b767c47
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.SimpleAnnotationValueVisitor6;
+import javax.lang.model.util.SimpleTypeVisitor6;
+
+final class AnnotationValues {
+  private AnnotationValues() {}
+
+  static boolean asBoolean(AnnotationValue value) {
+    return value.accept(
+        new SimpleAnnotationValueVisitor6<Boolean, Void>() {
+          @Override protected Boolean defaultAction(Object o, Void p) {
+            throw new IllegalArgumentException();
+          }
+
+          @Override public Boolean visitBoolean(boolean b, Void p) {
+            return b;
+          }
+        }, null);
+  }
+
+  static TypeElement asType(AnnotationValue value) {
+    return value.accept(
+        new SimpleAnnotationValueVisitor6<TypeElement, Void>() {
+          @Override protected TypeElement defaultAction(Object o, Void p) {
+            throw new IllegalArgumentException();
+          }
+
+          @Override public TypeElement visitType(TypeMirror t, Void p) {
+            return t.accept(
+                new SimpleTypeVisitor6<TypeElement, Void>() {
+                  @Override
+                  protected TypeElement defaultAction(TypeMirror e, Void p) {
+                    throw new AssertionError();
+                  }
+
+                  @Override
+                  public TypeElement visitDeclared(DeclaredType t, Void p) {
+                    return Iterables.getOnlyElement(ElementFilter.typesIn(
+                        ImmutableList.of(t.asElement())));
+                  }
+                }, null);
+          }
+        }, null);
+  }
+
+  static ImmutableList<? extends AnnotationValue> asList(AnnotationValue value) {
+    return value.accept(
+        new SimpleAnnotationValueVisitor6<ImmutableList<? extends AnnotationValue>, Void>() {
+          @Override
+          protected ImmutableList<? extends AnnotationValue> defaultAction(Object o, Void p) {
+            throw new IllegalArgumentException();
+          }
+
+          @Override
+          public ImmutableList<? extends AnnotationValue> visitArray(
+              List<? extends AnnotationValue> vals, Void p) {
+            return ImmutableList.copyOf(vals);
+          }
+        }, null);
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
new file mode 100644
index 0000000..ad4ccb8
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.auto.common.MoreElements.getPackage;
+import static com.google.auto.factory.processor.Elements2.isValidSupertypeForClass;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static javax.lang.model.element.ElementKind.PACKAGE;
+import static javax.lang.model.util.ElementFilter.typesIn;
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.Messager;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+
+/**
+ * This is a value object that mirrors the static declaration of an {@link AutoFactory} annotation.
+ *
+ * @author Gregory Kick
+ */
+@AutoValue
+abstract class AutoFactoryDeclaration {
+  abstract TypeElement targetType();
+  abstract Element target();
+  abstract Optional<String> className();
+  abstract TypeElement extendingType();
+  abstract ImmutableSet<TypeElement> implementingTypes();
+  abstract boolean allowSubclasses();
+  abstract AnnotationMirror mirror();
+  abstract ImmutableMap<String, AnnotationValue> valuesMap();
+
+  String getFactoryName() {
+    CharSequence packageName = getPackage(targetType()).getQualifiedName();
+    StringBuilder builder = new StringBuilder(packageName);
+    if (packageName.length() > 0) {
+      builder.append('.');
+    }
+    if (className().isPresent()) {
+      builder.append(className().get());
+    } else {
+      for (String enclosingSimpleName : targetEnclosingSimpleNames()) {
+        builder.append(enclosingSimpleName).append('_');
+      }
+      builder.append(targetType().getSimpleName()).append("Factory");
+    }
+    return builder.toString();
+  }
+
+  private ImmutableList<String> targetEnclosingSimpleNames() {
+    ImmutableList.Builder<String> simpleNames = ImmutableList.builder();
+    for (Element element = targetType().getEnclosingElement();
+        !element.getKind().equals(PACKAGE);
+        element = element.getEnclosingElement()) {
+      simpleNames.add(element.getSimpleName().toString());
+    }
+    return simpleNames.build().reverse();
+  }
+
+  static final class Factory {
+    private final Elements elements;
+    private final Messager messager;
+
+    Factory(Elements elements, Messager messager) {
+      this.elements = elements;
+      this.messager = messager;
+    }
+
+    Optional<AutoFactoryDeclaration> createIfValid(Element element) {
+      checkNotNull(element);
+      AnnotationMirror mirror = Mirrors.getAnnotationMirror(element, AutoFactory.class).get();
+      checkArgument(Mirrors.getQualifiedName(mirror.getAnnotationType()).
+          contentEquals(AutoFactory.class.getName()));
+      Map<String, AnnotationValue> values =
+          Mirrors.simplifyAnnotationValueMap(elements.getElementValuesWithDefaults(mirror));
+      checkState(values.size() == 4);
+
+      // className value is a string, so we can just call toString
+      AnnotationValue classNameValue = values.get("className");
+      String className = classNameValue.getValue().toString();
+      if (!className.isEmpty() && !isValidIdentifier(className)) {
+        messager.printMessage(ERROR,
+            String.format("\"%s\" is not a valid Java identifier", className),
+            element, mirror, classNameValue);
+        return Optional.absent();
+      }
+
+      AnnotationValue extendingValue = checkNotNull(values.get("extending"));
+      TypeElement extendingType = AnnotationValues.asType(extendingValue);
+      if (extendingType == null) {
+        messager.printMessage(ERROR, "Unable to find the type: "
+            + extendingValue.getValue().toString(),
+                element, mirror, extendingValue);
+        return Optional.absent();
+      } else if (!isValidSupertypeForClass(extendingType)) {
+        messager.printMessage(ERROR,
+            String.format("%s is not a valid supertype for a factory. "
+                + "Supertypes must be non-final classes.",
+                    extendingType.getQualifiedName()),
+            element, mirror, extendingValue);
+        return Optional.absent();
+      }
+      ImmutableList<ExecutableElement> noParameterConstructors =
+          FluentIterable.from(ElementFilter.constructorsIn(extendingType.getEnclosedElements()))
+              .filter(new Predicate<ExecutableElement>() {
+                @Override public boolean apply(ExecutableElement constructor) {
+                  return constructor.getParameters().isEmpty();
+                }
+              })
+              .toList();
+      if (noParameterConstructors.size() == 0) {
+        messager.printMessage(ERROR,
+            String.format("%s is not a valid supertype for a factory. "
+                + "Factory supertypes must have a no-arg constructor.",
+                    extendingType.getQualifiedName()),
+            element, mirror, extendingValue);
+        return Optional.absent();
+      } else if (noParameterConstructors.size() > 1) {
+        throw new IllegalStateException("Multiple constructors with no parameters??");
+      }
+
+      AnnotationValue implementingValue = checkNotNull(values.get("implementing"));
+      ImmutableSet.Builder<TypeElement> builder = ImmutableSet.builder();
+      for (AnnotationValue implementingTypeValue : AnnotationValues.asList(implementingValue)) {
+        builder.add(AnnotationValues.asType(implementingTypeValue));
+      }
+      ImmutableSet<TypeElement> implementingTypes = builder.build();
+
+      AnnotationValue allowSubclassesValue = checkNotNull(values.get("allowSubclasses"));
+      boolean allowSubclasses = AnnotationValues.asBoolean(allowSubclassesValue);
+
+      return Optional.<AutoFactoryDeclaration>of(
+          new AutoValue_AutoFactoryDeclaration(
+              getAnnotatedType(element),
+              element,
+              className.isEmpty() ? Optional.<String>absent() : Optional.of(className),
+              extendingType,
+              implementingTypes,
+              allowSubclasses,
+              mirror,
+              ImmutableMap.copyOf(values)));
+    }
+
+    private static TypeElement getAnnotatedType(Element element) {
+      List<TypeElement> types = ImmutableList.of();
+      while (types.isEmpty()) {
+        types = typesIn(Arrays.asList(element));
+        element = element.getEnclosingElement();
+      }
+      return getOnlyElement(types);
+    }
+
+    static boolean isValidIdentifier(String identifier) {
+      return SourceVersion.isIdentifier(identifier) && !SourceVersion.isKeyword(identifier);
+    }
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
new file mode 100644
index 0000000..cf3d5eb
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import com.google.auto.service.AutoService;
+import com.google.common.base.Optional;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Iterables;
+import com.google.googlejavaformat.java.filer.FormattingFiler;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic.Kind;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+
+/**
+ * The annotation processor that generates factories for {@link AutoFactory} annotations.
+ *
+ * @author Gregory Kick
+ */
+@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
+@AutoService(Processor.class)
+public final class AutoFactoryProcessor extends AbstractProcessor {
+  private FactoryDescriptorGenerator factoryDescriptorGenerator;
+  private AutoFactoryDeclaration.Factory declarationFactory;
+  private ProvidedChecker providedChecker;
+  private Messager messager;
+  private Elements elements;
+  private Types types;
+  private FactoryWriter factoryWriter;
+
+  @Override
+  public synchronized void init(ProcessingEnvironment processingEnv) {
+    super.init(processingEnv);
+    elements = processingEnv.getElementUtils();
+    types = processingEnv.getTypeUtils();
+    messager = processingEnv.getMessager();
+    factoryWriter =
+        new FactoryWriter(
+            new FormattingFiler(processingEnv.getFiler()),
+            elements,
+            processingEnv.getSourceVersion());
+    providedChecker = new ProvidedChecker(messager);
+    declarationFactory = new AutoFactoryDeclaration.Factory(elements, messager);
+    factoryDescriptorGenerator =
+        new FactoryDescriptorGenerator(messager, types, declarationFactory);
+  }
+
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    try {
+      doProcess(roundEnv);
+    } catch (Throwable e) {
+      messager.printMessage(Kind.ERROR, "Failed to process @AutoFactory annotations:\n"
+          + Throwables.getStackTraceAsString(e));
+    }
+    return false;
+  }
+
+  private void doProcess(RoundEnvironment roundEnv) {
+    for (Element element : roundEnv.getElementsAnnotatedWith(Provided.class)) {
+      providedChecker.checkProvidedParameter(element);
+    }
+
+    ImmutableListMultimap.Builder<String, FactoryMethodDescriptor> indexedMethods =
+        ImmutableListMultimap.builder();
+    ImmutableSetMultimap.Builder<String, ImplementationMethodDescriptor>
+        implementationMethodDescriptorsBuilder = ImmutableSetMultimap.builder();
+    for (Element element : roundEnv.getElementsAnnotatedWith(AutoFactory.class)) {
+      Optional<AutoFactoryDeclaration> declaration = declarationFactory.createIfValid(element);
+      if (declaration.isPresent()) {
+        String factoryName = declaration.get().getFactoryName();
+        TypeElement extendingType = declaration.get().extendingType();
+        implementationMethodDescriptorsBuilder.putAll(
+            factoryName, implementationMethods(extendingType, element));
+        for (TypeElement implementingType : declaration.get().implementingTypes()) {
+          implementationMethodDescriptorsBuilder.putAll(
+              factoryName, implementationMethods(implementingType, element));
+        }
+      }
+
+      ImmutableSet<FactoryMethodDescriptor> descriptors =
+          factoryDescriptorGenerator.generateDescriptor(element);
+      for (FactoryMethodDescriptor descriptor : descriptors) {
+        indexedMethods.put(descriptor.factoryName(), descriptor);
+      }
+    }
+
+    ImmutableSetMultimap<String, ImplementationMethodDescriptor>
+        implementationMethodDescriptors = implementationMethodDescriptorsBuilder.build();
+
+    for (Entry<String, Collection<FactoryMethodDescriptor>> entry
+        : indexedMethods.build().asMap().entrySet()) {
+      ImmutableSet.Builder<TypeMirror> extending = ImmutableSet.builder();
+      ImmutableSortedSet.Builder<TypeMirror> implementing =
+          ImmutableSortedSet.orderedBy(
+              new Comparator<TypeMirror>() {
+                @Override
+                public int compare(TypeMirror first, TypeMirror second) {
+                  String firstName = MoreTypes.asTypeElement(first).getQualifiedName().toString();
+                  String secondName = MoreTypes.asTypeElement(second).getQualifiedName().toString();
+                  return firstName.compareTo(secondName);
+                }
+              });
+      boolean publicType = false;
+      Boolean allowSubclasses = null;
+      boolean skipCreation = false;
+      for (FactoryMethodDescriptor methodDescriptor : entry.getValue()) {
+        extending.add(methodDescriptor.declaration().extendingType().asType());
+        for (TypeElement implementingType : methodDescriptor.declaration().implementingTypes()) {
+          implementing.add(implementingType.asType());
+        }
+        publicType |= methodDescriptor.publicMethod();
+        if (allowSubclasses == null) {
+          allowSubclasses = methodDescriptor.declaration().allowSubclasses();
+        } else if (!allowSubclasses.equals(methodDescriptor.declaration().allowSubclasses())) {
+          skipCreation = true;
+          messager.printMessage(Kind.ERROR,
+              "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.",
+              methodDescriptor.declaration().target(),
+              methodDescriptor.declaration().mirror(),
+              methodDescriptor.declaration().valuesMap().get("allowSubclasses"));
+        }
+      }
+      if (!skipCreation) {
+        try {
+          factoryWriter.writeFactory(
+              FactoryDescriptor.create(
+                  entry.getKey(),
+                  Iterables.getOnlyElement(extending.build()),
+                  implementing.build(),
+                  publicType,
+                  ImmutableSet.copyOf(entry.getValue()),
+                  implementationMethodDescriptors.get(entry.getKey()),
+                  allowSubclasses));
+        } catch (IOException e) {
+          messager.printMessage(Kind.ERROR, "failed: " + e);
+        }
+      }
+    }
+  }
+
+  private ImmutableSet<ImplementationMethodDescriptor> implementationMethods(
+      TypeElement supertype, Element autoFactoryElement) {
+    ImmutableSet.Builder<ImplementationMethodDescriptor> implementationMethodsBuilder =
+        ImmutableSet.builder();
+    for (ExecutableElement implementationMethod :
+        ElementFilter.methodsIn(elements.getAllMembers(supertype))) {
+      if (implementationMethod.getModifiers().contains(Modifier.ABSTRACT)) {
+        ExecutableType methodType =
+            Elements2.getExecutableElementAsMemberOf(
+                types, implementationMethod, supertype);
+        ImmutableSet<Parameter> passedParameters =
+            Parameter.forParameterList(
+                implementationMethod.getParameters(), methodType.getParameterTypes(), types);
+        implementationMethodsBuilder.add(
+            ImplementationMethodDescriptor.builder()
+                .name(implementationMethod.getSimpleName().toString())
+                .returnType(getAnnotatedType(autoFactoryElement))
+                .publicMethod()
+                .passedParameters(passedParameters)
+                .isVarArgs(implementationMethod.isVarArgs())
+                .build());
+      }
+    }
+    return implementationMethodsBuilder.build();
+  }
+
+  private TypeMirror getAnnotatedType(Element element) {
+    List<TypeElement> types = ImmutableList.of();
+    while (types.isEmpty()) {
+      types = ElementFilter.typesIn(Arrays.asList(element));
+      element = element.getEnclosingElement();
+    }
+    return Iterables.getOnlyElement(types).asType();
+  }
+
+  @Override
+  public Set<String> getSupportedAnnotationTypes() {
+    return ImmutableSet.of(AutoFactory.class.getName(), Provided.class.getName());
+  }
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latestSupported();
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Elements2.java b/factory/src/main/java/com/google/auto/factory/processor/Elements2.java
new file mode 100644
index 0000000..30230f7
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/Elements2.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static javax.lang.model.element.ElementKind.CLASS;
+import static javax.lang.model.element.ElementKind.PACKAGE;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.STATIC;
+
+import com.google.common.collect.ImmutableSet;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Types;
+
+final class Elements2 {
+  private Elements2() { }
+
+  static ImmutableSet<ExecutableElement> getConstructors(TypeElement type) {
+    checkNotNull(type);
+    checkArgument(type.getKind() == CLASS);
+    return ImmutableSet.copyOf(ElementFilter.constructorsIn(type.getEnclosedElements()));
+  }
+
+  static boolean isValidSupertypeForClass(TypeElement type) {
+    if (!type.getKind().equals(CLASS)) {
+      return false;
+    }
+    if (type.getModifiers().contains(FINAL)) {
+      return false;
+    }
+    if (!type.getEnclosingElement().getKind().equals(PACKAGE)
+        && !type.getModifiers().contains(STATIC)) {
+      return false;
+    }
+    if (type.getSimpleName().length() == 0) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Given an executable element in a supertype, returns its ExecutableType when it is viewed as a
+   * member of a subtype.
+   */
+  static ExecutableType getExecutableElementAsMemberOf(
+      Types types, ExecutableElement executableElement, TypeElement subTypeElement) {
+    checkNotNull(types);
+    checkNotNull(executableElement);
+    checkNotNull(subTypeElement);
+    TypeMirror subTypeMirror = subTypeElement.asType();
+    if (!subTypeMirror.getKind().equals(TypeKind.DECLARED)) {
+      throw new IllegalStateException(
+          "Expected subTypeElement.asType() to return a class/interface type.");
+    }
+    TypeMirror subExecutableTypeMirror = types.asMemberOf(
+        (DeclaredType) subTypeMirror, executableElement);
+    if (!subExecutableTypeMirror.getKind().equals(TypeKind.EXECUTABLE)) {
+      throw new IllegalStateException("Expected subExecutableTypeMirror to be an executable type.");
+    }
+    return (ExecutableType) subExecutableTypeMirror;
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java
new file mode 100644
index 0000000..68ae678
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A value object representing a factory to be generated.
+ *
+ * @author Gregory Kick
+ */
+@AutoValue
+abstract class FactoryDescriptor {
+  private static final CharMatcher invalidIdentifierCharacters =
+      new CharMatcher() {
+        @Override
+        public boolean matches(char c) {
+          return !Character.isJavaIdentifierPart(c);
+        }
+      };
+
+  abstract String name();
+  abstract TypeMirror extendingType();
+  abstract ImmutableSet<TypeMirror> implementingTypes();
+  abstract boolean publicType();
+  abstract ImmutableSet<FactoryMethodDescriptor> methodDescriptors();
+  abstract ImmutableSet<ImplementationMethodDescriptor> implementationMethodDescriptors();
+  abstract boolean allowSubclasses();
+  abstract ImmutableMap<Key, ProviderField> providers();
+
+  final AutoFactoryDeclaration declaration() {
+    return Iterables.getFirst(methodDescriptors(), null).declaration();
+  }
+
+  private static class UniqueNameSet {
+    private final Set<String> uniqueNames = new HashSet<String>();
+
+    /**
+     * Generates a unique name using {@code base}. If {@code base} has not yet been added, it will
+     * be returned as-is. If your {@code base} is healthy, this will always return {@code base}.
+     */
+    String getUniqueName(CharSequence base) {
+      String name = base.toString();
+      for (int differentiator = 2; !uniqueNames.add(name); differentiator++) {
+        name = base.toString() + differentiator;
+      }
+      return name;
+    }
+  }
+
+  static FactoryDescriptor create(
+      String name,
+      TypeMirror extendingType,
+      ImmutableSet<TypeMirror> implementingTypes,
+      boolean publicType,
+      ImmutableSet<FactoryMethodDescriptor> methodDescriptors,
+      ImmutableSet<ImplementationMethodDescriptor> implementationMethodDescriptors,
+      boolean allowSubclasses) {
+    ImmutableSetMultimap.Builder<Key, Parameter> parametersForProviders =
+        ImmutableSetMultimap.builder();
+    for (FactoryMethodDescriptor descriptor : methodDescriptors) {
+      for (Parameter parameter : descriptor.providedParameters()) {
+        parametersForProviders.put(parameter.key(), parameter);
+      }
+    }
+    ImmutableMap.Builder<Key, ProviderField> providersBuilder = ImmutableMap.builder();
+    UniqueNameSet uniqueNames = new UniqueNameSet();
+    for (Entry<Key, Collection<Parameter>> entry :
+        parametersForProviders.build().asMap().entrySet()) {
+      Key key = entry.getKey();
+      switch (entry.getValue().size()) {
+        case 0:
+          throw new AssertionError();
+        case 1:
+          Parameter parameter = Iterables.getOnlyElement(entry.getValue());
+          providersBuilder.put(
+              key,
+              ProviderField.create(
+                  uniqueNames.getUniqueName(parameter.name() + "Provider"),
+                  key,
+                  parameter.nullable()));
+          break;
+        default:
+          String providerName =
+              uniqueNames.getUniqueName(
+                  invalidIdentifierCharacters.replaceFrom(key.toString(), '_') + "Provider");
+          Optional<AnnotationMirror> nullable = Optional.absent();
+          for (Parameter param : entry.getValue()) {
+            nullable = nullable.or(param.nullable());
+          }
+          providersBuilder.put(key, ProviderField.create(providerName, key, nullable));
+          break;
+      }
+    }
+
+    ImmutableBiMap<FactoryMethodDescriptor, ImplementationMethodDescriptor>
+        duplicateMethodDescriptors =
+            createDuplicateMethodDescriptorsBiMap(
+                methodDescriptors, implementationMethodDescriptors);
+
+    ImmutableSet<FactoryMethodDescriptor> deduplicatedMethodDescriptors =
+        getDeduplicatedMethodDescriptors(methodDescriptors, duplicateMethodDescriptors);
+
+    ImmutableSet<ImplementationMethodDescriptor> deduplicatedImplementationMethodDescriptors =
+        ImmutableSet.copyOf(
+            Sets.difference(implementationMethodDescriptors, duplicateMethodDescriptors.values()));
+
+    return new AutoValue_FactoryDescriptor(
+        name,
+        extendingType,
+        implementingTypes,
+        publicType,
+        deduplicatedMethodDescriptors,
+        deduplicatedImplementationMethodDescriptors,
+        allowSubclasses,
+        providersBuilder.build());
+  }
+
+  /**
+   * Creates a bi-map of duplicate {@link ImplementationMethodDescriptor}s by their respective
+   * {@link FactoryMethodDescriptor}.
+   */
+  private static ImmutableBiMap<FactoryMethodDescriptor, ImplementationMethodDescriptor>
+      createDuplicateMethodDescriptorsBiMap(
+          ImmutableSet<FactoryMethodDescriptor> factoryMethodDescriptors,
+          ImmutableSet<ImplementationMethodDescriptor> implementationMethodDescriptors) {
+
+    ImmutableBiMap.Builder<FactoryMethodDescriptor, ImplementationMethodDescriptor> builder =
+        ImmutableBiMap.builder();
+
+    for (FactoryMethodDescriptor factoryMethodDescriptor : factoryMethodDescriptors) {
+      for (ImplementationMethodDescriptor implementationMethodDescriptor :
+          implementationMethodDescriptors) {
+
+        boolean areDuplicateMethodDescriptors =
+            areDuplicateMethodDescriptors(factoryMethodDescriptor, implementationMethodDescriptor);
+        if (areDuplicateMethodDescriptors) {
+          builder.put(factoryMethodDescriptor, implementationMethodDescriptor);
+          break;
+        }
+      }
+    }
+
+    return builder.build();
+  }
+
+  /**
+   * Returns a set of deduplicated {@link FactoryMethodDescriptor}s from the set of original
+   * descriptors and the bi-map of duplicate descriptors.
+   *
+   * <p>Modifies the duplicate {@link FactoryMethodDescriptor}s such that they are overriding and
+   * reflect properties from the {@link ImplementationMethodDescriptor} they are implementing.
+   */
+  private static ImmutableSet<FactoryMethodDescriptor> getDeduplicatedMethodDescriptors(
+      ImmutableSet<FactoryMethodDescriptor> methodDescriptors,
+      ImmutableBiMap<FactoryMethodDescriptor, ImplementationMethodDescriptor>
+          duplicateMethodDescriptors) {
+
+    ImmutableSet.Builder<FactoryMethodDescriptor> deduplicatedMethodDescriptors =
+        ImmutableSet.builder();
+
+    for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) {
+      ImplementationMethodDescriptor duplicateMethodDescriptor =
+          duplicateMethodDescriptors.get(methodDescriptor);
+
+      FactoryMethodDescriptor newMethodDescriptor =
+         (duplicateMethodDescriptor != null)
+              ? methodDescriptor
+                  .toBuilder()
+                  .overridingMethod(true)
+                  .publicMethod(duplicateMethodDescriptor.publicMethod())
+                  .returnType(duplicateMethodDescriptor.returnType())
+                  .build()
+              : methodDescriptor;
+      deduplicatedMethodDescriptors.add(newMethodDescriptor);
+    }
+
+    return deduplicatedMethodDescriptors.build();
+  }
+
+  /**
+   * Returns true if the given {@link FactoryMethodDescriptor} and
+   * {@link ImplementationMethodDescriptor} are duplicates.
+   *
+   * <p>Descriptors are duplicates if they have the same name and if they have the same passed types
+   * in the same order.
+   */
+  private static boolean areDuplicateMethodDescriptors(
+      FactoryMethodDescriptor factory,
+      ImplementationMethodDescriptor implementation) {
+
+    if (!factory.name().equals(implementation.name())) {
+      return false;
+    }
+
+    // Descriptors are identical if they have the same passed types in the same order.
+    return Iterables.elementsEqual(
+        Iterables.transform(factory.passedParameters(), Parameter::type),
+        Iterables.transform(implementation.passedParameters(), Parameter::type));
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java
new file mode 100644
index 0000000..d2331f4
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimaps;
+import javax.annotation.processing.Messager;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.ElementKindVisitor6;
+import javax.lang.model.util.Types;
+
+/**
+ * A service that traverses an element and returns the set of factory methods defined therein.
+ *
+ * @author Gregory Kick
+ */
+final class FactoryDescriptorGenerator {
+  private final Messager messager;
+  private final Types types;
+  private final AutoFactoryDeclaration.Factory declarationFactory;
+
+  FactoryDescriptorGenerator(
+      Messager messager,
+      Types types,
+      AutoFactoryDeclaration.Factory declarationFactory) {
+    this.messager = messager;
+    this.types = types;
+    this.declarationFactory = declarationFactory;
+  }
+
+  ImmutableSet<FactoryMethodDescriptor> generateDescriptor(Element element) {
+    final AnnotationMirror mirror = Mirrors.getAnnotationMirror(element, AutoFactory.class).get();
+    final Optional<AutoFactoryDeclaration> declaration = declarationFactory.createIfValid(element);
+    if (!declaration.isPresent()) {
+      return ImmutableSet.of();
+    }
+    return element.accept(new ElementKindVisitor6<ImmutableSet<FactoryMethodDescriptor>, Void>() {
+      @Override
+      protected ImmutableSet<FactoryMethodDescriptor> defaultAction(Element e, Void p) {
+        throw new AssertionError("@AutoFactory applied to an impossible element");
+      }
+
+      @Override
+      public ImmutableSet<FactoryMethodDescriptor> visitTypeAsClass(TypeElement type, Void p) {
+        if (type.getModifiers().contains(ABSTRACT)) {
+          // applied to an abstract factory
+          messager.printMessage(ERROR,
+              "Auto-factory doesn't support being applied to abstract classes.", type, mirror);
+          return ImmutableSet.of();
+        } else {
+          // applied to the type to be created
+          ImmutableSet<ExecutableElement> constructors = Elements2.getConstructors(type);
+          if (constructors.isEmpty()) {
+            return generateDescriptorForDefaultConstructor(declaration.get(), type);
+          } else {
+            return FluentIterable.from(constructors)
+                .transform(new Function<ExecutableElement, FactoryMethodDescriptor>() {
+                  @Override public FactoryMethodDescriptor apply(ExecutableElement constructor) {
+                    return generateDescriptorForConstructor(declaration.get(), constructor);
+                  }
+                })
+                .toSet();
+          }
+        }
+      }
+
+      @Override
+      public ImmutableSet<FactoryMethodDescriptor> visitTypeAsInterface(TypeElement type, Void p) {
+        // applied to the factory interface
+        messager.printMessage(ERROR,
+            "Auto-factory doesn't support being applied to interfaces.", type, mirror);
+        return ImmutableSet.of();
+      }
+
+      @Override
+      public ImmutableSet<FactoryMethodDescriptor> visitExecutableAsConstructor(ExecutableElement e,
+          Void p) {
+        // applied to a constructor of a type to be created
+        return ImmutableSet.of(generateDescriptorForConstructor(declaration.get(), e));
+      }
+    }, null);
+  }
+
+  FactoryMethodDescriptor generateDescriptorForConstructor(final AutoFactoryDeclaration declaration,
+      ExecutableElement constructor) {
+    checkNotNull(constructor);
+    checkArgument(constructor.getKind() == ElementKind.CONSTRUCTOR);
+    TypeElement classElement = MoreElements.asType(constructor.getEnclosingElement());
+    ImmutableListMultimap<Boolean, ? extends VariableElement> parameterMap =
+        Multimaps.index(constructor.getParameters(), Functions.forPredicate(
+            new Predicate<VariableElement>() {
+              @Override
+              public boolean apply(VariableElement parameter) {
+                return isAnnotationPresent(parameter, Provided.class);
+              }
+            }));
+    ImmutableSet<Parameter> providedParameters =
+        Parameter.forParameterList(parameterMap.get(true), types);
+    ImmutableSet<Parameter> passedParameters =
+        Parameter.forParameterList(parameterMap.get(false), types);
+    return FactoryMethodDescriptor.builder(declaration)
+        .name("create")
+        .returnType(classElement.asType())
+        .publicMethod(classElement.getModifiers().contains(PUBLIC))
+        .providedParameters(providedParameters)
+        .passedParameters(passedParameters)
+        .creationParameters(Parameter.forParameterList(constructor.getParameters(), types))
+        .isVarArgs(constructor.isVarArgs())
+        .build();
+  }
+
+  private ImmutableSet<FactoryMethodDescriptor> generateDescriptorForDefaultConstructor(
+      AutoFactoryDeclaration declaration, TypeElement type) {
+    return ImmutableSet.of(
+        FactoryMethodDescriptor.builder(declaration)
+            .name("create")
+            .returnType(type.asType())
+            .publicMethod(type.getModifiers().contains(PUBLIC))
+            .passedParameters(ImmutableSet.<Parameter>of())
+            .creationParameters(ImmutableSet.<Parameter>of())
+            .providedParameters(ImmutableSet.<Parameter>of())
+            .build());
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
new file mode 100644
index 0000000..c3a0159
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A value object representing a factory method to be generated.
+ *
+ * @author Gregory Kick
+ */
+@AutoValue
+abstract class FactoryMethodDescriptor {
+  abstract AutoFactoryDeclaration declaration();
+  abstract String name();
+  abstract TypeMirror returnType();
+  abstract boolean publicMethod();
+  abstract boolean overridingMethod();
+  abstract ImmutableSet<Parameter> passedParameters();
+  abstract ImmutableSet<Parameter> providedParameters();
+  abstract ImmutableSet<Parameter> creationParameters();
+  abstract Builder toBuilder();
+  abstract boolean isVarArgs();
+
+  final String factoryName() {
+    return declaration().getFactoryName();
+  }
+
+  static Builder builder(AutoFactoryDeclaration declaration) {
+    return new AutoValue_FactoryMethodDescriptor.Builder()
+        .declaration(checkNotNull(declaration))
+        .publicMethod(false)
+        .overridingMethod(false)
+        .isVarArgs(false);
+  }
+
+  @AutoValue.Builder
+  static abstract class Builder {
+    abstract Builder declaration(AutoFactoryDeclaration declaration);
+    abstract Builder name(String name);
+    abstract Builder returnType(TypeMirror returnType);
+    abstract Builder publicMethod(boolean publicMethod);
+    abstract Builder overridingMethod(boolean overridingMethod);
+    abstract Builder passedParameters(Iterable<Parameter> passedParameters);
+    abstract Builder providedParameters(Iterable<Parameter> providedParameters);
+    abstract Builder creationParameters(Iterable<Parameter> creationParameters);
+    abstract Builder isVarArgs(boolean isVarargs);
+    abstract FactoryMethodDescriptor buildImpl();
+
+    FactoryMethodDescriptor build() {
+      FactoryMethodDescriptor descriptor = buildImpl();
+      checkState(descriptor.creationParameters().equals(
+          Sets.union(descriptor.passedParameters(), descriptor.providedParameters())));
+      return descriptor;
+    }
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
new file mode 100644
index 0000000..3bb68e1
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec;
+import static com.squareup.javapoet.MethodSpec.constructorBuilder;
+import static com.squareup.javapoet.MethodSpec.methodBuilder;
+import static com.squareup.javapoet.TypeSpec.classBuilder;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.element.Modifier.STATIC;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.TypeVariableName;
+import java.io.IOException;
+import java.util.Iterator;
+import javax.annotation.processing.Filer;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Elements;
+
+final class FactoryWriter {
+
+  private final Filer filer;
+  private final Elements elements;
+  private final SourceVersion sourceVersion;
+
+  FactoryWriter(Filer filer, Elements elements, SourceVersion sourceVersion) {
+    this.filer = filer;
+    this.elements = elements;
+    this.sourceVersion = sourceVersion;
+  }
+
+  private static final Joiner ARGUMENT_JOINER = Joiner.on(", ");
+
+  void writeFactory(final FactoryDescriptor descriptor)
+      throws IOException {
+    String factoryName = getSimpleName(descriptor.name()).toString();
+    TypeSpec.Builder factory =
+        classBuilder(factoryName)
+            .addOriginatingElement(descriptor.declaration().targetType());
+    generatedAnnotationSpec(
+            elements,
+            sourceVersion,
+            AutoFactoryProcessor.class,
+            "https://github.com/google/auto/tree/master/factory")
+        .ifPresent(factory::addAnnotation);
+    if (!descriptor.allowSubclasses()) {
+      factory.addModifiers(FINAL);
+    }
+    if (descriptor.publicType()) {
+      factory.addModifiers(PUBLIC);
+    }
+
+    factory.superclass(TypeName.get(descriptor.extendingType()));
+    for (TypeMirror implementingType : descriptor.implementingTypes()) {
+      factory.addSuperinterface(TypeName.get(implementingType));
+    }
+
+    ImmutableSet<TypeVariableName> factoryTypeVariables = getFactoryTypeVariables(descriptor);
+
+    addFactoryTypeParameters(factory, factoryTypeVariables);
+    addConstructorAndProviderFields(factory, descriptor);
+    addFactoryMethods(factory, descriptor, factoryTypeVariables);
+    addImplementationMethods(factory, descriptor);
+    addCheckNotNullMethod(factory, descriptor);
+
+    JavaFile.builder(getPackage(descriptor.name()), factory.build())
+        .skipJavaLangImports(true)
+        .build()
+        .writeTo(filer);
+  }
+
+  private static void addFactoryTypeParameters(
+      TypeSpec.Builder factory, ImmutableSet<TypeVariableName> typeVariableNames) {
+    factory.addTypeVariables(typeVariableNames);
+  }
+
+  private static void addConstructorAndProviderFields(
+      TypeSpec.Builder factory, FactoryDescriptor descriptor) {
+    MethodSpec.Builder constructor = constructorBuilder().addAnnotation(Inject.class);
+    if (descriptor.publicType()) {
+      constructor.addModifiers(PUBLIC);
+    }
+    Iterator<ProviderField> providerFields = descriptor.providers().values().iterator();
+    for (int argumentIndex = 1; providerFields.hasNext(); argumentIndex++) {
+      ProviderField provider = providerFields.next();
+      TypeName typeName = TypeName.get(provider.key().type().get()).box();
+      TypeName providerType = ParameterizedTypeName.get(ClassName.get(Provider.class), typeName);
+      factory.addField(providerType, provider.name(), PRIVATE, FINAL);
+      if (provider.key().qualifier().isPresent()) {
+        // only qualify the constructor parameter
+        providerType = providerType.annotated(AnnotationSpec.get(provider.key().qualifier().get()));
+      }
+      constructor.addParameter(providerType, provider.name());
+      constructor.addStatement("this.$1L = checkNotNull($1L, $2L)", provider.name(), argumentIndex);
+    }
+
+    factory.addMethod(constructor.build());
+  }
+
+  private static void addFactoryMethods(
+      TypeSpec.Builder factory,
+      FactoryDescriptor descriptor,
+      ImmutableSet<TypeVariableName> factoryTypeVariables) {
+    for (FactoryMethodDescriptor methodDescriptor : descriptor.methodDescriptors()) {
+      MethodSpec.Builder method =
+          MethodSpec.methodBuilder(methodDescriptor.name())
+              .addTypeVariables(getMethodTypeVariables(methodDescriptor, factoryTypeVariables))
+              .returns(TypeName.get(methodDescriptor.returnType()))
+              .varargs(methodDescriptor.isVarArgs());
+      if (methodDescriptor.overridingMethod()) {
+        method.addAnnotation(Override.class);
+      }
+      if (methodDescriptor.publicMethod()) {
+        method.addModifiers(PUBLIC);
+      }
+      CodeBlock.Builder args = CodeBlock.builder();
+      method.addParameters(parameters(methodDescriptor.passedParameters()));
+      Iterator<Parameter> parameters = methodDescriptor.creationParameters().iterator();
+      for (int argumentIndex = 1; parameters.hasNext(); argumentIndex++) {
+        Parameter parameter = parameters.next();
+        boolean checkNotNull = !parameter.nullable().isPresent();
+        CodeBlock argument;
+        if (methodDescriptor.passedParameters().contains(parameter)) {
+          argument = CodeBlock.of(parameter.name());
+          if (parameter.isPrimitive()) {
+            checkNotNull = false;
+          }
+        } else {
+          ProviderField provider = descriptor.providers().get(parameter.key());
+          argument = CodeBlock.of(provider.name());
+          if (parameter.isProvider()) {
+            // Providers are checked for nullness in the Factory's constructor.
+            checkNotNull = false;
+          } else {
+            argument = CodeBlock.of("$L.get()", argument);
+          }
+        }
+        if (checkNotNull) {
+          argument = CodeBlock.of("checkNotNull($L, $L)", argument, argumentIndex);
+        }
+        args.add(argument);
+        if (parameters.hasNext()) {
+          args.add(", ");
+        }
+      }
+      method.addStatement("return new $T($L)", methodDescriptor.returnType(), args.build());
+      factory.addMethod(method.build());
+    }
+  }
+
+  private static void addImplementationMethods(
+      TypeSpec.Builder factory, FactoryDescriptor descriptor) {
+    for (ImplementationMethodDescriptor methodDescriptor :
+        descriptor.implementationMethodDescriptors()) {
+      MethodSpec.Builder implementationMethod =
+          methodBuilder(methodDescriptor.name())
+              .addAnnotation(Override.class)
+              .returns(TypeName.get(methodDescriptor.returnType()))
+              .varargs(methodDescriptor.isVarArgs());
+      if (methodDescriptor.publicMethod()) {
+        implementationMethod.addModifiers(PUBLIC);
+      }
+      implementationMethod.addParameters(parameters(methodDescriptor.passedParameters()));
+      implementationMethod.addStatement(
+          "return create($L)",
+          FluentIterable.from(methodDescriptor.passedParameters())
+              .transform(
+                  new Function<Parameter, String>() {
+                    @Override
+                    public String apply(Parameter parameter) {
+                      return parameter.name();
+                    }
+                  })
+              .join(ARGUMENT_JOINER));
+      factory.addMethod(implementationMethod.build());
+    }
+  }
+
+  /**
+   * {@link ParameterSpec}s to match {@code parameters}. Note that the type of the {@link
+   * ParameterSpec}s match {@link Parameter#type()} and not {@link Key#type()}.
+   */
+  private static Iterable<ParameterSpec> parameters(Iterable<Parameter> parameters) {
+    ImmutableList.Builder<ParameterSpec> builder = ImmutableList.builder();
+    for (Parameter parameter : parameters) {
+      ParameterSpec.Builder parameterBuilder =
+          ParameterSpec.builder(TypeName.get(parameter.type().get()), parameter.name());
+      for (AnnotationMirror annotation :
+          Iterables.concat(parameter.nullable().asSet(), parameter.key().qualifier().asSet())) {
+        parameterBuilder.addAnnotation(AnnotationSpec.get(annotation));
+      }
+      builder.add(parameterBuilder.build());
+    }
+    return builder.build();
+  }
+
+  private static void addCheckNotNullMethod(
+      TypeSpec.Builder factory, FactoryDescriptor descriptor) {
+    if (shouldGenerateCheckNotNull(descriptor)) {
+      TypeVariableName typeVariable = TypeVariableName.get("T");
+      factory.addMethod(
+          methodBuilder("checkNotNull")
+              .addModifiers(PRIVATE, STATIC)
+              .addTypeVariable(typeVariable)
+              .returns(typeVariable)
+              .addParameter(typeVariable, "reference")
+              .addParameter(TypeName.INT, "argumentIndex")
+              .beginControlFlow("if (reference == null)")
+              .addStatement(
+                  "throw new $T($S + argumentIndex)",
+                  NullPointerException.class,
+                  "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+                      + "index: ")
+              .endControlFlow()
+              .addStatement("return reference")
+              .build());
+    }
+  }
+
+  private static boolean shouldGenerateCheckNotNull(FactoryDescriptor descriptor) {
+    if (!descriptor.providers().isEmpty()) {
+      return true;
+    }
+    for (FactoryMethodDescriptor method : descriptor.methodDescriptors()) {
+      for (Parameter parameter : method.creationParameters()) {
+        if (!parameter.nullable().isPresent() && !parameter.type().get().getKind().isPrimitive()) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private static CharSequence getSimpleName(CharSequence fullyQualifiedName) {
+    int lastDot = lastIndexOf(fullyQualifiedName, '.');
+    return fullyQualifiedName.subSequence(lastDot + 1, fullyQualifiedName.length());
+  }
+
+  private static String getPackage(CharSequence fullyQualifiedName) {
+    int lastDot = lastIndexOf(fullyQualifiedName, '.');
+    return lastDot == -1 ? "" : fullyQualifiedName.subSequence(0, lastDot).toString();
+  }
+
+  private static int lastIndexOf(CharSequence charSequence, char c) {
+    for (int i = charSequence.length() - 1; i >= 0; i--) {
+      if (charSequence.charAt(i) == c) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  private static ImmutableSet<TypeVariableName> getFactoryTypeVariables(
+      FactoryDescriptor descriptor) {
+    ImmutableSet.Builder<TypeVariableName> typeVariables = ImmutableSet.builder();
+    for (ProviderField provider : descriptor.providers().values()) {
+      typeVariables.addAll(getReferencedTypeParameterNames(provider.key().type().get()));
+    }
+    return typeVariables.build();
+  }
+
+  private static ImmutableSet<TypeVariableName> getMethodTypeVariables(
+      FactoryMethodDescriptor methodDescriptor,
+      ImmutableSet<TypeVariableName> factoryTypeVariables) {
+    ImmutableSet.Builder<TypeVariableName> typeVariables = ImmutableSet.builder();
+    typeVariables.addAll(getReferencedTypeParameterNames(methodDescriptor.returnType()));
+    for (Parameter parameter : methodDescriptor.passedParameters()) {
+      typeVariables.addAll(getReferencedTypeParameterNames(parameter.type().get()));
+    }
+    return Sets.difference(typeVariables.build(), factoryTypeVariables).immutableCopy();
+  }
+
+  private static ImmutableSet<TypeVariableName> getReferencedTypeParameterNames(TypeMirror type) {
+    ImmutableSet.Builder<TypeVariableName> typeVariableNames = ImmutableSet.builder();
+    for (TypeVariable typeVariable : TypeVariables.getReferencedTypeVariables(type)) {
+      typeVariableNames.add(TypeVariableName.get(typeVariable));
+    }
+    return typeVariableNames.build();
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java
new file mode 100644
index 0000000..9ddc249
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import javax.lang.model.type.TypeMirror;
+
+@AutoValue
+abstract class ImplementationMethodDescriptor {
+  abstract String name();
+  abstract TypeMirror returnType();
+  abstract boolean publicMethod();
+  abstract ImmutableSet<Parameter> passedParameters();
+  abstract boolean isVarArgs();
+
+  static Builder builder() {
+    return new AutoValue_ImplementationMethodDescriptor.Builder()
+        .publicMethod(true)
+        .isVarArgs(false);
+  }
+
+  @AutoValue.Builder
+  static abstract class Builder {
+    abstract Builder name(String name);
+    
+    abstract Builder returnType(TypeMirror returnTypeElement);
+
+    abstract Builder publicMethod(boolean publicMethod);
+
+    final Builder publicMethod() {
+      return publicMethod(true);
+    }
+
+    abstract Builder passedParameters(Iterable<Parameter> passedParameters);
+
+    abstract Builder isVarArgs(boolean isVarargs);
+
+    abstract ImplementationMethodDescriptor build();
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Key.java b/factory/src/main/java/com/google/auto/factory/processor/Key.java
new file mode 100644
index 0000000..04bd4f3
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/Key.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.auto.factory.processor.Mirrors.isProvider;
+import static com.google.auto.factory.processor.Mirrors.unwrapOptionalEquivalence;
+import static com.google.auto.factory.processor.Mirrors.wrapOptionalInEquivalence;
+
+import com.google.auto.common.AnnotationMirrors;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
+import javax.inject.Qualifier;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+
+/**
+ * A value object for types and qualifiers.
+ *
+ * @author Gregory Kick
+ */
+@AutoValue
+// TODO(ronshapiro): reuse dagger.model.Key?
+abstract class Key {
+
+  abstract Equivalence.Wrapper<TypeMirror> type();
+
+  abstract Optional<Equivalence.Wrapper<AnnotationMirror>> qualifierWrapper();
+
+  Optional<AnnotationMirror> qualifier() {
+    return unwrapOptionalEquivalence(qualifierWrapper());
+  }
+
+  /**
+   * Constructs a key based on the type {@code type} and any {@link Qualifier}s in {@code
+   * annotations}.
+   *
+   * <p>If {@code type} is a {@code Provider<T>}, the returned {@link Key}'s {@link #type()} is
+   * {@code T}. If {@code type} is a primitive, the returned {@link Key}'s {@link #type()} is the
+   * corresponding {@linkplain Types#boxedClass(PrimitiveType) boxed type}.
+   *
+   * <p>For example:
+   * <table>
+   *   <tr><th>Input type                <th>{@code Key.type()}
+   *   <tr><td>{@code String}            <td>{@code String}
+   *   <tr><td>{@code Provider<String>}  <td>{@code String}
+   *   <tr><td>{@code int}               <td>{@code Integer}
+   * </table>
+   */
+  static Key create(
+      TypeMirror type, Iterable<? extends AnnotationMirror> annotations, Types types) {
+    ImmutableSet.Builder<AnnotationMirror> qualifiers = ImmutableSet.builder();
+    for (AnnotationMirror annotation : annotations) {
+      if (isAnnotationPresent(annotation.getAnnotationType().asElement(), Qualifier.class)) {
+        qualifiers.add(annotation);
+      }
+    }
+
+    // TODO(gak): check for only one qualifier rather than using the first
+    Optional<AnnotationMirror> qualifier = FluentIterable.from(qualifiers.build()).first();
+
+    TypeMirror keyType =
+        isProvider(type)
+            ? MoreTypes.asDeclared(type).getTypeArguments().get(0)
+            : boxedType(type, types);
+    return new AutoValue_Key(
+        MoreTypes.equivalence().wrap(keyType),
+        wrapOptionalInEquivalence(AnnotationMirrors.equivalence(), qualifier));
+  }
+
+  /**
+   * If {@code type} is a primitive type, returns the boxed equivalent; otherwise returns
+   * {@code type}.
+   */
+  private static TypeMirror boxedType(TypeMirror type, Types types) {
+    return type.getKind().isPrimitive()
+        ? types.boxedClass(MoreTypes.asPrimitiveType(type)).asType()
+        : type;
+  }
+
+  @Override
+  public String toString() {
+    String typeQualifiedName = MoreTypes.asTypeElement(type().get()).toString();
+    return qualifier().isPresent()
+        ? qualifier().get() + "/" + typeQualifiedName
+        : typeQualifiedName;
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java
new file mode 100644
index 0000000..c5b7d8b
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import com.google.auto.common.MoreTypes;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.inject.Provider;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleElementVisitor6;
+
+final class Mirrors {
+  private Mirrors() { }
+
+  static Name getQualifiedName(DeclaredType type) {
+    return type.asElement().accept(new SimpleElementVisitor6<Name, Void>() {
+      @Override
+      protected Name defaultAction(Element e, Void p) {
+        throw new AssertionError("DeclaredTypes should be TypeElements");
+      }
+
+      @Override
+      public Name visitType(TypeElement e, Void p) {
+        return e.getQualifiedName();
+      }
+    }, null);
+  }
+
+  /** {@code true} if {@code type} is a {@link Provider}. */
+  static boolean isProvider(TypeMirror type) {
+    return MoreTypes.isType(type) && MoreTypes.isTypeOf(Provider.class, type);
+  }
+
+  /**
+   * Returns an annotation value map  with {@link String} keys instead of {@link ExecutableElement}
+   * instances.
+   */
+  static ImmutableMap<String, AnnotationValue> simplifyAnnotationValueMap(
+      Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValueMap) {
+    ImmutableMap.Builder<String, AnnotationValue> builder = ImmutableMap.builder();
+    for (Entry<? extends ExecutableElement, ? extends AnnotationValue> entry
+        : annotationValueMap.entrySet()) {
+      builder.put(entry.getKey().getSimpleName().toString(), entry.getValue());
+    }
+    return builder.build();
+  }
+
+  /**
+   * Get the {@link AnnotationMirror} for the type {@code annotationType} present on the given
+   * {@link Element} if it exists.
+   */
+  static Optional<AnnotationMirror> getAnnotationMirror(Element element,
+      Class<? extends Annotation> annotationType) {
+    String annotationName = annotationType.getName();
+    for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
+      if (getQualifiedName(annotationMirror.getAnnotationType()).contentEquals(annotationName)) {
+        return Optional.of(annotationMirror);
+      }
+    }
+    return Optional.absent();
+  }
+
+  /**
+   * Wraps an {@link Optional} of a type in an {@code Optional} of a {@link Equivalence.Wrapper} for
+   * that type.
+   */
+  // TODO(ronshapiro): this is used in AutoFactory and Dagger, consider moving it into auto-common.
+  static <T> Optional<Equivalence.Wrapper<T>> wrapOptionalInEquivalence(
+      Equivalence<T> equivalence, Optional<T> optional) {
+    return optional.isPresent()
+        ? Optional.of(equivalence.wrap(optional.get()))
+        : Optional.<Equivalence.Wrapper<T>>absent();
+  }
+
+  /**
+   * Unwraps an {@link Optional} of a {@link Equivalence.Wrapper} into an {@code Optional} of the
+   * underlying type.
+   */
+  static <T> Optional<T> unwrapOptionalEquivalence(
+      Optional<Equivalence.Wrapper<T>> wrappedOptional) {
+    return wrappedOptional.isPresent()
+        ? Optional.of(wrappedOptional.get().get())
+        : Optional.<T>absent();
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Parameter.java b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java
new file mode 100644
index 0000000..781225a
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.auto.factory.processor.Mirrors.unwrapOptionalEquivalence;
+import static com.google.auto.factory.processor.Mirrors.wrapOptionalInEquivalence;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.auto.common.AnnotationMirrors;
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
+import javax.inject.Provider;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+
+/**
+ * Model for a parameter from an {@link com.google.auto.factory.AutoFactory} constructor or
+ * implementation method.
+ */
+@AutoValue
+abstract class Parameter {
+
+  /**
+   * The original type of the parameter, while {@code key().type()} erases the wrapped {@link
+   * Provider}, if any.
+   */
+  abstract Equivalence.Wrapper<TypeMirror> type();
+
+  boolean isProvider() {
+    return Mirrors.isProvider(type().get());
+  }
+
+  boolean isPrimitive() {
+    return type().get().getKind().isPrimitive();
+  }
+
+  /** The name of the parameter. */
+  abstract String name();
+
+  abstract Key key();
+  abstract Optional<Equivalence.Wrapper<AnnotationMirror>> nullableWrapper();
+
+  Optional<AnnotationMirror> nullable() {
+    return unwrapOptionalEquivalence(nullableWrapper());
+  }
+
+  private static Parameter forVariableElement(
+      VariableElement variable, TypeMirror type, Types types) {
+    Optional<AnnotationMirror> nullable = Optional.absent();
+    Iterable<? extends AnnotationMirror> annotations =
+        Iterables.concat(variable.getAnnotationMirrors(), type.getAnnotationMirrors());
+    for (AnnotationMirror annotation : annotations) {
+      if (isNullable(annotation)) {
+        nullable = Optional.of(annotation);
+        break;
+      }
+    }
+
+    Key key = Key.create(type, annotations, types);
+    return new AutoValue_Parameter(
+        MoreTypes.equivalence().wrap(type),
+        variable.getSimpleName().toString(),
+        key,
+        wrapOptionalInEquivalence(AnnotationMirrors.equivalence(), nullable));
+  }
+
+  private static boolean isNullable(AnnotationMirror annotation) {
+    TypeElement annotationType = MoreElements.asType(annotation.getAnnotationType().asElement());
+    return annotationType.getSimpleName().contentEquals("Nullable")
+        || annotationType
+            .getQualifiedName()
+            .toString()
+            // For NullableDecl and NullableType compatibility annotations
+            .startsWith("org.checkerframework.checker.nullness.compatqual.Nullable");
+  }
+
+  static ImmutableSet<Parameter> forParameterList(
+      List<? extends VariableElement> variables,
+      List<? extends TypeMirror> variableTypes,
+      Types types) {
+    checkArgument(variables.size() == variableTypes.size());
+    ImmutableSet.Builder<Parameter> builder = ImmutableSet.builder();
+    Set<String> names = Sets.newHashSetWithExpectedSize(variables.size());
+    for (int i = 0; i < variables.size(); i++) {
+      Parameter parameter = forVariableElement(variables.get(i), variableTypes.get(i), types);
+      checkArgument(names.add(parameter.name()));
+      builder.add(parameter);
+    }
+    ImmutableSet<Parameter> parameters = builder.build();
+    checkArgument(variables.size() == parameters.size());
+    return parameters;
+  }
+
+  static ImmutableSet<Parameter> forParameterList(
+      List<? extends VariableElement> variables, Types types) {
+    List<TypeMirror> variableTypes = Lists.newArrayListWithExpectedSize(variables.size());
+    for (VariableElement var : variables) {
+      variableTypes.add(var.asType());
+    }
+    return forParameterList(variables, variableTypes, types);
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java
new file mode 100644
index 0000000..fe4c1fd
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.common.base.Preconditions.checkArgument;
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import javax.annotation.processing.Messager;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.ElementKindVisitor6;
+
+final class ProvidedChecker {
+  private final Messager messager;
+
+  ProvidedChecker(Messager messager) {
+    this.messager = messager;
+  }
+
+  void checkProvidedParameter(Element element) {
+    checkArgument(isAnnotationPresent(element, Provided.class), "%s not annoated with @Provided",
+        element);
+    element.accept(new ElementKindVisitor6<Void, Void>() {
+      @Override
+      protected Void defaultAction(Element e, Void p) {
+        throw new AssertionError("Provided can only be applied to parameters");
+      }
+
+      @Override
+      public Void visitVariableAsParameter(final VariableElement providedParameter, Void p) {
+        providedParameter.getEnclosingElement().accept(new ElementKindVisitor6<Void, Void>() {
+          @Override
+          protected Void defaultAction(Element e, Void p) {
+            raiseError(providedParameter, "@%s may only be applied to constructor parameters");
+            return null;
+          }
+
+          @Override
+          public Void visitExecutableAsConstructor(ExecutableElement constructor, Void p) {
+            if (!(annotatedWithAutoFactory(constructor)
+                || annotatedWithAutoFactory(constructor.getEnclosingElement()))) {
+              raiseError(providedParameter,
+                  "@%s may only be applied to constructors requesting an auto-factory");
+            }
+            return null;
+          }
+        }, p);
+        return null;
+      }
+    }, null);
+  }
+
+  private void raiseError(VariableElement providedParameter, String messageFormat) {
+    messager.printMessage(ERROR, String.format(messageFormat, Provided.class.getSimpleName()),
+        providedParameter, Mirrors.getAnnotationMirror(providedParameter, Provided.class).get());
+  }
+
+  private static boolean annotatedWithAutoFactory(Element e) {
+    return isAnnotationPresent(e, AutoFactory.class);
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java b/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java
new file mode 100644
index 0000000..855c1fe
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 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.factory.processor;
+
+import static com.google.auto.factory.processor.Mirrors.unwrapOptionalEquivalence;
+import static com.google.auto.factory.processor.Mirrors.wrapOptionalInEquivalence;
+
+import com.google.auto.common.AnnotationMirrors;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Optional;
+import javax.lang.model.element.AnnotationMirror;
+
+@AutoValue
+abstract class ProviderField {
+  abstract String name();
+  abstract Key key();
+  abstract Optional<Equivalence.Wrapper<AnnotationMirror>> nullableWrapper();
+
+  Optional<AnnotationMirror> nullable() {
+    return unwrapOptionalEquivalence(nullableWrapper());
+  }
+
+  static ProviderField create(String name, Key key, Optional<AnnotationMirror> nullable) {
+    return new AutoValue_ProviderField(
+        name, key, wrapOptionalInEquivalence(AnnotationMirrors.equivalence(), nullable));
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java b/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java
new file mode 100644
index 0000000..1f89473
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2019 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.factory.processor;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
+import java.util.Set;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.UnionType;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.SimpleTypeVisitor8;
+
+final class TypeVariables {
+  private TypeVariables() {}
+
+  static ImmutableSet<TypeVariable> getReferencedTypeVariables(TypeMirror type) {
+    checkNotNull(type);
+    return type.accept(ReferencedTypeVariables.INSTANCE, new HashSet<>());
+  }
+
+  private static final class ReferencedTypeVariables extends
+      SimpleTypeVisitor8<ImmutableSet<TypeVariable>, Set<Element>> {
+
+    private static final ReferencedTypeVariables INSTANCE = new ReferencedTypeVariables();
+
+    ReferencedTypeVariables() {
+      super(ImmutableSet.of());
+    }
+
+    @Override
+    public ImmutableSet<TypeVariable> visitArray(ArrayType t, Set<Element> visited) {
+      return t.getComponentType().accept(this, visited);
+    }
+
+    @Override
+    public ImmutableSet<TypeVariable> visitDeclared(
+        DeclaredType t, Set<Element> visited) {
+      if (!visited.add(t.asElement())) {
+        return ImmutableSet.of();
+      }
+      ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder();
+      for (TypeMirror typeArgument : t.getTypeArguments()) {
+        typeVariables.addAll(typeArgument.accept(this, visited));
+      }
+      return typeVariables.build();
+    }
+
+    @Override
+    public ImmutableSet<TypeVariable> visitTypeVariable(
+        TypeVariable t, Set<Element> visited) {
+      if (!visited.add(t.asElement())) {
+        return ImmutableSet.of();
+      }
+      ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder();
+      typeVariables.add(t);
+      typeVariables.addAll(t.getLowerBound().accept(this, visited));
+      typeVariables.addAll(t.getUpperBound().accept(this, visited));
+      return typeVariables.build();
+    }
+
+    @Override
+    public ImmutableSet<TypeVariable> visitUnion(
+        UnionType t, Set<Element> visited) {
+      ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder();
+      for (TypeMirror unionType : t.getAlternatives()) {
+        typeVariables.addAll(unionType.accept(this, visited));
+      }
+      return typeVariables.build();
+    }
+
+    @Override
+    public ImmutableSet<TypeVariable> visitIntersection(
+        IntersectionType t, Set<Element> visited) {
+      ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder();
+      for (TypeMirror intersectionType : t.getBounds()) {
+        typeVariables.addAll(intersectionType.accept(this, visited));
+      }
+      return typeVariables.build();
+    }
+
+    @Override
+    public ImmutableSet<TypeVariable> visitWildcard(
+        WildcardType t, Set<Element> visited) {
+      ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder();
+      TypeMirror extendsBound = t.getExtendsBound();
+      if (extendsBound != null) {
+        typeVariables.addAll(extendsBound.accept(this, visited));
+      }
+      TypeMirror superBound = t.getSuperBound();
+      if (superBound != null) {
+        typeVariables.addAll(superBound.accept(this, visited));
+      }
+      return typeVariables.build();
+    }
+  }
+}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/package-info.java b/factory/src/main/java/com/google/auto/factory/processor/package-info.java
new file mode 100644
index 0000000..7339020
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/processor/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013 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.
+ */
+/**
+ * This package contains the annotation processor that implements the
+ * {@link com.google.auto.factory.AutoFactory} API.
+ */
+package com.google.auto.factory.processor;
\ No newline at end of file
diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java
new file mode 100644
index 0000000..1b23a65
--- /dev/null
+++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.auto.factory.processor.AutoFactoryDeclaration.Factory.isValidIdentifier;
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AutoFactoryDeclarationTest {
+  @Test public void identifiers() {
+    assertThat(isValidIdentifier("String")).isTrue();
+    assertThat(isValidIdentifier("9CantStartWithNumber")).isFalse();
+    assertThat(isValidIdentifier("enum")).isFalse();
+    assertThat(isValidIdentifier("goto")).isFalse();
+    assertThat(isValidIdentifier("InvalidCharacter!")).isFalse();
+  }
+}
diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java
new file mode 100644
index 0000000..3088bb2
--- /dev/null
+++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright 2013 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.factory.processor;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
+import static com.google.testing.compile.JavaSourcesSubject.assertThat;
+import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Resources;
+import com.google.testing.compile.CompilationRule;
+import com.google.testing.compile.JavaFileObjects;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.SourceVersion;
+import javax.tools.JavaFileObject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Functional tests for the {@link AutoFactoryProcessor}.
+ */
+@RunWith(JUnit4.class)
+public class AutoFactoryProcessorTest {
+
+  @Rule public final CompilationRule compilationRule = new CompilationRule();
+
+  @Test public void simpleClass() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/SimpleClass.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/SimpleClassFactory.java"));
+  }
+
+  @Test
+  public void nestedClasses() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/NestedClasses.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(
+            loadExpectedFile("expected/NestedClasses_SimpleNestedClassFactory.java"),
+            loadExpectedFile("expected/NestedClassCustomNamedFactory.java"));
+  }
+
+  @Test public void simpleClassNonFinal() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/SimpleClassNonFinal.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/SimpleClassNonFinalFactory.java"));
+  }
+
+  @Test public void publicClass() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/PublicClass.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/PublicClassFactory.java"));
+  }
+
+  @Test public void simpleClassCustomName() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/SimpleClassCustomName.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/CustomNamedFactory.java"));
+  }
+
+  @Test public void simpleClassMixedDeps() {
+    assertAbout(javaSources())
+        .that(
+            ImmutableSet.of(
+                JavaFileObjects.forResource("good/SimpleClassMixedDeps.java"),
+                JavaFileObjects.forResource("support/AQualifier.java")))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/SimpleClassMixedDepsFactory.java"));
+  }
+
+  @Test public void simpleClassPassedDeps() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/SimpleClassPassedDeps.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/SimpleClassPassedDepsFactory.java"));
+  }
+
+  @Test public void simpleClassProvidedDeps() {
+    assertAbout(javaSources())
+        .that(
+            ImmutableSet.of(
+                JavaFileObjects.forResource("support/AQualifier.java"),
+                JavaFileObjects.forResource("support/BQualifier.java"),
+                JavaFileObjects.forResource("good/SimpleClassProvidedDeps.java")))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/SimpleClassProvidedDepsFactory.java"));
+  }
+
+  @Test
+  public void simpleClassProvidedProviderDeps() {
+    assertAbout(javaSources())
+        .that(
+            ImmutableSet.of(
+                JavaFileObjects.forResource("support/AQualifier.java"),
+                JavaFileObjects.forResource("support/BQualifier.java"),
+                JavaFileObjects.forResource("good/SimpleClassProvidedProviderDeps.java")))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/SimpleClassProvidedProviderDepsFactory.java"));
+  }
+
+  @Test public void constructorAnnotated() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/ConstructorAnnotated.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/ConstructorAnnotatedFactory.java"));
+  }
+
+  @Test public void constructorAnnotatedNonFinal() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/ConstructorAnnotatedNonFinal.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/ConstructorAnnotatedNonFinalFactory.java"));
+  }
+
+  @Test public void simpleClassImplementingMarker() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/SimpleClassImplementingMarker.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/SimpleClassImplementingMarkerFactory.java"));
+  }
+
+  @Test public void simpleClassImplementingSimpleInterface() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/SimpleClassImplementingSimpleInterface.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(
+            loadExpectedFile("expected/SimpleClassImplementingSimpleInterfaceFactory.java"));
+  }
+
+  @Test public void mixedDepsImplementingInterfaces() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/MixedDepsImplementingInterfaces.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/MixedDepsImplementingInterfacesFactory.java"));
+  }
+
+  @Test public void failsWithMixedFinals() {
+    JavaFileObject file = JavaFileObjects.forResource("bad/MixedFinals.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .failsToCompile()
+        .withErrorContaining(
+            "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.")
+            .in(file).onLine(24)
+         .and().withErrorContaining(
+            "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.")
+            .in(file).onLine(27);
+  }
+
+  @Test public void providedButNoAutoFactory() {
+    JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedButNoAutoFactory.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .failsToCompile()
+        .withErrorContaining(
+            "@Provided may only be applied to constructors requesting an auto-factory")
+                .in(file).onLine(21).atColumn(38);
+  }
+
+  @Test public void providedOnMethodParameter() {
+    JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedOnMethodParameter.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .failsToCompile()
+        .withErrorContaining(
+            "@Provided may only be applied to constructor parameters")
+                .in(file).onLine(21).atColumn(23);
+  }
+
+  @Test public void invalidCustomName() {
+    JavaFileObject file = JavaFileObjects.forResource("bad/InvalidCustomName.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .failsToCompile()
+        .withErrorContaining("\"SillyFactory!\" is not a valid Java identifier")
+            .in(file).onLine(20);
+  }
+
+  @Test public void factoryExtendingAbstractClass() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/FactoryExtendingAbstractClass.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/FactoryExtendingAbstractClassFactory.java"));
+  }
+
+  @Test public void factoryExtendingAbstractClass_withConstructorParams() {
+    JavaFileObject file =
+        JavaFileObjects.forResource("good/FactoryExtendingAbstractClassWithConstructorParams.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .failsToCompile()
+        .withErrorContaining(
+            "tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory "
+                + "is not a valid supertype for a factory. "
+                + "Factory supertypes must have a no-arg constructor.")
+                    .in(file).onLine(21);
+  }
+
+  @Test public void factoryExtendingAbstractClass_multipleConstructors() {
+    JavaFileObject file = JavaFileObjects.forResource(
+        "good/FactoryExtendingAbstractClassWithMultipleConstructors.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError();
+  }
+
+  @Test public void factoryExtendingInterface() {
+    JavaFileObject file = JavaFileObjects.forResource("bad/InterfaceSupertype.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .failsToCompile()
+        .withErrorContaining("java.lang.Runnable is not a valid supertype for a factory. "
+            + "Supertypes must be non-final classes.")
+                .in(file).onLine(20);
+  }
+
+  @Test public void factoryExtendingEnum() {
+    JavaFileObject file = JavaFileObjects.forResource("bad/EnumSupertype.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .failsToCompile()
+        .withErrorContaining(
+            "java.util.concurrent.TimeUnit is not a valid supertype for a factory. "
+                + "Supertypes must be non-final classes.")
+                    .in(file).onLine(21);
+  }
+
+  @Test public void factoryExtendingFinalClass() {
+    JavaFileObject file = JavaFileObjects.forResource("bad/FinalSupertype.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .failsToCompile()
+        .withErrorContaining("java.lang.Boolean is not a valid supertype for a factory. "
+            + "Supertypes must be non-final classes.")
+                .in(file).onLine(20);
+  }
+
+  @Test public void factoryImplementingGenericInterfaceExtension() {
+    JavaFileObject file =
+        JavaFileObjects.forResource("good/FactoryImplementingGenericInterfaceExtension.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(
+            loadExpectedFile("expected/FactoryImplementingGenericInterfaceExtensionFactory.java"));
+  }
+
+  @Test public void multipleFactoriesImpementingInterface() {
+    JavaFileObject file =
+        JavaFileObjects.forResource("good/MultipleFactoriesImplementingInterface.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(
+            loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassAFactory.java"),
+            loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassBFactory.java"));
+  }
+
+  @Test public void classUsingQualifierWithArgs() {
+    assertAbout(javaSources())
+        .that(
+            ImmutableSet.of(
+                JavaFileObjects.forResource("support/QualifierWithArgs.java"),
+                JavaFileObjects.forResource("good/ClassUsingQualifierWithArgs.java")))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/ClassUsingQualifierWithArgsFactory.java"));
+  }
+
+  @Test public void factoryImplementingInterfaceWhichRedeclaresCreateMethods() {
+    JavaFileObject file =
+        JavaFileObjects.forResource("good/FactoryImplementingCreateMethod.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(
+            loadExpectedFile("expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java"));
+  }
+
+  @Test public void nullableParams() {
+    assertAbout(javaSources())
+        .that(
+            ImmutableSet.of(
+                JavaFileObjects.forResource("good/SimpleClassNullableParameters.java"),
+                JavaFileObjects.forResource("support/AQualifier.java"),
+                JavaFileObjects.forResource("support/BQualifier.java")))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/SimpleClassNullableParametersFactory.java"));
+  }
+
+  @Test public void customNullableType() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/CustomNullable.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/CustomNullableFactory.java"));
+  }
+
+  @Test public void checkerFrameworkNullableType() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/CheckerFrameworkNullable.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/CheckerFrameworkNullableFactory.java"));
+  }
+
+  @Test public void multipleProvidedParamsWithSameKey() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/MultipleProvidedParamsSameKey.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/MultipleProvidedParamsSameKeyFactory.java"));
+  }
+
+  @Test public void providerArgumentToCreateMethod() {
+    assertAbout(javaSource())
+        .that(JavaFileObjects.forResource("good/ProviderArgumentToCreateMethod.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/ProviderArgumentToCreateMethodFactory.java"));
+  }
+
+  @Test public void multipleFactoriesConflictingParameterNames() {
+    assertThat(
+            JavaFileObjects.forResource("good/MultipleFactoriesConflictingParameterNames.java"),
+            JavaFileObjects.forResource("support/AQualifier.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(
+            loadExpectedFile("expected/MultipleFactoriesConflictingParameterNamesFactory.java"));
+  }
+
+  @Test public void factoryVarargs() {
+    assertThat(JavaFileObjects.forResource("good/SimpleClassVarargs.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/SimpleClassVarargsFactory.java"));
+  }
+
+  @Test public void onlyPrimitives() {
+    assertThat(JavaFileObjects.forResource("good/OnlyPrimitives.java"))
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/OnlyPrimitivesFactory.java"));
+  }
+
+  @Test
+  public void defaultPackage() {
+    JavaFileObject file = JavaFileObjects.forResource("good/DefaultPackage.java");
+    assertAbout(javaSource())
+        .that(file)
+        .processedWith(new AutoFactoryProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(loadExpectedFile("expected/DefaultPackageFactory.java"));
+  }
+
+  private JavaFileObject loadExpectedFile(String resourceName) {
+    try {
+      List<String> sourceLines = Resources.readLines(Resources.getResource(resourceName), UTF_8);
+      if (!isJavaxAnnotationProcessingGeneratedAvailable()) {
+        replaceGeneratedImport(sourceLines);
+      }
+      return JavaFileObjects.forSourceLines(
+          resourceName.replace('/', '.').replace(".java", ""), sourceLines);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  private boolean isJavaxAnnotationProcessingGeneratedAvailable() {
+    return SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0;
+  }
+
+  private static void replaceGeneratedImport(List<String> sourceLines) {
+    int i = 0;
+    int firstImport = Integer.MAX_VALUE;
+    int lastImport = -1;
+    for (String line : sourceLines) {
+      if (line.startsWith("import ") && !line.startsWith("import static ")) {
+        firstImport = Math.min(firstImport, i);
+        lastImport = Math.max(lastImport, i);
+      }
+      i++;
+    }
+    if (lastImport >= 0) {
+      List<String> importLines = sourceLines.subList(firstImport, lastImport + 1);
+      importLines.replaceAll(
+          line ->
+              line.startsWith("import javax.annotation.processing.Generated;")
+                  ? "import javax.annotation.Generated;"
+                  : line);
+      Collections.sort(importLines);
+    }
+  }
+}
diff --git a/factory/src/test/resources/bad/EnumSupertype.java b/factory/src/test/resources/bad/EnumSupertype.java
new file mode 100644
index 0000000..00913b0
--- /dev/null
+++ b/factory/src/test/resources/bad/EnumSupertype.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import java.util.concurrent.TimeUnit;
+
+@AutoFactory(extending = TimeUnit.class)
+final class InterfaceSupertype {}
diff --git a/factory/src/test/resources/bad/FinalSupertype.java b/factory/src/test/resources/bad/FinalSupertype.java
new file mode 100644
index 0000000..4822f11
--- /dev/null
+++ b/factory/src/test/resources/bad/FinalSupertype.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory(extending = Boolean.class)
+final class InterfaceSupertype {}
diff --git a/factory/src/test/resources/bad/InterfaceSupertype.java b/factory/src/test/resources/bad/InterfaceSupertype.java
new file mode 100644
index 0000000..2bdce5e
--- /dev/null
+++ b/factory/src/test/resources/bad/InterfaceSupertype.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory(extending = Runnable.class)
+final class InterfaceSupertype {}
diff --git a/factory/src/test/resources/bad/InvalidCustomName.java b/factory/src/test/resources/bad/InvalidCustomName.java
new file mode 100644
index 0000000..5734ee7
--- /dev/null
+++ b/factory/src/test/resources/bad/InvalidCustomName.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory(className = "SillyFactory!")
+final class InvalidCustomName { }
diff --git a/factory/src/test/resources/bad/MixedFinals.java b/factory/src/test/resources/bad/MixedFinals.java
new file mode 100644
index 0000000..4b9ef20
--- /dev/null
+++ b/factory/src/test/resources/bad/MixedFinals.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+final class MixedFinals {
+  @AutoFactory(allowSubclasses = false)
+  MixedFinals() {}
+
+  @AutoFactory(allowSubclasses = true)
+  MixedFinals(String s) {}
+
+  @AutoFactory(allowSubclasses = true)
+  MixedFinals(String s, Integer i) {}
+}
diff --git a/factory/src/test/resources/bad/ProvidedButNoAutoFactory.java b/factory/src/test/resources/bad/ProvidedButNoAutoFactory.java
new file mode 100644
index 0000000..40269a4
--- /dev/null
+++ b/factory/src/test/resources/bad/ProvidedButNoAutoFactory.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.Provided;
+
+final class ProvidedButNoAutoFactory {
+  ProvidedButNoAutoFactory(Object a, @Provided Object b) {}
+}
diff --git a/factory/src/test/resources/bad/ProvidedOnMethodParameter.java b/factory/src/test/resources/bad/ProvidedOnMethodParameter.java
new file mode 100644
index 0000000..ab5982a
--- /dev/null
+++ b/factory/src/test/resources/bad/ProvidedOnMethodParameter.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.Provided;
+
+final class ProvidedOnMethodParameter {
+  void blah(Object a, @Provided Object b) {}
+}
diff --git a/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java
new file mode 100644
index 0000000..79175c7
--- /dev/null
+++ b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+)
+final class CheckerFrameworkNullableFactory {
+
+  private final Provider<String> java_lang_StringProvider;
+
+  @Inject
+  CheckerFrameworkNullableFactory(
+      Provider<String> java_lang_StringProvider) {
+    this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1);
+  }
+
+  CheckerFrameworkNullable create(
+      @NullableDecl String nullableDecl, @NullableType String nullableType) {
+    return new CheckerFrameworkNullable(
+        nullableDecl,
+        java_lang_StringProvider.get(),
+        nullableType,
+        java_lang_StringProvider.get());
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java
new file mode 100644
index 0000000..b5bd89c
--- /dev/null
+++ b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class ClassUsingQualifierWithArgsFactory {
+  private final Provider<String> providedDepAProvider;
+
+  @Inject ClassUsingQualifierWithArgsFactory(
+      @QualifierWithArgs(name="Fred", count=3) Provider<String> providedDepAProvider) {
+    this.providedDepAProvider = checkNotNull(providedDepAProvider, 1);
+  }
+
+  ClassUsingQualifierWithArgs create() {
+    return new ClassUsingQualifierWithArgs(checkNotNull(providedDepAProvider.get(), 1));
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java
new file mode 100644
index 0000000..6e9d242
--- /dev/null
+++ b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class ConstructorAnnotatedFactory {
+  private final Provider<Object> objProvider;
+
+  @Inject ConstructorAnnotatedFactory(Provider<Object> objProvider) {
+    this.objProvider = checkNotNull(objProvider, 1);
+  }
+
+  ConstructorAnnotated create() {
+    return new ConstructorAnnotated();
+  }
+
+  ConstructorAnnotated create(String s) {
+    return new ConstructorAnnotated(checkNotNull(s, 1));
+  }
+
+  ConstructorAnnotated create(int i) {
+    return new ConstructorAnnotated(checkNotNull(objProvider.get(), 1), i);
+  }
+
+  ConstructorAnnotated create(char c) {
+    return new ConstructorAnnotated(checkNotNull(objProvider.get(), 1), c);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java
new file mode 100644
index 0000000..f662642
--- /dev/null
+++ b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+class ConstructorAnnotatedNonFinalFactory {
+  private final Provider<Object> objProvider;
+
+  @Inject ConstructorAnnotatedNonFinalFactory(Provider<Object> objProvider) {
+    this.objProvider = checkNotNull(objProvider, 1);
+  }
+
+  ConstructorAnnotatedNonFinal create() {
+    return new ConstructorAnnotatedNonFinal();
+  }
+
+  ConstructorAnnotatedNonFinal create(String s) {
+    return new ConstructorAnnotatedNonFinal(checkNotNull(s, 1));
+  }
+
+  ConstructorAnnotatedNonFinal create(int i) {
+    return new ConstructorAnnotatedNonFinal(checkNotNull(objProvider.get(), 1), i);
+  }
+
+  ConstructorAnnotatedNonFinal create(char c) {
+    return new ConstructorAnnotatedNonFinal(checkNotNull(objProvider.get(), 1), c);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/CustomNamedFactory.java b/factory/src/test/resources/expected/CustomNamedFactory.java
new file mode 100644
index 0000000..c388387
--- /dev/null
+++ b/factory/src/test/resources/expected/CustomNamedFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class CustomNamedFactory {
+  @Inject CustomNamedFactory() {}
+
+  SimpleClassCustomName create() {
+    return new SimpleClassCustomName();
+  }
+}
diff --git a/factory/src/test/resources/expected/CustomNullableFactory.java b/factory/src/test/resources/expected/CustomNullableFactory.java
new file mode 100644
index 0000000..31bb5bf
--- /dev/null
+++ b/factory/src/test/resources/expected/CustomNullableFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class CustomNullableFactory {
+
+  private final Provider<Object> objectProvider;
+
+  @Inject
+  CustomNullableFactory(Provider<Object> objectProvider) {
+    this.objectProvider = checkNotNull(objectProvider, 1);
+  }
+
+  CustomNullable create(@CustomNullable.Nullable String string) {
+    return new CustomNullable(string, objectProvider.get());
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/DefaultPackageFactory.java b/factory/src/test/resources/expected/DefaultPackageFactory.java
new file mode 100644
index 0000000..d1d4cc8
--- /dev/null
+++ b/factory/src/test/resources/expected/DefaultPackageFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 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.
+ */
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
+public final class DefaultPackageFactory {
+  @Inject
+  public DefaultPackageFactory() {}
+
+  public DefaultPackage create() {
+    return new DefaultPackage();
+  }
+}
diff --git a/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java
new file mode 100644
index 0000000..c56afb0
--- /dev/null
+++ b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class FactoryExtendingAbstractClassFactory
+    extends FactoryExtendingAbstractClass.AbstractFactory {
+  @Inject FactoryExtendingAbstractClassFactory() {}
+
+  FactoryExtendingAbstractClass create() {
+    return new FactoryExtendingAbstractClass();
+  }
+
+  @Override public FactoryExtendingAbstractClass newInstance() {
+    return create();
+  }
+}
diff --git a/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java
new file mode 100644
index 0000000..f23dc9a
--- /dev/null
+++ b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 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 tests;
+
+import java.util.List;
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class FactoryImplementingCreateMethod_ConcreteClassFactory
+    implements FactoryImplementingCreateMethod.FactoryInterfaceWithCreateMethod {
+
+  @Inject
+  FactoryImplementingCreateMethod_ConcreteClassFactory() {}
+
+  @Override
+  public FactoryImplementingCreateMethod.ConcreteClass create() {
+    return new FactoryImplementingCreateMethod.ConcreteClass();
+  }
+
+  @Override
+  public FactoryImplementingCreateMethod.ConcreteClass create(int aDifferentArgumentName) {
+    return new FactoryImplementingCreateMethod.ConcreteClass(aDifferentArgumentName);
+  }
+
+  @Override
+  public FactoryImplementingCreateMethod.ConcreteClass create(List<Integer> genericWithDifferentArgumentName) {
+    return new FactoryImplementingCreateMethod.ConcreteClass(
+        checkNotNull(genericWithDifferentArgumentName, 1));
+  }
+
+  FactoryImplementingCreateMethod.ConcreteClass create(int a, boolean b) {
+    return new FactoryImplementingCreateMethod.ConcreteClass(a, b);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java
new file mode 100644
index 0000000..3bfc526
--- /dev/null
+++ b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class FactoryImplementingGenericInterfaceExtensionFactory
+    implements FactoryImplementingGenericInterfaceExtension.MyFactory {
+  private final Provider<String> sProvider;
+  @Inject
+  FactoryImplementingGenericInterfaceExtensionFactory(Provider<String> sProvider) {
+    this.sProvider = checkNotNull(sProvider, 1);
+  }
+  FactoryImplementingGenericInterfaceExtension create(Integer i) {
+    return new FactoryImplementingGenericInterfaceExtension(
+        checkNotNull(sProvider.get(), 1), checkNotNull(i, 2));
+  }
+  @Override
+  public FactoryImplementingGenericInterfaceExtension make(Integer arg) {
+    return create(arg);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java
new file mode 100644
index 0000000..19f2c13
--- /dev/null
+++ b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+/**
+ * @author Gregory Kick
+ */
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+)
+final class MixedDepsImplementingInterfacesFactory
+    implements MixedDepsImplementingInterfaces.FromInt, MixedDepsImplementingInterfaces.FromObject,
+        MixedDepsImplementingInterfaces.MarkerA, MixedDepsImplementingInterfaces.MarkerB {
+  private final Provider<String> sProvider;
+
+  @Inject MixedDepsImplementingInterfacesFactory(Provider<String> sProvider) {
+    this.sProvider = checkNotNull(sProvider, 1);
+  }
+
+  MixedDepsImplementingInterfaces create(int i) {
+    return new MixedDepsImplementingInterfaces(checkNotNull(sProvider.get(), 1), i);
+  }
+
+  MixedDepsImplementingInterfaces create(Object o) {
+    return new MixedDepsImplementingInterfaces(checkNotNull(o, 1));
+  }
+
+  @Override public MixedDepsImplementingInterfaces fromInt(int i) {
+    return create(i);
+  }
+
+  @Override public MixedDepsImplementingInterfaces fromObject(Object o) {
+    return create(o);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java
new file mode 100644
index 0000000..fac6e13
--- /dev/null
+++ b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+)
+final class MultipleFactoriesConflictingParameterNamesFactory {
+
+  private final Provider<String> stringProvider;
+  private final Provider<Object> java_lang_ObjectProvider;
+  private final Provider<String> stringProvider2;
+  private final Provider<Object> _tests_AQualifier_java_lang_ObjectProvider;
+
+  @Inject
+  MultipleFactoriesConflictingParameterNamesFactory(
+      Provider<String> stringProvider,
+      Provider<Object> java_lang_ObjectProvider,
+      @AQualifier Provider<String> stringProvider2,
+      @AQualifier Provider<Object> _tests_AQualifier_java_lang_ObjectProvider) {
+    this.stringProvider = checkNotNull(stringProvider, 1);
+    this.java_lang_ObjectProvider = checkNotNull(java_lang_ObjectProvider, 2);
+    this.stringProvider2 = checkNotNull(stringProvider2, 3);
+    this._tests_AQualifier_java_lang_ObjectProvider =
+        checkNotNull(_tests_AQualifier_java_lang_ObjectProvider, 4);
+  }
+
+  MultipleFactoriesConflictingParameterNames create(Object unused) {
+    return new MultipleFactoriesConflictingParameterNames(
+        checkNotNull(stringProvider.get(), 1),
+        checkNotNull(java_lang_ObjectProvider.get(), 2),
+        java_lang_ObjectProvider,
+        checkNotNull(unused, 4));
+  }
+
+  MultipleFactoriesConflictingParameterNames create() {
+    return new MultipleFactoriesConflictingParameterNames(
+        checkNotNull(stringProvider2.get(), 1),
+        checkNotNull(_tests_AQualifier_java_lang_ObjectProvider.get(), 2),
+        _tests_AQualifier_java_lang_ObjectProvider);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java
new file mode 100644
index 0000000..5ee2b2f
--- /dev/null
+++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class MultipleFactoriesImplementingInterface_ClassAFactory
+    implements MultipleFactoriesImplementingInterface.Base.Factory {
+  @Inject
+  MultipleFactoriesImplementingInterface_ClassAFactory() {}
+
+  MultipleFactoriesImplementingInterface.ClassA create() {
+    return new MultipleFactoriesImplementingInterface.ClassA();
+  }
+
+  @Override
+  public MultipleFactoriesImplementingInterface.ClassA abstractNonDefaultCreate() {
+    return create();
+  }
+}
diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java
new file mode 100644
index 0000000..f654068
--- /dev/null
+++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class MultipleFactoriesImplementingInterface_ClassBFactory
+    implements MultipleFactoriesImplementingInterface.Base.Factory {
+  @Inject
+  MultipleFactoriesImplementingInterface_ClassBFactory() {}
+
+  MultipleFactoriesImplementingInterface.ClassB create() {
+    return new MultipleFactoriesImplementingInterface.ClassB();
+  }
+
+  @Override
+  public MultipleFactoriesImplementingInterface.ClassB abstractNonDefaultCreate() {
+    return create();
+  }
+}
diff --git a/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java
new file mode 100644
index 0000000..de7bad7
--- /dev/null
+++ b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class MultipleProvidedParamsSameKeyFactory {
+  private final Provider<String> java_lang_StringProvider;
+
+  @Inject
+  MultipleProvidedParamsSameKeyFactory(Provider<String> java_lang_StringProvider) {
+    this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1);
+  }
+
+  MultipleProvidedParamsSameKey create() {
+    return new MultipleProvidedParamsSameKey(
+        checkNotNull(java_lang_StringProvider.get(), 1),
+        checkNotNull(java_lang_StringProvider.get(), 2),
+        java_lang_StringProvider.get(),
+        java_lang_StringProvider,
+        java_lang_StringProvider);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java
new file mode 100644
index 0000000..bf6a468
--- /dev/null
+++ b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class NestedClassCustomNamedFactory {
+  @Inject NestedClassCustomNamedFactory() {}
+
+  NestedClasses.SimpleNestedClassWithCustomFactory create() {
+    return new NestedClasses.SimpleNestedClassWithCustomFactory();
+  }
+}
diff --git a/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java
new file mode 100644
index 0000000..f982e86
--- /dev/null
+++ b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class NestedClasses_SimpleNestedClassFactory {
+  @Inject NestedClasses_SimpleNestedClassFactory() {}
+
+  NestedClasses.SimpleNestedClass create() {
+    return new NestedClasses.SimpleNestedClass();
+  }
+}
diff --git a/factory/src/test/resources/expected/OnlyPrimitivesFactory.java b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java
new file mode 100644
index 0000000..ec60c58
--- /dev/null
+++ b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class OnlyPrimitivesFactory {
+  @Inject OnlyPrimitivesFactory() {}
+
+  OnlyPrimitives create(int i, long l) {
+    return new OnlyPrimitives(i, l);
+  }
+}
diff --git a/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java
new file mode 100644
index 0000000..4d1a4cf
--- /dev/null
+++ b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+)
+final class ProviderArgumentToCreateMethodFactory
+    implements ProviderArgumentToCreateMethod.CustomCreator{
+  @Inject ProviderArgumentToCreateMethodFactory() {}
+
+  ProviderArgumentToCreateMethod create(Provider<String> stringProvider) {
+    return new ProviderArgumentToCreateMethod(checkNotNull(stringProvider, 1));
+  }
+
+  @Override
+  public ProviderArgumentToCreateMethod newInstance(Provider<String> stringProvider) {
+    return create(stringProvider);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/PublicClassFactory.java b/factory/src/test/resources/expected/PublicClassFactory.java
new file mode 100644
index 0000000..06671dc
--- /dev/null
+++ b/factory/src/test/resources/expected/PublicClassFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+public final class PublicClassFactory {
+  @Inject public PublicClassFactory() {}
+
+  public PublicClass create() {
+    return new PublicClass();
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassFactory.java b/factory/src/test/resources/expected/SimpleClassFactory.java
new file mode 100644
index 0000000..308d2cd
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class SimpleClassFactory {
+  @Inject SimpleClassFactory() {}
+
+  SimpleClass create() {
+    return new SimpleClass();
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java
new file mode 100644
index 0000000..6c611e9
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 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 tests;
+
+import java.util.RandomAccess;
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class SimpleClassImplementingMarkerFactory implements RandomAccess {
+  @Inject SimpleClassImplementingMarkerFactory() {}
+
+  SimpleClassImplementingMarker create() {
+    return new SimpleClassImplementingMarker();
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java
new file mode 100644
index 0000000..720e7d0
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class SimpleClassImplementingSimpleInterfaceFactory
+    implements SimpleClassImplementingSimpleInterface.SimpleInterface {
+  @Inject SimpleClassImplementingSimpleInterfaceFactory() {}
+
+  SimpleClassImplementingSimpleInterface create() {
+    return new SimpleClassImplementingSimpleInterface();
+  }
+
+  @Override public SimpleClassImplementingSimpleInterface newInstance() {
+    return create();
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java
new file mode 100644
index 0000000..ccdea61
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class SimpleClassMixedDepsFactory {
+  private final Provider<String> providedDepAProvider;
+
+  @Inject SimpleClassMixedDepsFactory(
+      @AQualifier Provider<String> providedDepAProvider) {
+    this.providedDepAProvider = checkNotNull(providedDepAProvider, 1);
+  }
+
+  SimpleClassMixedDeps create(String depB) {
+    return new SimpleClassMixedDeps(
+        checkNotNull(providedDepAProvider.get(), 1), checkNotNull(depB, 2));
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java
new file mode 100644
index 0000000..d323812
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+class SimpleClassNonFinalFactory {
+  @Inject SimpleClassNonFinalFactory() {}
+
+  SimpleClassNonFinal create() {
+    return new SimpleClassNonFinal();
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java
new file mode 100644
index 0000000..e354038
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 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 tests;
+
+import javax.annotation.Nullable;
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class SimpleClassNullableParametersFactory {
+  private final Provider<String> providedNullableProvider;
+
+  private final Provider<String> providedQualifiedNullableProvider;
+
+  @Inject
+  SimpleClassNullableParametersFactory(
+      Provider<String> providedNullableProvider,
+      @BQualifier Provider<String> providedQualifiedNullableProvider) {
+    this.providedNullableProvider = checkNotNull(providedNullableProvider, 1);
+    this.providedQualifiedNullableProvider = checkNotNull(providedQualifiedNullableProvider, 2);
+  }
+
+  SimpleClassNullableParameters create(
+      @Nullable String nullable, @Nullable @AQualifier String qualifiedNullable) {
+    return new SimpleClassNullableParameters(
+        nullable,
+        qualifiedNullable,
+        providedNullableProvider.get(),
+        providedQualifiedNullableProvider.get());
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java
new file mode 100644
index 0000000..3260c36
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class SimpleClassPassedDepsFactory {
+  @Inject SimpleClassPassedDepsFactory() {}
+
+  SimpleClassPassedDeps create(String depA, String depB) {
+    return new SimpleClassPassedDeps(checkNotNull(depA, 1), checkNotNull(depB, 2));
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java
new file mode 100644
index 0000000..05d1e5a
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class SimpleClassProvidedDepsFactory {
+  private final Provider<Integer> providedPrimitiveAProvider;
+  private final Provider<Integer> providedPrimitiveBProvider;
+  private final Provider<String> providedDepAProvider;
+  private final Provider<String> providedDepBProvider;
+
+  @Inject
+  SimpleClassProvidedDepsFactory(
+      @AQualifier Provider<Integer> providedPrimitiveAProvider,
+      @BQualifier Provider<Integer> providedPrimitiveBProvider,
+      @AQualifier Provider<String> providedDepAProvider,
+      @BQualifier Provider<String> providedDepBProvider) {
+    this.providedPrimitiveAProvider = checkNotNull(providedPrimitiveAProvider, 1);
+    this.providedPrimitiveBProvider = checkNotNull(providedPrimitiveBProvider, 2);
+    this.providedDepAProvider = checkNotNull(providedDepAProvider, 3);
+    this.providedDepBProvider = checkNotNull(providedDepBProvider, 4);
+  }
+
+  SimpleClassProvidedDeps create() {
+    return new SimpleClassProvidedDeps(
+        checkNotNull(providedPrimitiveAProvider.get(), 1),
+        checkNotNull(providedPrimitiveBProvider.get(), 2),
+        checkNotNull(providedDepAProvider.get(), 3),
+        checkNotNull(providedDepBProvider.get(), 4));
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java
new file mode 100644
index 0000000..aafdcec
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+  comments = "https://github.com/google/auto/tree/master/factory"
+  )
+final class SimpleClassProvidedProviderDepsFactory {
+  private final Provider<String> providedDepAProvider;
+  private final Provider<String> providedDepBProvider;
+
+  @Inject
+  SimpleClassProvidedProviderDepsFactory(
+      @AQualifier Provider<String> providedDepAProvider,
+      @BQualifier Provider<String> providedDepBProvider) {
+    this.providedDepAProvider = checkNotNull(providedDepAProvider, 1);
+    this.providedDepBProvider = checkNotNull(providedDepBProvider, 2);
+  }
+
+  SimpleClassProvidedProviderDeps create() {
+    return new SimpleClassProvidedProviderDeps(providedDepAProvider, providedDepBProvider);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassVarargsFactory.java b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java
new file mode 100644
index 0000000..51c7f46
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016 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 tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+)
+final class SimpleClassVarargsFactory implements SimpleClassVarargs.InterfaceWithVarargs {
+  @Inject SimpleClassVarargsFactory() {}
+
+  SimpleClassVarargs create(String... args) {
+    return new SimpleClassVarargs(checkNotNull(args, 1));
+  }
+
+  @Override
+  public SimpleClassVarargs build(String... args) {
+    return create(args);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/good/CheckerFrameworkNullable.java b/factory/src/test/resources/good/CheckerFrameworkNullable.java
new file mode 100644
index 0000000..7f2a0fe
--- /dev/null
+++ b/factory/src/test/resources/good/CheckerFrameworkNullable.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
+
+@AutoFactory
+final class CheckerFrameworkNullable {
+
+  CheckerFrameworkNullable(
+      @NullableDecl String nullableDecl,
+      @Provided @NullableDecl String providedNullableDecl,
+      @NullableType String nullableType,
+      @Provided @NullableType String providedNullableType) {}
+}
diff --git a/factory/src/test/resources/good/ClassUsingQualifierWithArgs.java b/factory/src/test/resources/good/ClassUsingQualifierWithArgs.java
new file mode 100644
index 0000000..4a5b8c3
--- /dev/null
+++ b/factory/src/test/resources/good/ClassUsingQualifierWithArgs.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
+/**
+ * @author Justine Tunney
+ */
+@AutoFactory
+@SuppressWarnings("unused")
+final class ClassUsingQualifierWithArgs {
+  private final String providedDepA;
+
+  ClassUsingQualifierWithArgs(
+      @Provided @QualifierWithArgs(name = "Fred", count = 3) String providedDepA) {
+    this.providedDepA = providedDepA;
+  }
+}
diff --git a/factory/src/test/resources/good/ConstructorAnnotated.java b/factory/src/test/resources/good/ConstructorAnnotated.java
new file mode 100644
index 0000000..fdc02f3
--- /dev/null
+++ b/factory/src/test/resources/good/ConstructorAnnotated.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
+final class ConstructorAnnotated {
+  @AutoFactory ConstructorAnnotated() {}
+  ConstructorAnnotated(Object obj) {}
+  @AutoFactory ConstructorAnnotated(String s) {}
+  @AutoFactory ConstructorAnnotated(@Provided Object obj, int i) {}
+  @AutoFactory ConstructorAnnotated(@Provided Object obj, char c) {}
+}
diff --git a/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java b/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java
new file mode 100644
index 0000000..5bed1e6
--- /dev/null
+++ b/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
+final class ConstructorAnnotatedNonFinal {
+  @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal() {}
+  ConstructorAnnotatedNonFinal(Object obj) {}
+  @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(String s) {}
+  @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(@Provided Object obj, int i) {}
+  @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(@Provided Object obj, char c) {}
+}
diff --git a/factory/src/test/resources/good/CustomNullable.java b/factory/src/test/resources/good/CustomNullable.java
new file mode 100644
index 0000000..bdf2e7c
--- /dev/null
+++ b/factory/src/test/resources/good/CustomNullable.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
+@AutoFactory
+final class CustomNullable {
+
+  private final String string;
+  private final Object object;
+
+  CustomNullable(
+      @CustomNullable.Nullable String string, @CustomNullable.Nullable @Provided Object object) {
+    this.string = string;
+    this.object = object;
+  }
+
+  @interface Nullable {}
+}
diff --git a/factory/src/test/resources/good/DefaultPackage.java b/factory/src/test/resources/good/DefaultPackage.java
new file mode 100644
index 0000000..f4efee6
--- /dev/null
+++ b/factory/src/test/resources/good/DefaultPackage.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2019 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.
+ */
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory
+public final class DefaultPackage {}
diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClass.java b/factory/src/test/resources/good/FactoryExtendingAbstractClass.java
new file mode 100644
index 0000000..5511e99
--- /dev/null
+++ b/factory/src/test/resources/good/FactoryExtendingAbstractClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import tests.FactoryExtendingAbstractClass.AbstractFactory;
+
+@AutoFactory(extending = AbstractFactory.class)
+final class FactoryExtendingAbstractClass {
+  static abstract class AbstractFactory {
+    abstract FactoryExtendingAbstractClass newInstance();
+  }
+}
diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java
new file mode 100644
index 0000000..98c5f66
--- /dev/null
+++ b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory;
+
+@AutoFactory(extending = AbstractFactory.class)
+final class FactoryExtendingAbstractClassWithConstructorParams {
+  static abstract class AbstractFactory {
+    protected AbstractFactory(Object obj) {}
+    
+    abstract FactoryExtendingAbstractClassWithConstructorParams newInstance();
+  }
+}
diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java
new file mode 100644
index 0000000..43e94ce
--- /dev/null
+++ b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import tests.FactoryExtendingAbstractClassWithMultipleConstructors.AbstractFactory;
+
+@AutoFactory(extending = AbstractFactory.class)
+final class FactoryExtendingAbstractClassWithMultipleConstructors {
+  static abstract class AbstractFactory {
+    protected AbstractFactory(Object obj) {}
+    protected AbstractFactory() {}
+    
+    abstract FactoryExtendingAbstractClassWithMultipleConstructors newInstance();
+  }
+}
diff --git a/factory/src/test/resources/good/FactoryImplementingCreateMethod.java b/factory/src/test/resources/good/FactoryImplementingCreateMethod.java
new file mode 100644
index 0000000..db15eef
--- /dev/null
+++ b/factory/src/test/resources/good/FactoryImplementingCreateMethod.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import java.util.List;
+
+final class FactoryImplementingCreateMethod {
+
+  interface Interface {}
+
+  interface FactoryInterfaceWithCreateMethod {
+    Interface create();
+
+    Interface create(int a);
+    
+    Interface create(List<Integer> generic);
+  }
+
+  @AutoFactory(implementing = FactoryInterfaceWithCreateMethod.class)
+  static class ConcreteClass implements Interface {
+    // Will generate a method with a signature that matches one from the interface.
+    ConcreteClass() {}
+
+    // Will generate a method with a signature that matches one from the interface.
+    ConcreteClass(int aDifferentArgumentName) {}
+
+    // Will generate a method with a signature that matches one from the interface.
+    ConcreteClass(List<Integer> genericWithDifferentArgumentName) {}
+
+    ConcreteClass(int a, boolean b) {}
+  }
+}
diff --git a/factory/src/test/resources/good/FactoryImplementingGenericInterfaceExtension.java b/factory/src/test/resources/good/FactoryImplementingGenericInterfaceExtension.java
new file mode 100644
index 0000000..0d0565f
--- /dev/null
+++ b/factory/src/test/resources/good/FactoryImplementingGenericInterfaceExtension.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
+class FactoryImplementingGenericInterfaceExtension {
+
+  @AutoFactory(implementing = MyFactory.class)
+  FactoryImplementingGenericInterfaceExtension(@Provided String s, Integer i) {}
+
+  interface MyFactory
+      extends GenericFactory<FactoryImplementingGenericInterfaceExtension, Integer> {}
+
+  interface GenericFactory<T, S> {
+    T make(S arg);
+  }
+}
diff --git a/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java b/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java
new file mode 100644
index 0000000..c7435ed
--- /dev/null
+++ b/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
+/**
+ * @author Gregory Kick
+ */
+final class MixedDepsImplementingInterfaces {
+  @AutoFactory(implementing = {FromInt.class, MarkerA.class})
+  MixedDepsImplementingInterfaces(@Provided String s, int i) {}
+  
+  @AutoFactory(implementing = {FromObject.class, MarkerB.class})
+  MixedDepsImplementingInterfaces(Object o) {}
+
+  interface FromInt {
+    MixedDepsImplementingInterfaces fromInt(int i);
+  }
+  
+  interface FromObject {
+    MixedDepsImplementingInterfaces fromObject(Object o);
+  }
+  
+  interface MarkerA {}
+
+  interface MarkerB {}
+}
diff --git a/factory/src/test/resources/good/MultipleFactoriesConflictingParameterNames.java b/factory/src/test/resources/good/MultipleFactoriesConflictingParameterNames.java
new file mode 100644
index 0000000..b6a3106
--- /dev/null
+++ b/factory/src/test/resources/good/MultipleFactoriesConflictingParameterNames.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import javax.inject.Provider;
+
+class MultipleFactoriesConflictingParameterNames {
+
+  @AutoFactory
+  MultipleFactoriesConflictingParameterNames(
+      @Provided String string,
+      @Provided Object duplicatedKey_nameDoesntMatter,
+      @Provided Provider<Object> duplicatedKeyProvider_nameDoesntMatter,
+      // used to disambiguate with the second constructor since qualifiers aren't part of the type
+      // system
+      Object unused) {}
+
+  @AutoFactory
+  MultipleFactoriesConflictingParameterNames(
+      @Provided @AQualifier String string,
+      @Provided @AQualifier Object qualifiedDuplicatedKey_nameDoesntMatter,
+      @Provided @AQualifier Provider<Object> qualifiedDuplicatedKeyProvider_nameDoesntMatter) {}
+}
diff --git a/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java b/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java
new file mode 100644
index 0000000..2eecf1a
--- /dev/null
+++ b/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+class MultipleFactoriesImplementingInterface {  
+  static interface Base {
+    static interface Factory {
+      public abstract Base abstractNonDefaultCreate();
+    }
+  }
+
+  @AutoFactory(implementing = Base.Factory.class)
+  static class ClassA implements Base { }
+
+  @AutoFactory(implementing = Base.Factory.class)
+  static class ClassB implements Base {}
+}  
diff --git a/factory/src/test/resources/good/MultipleProvidedParamsSameKey.java b/factory/src/test/resources/good/MultipleProvidedParamsSameKey.java
new file mode 100644
index 0000000..b338d34
--- /dev/null
+++ b/factory/src/test/resources/good/MultipleProvidedParamsSameKey.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import javax.annotation.Nullable;
+import javax.inject.Provider;
+
+@AutoFactory
+final class MultipleProvidedParamsSameKey {
+  private final String one;
+  private final String two;
+  private final String three;
+  private final Provider<String> providerOne;
+  private final Provider<String> providerTwo;
+
+  public MultipleProvidedParamsSameKey(
+      @Provided String one,
+      @Provided String two,
+      @Nullable @Provided String three,
+      @Provided Provider<String> providerOne,
+      @Provided Provider<String> providerTwo) {
+    this.one = one;
+    this.two = two;
+    this.three = three;
+    this.providerOne = providerOne;
+    this.providerTwo = providerTwo;
+  }
+}
diff --git a/factory/src/test/resources/good/NestedClasses.java b/factory/src/test/resources/good/NestedClasses.java
new file mode 100644
index 0000000..93dbe84
--- /dev/null
+++ b/factory/src/test/resources/good/NestedClasses.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+final class NestedClasses {
+
+  @AutoFactory
+  static final class SimpleNestedClass {}
+
+  @AutoFactory(className = "NestedClassCustomNamedFactory")
+  static final class SimpleNestedClassWithCustomFactory {}
+}
diff --git a/factory/src/test/resources/good/OnlyPrimitives.java b/factory/src/test/resources/good/OnlyPrimitives.java
new file mode 100644
index 0000000..ed39123
--- /dev/null
+++ b/factory/src/test/resources/good/OnlyPrimitives.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+/** @author Ron Shapiro */
+@AutoFactory
+final class OnlyPrimitives {
+  OnlyPrimitives(int i, long l) {}
+}
diff --git a/factory/src/test/resources/good/ProviderArgumentToCreateMethod.java b/factory/src/test/resources/good/ProviderArgumentToCreateMethod.java
new file mode 100644
index 0000000..f1f3735
--- /dev/null
+++ b/factory/src/test/resources/good/ProviderArgumentToCreateMethod.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import javax.inject.Provider;
+
+@AutoFactory(implementing = ProviderArgumentToCreateMethod.CustomCreator.class)
+final class ProviderArgumentToCreateMethod {
+  private final Provider<String> stringProvider;
+
+  ProviderArgumentToCreateMethod(Provider<String> stringProvider) {
+    this.stringProvider = stringProvider;
+  }
+
+  interface CustomCreator {
+    ProviderArgumentToCreateMethod newInstance(Provider<String> stringProvider);
+  }
+}
diff --git a/factory/src/test/resources/good/PublicClass.java b/factory/src/test/resources/good/PublicClass.java
new file mode 100644
index 0000000..78da11d
--- /dev/null
+++ b/factory/src/test/resources/good/PublicClass.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory
+public final class PublicClass {}
diff --git a/factory/src/test/resources/good/SimpleClass.java b/factory/src/test/resources/good/SimpleClass.java
new file mode 100644
index 0000000..227a4bb
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClass.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory
+final class SimpleClass {}
diff --git a/factory/src/test/resources/good/SimpleClassCustomName.java b/factory/src/test/resources/good/SimpleClassCustomName.java
new file mode 100644
index 0000000..f842c16
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassCustomName.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory(className = "CustomNamedFactory")
+final class SimpleClassCustomName {}
diff --git a/factory/src/test/resources/good/SimpleClassImplementingMarker.java b/factory/src/test/resources/good/SimpleClassImplementingMarker.java
new file mode 100644
index 0000000..24e3abc
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassImplementingMarker.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import java.util.RandomAccess;
+
+@AutoFactory(implementing = RandomAccess.class)
+class SimpleClassImplementingMarker {
+}
diff --git a/factory/src/test/resources/good/SimpleClassImplementingSimpleInterface.java b/factory/src/test/resources/good/SimpleClassImplementingSimpleInterface.java
new file mode 100644
index 0000000..ddc324c
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassImplementingSimpleInterface.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import tests.SimpleClassImplementingSimpleInterface.SimpleInterface;
+
+@AutoFactory(implementing = SimpleInterface.class)
+final class SimpleClassImplementingSimpleInterface {
+  interface SimpleInterface {
+    SimpleClassImplementingSimpleInterface newInstance();
+  }
+}
diff --git a/factory/src/test/resources/good/SimpleClassMixedDeps.java b/factory/src/test/resources/good/SimpleClassMixedDeps.java
new file mode 100644
index 0000000..05d627c
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassMixedDeps.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
+/**
+ * @author Gregory Kick
+ */
+@AutoFactory
+@SuppressWarnings("unused")
+final class SimpleClassMixedDeps {
+  private final String providedDepA;
+  private final String depB;
+
+  SimpleClassMixedDeps(@Provided @AQualifier String providedDepA, String depB) {
+    this.providedDepA = providedDepA;
+    this.depB = depB;
+  }
+}
diff --git a/factory/src/test/resources/good/SimpleClassNonFinal.java b/factory/src/test/resources/good/SimpleClassNonFinal.java
new file mode 100644
index 0000000..a05d61b
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassNonFinal.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory(allowSubclasses = true)
+final class SimpleClassNonFinal {}
diff --git a/factory/src/test/resources/good/SimpleClassNullableParameters.java b/factory/src/test/resources/good/SimpleClassNullableParameters.java
new file mode 100644
index 0000000..dcaf618
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassNullableParameters.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import javax.annotation.Nullable;
+
+@AutoFactory
+@SuppressWarnings("unused")
+final class SimpleClassNullableParameters {
+  @Nullable private final String nullable;
+  @Nullable private final String qualifiedNullable;
+  @Nullable private final String providedNullable;
+  @Nullable private final String providedQualifiedNullable;
+
+  // TODO(ronshapiro): with Java 8, test Provider<@Nullable String> parameters and provider fields
+  SimpleClassNullableParameters(
+      @Nullable String nullable,
+      @Nullable @AQualifier String qualifiedNullable,
+      @Nullable @Provided String providedNullable,
+      @Nullable @Provided @BQualifier String providedQualifiedNullable) {
+    this.nullable = nullable;
+    this.qualifiedNullable = qualifiedNullable;
+    this.providedNullable = providedNullable;
+    this.providedQualifiedNullable = providedQualifiedNullable;
+  }
+}
diff --git a/factory/src/test/resources/good/SimpleClassPassedDeps.java b/factory/src/test/resources/good/SimpleClassPassedDeps.java
new file mode 100644
index 0000000..b002fbf
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassPassedDeps.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+/**
+ * @author Gregory Kick
+ */
+@AutoFactory
+@SuppressWarnings("unused")
+final class SimpleClassPassedDeps {
+  private final String depA;
+  private final String depB;
+
+  SimpleClassPassedDeps(String depA, String depB) {
+    this.depA = depA;
+    this.depB = depB;
+  }
+}
diff --git a/factory/src/test/resources/good/SimpleClassProvidedDeps.java b/factory/src/test/resources/good/SimpleClassProvidedDeps.java
new file mode 100644
index 0000000..ffcefd2
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassProvidedDeps.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
+/**
+ * @author Gregory Kick
+ */
+@AutoFactory
+@SuppressWarnings("unused")
+final class SimpleClassProvidedDeps {
+  private final int providedPrimitiveA;
+  private final int providedPrimitiveB;
+  private final String providedDepA;
+  private final String providedDepB;
+
+  SimpleClassProvidedDeps(
+      @Provided @AQualifier int providedPrimitiveA,
+      @Provided @BQualifier int providedPrimitiveB,
+      @Provided @AQualifier String providedDepA,
+      @Provided @BQualifier String providedDepB) {
+    this.providedPrimitiveA = providedPrimitiveA;
+    this.providedPrimitiveB = providedPrimitiveB;
+    this.providedDepA = providedDepA;
+    this.providedDepB = providedDepB;
+  }
+}
diff --git a/factory/src/test/resources/good/SimpleClassProvidedProviderDeps.java b/factory/src/test/resources/good/SimpleClassProvidedProviderDeps.java
new file mode 100644
index 0000000..ff01360
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassProvidedProviderDeps.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import javax.inject.Provider;
+
+/**
+ * @author Gregory Kick
+ */
+@AutoFactory
+@SuppressWarnings("unused")
+final class SimpleClassProvidedProviderDeps {
+  private final Provider<String> providedDepA;
+  private final Provider<String> providedDepB;
+
+  SimpleClassProvidedProviderDeps(
+      @Provided @AQualifier Provider<String> providedDepA,
+      @Provided @BQualifier Provider<String> providedDepB) {
+    this.providedDepA = providedDepA;
+    this.providedDepB = providedDepB;
+  }
+}
diff --git a/factory/src/test/resources/good/SimpleClassVarargs.java b/factory/src/test/resources/good/SimpleClassVarargs.java
new file mode 100644
index 0000000..57beb74
--- /dev/null
+++ b/factory/src/test/resources/good/SimpleClassVarargs.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory(implementing = SimpleClassVarargs.InterfaceWithVarargs.class)
+final class SimpleClassVarargs {
+  private final String[] args;
+
+  SimpleClassVarargs(String... args) {
+    this.args = args;
+  }
+
+  interface InterfaceWithVarargs {
+    SimpleClassVarargs build(String... args);
+  }
+}
diff --git a/factory/src/test/resources/support/AQualifier.java b/factory/src/test/resources/support/AQualifier.java
new file mode 100644
index 0000000..5836941
--- /dev/null
+++ b/factory/src/test/resources/support/AQualifier.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 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 tests;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import javax.inject.Qualifier;
+
+/**
+ * @author Gregory Kick
+ */
+@Documented
+@Qualifier
+@Retention(RUNTIME)
+@interface AQualifier {}
diff --git a/factory/src/test/resources/support/BQualifier.java b/factory/src/test/resources/support/BQualifier.java
new file mode 100644
index 0000000..0b7a167
--- /dev/null
+++ b/factory/src/test/resources/support/BQualifier.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 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 tests;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import javax.inject.Qualifier;
+
+/**
+ * @author Gregory Kick
+ */
+@Documented
+@Qualifier
+@Retention(RUNTIME)
+@interface BQualifier {}
diff --git a/factory/src/test/resources/support/QualifierWithArgs.java b/factory/src/test/resources/support/QualifierWithArgs.java
new file mode 100644
index 0000000..81e3f84
--- /dev/null
+++ b/factory/src/test/resources/support/QualifierWithArgs.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 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 tests;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import javax.inject.Qualifier;
+
+/**
+ * @author Justine Tunney
+ */
+@Documented
+@Qualifier
+@Retention(RUNTIME)
+@interface QualifierWithArgs {
+  String name();
+  int count();
+}
diff --git a/service/README.md b/service/README.md
new file mode 100644
index 0000000..5016456
--- /dev/null
+++ b/service/README.md
@@ -0,0 +1,112 @@
+# AutoService
+
+A configuration/metadata generator for java.util.ServiceLoader-style service
+providers
+
+## AutoWhat‽
+
+[Java][java] annotation processors and other systems use
+[java.util.ServiceLoader][sl] to register implementations of well-known types
+using META-INF metadata. However, it is easy for a developer to forget to update
+or correctly specify the service descriptors. \
+AutoService generates this metadata for the developer, for any class annotated
+with `@AutoService`, avoiding typos, providing resistance to errors from
+refactoring, etc.
+
+## Example
+
+Say you have:
+
+```java
+package foo.bar;
+
+import javax.annotation.processing.Processor;
+
+@AutoService(Processor.class)
+final class MyProcessor implements Processor {
+  // …
+}
+```
+
+AutoService will generate the file
+`META-INF/services/javax.annotation.processing.Processor` in the output classes
+folder. The file will contain:
+
+```
+foo.bar.MyProcessor
+```
+
+In the case of javax.annotation.processing.Processor, if this metadata file is
+included in a jar, and that jar is on javac's classpath, then `javac` will
+automatically load it, and include it in its normal annotation processing
+environment. Other users of java.util.ServiceLoader may use the infrastructure
+to different ends, but this metadata will provide auto-loading appropriately.
+
+## Getting Started
+
+You will need `auto-service-annotations-${version}.jar` in your compile-time
+classpath, and you will need `auto-service-${version}.jar` in your
+annotation-processor classpath.
+
+In Maven, you can write:
+
+```xml
+<dependencies>
+  <dependency>
+    <groupId>com.google.auto.service</groupId>
+    <artifactId>auto-service-annotations</artifactId>
+    <version>${auto-service.version}</version>
+  </dependency>
+</dependencies>
+
+...
+
+<plugins>
+  <plugin>
+    <artifactId>maven-compiler-plugin</artifactId>
+    <configuration>
+      <annotationProcessorPaths>
+        <path>
+          <groupId>com.google.auto.service</groupId>
+          <artifactId>auto-service</artifactId>
+          <version>${auto-service.version}</version>
+        </path>
+      </annotationProcessorPaths>
+    </configuration>
+  </plugin>
+</plugins>
+```
+
+Alternatively, you can include the processor itself (which transitively depends
+on the annotation) in your compile-time classpath. (However, note that doing so
+may pull unnecessary classes into your runtime classpath.)
+
+```xml
+<dependencies>
+  <dependency>
+    <groupId>com.google.auto.service</groupId>
+    <artifactId>auto-service</artifactId>
+    <version>${version}</version>
+    <optional>true</optional>
+  </dependency>
+</dependencies>
+```
+
+## License
+
+    Copyright 2013 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.
+
+[java]: https://en.wikipedia.org/wiki/Java_(programming_language)
+[sl]: http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html
diff --git a/service/annotations/pom.xml b/service/annotations/pom.xml
new file mode 100644
index 0000000..e84147d
--- /dev/null
+++ b/service/annotations/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2013 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.google.auto.service</groupId>
+    <artifactId>auto-service-aggregator</artifactId>
+    <version>HEAD-SNAPSHOT</version>
+  </parent>
+
+  <groupId>com.google.auto.service</groupId>
+  <artifactId>auto-service-annotations</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>AutoService</name>
+  <description>
+    Provider-configuration files for ServiceLoader.
+  </description>
+  <url>https://github.com/google/auto/tree/master/service</url>
+
+  <scm>
+    <url>http://github.com/google/auto</url>
+    <connection>scm:git:git://github.com/google/auto.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/google/auto.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestEntries>
+              <Automatic-Module-Name>com.google.auto.service</Automatic-Module-Name>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <!-- disable processing because the definition in META-INF/services breaks javac -->
+          <compilerArgument>-proc:none</compilerArgument>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/service/annotations/src/main/java/com/google/auto/service/AutoService.java b/service/annotations/src/main/java/com/google/auto/service/AutoService.java
new file mode 100644
index 0000000..260766c
--- /dev/null
+++ b/service/annotations/src/main/java/com/google/auto/service/AutoService.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2008 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.service;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for service providers as described in {@link java.util.ServiceLoader}. The
+ * annotation processor generates the configuration files that allow the annotated class to be
+ * loaded with {@link java.util.ServiceLoader#load(Class)}.
+ *
+ * <p>The annotated class must conform to the service provider specification. Specifically, it must:
+ *
+ * <ul>
+ *   <li>be a non-inner, non-anonymous, concrete class
+ *   <li>have a publicly accessible no-arg constructor
+ *   <li>implement the interface type returned by {@code value()}
+ * </ul>
+ */
+@Documented
+@Retention(CLASS)
+@Target(TYPE)
+public @interface AutoService {
+  /** Returns the interfaces implemented by this service provider. */
+  Class<?>[] value();
+}
diff --git a/service/pom.xml b/service/pom.xml
new file mode 100644
index 0000000..a64ca05
--- /dev/null
+++ b/service/pom.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2013 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+
+  <groupId>com.google.auto.service</groupId>
+  <artifactId>auto-service-aggregator</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>AutoService Aggregator</name>
+  <description>
+    Aggregator POM for @AutoService
+  </description>
+  <packaging>pom</packaging>
+  <url>https://github.com/google/auto/tree/master/service</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <java.version>1.8</java.version>
+    <guava.version>27.0.1-jre</guava.version>
+    <truth.version>1.0.1</truth.version>
+  </properties>
+
+  <scm>
+    <url>http://github.com/google/auto</url>
+    <connection>scm:git:git://github.com/google/auto.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/google/auto.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <issueManagement>
+    <system>GitHub Issues</system>
+    <url>http://github.com/google/auto/issues</url>
+  </issueManagement>
+
+  <licenses>
+    <license>
+      <name>Apache 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+    </license>
+  </licenses>
+
+  <organization>
+    <name>Google LLC</name>
+    <url>http://www.google.com</url>
+  </organization>
+
+  <modules>
+    <module>annotations</module>
+    <module>processor</module>
+  </modules>
+
+  <dependencyManagement>
+    <dependencies>
+      <!-- main dependencies -->
+
+      <dependency>
+        <groupId>com.google.guava</groupId>
+        <artifactId>guava</artifactId>
+        <version>${guava.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.guava</groupId>
+        <artifactId>guava-gwt</artifactId>
+        <version>${guava.version}</version>
+      </dependency>
+
+      <!-- test dependencies -->
+
+      <dependency>
+        <groupId>com.google.testing.compile</groupId>
+        <artifactId>compile-testing</artifactId>
+        <version>0.18</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.truth</groupId>
+        <artifactId>truth</artifactId>
+        <version>${truth.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>4.12</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.7.0</version>
+          <configuration>
+            <source>${java.version}</source>
+            <target>${java.version}</target>
+            <compilerArgument>-Xlint:all</compilerArgument>
+            <showWarnings>true</showWarnings>
+            <showDeprecation>true</showDeprecation>
+          </configuration>
+          <dependencies>
+            <dependency>
+              <groupId>org.codehaus.plexus</groupId>
+              <artifactId>plexus-java</artifactId>
+              <version>0.9.4</version>
+            </dependency>
+          </dependencies>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>3.0.2</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+</project>
diff --git a/service/processor/pom.xml b/service/processor/pom.xml
new file mode 100644
index 0000000..22fc20e
--- /dev/null
+++ b/service/processor/pom.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2013 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.google.auto.service</groupId>
+    <artifactId>auto-service-aggregator</artifactId>
+    <version>HEAD-SNAPSHOT</version>
+  </parent>
+
+  <groupId>com.google.auto.service</groupId>
+  <artifactId>auto-service</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>AutoService Processor</name>
+  <description>
+    Provider-configuration files for ServiceLoader.
+  </description>
+  <url>https://github.com/google/auto/tree/master/service</url>
+
+  <scm>
+    <url>http://github.com/google/auto</url>
+    <connection>scm:git:git://github.com/google/auto.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/google/auto.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.auto.service</groupId>
+      <artifactId>auto-service-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto</groupId>
+      <artifactId>auto-common</artifactId>
+      <version>0.10</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <!-- test dependencies -->
+    <dependency>
+      <groupId>com.google.testing.compile</groupId>
+      <artifactId>compile-testing</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <!-- disable processing because the definition in META-INF/services breaks javac -->
+          <compilerArgument>-proc:none</compilerArgument>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java b/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java
new file mode 100644
index 0000000..3bf42d9
--- /dev/null
+++ b/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2008 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.service.processor;
+
+import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
+import static com.google.auto.common.MoreElements.getAnnotationMirror;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedOptions;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+/**
+ * Processes {@link AutoService} annotations and generates the service provider
+ * configuration files described in {@link java.util.ServiceLoader}.
+ * <p>
+ * Processor Options:<ul>
+ *   <li>debug - turns on debug statements</li>
+ * </ul>
+ */
+@SupportedOptions({ "debug", "verify" })
+public class AutoServiceProcessor extends AbstractProcessor {
+
+  @VisibleForTesting
+  static final String MISSING_SERVICES_ERROR = "No service interfaces provided for element!";
+
+  /**
+   * Maps the class names of service provider interfaces to the
+   * class names of the concrete classes which implement them.
+   * <p>
+   * For example,
+   *   {@code "com.google.apphosting.LocalRpcService" ->
+   *   "com.google.apphosting.datastore.LocalDatastoreService"}
+   */
+  private Multimap<String, String> providers = HashMultimap.create();
+
+  @Override
+  public ImmutableSet<String> getSupportedAnnotationTypes() {
+    return ImmutableSet.of(AutoService.class.getName());
+  }
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latestSupported();
+  }
+
+  /**
+   * <ol>
+   *  <li> For each class annotated with {@link AutoService}<ul>
+   *      <li> Verify the {@link AutoService} interface value is correct
+   *      <li> Categorize the class by its service interface
+   *      </ul>
+   *
+   *  <li> For each {@link AutoService} interface <ul>
+   *       <li> Create a file named {@code META-INF/services/<interface>}
+   *       <li> For each {@link AutoService} annotated class for this interface <ul>
+   *           <li> Create an entry in the file
+   *           </ul>
+   *       </ul>
+   * </ol>
+   */
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    try {
+      return processImpl(annotations, roundEnv);
+    } catch (Exception e) {
+      // We don't allow exceptions of any kind to propagate to the compiler
+      StringWriter writer = new StringWriter();
+      e.printStackTrace(new PrintWriter(writer));
+      fatalError(writer.toString());
+      return true;
+    }
+  }
+
+  private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    if (roundEnv.processingOver()) {
+      generateConfigFiles();
+    } else {
+      processAnnotations(annotations, roundEnv);
+    }
+
+    return true;
+  }
+
+  private void processAnnotations(Set<? extends TypeElement> annotations,
+      RoundEnvironment roundEnv) {
+
+    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
+
+    log(annotations.toString());
+    log(elements.toString());
+
+    for (Element e : elements) {
+      // TODO(gak): check for error trees?
+      TypeElement providerImplementer = (TypeElement) e;
+      AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
+      Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
+      if (providerInterfaces.isEmpty()) {
+        error(MISSING_SERVICES_ERROR, e, annotationMirror);
+        continue;
+      }
+      for (DeclaredType providerInterface : providerInterfaces) {
+        TypeElement providerType = MoreTypes.asTypeElement(providerInterface);
+
+        log("provider interface: " + providerType.getQualifiedName());
+        log("provider implementer: " + providerImplementer.getQualifiedName());
+
+        if (checkImplementer(providerImplementer, providerType)) {
+          providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
+        } else {
+          String message = "ServiceProviders must implement their service provider interface. "
+              + providerImplementer.getQualifiedName() + " does not implement "
+              + providerType.getQualifiedName();
+          error(message, e, annotationMirror);
+        }
+      }
+    }
+  }
+
+  private void generateConfigFiles() {
+    Filer filer = processingEnv.getFiler();
+
+    for (String providerInterface : providers.keySet()) {
+      String resourceFile = "META-INF/services/" + providerInterface;
+      log("Working on resource file: " + resourceFile);
+      try {
+        SortedSet<String> allServices = Sets.newTreeSet();
+        try {
+          // would like to be able to print the full path
+          // before we attempt to get the resource in case the behavior
+          // of filer.getResource does change to match the spec, but there's
+          // no good way to resolve CLASS_OUTPUT without first getting a resource.
+          FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
+              resourceFile);
+          log("Looking for existing resource file at " + existingFile.toUri());
+          Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
+          log("Existing service entries: " + oldServices);
+          allServices.addAll(oldServices);
+        } catch (IOException e) {
+          // According to the javadoc, Filer.getResource throws an exception
+          // if the file doesn't already exist.  In practice this doesn't
+          // appear to be the case.  Filer.getResource will happily return a
+          // FileObject that refers to a non-existent file but will throw
+          // IOException if you try to open an input stream for it.
+          log("Resource file did not already exist.");
+        }
+
+        Set<String> newServices = new HashSet<String>(providers.get(providerInterface));
+        if (allServices.containsAll(newServices)) {
+          log("No new service entries being added.");
+          return;
+        }
+
+        allServices.addAll(newServices);
+        log("New service file contents: " + allServices);
+        FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
+            resourceFile);
+        OutputStream out = fileObject.openOutputStream();
+        ServicesFiles.writeServiceFile(allServices, out);
+        out.close();
+        log("Wrote to: " + fileObject.toUri());
+      } catch (IOException e) {
+        fatalError("Unable to create " + resourceFile + ", " + e);
+        return;
+      }
+    }
+  }
+
+  /**
+   * Verifies {@link ServiceProvider} constraints on the concrete provider class.
+   * Note that these constraints are enforced at runtime via the ServiceLoader,
+   * we're just checking them at compile time to be extra nice to our users.
+   */
+  private boolean checkImplementer(TypeElement providerImplementer, TypeElement providerType) {
+
+    String verify = processingEnv.getOptions().get("verify");
+    if (verify == null || !Boolean.valueOf(verify)) {
+      return true;
+    }
+
+    // TODO: We're currently only enforcing the subtype relationship
+    // constraint. It would be nice to enforce them all.
+
+    Types types = processingEnv.getTypeUtils();
+
+    return types.isSubtype(providerImplementer.asType(), providerType.asType());
+  }
+
+  /**
+   * Returns the binary name of a reference type. For example,
+   * {@code com.google.Foo$Bar}, instead of {@code com.google.Foo.Bar}.
+   *
+   */
+  private String getBinaryName(TypeElement element) {
+    return getBinaryNameImpl(element, element.getSimpleName().toString());
+  }
+
+  private String getBinaryNameImpl(TypeElement element, String className) {
+    Element enclosingElement = element.getEnclosingElement();
+
+    if (enclosingElement instanceof PackageElement) {
+      PackageElement pkg = (PackageElement) enclosingElement;
+      if (pkg.isUnnamed()) {
+        return className;
+      }
+      return pkg.getQualifiedName() + "." + className;
+    }
+
+    TypeElement typeElement = (TypeElement) enclosingElement;
+    return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className);
+  }
+
+  /**
+   * Returns the contents of a {@code Class[]}-typed "value" field in a given {@code annotationMirror}.
+   */
+  private ImmutableSet<DeclaredType> getValueFieldOfClasses(AnnotationMirror annotationMirror) {
+    return getAnnotationValue(annotationMirror, "value")
+        .accept(
+            new SimpleAnnotationValueVisitor8<ImmutableSet<DeclaredType>, Void>() {
+              @Override
+              public ImmutableSet<DeclaredType> visitType(TypeMirror typeMirror, Void v) {
+                // TODO(ronshapiro): class literals may not always be declared types, i.e. int.class,
+                // int[].class
+                return ImmutableSet.of(MoreTypes.asDeclared(typeMirror));
+              }
+
+              @Override
+              public ImmutableSet<DeclaredType> visitArray(
+                  List<? extends AnnotationValue> values, Void v) {
+                return values
+                    .stream()
+                    .flatMap(value -> value.accept(this, null).stream())
+                    .collect(toImmutableSet());
+              }
+            },
+            null);
+  }
+
+  private void log(String msg) {
+    if (processingEnv.getOptions().containsKey("debug")) {
+      processingEnv.getMessager().printMessage(Kind.NOTE, msg);
+    }
+  }
+
+  private void error(String msg, Element element, AnnotationMirror annotation) {
+    processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation);
+  }
+
+  private void fatalError(String msg) {
+    processingEnv.getMessager().printMessage(Kind.ERROR, "FATAL ERROR: " + msg);
+  }
+}
diff --git a/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java b/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java
new file mode 100644
index 0000000..c61b3ba
--- /dev/null
+++ b/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2008 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.service.processor;
+
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.google.common.io.Closer;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A helper class for reading and writing Services files.
+ */
+final class ServicesFiles {
+  public static final String SERVICES_PATH = "META-INF/services";
+
+  private ServicesFiles() { }
+
+  /**
+   * Returns an absolute path to a service file given the class
+   * name of the service.
+   *
+   * @param serviceName not {@code null}
+   * @return SERVICES_PATH + serviceName
+   */
+  static String getPath(String serviceName) {
+    return SERVICES_PATH + "/" + serviceName;
+  }
+
+  /**
+   * Reads the set of service classes from a service file.
+   *
+   * @param input not {@code null}. Closed after use.
+   * @return a not {@code null Set} of service class names.
+   * @throws IOException
+   */
+  static Set<String> readServiceFile(InputStream input) throws IOException {
+    HashSet<String> serviceClasses = new HashSet<String>();
+    Closer closer = Closer.create();
+    try {
+      // TODO(gak): use CharStreams
+      BufferedReader r = closer.register(new BufferedReader(new InputStreamReader(input, UTF_8)));
+      String line;
+      while ((line = r.readLine()) != null) {
+        int commentStart = line.indexOf('#');
+        if (commentStart >= 0) {
+          line = line.substring(0, commentStart);
+        }
+        line = line.trim();
+        if (!line.isEmpty()) {
+          serviceClasses.add(line);
+        }
+      }
+      return serviceClasses;
+    } catch (Throwable t) {
+      throw closer.rethrow(t);
+    } finally {
+      closer.close();
+    }
+  }
+
+  /**
+   * Writes the set of service class names to a service file.
+   *
+   * @param output not {@code null}. Not closed after use.
+   * @param services a not {@code null Collection} of service class names.
+   * @throws IOException
+   */
+  static void writeServiceFile(Collection<String> services, OutputStream output)
+      throws IOException {
+    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
+    for (String service : services) {
+      writer.write(service);
+      writer.newLine();
+    }
+    writer.flush();
+  }
+}
\ No newline at end of file
diff --git a/service/processor/src/main/java/com/google/auto/service/processor/package-info.java b/service/processor/src/main/java/com/google/auto/service/processor/package-info.java
new file mode 100644
index 0000000..a9f0adb
--- /dev/null
+++ b/service/processor/src/main/java/com/google/auto/service/processor/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013 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.
+ */
+/**
+ * This package contains the annotation processor that implements the
+ * {@link com.google.auto.service.AutoService} API.
+ */
+package com.google.auto.service.processor;
\ No newline at end of file
diff --git a/service/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors b/service/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors
new file mode 100644
index 0000000..dd17374
--- /dev/null
+++ b/service/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors
@@ -0,0 +1 @@
+com.google.auto.service.processor.AutoServiceProcessor,AGGREGATING
diff --git a/service/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/service/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..a4406e1
--- /dev/null
+++ b/service/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+com.google.auto.service.processor.AutoServiceProcessor
diff --git a/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java b/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java
new file mode 100644
index 0000000..d3e00a7
--- /dev/null
+++ b/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2008 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.service.processor;
+
+import static com.google.auto.service.processor.AutoServiceProcessor.MISSING_SERVICES_ERROR;
+import static com.google.testing.compile.JavaSourcesSubject.assertThat;
+
+import com.google.testing.compile.JavaFileObjects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests the {@link AutoServiceProcessor}.
+ */
+@RunWith(JUnit4.class)
+public class AutoServiceProcessorTest {
+  @Test
+  public void autoService() {
+      assertThat(
+            JavaFileObjects.forResource("test/SomeService.java"),
+            JavaFileObjects.forResource("test/SomeServiceProvider1.java"),
+            JavaFileObjects.forResource("test/SomeServiceProvider2.java"),
+            JavaFileObjects.forResource("test/Enclosing.java"),
+            JavaFileObjects.forResource("test/AnotherService.java"),
+            JavaFileObjects.forResource("test/AnotherServiceProvider.java"))
+        .processedWith(new AutoServiceProcessor())
+        .compilesWithoutError()
+        .and().generatesFiles(
+            JavaFileObjects.forResource("META-INF/services/test.SomeService"),
+            JavaFileObjects.forResource("META-INF/services/test.AnotherService"));
+  }
+
+  @Test
+  public void multiService() {
+    assertThat(
+            JavaFileObjects.forResource("test/SomeService.java"),
+            JavaFileObjects.forResource("test/AnotherService.java"),
+            JavaFileObjects.forResource("test/MultiServiceProvider.java"))
+        .processedWith(new AutoServiceProcessor())
+        .compilesWithoutError()
+        .and().generatesFiles(
+            JavaFileObjects.forResource("META-INF/services/test.SomeServiceMulti"),
+            JavaFileObjects.forResource("META-INF/services/test.AnotherServiceMulti"));
+  }
+
+  @Test
+  public void badMultiService() {
+    assertThat(JavaFileObjects.forResource("test/NoServices.java"))
+        .processedWith(new AutoServiceProcessor())
+        .failsToCompile()
+        .withErrorContaining(MISSING_SERVICES_ERROR);
+  }
+}
diff --git a/service/processor/src/test/resources/META-INF/services/test.AnotherService b/service/processor/src/test/resources/META-INF/services/test.AnotherService
new file mode 100644
index 0000000..7453e4d
--- /dev/null
+++ b/service/processor/src/test/resources/META-INF/services/test.AnotherService
@@ -0,0 +1 @@
+test.AnotherServiceProvider
diff --git a/service/processor/src/test/resources/META-INF/services/test.AnotherServiceMulti b/service/processor/src/test/resources/META-INF/services/test.AnotherServiceMulti
new file mode 100644
index 0000000..f6ef36a
--- /dev/null
+++ b/service/processor/src/test/resources/META-INF/services/test.AnotherServiceMulti
@@ -0,0 +1 @@
+test.MultiServiceProvider
diff --git a/service/processor/src/test/resources/META-INF/services/test.SomeService b/service/processor/src/test/resources/META-INF/services/test.SomeService
new file mode 100644
index 0000000..1b71d9d
--- /dev/null
+++ b/service/processor/src/test/resources/META-INF/services/test.SomeService
@@ -0,0 +1,3 @@
+test.Enclosing$NestedSomeServiceProvider
+test.SomeServiceProvider1
+test.SomeServiceProvider2
diff --git a/service/processor/src/test/resources/META-INF/services/test.SomeServiceMulti b/service/processor/src/test/resources/META-INF/services/test.SomeServiceMulti
new file mode 100644
index 0000000..f6ef36a
--- /dev/null
+++ b/service/processor/src/test/resources/META-INF/services/test.SomeServiceMulti
@@ -0,0 +1 @@
+test.MultiServiceProvider
diff --git a/service/processor/src/test/resources/test/AnotherService.java b/service/processor/src/test/resources/test/AnotherService.java
new file mode 100644
index 0000000..c096c22
--- /dev/null
+++ b/service/processor/src/test/resources/test/AnotherService.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2008 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 test;
+
+interface AnotherService { }
\ No newline at end of file
diff --git a/service/processor/src/test/resources/test/AnotherServiceProvider.java b/service/processor/src/test/resources/test/AnotherServiceProvider.java
new file mode 100644
index 0000000..c5e5c11
--- /dev/null
+++ b/service/processor/src/test/resources/test/AnotherServiceProvider.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2008 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 test;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(AnotherService.class)
+public class AnotherServiceProvider implements AnotherService { }
\ No newline at end of file
diff --git a/service/processor/src/test/resources/test/Enclosing.java b/service/processor/src/test/resources/test/Enclosing.java
new file mode 100644
index 0000000..26dd585
--- /dev/null
+++ b/service/processor/src/test/resources/test/Enclosing.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2008 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 test;
+
+import com.google.auto.service.AutoService;
+
+public class Enclosing {
+  @AutoService(SomeService.class)
+  public static class NestedSomeServiceProvider implements SomeService { }
+}
\ No newline at end of file
diff --git a/service/processor/src/test/resources/test/MultiServiceProvider.java b/service/processor/src/test/resources/test/MultiServiceProvider.java
new file mode 100644
index 0000000..478c7e7
--- /dev/null
+++ b/service/processor/src/test/resources/test/MultiServiceProvider.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 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 test;
+
+import com.google.auto.service.AutoService;
+
+@AutoService({SomeService.class, AnotherService.class})
+public class MultiServiceProvider implements SomeService, AnotherService {}
diff --git a/service/processor/src/test/resources/test/NoServices.java b/service/processor/src/test/resources/test/NoServices.java
new file mode 100644
index 0000000..e9523ca
--- /dev/null
+++ b/service/processor/src/test/resources/test/NoServices.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2008 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 test;
+
+import com.google.auto.service.AutoService;
+
+@AutoService({})
+public class NoServices {}
diff --git a/service/processor/src/test/resources/test/SomeService.java b/service/processor/src/test/resources/test/SomeService.java
new file mode 100644
index 0000000..d29c409
--- /dev/null
+++ b/service/processor/src/test/resources/test/SomeService.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2008 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 test;
+
+interface SomeService { }
\ No newline at end of file
diff --git a/service/processor/src/test/resources/test/SomeServiceProvider1.java b/service/processor/src/test/resources/test/SomeServiceProvider1.java
new file mode 100644
index 0000000..008136b
--- /dev/null
+++ b/service/processor/src/test/resources/test/SomeServiceProvider1.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2008 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 test;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(SomeService.class)
+public class SomeServiceProvider1 implements SomeService { }
\ No newline at end of file
diff --git a/service/processor/src/test/resources/test/SomeServiceProvider2.java b/service/processor/src/test/resources/test/SomeServiceProvider2.java
new file mode 100644
index 0000000..5444996
--- /dev/null
+++ b/service/processor/src/test/resources/test/SomeServiceProvider2.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2008 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 test;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(SomeService.class)
+public class SomeServiceProvider2 implements SomeService { }
\ No newline at end of file
diff --git a/util/generate-latest-docs.sh b/util/generate-latest-docs.sh
new file mode 100755
index 0000000..8617ba4
--- /dev/null
+++ b/util/generate-latest-docs.sh
@@ -0,0 +1,25 @@
+# see http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ for details
+
+if [ "$TRAVIS_REPO_SLUG" == "google/auto" ] && \
+   [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \
+   [ "$TRAVIS_PULL_REQUEST" == "false" ] && \
+   [ "$TRAVIS_BRANCH" == "master" ]; then
+  echo -e "Publishing javadoc...\n"
+  
+  mvn -f build-pom.xml javadoc:aggregate
+  TARGET="$(pwd)/target"
+
+  cd $HOME
+  git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/google/auto gh-pages > /dev/null
+  
+  cd gh-pages
+  git config --global user.email "travis@travis-ci.org"
+  git config --global user.name "travis-ci"
+  git rm -rf api/latest 
+  mv ${TARGET}/site/apidocs api/latest
+  git add -A -f api/latest
+  git commit -m "Latest javadoc on successful travis build $TRAVIS_BUILD_NUMBER auto-pushed to gh-pages"
+  git push -fq origin gh-pages > /dev/null
+
+  echo -e "Published Javadoc to gh-pages.\n"
+fi
diff --git a/util/mvn-deploy.sh b/util/mvn-deploy.sh
new file mode 100755
index 0000000..e7160eb
--- /dev/null
+++ b/util/mvn-deploy.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+if [ $# -lt 1 ]; then
+  echo "usage $0 <ssl-key> [<param> ...]"
+  exit 1;
+fi
+key=${1}
+shift
+params=${@}
+
+#validate key
+keystatus=$(gpg --list-keys | grep ${key} | awk '{print $1}')
+if [ "${keystatus}" != "pub" ]; then
+  echo "Could not find public key with label ${key}"
+  echo -n "Available keys from: "
+  gpg --list-keys | grep --invert-match '^sub'
+
+  exit 1
+fi
+
+mvn ${params} clean site:jar -P sonatype-oss-release -Dgpg.keyname=${key} deploy
diff --git a/util/publish-snapshot-on-commit.sh b/util/publish-snapshot-on-commit.sh
new file mode 100755
index 0000000..0b74c6b
--- /dev/null
+++ b/util/publish-snapshot-on-commit.sh
@@ -0,0 +1,12 @@
+# see https://coderwall.com/p/9b_lfq
+
+if [ "$TRAVIS_REPO_SLUG" == "google/auto" ] && \
+   [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \
+   [ "$TRAVIS_PULL_REQUEST" == "false" ] && \
+   [ "$TRAVIS_BRANCH" == "master" ]; then
+  echo -e "Publishing maven snapshot...\n"
+
+  mvn -f build-pom.xml clean source:jar deploy --settings="util/settings.xml" -DskipTests=true -Dmaven.javadoc.skip=true
+
+  echo -e "Published maven snapshot"
+fi
diff --git a/util/settings.xml b/util/settings.xml
new file mode 100644
index 0000000..91f444b
--- /dev/null
+++ b/util/settings.xml
@@ -0,0 +1,9 @@
+<settings>
+  <servers>
+    <server>
+      <id>sonatype-nexus-snapshots</id>
+      <username>${env.CI_DEPLOY_USERNAME}</username>
+      <password>${env.CI_DEPLOY_PASSWORD}</password>
+    </server>
+  </servers>
+</settings>
diff --git a/value/CHANGES.md b/value/CHANGES.md
new file mode 100644
index 0000000..32b6107
--- /dev/null
+++ b/value/CHANGES.md
@@ -0,0 +1,255 @@
+# AutoValue Changes
+
+**This document is obsolete.** For details of changes in releases since 1.5,
+see the [releases page](https://github.com/google/auto/releases) for the Auto
+project.
+
+## 1.4 → 1.5
+
+### Functional changes
+
+* A workaround for older Eclipse versions has been removed. If you need to use
+  an Eclipse version older than 4.5, you will need to stay on AutoValue 1.4.
+
+* The [retention](https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Retention.html)
+  of the `@AutoValue` annotation has changed from `SOURCE` to `CLASS`. This
+  means that it is possible for code-analysis tools to tell whether a class is
+  an `@AutoValue`. AutoValue itself uses this to enforce the check that one
+  `@AutoValue` class cannot extend another, even if the classes are compiled
+  separately.
+
+* It is now an error if `@Memoized` is applied to a method not inside an
+  `@AutoValue` class.
+
+* Type annotations are now handled more consistently. If `@Nullable` is a type
+  annotation, a property of type `@Nullable Integer` will have that type used
+  everywhere in the generated code. Associated bugs with nested type
+  annotations, like `Outer.@Inner`, have been fixed.
+
+### Bugs fixed since 1.4.1
+
+* `@Memoized` methods can now throw checked exceptions. Previously this failed
+  because the exceptions were not copied into the `throws` clause of the
+  generated override, so the call to `super.foo()` did not compile.
+
+* The generated `hashCode()` method uses `h = (int) (h ^ longProperty)` rather
+  than `h ^= longProperty` to avoid warnings about loss of precision.
+
+* Annotations are not copied from an abstract method to its implementation if
+  they are not visible from the latter. This can happen if the `@AutoValue`
+  inherits the abstract method from a class or interface in a different package.
+
+## 1.3 → 1.4
+
+*This is the last AutoValue version that compiles and runs on Java 6.* Future
+versions will require at least Java 8 to run. We will continue to generate code
+that is compatible with Java 7, so AutoValue can be used with `javac -source 7
+-target 7 -bootclasspath <rt.jar-from-jdk7>`, but using the `javac` from jdk8 or
+later.
+
+### Functional changes
+
+* Builder setters now reject a null parameter immediately unless the
+  corresponding property is `@Nullable`. Previously this check happened at
+  `build()` time, and in some cases didn't happen at all. This is the change
+  that is most likely to affect existing code.
+
+* Added `@Memoized`. A `@Memoized` method will be overridden in the generated
+  `AutoValue_Foo` class to save the value returned the first time it was called
+  and reuse that every other time.
+
+* Generalized support for property builders. Now, in addition to being able to
+  say `immutableListBuilder()` for a property of type `ImmutableList<T>`, you
+  can say `fooBuilder()` for a property of an arbitrary type that has a builder
+  following certain conventions. In particular, you can do this if the type of
+  `foo()` is itself an `@AutoValue` class with a builder. The default value of
+  `foo()`, if `fooBuilder()` is never called, is `fooBuilder().build()`.
+
+* If a property `foo()` or `getFoo()` has a builder method `fooBuilder()` then
+  the property can not now be `@Nullable`. An `ImmutableList`, for example,
+  starts off empty, not null, so `@Nullable` was misleading.
+
+* When an `@AutoValue` class `Foo` has a builder, the generated
+  `AutoValue_Foo.Builder` has a constructor `AutoValue_Foo.Builder(Foo)`. That
+  constructor was never documented and is now private. If you want to make a
+  `Foo.Builder` from a `Foo`, `Foo` should have an abstract method `Builder
+  toBuilder()`.
+
+  This change was necessary so that generalized property-builder support could
+  know whether or not the built class needs to be convertible back into its
+  builder. That's only necessary if there is a `toBuilder()`.
+
+* The Extension API is now a committed API, meaning we no longer warn that it is
+  likely to change incompatibly. A
+  [guide](https://github.com/google/auto/blob/master/value/userguide/extensions.md)
+  gives tips on writing extensions.
+
+* Extensions can now return null rather than generated code. In that case the
+  extension does not generate a class in the AutoValue hierarchy, but it can
+  still do other things like error checking or generating side files.
+
+* Access modifiers like `protected` are copied from builder methods to their
+  implementations, instead of the implementations always being public.
+  Change by @torquestomp.
+
+* AutoAnnotation now precomputes parts of the `hashCode` that are constant
+  because they come from defaulted methods. This avoids warnings about integer
+  overflow from tools that check that.
+
+* If a property is called `oAuth()`, its setter can be called
+  `setOAuth(x)`. Previously it had to be `setoAuth(x)`, which is still allowed.
+
+## Bugs fixed
+
+* AutoAnnotation now correctly handles types like `Class<? extends
+  Annotation>[]`. Previously it would try to create a generic array, which Java
+  doesn't allow. Change by @lukesandberg.
+
+* We guard against spurious exceptions due to a JDK bug in reading resources
+  from jars. (#365)
+
+* We don't propagate an exception if a corrupt jar is found in extension
+  loading.
+
+* AutoValue is ready for Java 9, where public classes are not necessarily
+  accessible, and javax.annotation.Generated is not necessarily present.
+
+* AutoValue now works correctly even if the version of AutoValue in the
+  `-classpath` is older than the one in the `-processorpath`.
+
+* Builders now behave correctly when there is a non-optional property called
+  `missing`. Previously a variable-hiding problem meant that we didn't detect
+  when it wasn't set.
+
+* If `@AutoValue class Foo` has a builder, we always generated two constructors,
+  `Builder()` and `Builder(Foo)`, but we only used the second one if `Foo` had a
+  `toBuilder()` method. Now we only generate that constructor if it is
+  needed. That avoids warnings about unused code.
+
+* `@AutoAnnotation` now works when the annotation and the factory method are in
+  the default (unnamed) package.
+
+## 1.2 → 1.3
+
+### Functional changes
+
+* Support for TYPE_USE `@Nullable`.
+  This is https://github.com/google/auto/pull/293 by @brychcy.
+
+* Restructured the code in AutoValueProcessor for handling extensions, to get
+  rid of warnings about abstract methods when those methods are going to be
+  implemented by an extension, and to fix a bug where extensions would not work
+  right if there was a toBuilder() method. Some of the code in this change is
+  based on https://github.com/google/auto/pull/299 by @rharter.
+
+* Added support for "optional getters", where a getter in an AutoValue Builder
+  can have type `Optional<T>` and it will return `Optional.of(x)` where `x` is
+  the value that has been set in the Builder, or `Optional.empty()` if no value
+  has been set.
+
+* In AutoValue builders, added support for setting a property of type
+  `Optional<T>` via a setter with an argument of type `T`.
+
+* Added logic to AutoValue to detect the confusing case where you think you
+  are using JavaBeans conventions (like getFoo()) but you aren't because at
+  least one method isn't.
+
+* Added a README.md describing EscapeVelocity.
+
+### Bugs fixed
+
+* Allow an `@AutoValue.Builder` to extend a parent builder using the `<B extends
+  Builder<B>>` idiom.
+
+* AutoAnnotation now factors in package names when detecting
+  overloads. Previously it treated all annotations with the same SimpleName as
+  being overload attempts.
+
+* Removed an inaccurate javadoc reference, which referred to an
+  artifact from an earlier draft version of the Extensions API. This is
+  https://github.com/google/auto/pull/322 by @lucastsa.
+
+## 1.1 → 1.2
+
+### Functional changes
+
+  * A **provisional** extension API has been introduced. This **will change**
+    in a later release. If you want to use it regardless, see the
+    [AutoValueExtension] class.
+
+  * Properties of primitive array type (e.g. `byte[]`) are no longer cloned
+    when read. If your `@AutoValue` class includes an array property, by default
+    it will get a compiler warning, which can be suppressed with
+    `@SuppressWarnings("mutable")`.
+
+  * An `@AutoValue.Builder` type can now define both the setter and builder
+    methods like so:
+
+    ```
+      ...
+      abstract void setStrings(ImmutableList<String>);
+      abstract ImmutableList.Builder<String> stringsBuilder();
+      ...
+    ```
+    At runtime, if `stringsBuilder()...` is called then it is an error to call
+    `setStrings(...)` afterwards.
+
+  * The classes in the autovalue jar are now shaded with a `$` so they never
+    appear in IDE autocompletion.
+
+  * AutoValue now uses its own implementation of a subset of Apache Velocity,
+    so there will no longer be problems with interference between the Velocity
+    that was bundled with AutoValue and other versions that might be present.
+
+### Bugs fixed
+
+  * Explicit check for nested `@AutoValue` classes being private, or not being
+    static. Otherwise the compiler errors could be hard to understand,
+    especially in IDEs.
+
+  * An Eclipse bug that could occasionally lead to exceptions in the IDE has
+    been fixed (GitHub issue #200).
+
+  * Fixed a bug where AutoValue generated incorrect code if a method with a
+    type parameter was inherited by a class that supplies a concrete type for
+    that parameter. For example `StringIterator implements Iterator<String>`,
+    where the type of `next()` is String, not `T`.
+
+  * In `AutoValueProcessor`, fixed an exception that happened if the same
+    abstract method was inherited from more than one parent (Github Issue #267).
+
+  * AutoValue now works correctly in an environment where
+    `@javax.annotation.Generated` does not exist.
+
+  * Properties marked `@Nullable` now get `@Nullable` on the corresponding
+    constructor parameters in the generated class.
+
+## 1.0 → 1.1
+
+### Functional changes
+
+  * Adds builders to AutoValue. Builders are nested classes annotated with
+    `@AutoValue.Builder`.
+
+  * Annotates constructor parameters with `@Nullable` if the corresponding
+    property methods are `@Nullable`.
+
+  * Changes Maven shading so org.apache.commons is shaded.
+
+  * Copies a `@GwtCompatible` annotation from the `@AutoValue` class to its
+    implementation subclass.
+
+### Bugs fixed
+
+  * Works around a bug in the Eclipse compiler that meant that annotations
+    would be incorrectly copied from `@AutoValue` methods to their
+    implementations.
+
+## 1.0 (Initial Release)
+
+  * Allows automatic generation of value type implementations
+
+    See [the AutoValue User's Guide](userguide/index.md)
+
+
+[AutoValueExtension]: src/main/java/com/google/auto/value/extension/AutoValueExtension.java
diff --git a/value/README.md b/value/README.md
new file mode 100644
index 0000000..f6c00c3
--- /dev/null
+++ b/value/README.md
@@ -0,0 +1,25 @@
+# AutoValue
+
+*Generated immutable value classes for Java 7+* <br />
+***Kevin Bourrillion, Éamonn McManus*** <br />
+**Google, Inc.**
+
+**Value classes** are extremely common in Java projects. These are classes for
+which you want to treat any two instances with suitably equal field values as
+interchangeable. That's right: we're talking about those classes where you wind
+up implementing `equals`, `hashCode` and `toString` in a bloated, repetitive,
+formulaic yet error-prone fashion.
+
+Writing these methods the first time is not too bad, with the aid of a few
+helper methods and IDE templates. But once written they continue to burden
+reviewers, editors and future readers. Their wide expanses of boilerplate
+sharply decrease the signal-to-noise ratio of your code... and they love to
+harbor hard-to-spot bugs.
+
+AutoValue provides an easier way to create immutable value classes, with a lot
+less code and less room for error, while **not restricting your freedom** to
+code almost any aspect of your class exactly the way you want it.
+
+For more information, consult the
+[detailed
+documentation](userguide/index.md)
diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml
new file mode 100644
index 0000000..d0d4a6b
--- /dev/null
+++ b/value/annotations/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2012 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.google.auto.value</groupId>
+    <artifactId>auto-value-parent</artifactId>
+    <version>HEAD-SNAPSHOT</version>
+  </parent>
+
+  <groupId>com.google.auto.value</groupId>
+  <artifactId>auto-value-annotations</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>AutoValue Annotations</name>
+  <description>
+    Immutable value-type code generation for Java 1.6+.
+  </description>
+  <url>https://github.com/google/auto/tree/master/value</url>
+
+  <properties>
+    <java.version>1.7</java.version>
+  </properties>
+
+  <scm>
+    <url>http://github.com/google/auto</url>
+    <connection>scm:git:git://github.com/google/auto.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/google/auto.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+  <build>
+    <sourceDirectory>../src/main/java</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <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>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-invoker-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+  <profiles>
+    <profile>
+      <id>disable-java8-doclint</id>
+      <activation>
+        <jdk>[1.8,)</jdk>
+      </activation>
+      <properties>
+        <additionalparam>-Xdoclint:none</additionalparam>
+      </properties>
+    </profile>
+  </profiles>
+</project>
diff --git a/value/pom.xml b/value/pom.xml
new file mode 100644
index 0000000..9f8be5a
--- /dev/null
+++ b/value/pom.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2012 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+
+  <groupId>com.google.auto.value</groupId>
+  <artifactId>auto-value-parent</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>AutoValue Parent</name>
+  <description>
+    Immutable value-type code generation for Java 7+.
+  </description>
+  <packaging>pom</packaging>
+  <url>https://github.com/google/auto/tree/master/value</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <java.version>1.8</java.version>
+    <guava.version>28.1-jre</guava.version>
+    <truth.version>1.0.1</truth.version>
+  </properties>
+
+  <scm>
+    <url>http://github.com/google/auto</url>
+    <connection>scm:git:git://github.com/google/auto.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/google/auto.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <issueManagement>
+    <system>GitHub Issues</system>
+    <url>http://github.com/google/auto/issues</url>
+  </issueManagement>
+
+  <licenses>
+    <license>
+      <name>Apache 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+    </license>
+  </licenses>
+
+  <organization>
+    <name>Google LLC</name>
+    <url>http://www.google.com</url>
+  </organization>
+
+  <modules>
+    <module>annotations</module>
+    <module>processor</module>
+    <module>src/it/functional</module>
+    <module>src/it/gwtserializer</module>
+  </modules>
+
+  <dependencyManagement>
+    <dependencies>
+      <!-- main dependencies -->
+
+      <dependency>
+        <groupId>com.google.guava</groupId>
+        <artifactId>guava</artifactId>
+        <version>${guava.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.guava</groupId>
+        <artifactId>guava-gwt</artifactId>
+        <version>${guava.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.squareup</groupId>
+        <artifactId>javapoet</artifactId>
+        <version>1.11.1</version>
+      </dependency>
+
+      <!-- test dependencies -->
+
+      <dependency>
+        <groupId>com.google.code.findbugs</groupId>
+        <artifactId>jsr305</artifactId>
+        <version>3.0.2</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.guava</groupId>
+        <artifactId>guava-testlib</artifactId>
+        <version>${guava.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.testing.compile</groupId>
+        <artifactId>compile-testing</artifactId>
+        <version>0.18</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.truth</groupId>
+        <artifactId>truth</artifactId>
+        <version>${truth.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.truth.extensions</groupId>
+        <artifactId>truth-java8-extension</artifactId>
+        <version>${truth.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>4.13</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.velocity</groupId>
+        <artifactId>velocity</artifactId>
+        <version>1.7</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.7.0</version>
+          <configuration>
+            <source>${java.version}</source>
+            <target>${java.version}</target>
+            <compilerArgument>-Xlint:all</compilerArgument>
+            <showWarnings>true</showWarnings>
+            <showDeprecation>true</showDeprecation>
+          </configuration>
+          <dependencies>
+            <dependency>
+              <groupId>org.codehaus.plexus</groupId>
+              <artifactId>plexus-java</artifactId>
+              <version>0.9.4</version>
+            </dependency>
+          </dependencies>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>3.0.2</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-invoker-plugin</artifactId>
+          <version>3.0.1</version>
+          <configuration>
+            <addTestClassPath>true</addTestClassPath>
+            <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
+            <pomIncludes>
+              <pomInclude>*/pom.xml</pomInclude>
+            </pomIncludes>
+            <streamLogs>true</streamLogs>
+          </configuration>
+          <executions>
+            <execution>
+              <id>integration-test</id>
+              <goals>
+                <goal>install</goal>
+                <goal>run</goal>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
+        <plugin>
+          <groupId>org.immutables.tools</groupId>
+          <artifactId>maven-shade-plugin</artifactId>
+          <version>4</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+</project>
diff --git a/value/processor/pom.xml b/value/processor/pom.xml
new file mode 100644
index 0000000..0aa9ec8
--- /dev/null
+++ b/value/processor/pom.xml
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2012 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.google.auto.value</groupId>
+    <artifactId>auto-value-parent</artifactId>
+    <version>HEAD-SNAPSHOT</version>
+  </parent>
+
+  <groupId>com.google.auto.value</groupId>
+  <artifactId>auto-value</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>AutoValue Processor</name>
+  <description>
+    Immutable value-type code generation for Java 1.6+.
+  </description>
+  <url>https://github.com/google/auto/tree/master/value</url>
+
+  <scm>
+    <url>http://github.com/google/auto</url>
+    <connection>scm:git:git://github.com/google/auto.git</connection>
+    <developerConnection>scm:git:ssh://git@github.com/google/auto.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.auto</groupId>
+      <artifactId>auto-common</artifactId>
+      <version>0.10</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto.service</groupId>
+      <artifactId>auto-service</artifactId>
+      <version>1.0-rc6</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.errorprone</groupId>
+      <artifactId>error_prone_annotations</artifactId>
+      <version>2.3.3</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.escapevelocity</groupId>
+      <artifactId>escapevelocity</artifactId>
+      <version>0.9.1</version>
+    </dependency>
+    <dependency>
+      <groupId>net.ltgt.gradle.incap</groupId>
+      <artifactId>incap</artifactId>
+      <version>0.2</version>
+    </dependency>
+    <dependency>
+      <groupId>net.ltgt.gradle.incap</groupId>
+      <artifactId>incap-processor</artifactId>
+      <version>0.2</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.squareup</groupId>
+      <artifactId>javapoet</artifactId>
+    </dependency>
+    <!-- test dependencies -->
+    <dependency>
+      <groupId>com.google.auto.value</groupId>
+      <artifactId>auto-value-annotations</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.velocity</groupId>
+      <artifactId>velocity</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava-testlib</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth.extensions</groupId>
+      <artifactId>truth-java8-extension</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.testing.compile</groupId>
+      <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>
+    <sourceDirectory>../src/main/java</sourceDirectory>
+    <testSourceDirectory>../src/test/java</testSourceDirectory>
+
+    <resources>
+      <resource>
+        <directory>../src/main/java</directory>
+        <includes>
+          <include>**/*.vm</include>
+        </includes>
+      </resource>
+    </resources>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <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>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-invoker-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.immutables.tools</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <minimizeJar>true</minimizeJar>
+              <artifactSet>
+                <excludes>
+                  <exclude>com.google.code.findbugs:jsr305</exclude>
+                </excludes>
+              </artifactSet>
+              <relocations>
+                <relocation>
+                  <pattern>org.objectweb</pattern>
+                  <shadedPattern>autovalue.shaded.org.objectweb$</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>com.google</pattern>
+                  <shadedPattern>autovalue.shaded.com.google$</shadedPattern>
+                  <excludes>
+                    <exclude>com.google.auto.value.**</exclude>
+                  </excludes>
+                </relocation>
+                <relocation>
+                  <pattern>com.squareup.javapoet</pattern>
+                  <shadedPattern>autovalue.shaded.com.squareup.javapoet$</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>net.ltgt.gradle.incap</pattern>
+                  <shadedPattern>autovalue.shaded.net.ltgt.gradle.incap$</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>org.checkerframework</pattern>
+                  <shadedPattern>autovalue.shaded.org.checkerframework$</shadedPattern>
+                </relocation>
+              </relocations>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <profiles>
+    <profile>
+      <id>disable-java8-doclint</id>
+      <activation>
+        <jdk>[1.8,)</jdk>
+      </activation>
+      <properties>
+        <additionalparam>-Xdoclint:none</additionalparam>
+      </properties>
+    </profile>
+  </profiles>
+</project>
diff --git a/value/src/it/functional/invoker.properties b/value/src/it/functional/invoker.properties
new file mode 100644
index 0000000..018eaf0
--- /dev/null
+++ b/value/src/it/functional/invoker.properties
@@ -0,0 +1,4 @@
+invoker.goals = clean verify
+
+invoker.profiles.1 =
+invoker.profiles.2 = eclipse
\ No newline at end of file
diff --git a/value/src/it/functional/pom.xml b/value/src/it/functional/pom.xml
new file mode 100644
index 0000000..4157da9
--- /dev/null
+++ b/value/src/it/functional/pom.xml
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2013 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.
+-->
+<!-- TODO(gak): see if we can manage these dependencies any better -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.google.auto.value</groupId>
+    <artifactId>auto-value-parent</artifactId>
+    <version>HEAD-SNAPSHOT</version>
+    <relativePath>../../../pom.xml</relativePath>
+  </parent>
+  <url>https://github.com/google/auto/tree/master/value</url>
+
+  <groupId>com.google.auto.value.it.functional</groupId>
+  <artifactId>functional</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>Auto-Value Functional Integration Test</name>
+  <properties>
+    <exclude.tests>this-matches-nothing</exclude.tests>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>com.google.auto.value</groupId>
+      <artifactId>auto-value-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto.value</groupId>
+      <artifactId>auto-value</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto.service</groupId>
+      <artifactId>auto-service</artifactId>
+      <version>1.0-rc6</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.gwt</groupId>
+      <artifactId>gwt-user</artifactId>
+      <version>2.8.2</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava-testlib</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth.extensions</groupId>
+      <artifactId>truth-java8-extension</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.testing.compile</groupId>
+      <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>
+    <dependency>
+      <groupId>org.eclipse.jdt</groupId>
+      <artifactId>ecj</artifactId>
+      <version>3.20.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.escapevelocity</groupId>
+      <artifactId>escapevelocity</artifactId>
+      <version>0.9.1</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+       <version>3.0.2</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.7.0</version>
+        <dependencies>
+          <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-java</artifactId>
+            <version>0.9.4</version>
+          </dependency>
+        </dependencies>
+        <configuration>
+          <source>${java.specification.version}</source>
+          <target>${java.specification.version}</target>
+          <compilerArgs>
+            <arg>-Xlint:all</arg>
+            <arg>-encoding</arg>
+            <arg>utf8</arg>
+          </compilerArgs>
+          <showWarnings>true</showWarnings>
+          <showDeprecation>true</showDeprecation>
+          <testExcludes>
+            <exclude>${exclude.tests}</exclude>
+          </testExcludes>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <version>2.7</version>
+        <configuration>
+          <!-- Build/test, but don't deploy -->
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>eclipse</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <version>3.7.0</version>
+            <dependencies>
+              <dependency>
+                <groupId>org.codehaus.plexus</groupId>
+                <artifactId>plexus-java</artifactId>
+                <version>0.9.4</version>
+              </dependency>
+            </dependencies>
+            <configuration>
+              <source>${java.specification.version}</source>
+              <target>${java.specification.version}</target>
+              <compilerArgs>
+                <arg>-Xlint:all</arg>
+                <arg>-encoding</arg>
+                <arg>utf8</arg>
+              </compilerArgs>
+              <showWarnings>true</showWarnings>
+              <showDeprecation>true</showDeprecation>
+              <testExcludes>
+                <exclude>${exclude.tests}</exclude>
+              </testExcludes>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+    <profile>
+      <id>exclude-java8-tests</id>
+      <activation>
+        <jdk>(,1.7]</jdk>
+      </activation>
+      <properties>
+        <exclude.tests>**/AutoValueJava8Test.java</exclude.tests>
+      </properties>
+    </profile>
+  </profiles>
+</project>
diff --git a/value/src/it/functional/src/main/java/PackagelessNestedValueType.java b/value/src/it/functional/src/main/java/PackagelessNestedValueType.java
new file mode 100644
index 0000000..77b1939
--- /dev/null
+++ b/value/src/it/functional/src/main/java/PackagelessNestedValueType.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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.
+ */
+
+import com.google.auto.value.AutoValue;
+import java.util.Map;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+public class PackagelessNestedValueType {
+  @AutoValue
+  public abstract static class Nested {
+    abstract Map<Integer, String> numberNames();
+
+    public static Nested create(Map<Integer, String> numberNames) {
+      return new AutoValue_PackagelessNestedValueType_Nested(numberNames);
+    }
+  }
+}
diff --git a/value/src/it/functional/src/main/java/PackagelessValueType.java b/value/src/it/functional/src/main/java/PackagelessValueType.java
new file mode 100644
index 0000000..62b172e
--- /dev/null
+++ b/value/src/it/functional/src/main/java/PackagelessValueType.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 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.
+ */
+
+import com.google.auto.value.AutoValue;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/**
+ * Simple package-less value type for tests.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@AutoValue
+public abstract class PackagelessValueType {
+  // The getters here are formatted as an illustration of what getters typically look in real
+  // classes. In particular they have doc comments.
+
+  /** @return A string that is a nullable string. */
+  @Nullable
+  public abstract String string();
+
+  /** @return An integer that is an integer. */
+  public abstract int integer();
+
+  /** @return A non-null map where the keys are strings and the values are longs. */
+  public abstract Map<String, Long> map();
+
+  public static PackagelessValueType create(
+      @Nullable String string, int integer, Map<String, Long> map) {
+    // The subclass AutoValue_PackagelessValueType is created by the annotation processor that is
+    // triggered by the presence of the @AutoValue annotation. It has a constructor for each
+    // of the abstract getter methods here, in order. The constructor stashes the values here
+    // in private final fields, and each method is implemented to return the value of the
+    // corresponding field.
+    return new AutoValue_PackagelessValueType(string, integer, map);
+  }
+}
diff --git a/value/src/it/functional/src/main/java/com/google/auto/value/NestedValueType.java b/value/src/it/functional/src/main/java/com/google/auto/value/NestedValueType.java
new file mode 100644
index 0000000..cd8b3b7
--- /dev/null
+++ b/value/src/it/functional/src/main/java/com/google/auto/value/NestedValueType.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 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;
+
+import java.util.Map;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+public class NestedValueType {
+  @AutoValue
+  public abstract static class Nested {
+    abstract Map<Integer, String> numberNames();
+
+    public static Nested create(Map<Integer, String> numberNames) {
+      return new AutoValue_NestedValueType_Nested(numberNames);
+    }
+  }
+}
diff --git a/value/src/it/functional/src/main/java/com/google/auto/value/SimpleValueType.java b/value/src/it/functional/src/main/java/com/google/auto/value/SimpleValueType.java
new file mode 100644
index 0000000..9d6c6c7
--- /dev/null
+++ b/value/src/it/functional/src/main/java/com/google/auto/value/SimpleValueType.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 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;
+
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/**
+ * Simple value type for tests.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@AutoValue
+public abstract class SimpleValueType {
+  // The getters here are formatted as an illustration of what getters typically look in real
+  // classes. In particular they have doc comments.
+
+  /** Returns a string that is a nullable string. */
+  @Nullable
+  public abstract String string();
+
+  /** Returns an integer that is an integer. */
+  public abstract int integer();
+
+  /** Returns a non-null map where the keys are strings and the values are longs. */
+  public abstract Map<String, Long> map();
+
+  public static SimpleValueType create(
+      @Nullable String string, int integer, Map<String, Long> map) {
+    // The subclass AutoValue_SimpleValueType is created by the annotation processor that is
+    // triggered by the presence of the @AutoValue annotation. It has a constructor for each
+    // of the abstract getter methods here, in order. The constructor stashes the values here
+    // in private final fields, and each method is implemented to return the value of the
+    // corresponding field.
+    return new AutoValue_SimpleValueType(string, integer, map);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/PackagelessValueTypeTest.java b/value/src/it/functional/src/test/java/PackagelessValueTypeTest.java
new file mode 100644
index 0000000..0ed3846
--- /dev/null
+++ b/value/src/it/functional/src/test/java/PackagelessValueTypeTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 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.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.NullPointerTester;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class PackagelessValueTypeTest {
+  @Test
+  public void testPackagelessValueType() {
+    final String happy = "happy";
+    final int testInt = 23;
+    final Map<String, Long> testMap = ImmutableMap.of("happy", 23L);
+    PackagelessValueType simple = PackagelessValueType.create(happy, testInt, testMap);
+    assertSame(happy, simple.string());
+    assertEquals(testInt, simple.integer());
+    assertSame(testMap, simple.map());
+    assertEquals(
+        "PackagelessValueType{string=happy, integer=23, map={happy=23}}", simple.toString());
+    int expectedHashCode = 1;
+    expectedHashCode = (expectedHashCode * 1000003) ^ happy.hashCode();
+    expectedHashCode = (expectedHashCode * 1000003) ^ ((Object) testInt).hashCode();
+    expectedHashCode = (expectedHashCode * 1000003) ^ testMap.hashCode();
+    assertEquals(expectedHashCode, simple.hashCode());
+  }
+
+  @Test
+  public void testNestedValueType() {
+    ImmutableMap<Integer, String> numberNames = ImmutableMap.of(1, "un", 2, "deux");
+    PackagelessNestedValueType.Nested nested =
+        PackagelessNestedValueType.Nested.create(numberNames);
+    assertEquals(numberNames, nested.numberNames());
+  }
+
+  @Test
+  public void testNull() {
+    NullPointerTester tester = new NullPointerTester();
+    tester.testAllPublicStaticMethods(PackagelessValueType.class);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationDefaultsTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationDefaultsTest.java
new file mode 100644
index 0000000..8f00850
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationDefaultsTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2014 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;
+
+import static org.junit.Assert.assertTrue;
+
+import com.google.auto.value.enums.MyEnum;
+import com.google.common.testing.EqualsTester;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class AutoAnnotationDefaultsTest {
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface EverythingWithDefaults {
+    byte aByte() default 1;
+
+    short aShort() default 2;
+
+    int anInt() default 3;
+
+    long aLong() default Long.MAX_VALUE;
+
+    float aFloat() default Float.MAX_VALUE;
+
+    double aDouble() default -Double.MAX_VALUE;
+
+    char aChar() default '#';
+
+    boolean aBoolean() default true;
+
+    String aString() default "maybe\nmaybe not\n";
+
+    String spaces() default "  ( x ) , ( y )"; // ensure the formatter doesn't eat spaces
+
+    MyEnum anEnum() default MyEnum.TWO;
+
+    byte[] bytes() default {-1, 0, 1};
+
+    short[] shorts() default {-2, 0, 2};
+
+    int[] ints() default {};
+
+    long[] longs() default {-Long.MAX_VALUE};
+
+    float[] floats() default {
+      -Float.MIN_VALUE, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NaN,
+    };
+
+    double[] doubles() default {
+      -Double.MIN_VALUE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NaN,
+    };
+
+    char[] chars() default {'f', '\n', '\uffef'};
+
+    boolean[] booleans() default {false, true};
+
+    String[] strings() default {"", "\uffef\n"};
+
+    MyEnum[] enums() default {MyEnum.ONE, MyEnum.TWO};
+  }
+
+  @AutoAnnotation
+  static EverythingWithDefaults newEverythingWithDefaults() {
+    return new AutoAnnotation_AutoAnnotationDefaultsTest_newEverythingWithDefaults();
+  }
+
+  @Test
+  public void testDefaults() throws Exception {
+    @EverythingWithDefaults
+    class Annotated {}
+    EverythingWithDefaults expected = Annotated.class.getAnnotation(EverythingWithDefaults.class);
+    EverythingWithDefaults actual = newEverythingWithDefaults();
+
+    // Iterate over the annotation members to see if any differ. We could just compare expected and
+    // actual but if the comparison failed it could be hard to see exactly what differed.
+    StringBuilder differencesBuilder = new StringBuilder();
+    for (Method member : EverythingWithDefaults.class.getDeclaredMethods()) {
+      String name = member.getName();
+      Object expectedValue = member.invoke(expected);
+      Object actualValue = member.invoke(actual);
+      if (!equal(expectedValue, actualValue)) {
+        differencesBuilder
+            .append("For ")
+            .append(name)
+            .append(" expected <")
+            .append(string(expectedValue))
+            .append("> but was <")
+            .append(string(actualValue))
+            .append(">\n");
+      }
+    }
+    String differences = differencesBuilder.toString();
+    assertTrue(differences, differences.isEmpty());
+
+    // All the members were the same. Check that the equals and hashCode results say so too.
+    new EqualsTester().addEqualityGroup(expected, actual).testEquals();
+  }
+
+  private static boolean equal(Object x, Object y) {
+    return Arrays.deepEquals(new Object[] {x}, new Object[] {y});
+  }
+
+  private static String string(Object x) {
+    String s = Arrays.deepToString(new Object[] {x});
+    return s.substring(1, s.length() - 1);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java
new file mode 100644
index 0000000..ca1ef6b
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java
@@ -0,0 +1,614 @@
+/*
+ * Copyright 2014 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;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.auto.value.annotations.Empty;
+import com.google.auto.value.annotations.GwtArrays;
+import com.google.auto.value.annotations.StringValues;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.primitives.Ints;
+import com.google.common.testing.EqualsTester;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class AutoAnnotationTest {
+  @AutoAnnotation
+  private static StringValues newStringValues(String[] value) {
+    return new AutoAnnotation_AutoAnnotationTest_newStringValues(value);
+  }
+
+  @Empty
+  @StringValues("oops")
+  static class AnnotatedClass {}
+
+  @Test
+  public void testSimple() {
+    StringValues expectedStringValues = AnnotatedClass.class.getAnnotation(StringValues.class);
+    StringValues actualStringValues = newStringValues(new String[] {"oops"});
+    StringValues otherStringValues = newStringValues(new String[] {});
+    new EqualsTester()
+        .addEqualityGroup(expectedStringValues, actualStringValues)
+        .addEqualityGroup(otherStringValues)
+        .testEquals();
+  }
+
+  @Test
+  public void testArraysAreCloned() {
+    String[] array = {"Jekyll"};
+    StringValues stringValues = newStringValues(array);
+    array[0] = "Hyde";
+    assertEquals("Jekyll", stringValues.value()[0]);
+    stringValues.value()[0] = "Hyde";
+    assertEquals("Jekyll", stringValues.value()[0]);
+  }
+
+  @Test
+  public void testGwtArraysAreCloned() {
+    String[] strings = {"Jekyll"};
+    int[] ints = {2, 3, 5};
+    GwtArrays arrays = newGwtArrays(strings, ints);
+    assertEquals(ImmutableList.of("Jekyll"), ImmutableList.copyOf(arrays.strings()));
+    assertEquals(ImmutableList.of(2, 3, 5), Ints.asList(arrays.ints()));
+    strings[0] = "Hyde";
+    ints[0] = -1;
+    assertEquals(ImmutableList.of("Jekyll"), ImmutableList.copyOf(arrays.strings()));
+    assertEquals(ImmutableList.of(2, 3, 5), Ints.asList(arrays.ints()));
+  }
+
+  @AutoAnnotation
+  private static GwtArrays newGwtArrays(String[] strings, int[] ints) {
+    return new AutoAnnotation_AutoAnnotationTest_newGwtArrays(strings, ints);
+  }
+
+  @AutoAnnotation
+  private static StringValues newStringValuesVarArgs(String... value) {
+    return new AutoAnnotation_AutoAnnotationTest_newStringValuesVarArgs(value);
+  }
+
+  @Test
+  public void testSimpleVarArgs() {
+    StringValues expectedStringValues = AnnotatedClass.class.getAnnotation(StringValues.class);
+    StringValues actualStringValues = newStringValuesVarArgs("oops");
+    StringValues otherStringValues = newStringValuesVarArgs(new String[] {});
+    new EqualsTester()
+        .addEqualityGroup(expectedStringValues, actualStringValues)
+        .addEqualityGroup(otherStringValues)
+        .testEquals();
+  }
+
+  @AutoAnnotation
+  private static Empty newEmpty() {
+    return new AutoAnnotation_AutoAnnotationTest_newEmpty();
+  }
+
+  @Test
+  public void testEmpty() {
+    Empty expectedEmpty = AnnotatedClass.class.getAnnotation(Empty.class);
+    Empty actualEmpty = newEmpty();
+    new EqualsTester().addEqualityGroup(expectedEmpty, actualEmpty).testEquals();
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface Everything {
+    byte aByte();
+
+    short aShort();
+
+    int anInt();
+
+    long aLong();
+
+    float aFloat();
+
+    double aDouble();
+
+    char aChar();
+
+    boolean aBoolean();
+
+    String aString();
+
+    RetentionPolicy anEnum();
+
+    StringValues anAnnotation();
+
+    byte[] bytes();
+
+    short[] shorts();
+
+    int[] ints();
+
+    long[] longs();
+
+    float[] floats();
+
+    double[] doubles();
+
+    char[] chars();
+
+    boolean[] booleans();
+
+    String[] strings();
+
+    RetentionPolicy[] enums();
+
+    StringValues[] annotations();
+  }
+
+  @AutoAnnotation
+  static Everything newEverything(
+      byte aByte,
+      short aShort,
+      int anInt,
+      long aLong,
+      float aFloat,
+      double aDouble,
+      char aChar,
+      boolean aBoolean,
+      String aString,
+      RetentionPolicy anEnum,
+      StringValues anAnnotation,
+      byte[] bytes,
+      short[] shorts,
+      int[] ints,
+      long[] longs,
+      float[] floats,
+      double[] doubles,
+      char[] chars,
+      boolean[] booleans,
+      String[] strings,
+      RetentionPolicy[] enums,
+      StringValues[] annotations) {
+    return new AutoAnnotation_AutoAnnotationTest_newEverything(
+        aByte,
+        aShort,
+        anInt,
+        aLong,
+        aFloat,
+        aDouble,
+        aChar,
+        aBoolean,
+        aString,
+        anEnum,
+        anAnnotation,
+        bytes,
+        shorts,
+        ints,
+        longs,
+        floats,
+        doubles,
+        chars,
+        booleans,
+        strings,
+        enums,
+        annotations);
+  }
+
+  @AutoAnnotation
+  static Everything newEverythingCollections(
+      byte aByte,
+      short aShort,
+      int anInt,
+      long aLong,
+      float aFloat,
+      double aDouble,
+      char aChar,
+      boolean aBoolean,
+      String aString,
+      RetentionPolicy anEnum,
+      StringValues anAnnotation,
+      Collection<Byte> bytes,
+      List<Short> shorts,
+      ArrayList<Integer> ints,
+      Set<Long> longs,
+      SortedSet<Float> floats,
+      TreeSet<Double> doubles,
+      LinkedHashSet<Character> chars,
+      ImmutableCollection<Boolean> booleans,
+      ImmutableList<String> strings,
+      ImmutableSet<RetentionPolicy> enums,
+      Set<StringValues> annotations) {
+    return new AutoAnnotation_AutoAnnotationTest_newEverythingCollections(
+        aByte,
+        aShort,
+        anInt,
+        aLong,
+        aFloat,
+        aDouble,
+        aChar,
+        aBoolean,
+        aString,
+        anEnum,
+        anAnnotation,
+        bytes,
+        shorts,
+        ints,
+        longs,
+        floats,
+        doubles,
+        chars,
+        booleans,
+        strings,
+        enums,
+        annotations);
+  }
+
+  @Everything(
+      aByte = 1,
+      aShort = 2,
+      anInt = 3,
+      aLong = -4,
+      aFloat = Float.NaN,
+      aDouble = Double.NaN,
+      aChar = '#',
+      aBoolean = true,
+      aString = "maybe\nmaybe not\n",
+      anEnum = RetentionPolicy.RUNTIME,
+      anAnnotation = @StringValues("whatever"),
+      bytes = {5, 6},
+      shorts = {},
+      ints = {7},
+      longs = {8, 9},
+      floats = {10, 11},
+      doubles = {Double.NEGATIVE_INFINITY, -12.0, Double.POSITIVE_INFINITY},
+      chars = {'?', '!', '\n'},
+      booleans = {false, true, false},
+      strings = {"ver", "vers", "vert", "verre", "vair"},
+      enums = {RetentionPolicy.CLASS, RetentionPolicy.RUNTIME},
+      annotations = {@StringValues({}), @StringValues({"foo", "bar"})})
+  private static class AnnotatedWithEverything {}
+
+  // Get an instance of @Everything via reflection on the class AnnotatedWithEverything,
+  // fabricate an instance using newEverything that is supposed to be equal to it, and
+  // fabricate another instance using newEverything that is supposed to be different.
+  private static final Everything EVERYTHING_FROM_REFLECTION =
+      AnnotatedWithEverything.class.getAnnotation(Everything.class);
+  private static final Everything EVERYTHING_FROM_AUTO =
+      newEverything(
+          (byte) 1,
+          (short) 2,
+          3,
+          -4,
+          Float.NaN,
+          Double.NaN,
+          '#',
+          true,
+          "maybe\nmaybe not\n",
+          RetentionPolicy.RUNTIME,
+          newStringValues(new String[] {"whatever"}),
+          new byte[] {5, 6},
+          new short[] {},
+          new int[] {7},
+          new long[] {8, 9},
+          new float[] {10, 11},
+          new double[] {Double.NEGATIVE_INFINITY, -12.0, Double.POSITIVE_INFINITY},
+          new char[] {'?', '!', '\n'},
+          new boolean[] {false, true, false},
+          new String[] {"ver", "vers", "vert", "verre", "vair"},
+          new RetentionPolicy[] {RetentionPolicy.CLASS, RetentionPolicy.RUNTIME},
+          new StringValues[] {
+            newStringValues(new String[] {}), newStringValues(new String[] {"foo", "bar"}),
+          });
+  private static final Everything EVERYTHING_FROM_AUTO_COLLECTIONS =
+      newEverythingCollections(
+          (byte) 1,
+          (short) 2,
+          3,
+          -4,
+          Float.NaN,
+          Double.NaN,
+          '#',
+          true,
+          "maybe\nmaybe not\n",
+          RetentionPolicy.RUNTIME,
+          newStringValues(new String[] {"whatever"}),
+          Arrays.asList((byte) 5, (byte) 6),
+          Collections.<Short>emptyList(),
+          new ArrayList<Integer>(Collections.singleton(7)),
+          ImmutableSet.of(8L, 9L),
+          ImmutableSortedSet.of(10f, 11f),
+          new TreeSet<Double>(
+              ImmutableList.of(Double.NEGATIVE_INFINITY, -12.0, Double.POSITIVE_INFINITY)),
+          new LinkedHashSet<Character>(ImmutableList.of('?', '!', '\n')),
+          ImmutableList.of(false, true, false),
+          ImmutableList.of("ver", "vers", "vert", "verre", "vair"),
+          ImmutableSet.of(RetentionPolicy.CLASS, RetentionPolicy.RUNTIME),
+          ImmutableSet.of(
+              newStringValues(new String[] {}), newStringValues(new String[] {"foo", "bar"})));
+  private static final Everything EVERYTHING_ELSE_FROM_AUTO =
+      newEverything(
+          (byte) 0,
+          (short) 0,
+          0,
+          0,
+          0,
+          0,
+          '0',
+          false,
+          "",
+          RetentionPolicy.SOURCE,
+          newStringValues(new String[] {""}),
+          new byte[0],
+          new short[0],
+          new int[0],
+          new long[0],
+          new float[0],
+          new double[0],
+          new char[0],
+          new boolean[0],
+          new String[0],
+          new RetentionPolicy[0],
+          new StringValues[0]);
+  private static final Everything EVERYTHING_ELSE_FROM_AUTO_COLLECTIONS =
+      newEverythingCollections(
+          (byte) 0,
+          (short) 0,
+          0,
+          0,
+          0,
+          0,
+          '0',
+          false,
+          "",
+          RetentionPolicy.SOURCE,
+          newStringValues(new String[] {""}),
+          ImmutableList.<Byte>of(),
+          Collections.<Short>emptyList(),
+          new ArrayList<Integer>(),
+          Collections.<Long>emptySet(),
+          ImmutableSortedSet.<Float>of(),
+          new TreeSet<Double>(),
+          new LinkedHashSet<Character>(),
+          ImmutableSet.<Boolean>of(),
+          ImmutableList.<String>of(),
+          ImmutableSet.<RetentionPolicy>of(),
+          Collections.<StringValues>emptySet());
+
+  @Test
+  public void testEqualsAndHashCode() {
+    new EqualsTester()
+        .addEqualityGroup(
+            EVERYTHING_FROM_REFLECTION, EVERYTHING_FROM_AUTO, EVERYTHING_FROM_AUTO_COLLECTIONS)
+        .addEqualityGroup(EVERYTHING_ELSE_FROM_AUTO, EVERYTHING_ELSE_FROM_AUTO_COLLECTIONS)
+        .testEquals();
+  }
+
+  public static class IntList extends ArrayList<Integer> {
+    IntList(Collection<Integer> c) {
+      super(c);
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface IntArray {
+    int[] ints();
+  }
+
+  @IntArray(ints = {1, 2, 3})
+  private static class AnnotatedWithIntArray {}
+
+  @AutoAnnotation
+  static IntArray newIntArray(IntList ints) {
+    return new AutoAnnotation_AutoAnnotationTest_newIntArray(ints);
+  }
+
+  /**
+   * Test that we can represent a primitive array member with a parameter whose type is a collection
+   * of the corresponding wrapper type, even if the wrapper type is not explicitly a type parameter.
+   * Specifically, if the member is an {@code int[]} then obviously we can represent it as a {@code
+   * List<Integer>}, but here we test that we can also represent it as an {@code IntList}, which is
+   * only a {@code List<Integer>} by virtue of inheritance. This is a separate test rather than just
+   * putting an {@code IntList} parameter into {@link #newEverythingCollections} because we want to
+   * check that we are still able to detect the primitive wrapper type even though it's hidden in
+   * this way. We need to generate a helper method for every primitive wrapper.
+   */
+  @Test
+  public void testDerivedPrimitiveCollection() {
+    IntList intList = new IntList(ImmutableList.of(1, 2, 3));
+    IntArray actual = newIntArray(intList);
+    IntArray expected = AnnotatedWithIntArray.class.getAnnotation(IntArray.class);
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testToString() {
+    String expected =
+        "@com.google.auto.value.AutoAnnotationTest.Everything("
+            + "aByte=1, aShort=2, anInt=3, aLong=-4, aFloat=NaN, aDouble=NaN, aChar='#', "
+            + "aBoolean=true, aString=\"maybe\\nmaybe not\\n\", anEnum=RUNTIME, "
+            + "anAnnotation=@com.google.auto.value.annotations.StringValues([\"whatever\"]), "
+            + "bytes=[5, 6], shorts=[], ints=[7], longs=[8, 9], floats=[10.0, 11.0], "
+            + "doubles=[-Infinity, -12.0, Infinity], "
+            + "chars=['?', '!', '\\n'], "
+            + "booleans=[false, true, false], "
+            + "strings=[\"ver\", \"vers\", \"vert\", \"verre\", \"vair\"], "
+            + "enums=[CLASS, RUNTIME], "
+            + "annotations=["
+            + "@com.google.auto.value.annotations.StringValues([]), "
+            + "@com.google.auto.value.annotations.StringValues([\"foo\", \"bar\"])"
+            + "]"
+            + ")";
+    assertEquals(expected, EVERYTHING_FROM_AUTO.toString());
+    assertEquals(expected, EVERYTHING_FROM_AUTO_COLLECTIONS.toString());
+  }
+
+  @Test
+  public void testStringQuoting() {
+    StringValues instance =
+        newStringValues(
+            new String[] {
+              "", "\r\n", "hello, world", "Éamonn", "\007\uffef",
+            });
+    String expected =
+        "@com.google.auto.value.annotations.StringValues("
+            + "[\"\", \"\\r\\n\", \"hello, world\", \"Éamonn\", \"\\007\\uffef\"])";
+    assertEquals(expected, instance.toString());
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface AnnotationsAnnotation {
+    Class<? extends Annotation>[] value();
+  }
+
+  @AnnotationsAnnotation(AnnotationsAnnotation.class)
+  static class AnnotatedWithAnnotationsAnnotation {}
+
+  @AutoAnnotation
+  static AnnotationsAnnotation newAnnotationsAnnotation(List<Class<? extends Annotation>> value) {
+    return new AutoAnnotation_AutoAnnotationTest_newAnnotationsAnnotation(value);
+  }
+
+  @Test
+  public void testGenericArray() {
+    AnnotationsAnnotation generated =
+        newAnnotationsAnnotation(
+            ImmutableList.<Class<? extends Annotation>>of(AnnotationsAnnotation.class));
+    AnnotationsAnnotation fromReflect =
+        AnnotatedWithAnnotationsAnnotation.class.getAnnotation(AnnotationsAnnotation.class);
+    assertEquals(fromReflect, generated);
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface ClassesAnnotation {
+    Class<?>[] value();
+  }
+
+  @ClassesAnnotation(AnnotationsAnnotation.class)
+  static class AnnotatedWithClassesAnnotation {}
+
+  @AutoAnnotation
+  static ClassesAnnotation newClassesAnnotation(List<Class<?>> value) {
+    return new AutoAnnotation_AutoAnnotationTest_newClassesAnnotation(value);
+  }
+
+  @Test
+  public void testWildcardArray() {
+    ClassesAnnotation generated =
+        newClassesAnnotation(Arrays.<Class<?>>asList(AnnotationsAnnotation.class));
+    ClassesAnnotation fromReflect =
+        AnnotatedWithClassesAnnotation.class.getAnnotation(ClassesAnnotation.class);
+    assertEquals(fromReflect, generated);
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface IntegersAnnotation {
+    int one() default Integer.MAX_VALUE;
+
+    int two() default Integer.MAX_VALUE;
+
+    int three();
+  }
+
+  @IntegersAnnotation(three = 23)
+  static class AnnotatedWithIntegersAnnotation {}
+
+  @AutoAnnotation
+  static IntegersAnnotation newIntegersAnnotation(int three) {
+    return new AutoAnnotation_AutoAnnotationTest_newIntegersAnnotation(three);
+  }
+
+  @Test
+  public void testConstantOverflowInHashCode() {
+    IntegersAnnotation generated = newIntegersAnnotation(23);
+    IntegersAnnotation fromReflect =
+        AnnotatedWithIntegersAnnotation.class.getAnnotation(IntegersAnnotation.class);
+    new EqualsTester().addEqualityGroup(generated, fromReflect).testEquals();
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface EverythingWithDefaults {
+    byte aByte() default 5;
+
+    short aShort() default 17;
+
+    int anInt() default 23;
+
+    long aLong() default 1729;
+
+    float aFloat() default 5;
+
+    double aDouble() default 17;
+
+    char aChar() default 'x';
+
+    boolean aBoolean() default true;
+
+    String aString() default "whatever";
+
+    RetentionPolicy anEnum() default RetentionPolicy.CLASS;
+    // We don't yet support defaulting annotation values.
+    // StringValues anAnnotation() default @StringValues({"foo", "bar"});
+    byte[] bytes() default {1, 2};
+
+    short[] shorts() default {3, 4};
+
+    int[] ints() default {5, 6};
+
+    long[] longs() default {7, 8};
+
+    float[] floats() default {9, 10};
+
+    double[] doubles() default {11, 12};
+
+    char[] chars() default {'D', 'E'};
+
+    boolean[] booleans() default {true, false};
+
+    String[] strings() default {"vrai", "faux"};
+
+    RetentionPolicy[] enums() default {RetentionPolicy.SOURCE, RetentionPolicy.CLASS};
+    // We don't yet support defaulting annotation values.
+    // StringValues[] annotations() default {
+    //   @StringValues({"foo", "bar"}), @StringValues({"baz", "buh"})
+    // };
+  }
+
+  @EverythingWithDefaults
+  static class AnnotatedWithEverythingWithDefaults {}
+
+  @AutoAnnotation
+  static EverythingWithDefaults newEverythingWithDefaults() {
+    return new AutoAnnotation_AutoAnnotationTest_newEverythingWithDefaults();
+  }
+
+  @Test
+  public void testDefaultedValues() {
+    EverythingWithDefaults generated = newEverythingWithDefaults();
+    EverythingWithDefaults fromReflect =
+        AnnotatedWithEverythingWithDefaults.class.getAnnotation(EverythingWithDefaults.class);
+    new EqualsTester().addEqualityGroup(generated, fromReflect).testEquals();
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfJava8Test.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfJava8Test.java
new file mode 100644
index 0000000..2695bdf
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfJava8Test.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for Java8-specific {@code @AutoOneOf} behaviour.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class AutoOneOfJava8Test {
+  @AutoOneOf(EqualsNullable.Kind.class)
+  public abstract static class EqualsNullable {
+
+    @Target(ElementType.TYPE_USE)
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Nullable {}
+
+    public enum Kind {
+      THING
+    }
+
+    public abstract Kind kind();
+
+    public abstract String thing();
+
+    public static EqualsNullable ofThing(String thing) {
+      return AutoOneOf_AutoOneOfJava8Test_EqualsNullable.thing(thing);
+    }
+
+    @Override
+    public abstract boolean equals(@Nullable Object x);
+
+    @Override
+    public abstract int hashCode();
+  }
+
+  /**
+   * Tests that a type annotation on the parameter of {@code equals(Object)} is copied into the
+   * implementation class.
+   */
+  @Test
+  public void equalsNullable() throws ReflectiveOperationException {
+    EqualsNullable x = EqualsNullable.ofThing("foo");
+    Class<? extends EqualsNullable> c = x.getClass();
+    Method equals = c.getMethod("equals", Object.class);
+    assertThat(equals.getDeclaringClass()).isNotSameInstanceAs(EqualsNullable.class);
+    AnnotatedType parameterType = equals.getAnnotatedParameterTypes()[0];
+    assertThat(parameterType.isAnnotationPresent(EqualsNullable.Nullable.class)).isTrue();
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java
new file mode 100644
index 0000000..1b58728
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright 2018 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.testing.EqualsTester;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class AutoOneOfTest {
+  @AutoValue
+  public abstract static class Dog {
+    public abstract String name();
+
+    public static Dog create(String name) {
+      return new AutoValue_AutoOneOfTest_Dog(name);
+    }
+
+    public void bark() {}
+  }
+
+  @AutoValue
+  public abstract static class Cat {
+    public static Cat create() {
+      return new AutoValue_AutoOneOfTest_Cat();
+    }
+
+    public void meow() {}
+  }
+
+  @AutoValue
+  public abstract static class TigerShark {
+    public static TigerShark create() {
+      return new AutoValue_AutoOneOfTest_TigerShark();
+    }
+
+    public void chomp() {}
+  }
+
+  @AutoOneOf(Pet.Kind.class)
+  public abstract static class Pet {
+
+    public static Pet create(Dog dog) {
+      return AutoOneOf_AutoOneOfTest_Pet.dog(dog);
+    }
+
+    public static Pet create(Cat cat) {
+      return AutoOneOf_AutoOneOfTest_Pet.cat(cat);
+    }
+
+    public static Pet create(TigerShark shark) {
+      return AutoOneOf_AutoOneOfTest_Pet.tigerShark(shark);
+    }
+
+    public abstract Dog dog();
+
+    public abstract Cat cat();
+
+    public abstract TigerShark tigerShark();
+
+    public enum Kind {
+      DOG,
+      CAT,
+      TIGER_SHARK
+    }
+
+    public abstract Kind getKind();
+  }
+
+  @Test
+  public void equality() {
+    Dog marvin1 = Dog.create("Marvin");
+    Pet petMarvin1 = Pet.create(marvin1);
+    Dog marvin2 = Dog.create("Marvin");
+    Pet petMarvin2 = Pet.create(marvin2);
+    Dog isis = Dog.create("Isis");
+    Pet petIsis = Pet.create(isis);
+    Cat cat = Cat.create();
+    new EqualsTester()
+        .addEqualityGroup(marvin1, marvin2)
+        .addEqualityGroup(petMarvin1, petMarvin2)
+        .addEqualityGroup(petIsis)
+        .addEqualityGroup(cat)
+        .addEqualityGroup("foo")
+        .testEquals();
+  }
+
+  @Test
+  public void getCorrectType() {
+    Dog marvin = Dog.create("Marvin");
+    Pet petMarvin = Pet.create(marvin);
+    assertThat(petMarvin.dog()).isSameInstanceAs(marvin);
+  }
+
+  @Test
+  public void getWrongType() {
+    Cat cat = Cat.create();
+    Pet petCat = Pet.create(cat);
+    try {
+      petCat.tigerShark();
+      fail();
+    } catch (UnsupportedOperationException e) {
+      assertThat(e).hasMessageThat().containsMatch("(?i:cat)");
+    }
+  }
+
+  @Test
+  public void string() {
+    Dog marvin = Dog.create("Marvin");
+    Pet petMarvin = Pet.create(marvin);
+    assertThat(petMarvin.toString()).isEqualTo("Pet{dog=Dog{name=Marvin}}");
+  }
+
+  @Test
+  public void getKind() {
+    Dog marvin = Dog.create("Marvin");
+    Pet petMarvin = Pet.create(marvin);
+    Cat cat = Cat.create();
+    Pet petCat = Pet.create(cat);
+    TigerShark shark = TigerShark.create();
+    Pet petShark = Pet.create(shark);
+    assertThat(petMarvin.getKind()).isEqualTo(Pet.Kind.DOG);
+    assertThat(petCat.getKind()).isEqualTo(Pet.Kind.CAT);
+    assertThat(petShark.getKind()).isEqualTo(Pet.Kind.TIGER_SHARK);
+  }
+
+  @Test
+  public void cannotBeNull() {
+    try {
+      Pet.create((Dog) null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  // Package-private case.
+
+  @AutoOneOf(IntegerOrString.Kind.class)
+  abstract static class IntegerOrString {
+    enum Kind {
+      INTEGER,
+      STRING
+    }
+
+    abstract Kind getKind();
+
+    abstract int integer();
+
+    abstract String string();
+
+    static IntegerOrString of(int x) {
+      return AutoOneOf_AutoOneOfTest_IntegerOrString.integer(x);
+    }
+
+    static IntegerOrString of(String x) {
+      return AutoOneOf_AutoOneOfTest_IntegerOrString.string(x);
+    }
+  }
+
+  @Test
+  public void packagePrivate() {
+    IntegerOrString integer = IntegerOrString.of(23);
+    IntegerOrString string = IntegerOrString.of("23");
+    assertThat(integer.getKind()).isEqualTo(IntegerOrString.Kind.INTEGER);
+    assertThat(string.getKind()).isEqualTo(IntegerOrString.Kind.STRING);
+    assertThat(integer.integer()).isEqualTo(23);
+    assertThat(string.string()).isEqualTo("23");
+    assertThat(integer).isNotEqualTo(string);
+    try {
+      integer.string();
+      fail();
+    } catch (UnsupportedOperationException e) {
+      assertThat(e).hasMessageThat().containsMatch("(?i:integer)");
+    }
+  }
+
+  @AutoOneOf(Pet.Kind.class)
+  public abstract static class PetWithGet {
+    public abstract Dog getDog();
+
+    public abstract Cat getCat();
+
+    public abstract TigerShark getTigerShark();
+
+    public static PetWithGet create(Dog dog) {
+      return AutoOneOf_AutoOneOfTest_PetWithGet.dog(dog);
+    }
+
+    public static PetWithGet create(Cat cat) {
+      return AutoOneOf_AutoOneOfTest_PetWithGet.cat(cat);
+    }
+
+    public static PetWithGet create(TigerShark shark) {
+      return AutoOneOf_AutoOneOfTest_PetWithGet.tigerShark(shark);
+    }
+
+    public abstract Pet.Kind getKind();
+  }
+
+  @Test
+  public void getPrefix() {
+    Dog marvin = Dog.create("Marvin");
+    PetWithGet petMarvin = PetWithGet.create(marvin);
+    assertThat(petMarvin.toString()).isEqualTo("PetWithGet{dog=Dog{name=Marvin}}");
+  }
+
+  @AutoOneOf(Primitive.Kind.class)
+  public abstract static class Primitive {
+    public enum Kind {
+      A_BYTE,
+      A_SHORT,
+      AN_INT,
+      A_LONG,
+      A_FLOAT,
+      A_DOUBLE,
+      A_CHAR,
+      A_BOOLEAN
+    }
+
+    public abstract Kind getKind();
+
+    public abstract byte aByte();
+
+    public abstract short aShort();
+
+    public abstract int anInt();
+
+    public abstract long aLong();
+
+    public abstract float aFloat();
+
+    public abstract double aDouble();
+
+    public abstract char aChar();
+
+    public abstract boolean aBoolean();
+
+    public static Primitive of(byte x) {
+      return AutoOneOf_AutoOneOfTest_Primitive.aByte(x);
+    }
+
+    public static Primitive of(short x) {
+      return AutoOneOf_AutoOneOfTest_Primitive.aShort(x);
+    }
+
+    public static Primitive of(int x) {
+      return AutoOneOf_AutoOneOfTest_Primitive.anInt(x);
+    }
+
+    public static Primitive of(long x) {
+      return AutoOneOf_AutoOneOfTest_Primitive.aLong(x);
+    }
+
+    public static Primitive of(float x) {
+      return AutoOneOf_AutoOneOfTest_Primitive.aFloat(x);
+    }
+
+    public static Primitive of(double x) {
+      return AutoOneOf_AutoOneOfTest_Primitive.aDouble(x);
+    }
+
+    public static Primitive of(char x) {
+      return AutoOneOf_AutoOneOfTest_Primitive.aChar(x);
+    }
+
+    public static Primitive of(boolean x) {
+      return AutoOneOf_AutoOneOfTest_Primitive.aBoolean(x);
+    }
+  }
+
+  @Test
+  public void primitive() {
+    Primitive primitive = Primitive.of(17);
+    assertThat(primitive.anInt()).isEqualTo(17);
+    assertThat(primitive.toString()).isEqualTo("Primitive{anInt=17}");
+  }
+
+  @AutoOneOf(OneOfOne.Kind.class)
+  public abstract static class OneOfOne {
+    public enum Kind {
+      DOG
+    }
+
+    public abstract Dog getDog();
+
+    public static OneOfOne create(Dog dog) {
+      return AutoOneOf_AutoOneOfTest_OneOfOne.dog(dog);
+    }
+
+    public abstract Kind getKind();
+  }
+
+  @Test
+  public void oneOfOne() {
+    Dog marvin = Dog.create("Marvin");
+    OneOfOne oneOfMarvin = OneOfOne.create(marvin);
+    assertThat(oneOfMarvin.toString()).isEqualTo("OneOfOne{dog=Dog{name=Marvin}}");
+    assertThat(oneOfMarvin.getKind()).isEqualTo(OneOfOne.Kind.DOG);
+  }
+
+  // We allow this for consistency, even though it's obviously pretty useless.
+  // The generated code might be rubbish, but it compiles. No concrete implementation is generated
+  // so there isn't really anything to test beyond that it compiles.
+  @AutoOneOf(OneOfNone.Kind.class)
+  public abstract static class OneOfNone {
+    public enum Kind {}
+
+    public abstract Kind getKind();
+  }
+
+  // Testing generics. Typically generics will be a bit messy because the @AutoOneOf class must
+  // have type parameters for every property that needs them, even though any given property
+  // might not use all the type parameters.
+  @AutoOneOf(TaskResult.Kind.class)
+  public abstract static class TaskResult<V extends Serializable> {
+    public enum Kind {
+      VALUE,
+      EXCEPTION
+    }
+
+    public abstract Kind getKind();
+
+    public abstract V value();
+
+    public abstract Throwable exception();
+
+    public V get() throws ExecutionException {
+      switch (getKind()) {
+        case VALUE:
+          return value();
+        case EXCEPTION:
+          throw new ExecutionException(exception());
+      }
+      throw new AssertionError(getKind());
+    }
+
+    static <V extends Serializable> TaskResult<V> value(V value) {
+      return AutoOneOf_AutoOneOfTest_TaskResult.value(value);
+    }
+
+    static TaskResult<?> exception(Throwable exception) {
+      return AutoOneOf_AutoOneOfTest_TaskResult.exception(exception);
+    }
+  }
+
+  @Test
+  public void taskResultValue() throws Exception {
+    TaskResult<String> result = TaskResult.value("foo");
+    assertThat(result.get()).isEqualTo("foo");
+  }
+
+  @Test
+  public void taskResultException() {
+    Exception exception = new IllegalArgumentException("oops");
+    TaskResult<?> result = TaskResult.exception(exception);
+    try {
+      result.get();
+      fail();
+    } catch (ExecutionException e) {
+      assertThat(e).hasCauseThat().isEqualTo(exception);
+    }
+  }
+
+  @AutoOneOf(CustomToString.Kind.class)
+  public abstract static class CustomToString {
+    public enum Kind {
+      ACE
+    }
+
+    public abstract Kind getKind();
+
+    public abstract String ace();
+
+    public static CustomToString ace(String ace) {
+      return AutoOneOf_AutoOneOfTest_CustomToString.ace(ace);
+    }
+
+    @Override
+    public String toString() {
+      return "blim";
+    }
+  }
+
+  // If you have an explicit toString() method, we won't override it.
+  @Test
+  public void customToString() {
+    CustomToString x = CustomToString.ace("ceg");
+    assertThat(x.toString()).isEqualTo("blim");
+  }
+
+  @AutoOneOf(AbstractToString.Kind.class)
+  public abstract static class AbstractToString {
+    public enum Kind {
+      ACE
+    }
+
+    public abstract Kind getKind();
+
+    public abstract String ace();
+
+    public static AbstractToString ace(String ace) {
+      return AutoOneOf_AutoOneOfTest_AbstractToString.ace(ace);
+    }
+
+    @Override
+    public abstract String toString();
+  }
+
+  // If you have an explicit abstract toString() method, we will implement it.
+  @Test
+  public void abstractToString() {
+    AbstractToString x = AbstractToString.ace("ceg");
+    assertThat(x.toString()).isEqualTo("AbstractToString{ace=ceg}");
+  }
+
+  // "package" is a reserved word. You probably don't want to have a property with that name,
+  // but if you insist, you can get one by using getFoo()-style methods. We leak our renaming
+  // scheme here (package0) and for users that that bothers they can just avoid having properties
+  // that are reserved words.
+  @AutoOneOf(LetterOrPackage.Kind.class)
+  public abstract static class LetterOrPackage {
+    public enum Kind {
+      LETTER,
+      PACKAGE
+    }
+
+    public abstract Kind getKind();
+
+    public abstract String getLetter();
+
+    public abstract String getPackage();
+
+    public static LetterOrPackage ofLetter(String letter) {
+      return AutoOneOf_AutoOneOfTest_LetterOrPackage.letter(letter);
+    }
+
+    public static LetterOrPackage ofPackage(String pkg) {
+      return AutoOneOf_AutoOneOfTest_LetterOrPackage.package0(pkg);
+    }
+  }
+
+  @Test
+  public void reservedWordProperty() {
+    LetterOrPackage pkg = LetterOrPackage.ofPackage("pacquet");
+    assertThat(pkg.toString()).isEqualTo("LetterOrPackage{package=pacquet}");
+  }
+
+  @AutoOneOf(ArrayValue.Kind.class)
+  public abstract static class ArrayValue {
+    public enum Kind {
+      STRING,
+      INTS
+    }
+
+    public abstract Kind getKind();
+
+    public abstract String string();
+
+    @SuppressWarnings("mutable")
+    public abstract int[] ints();
+
+    public static ArrayValue ofString(String string) {
+      return AutoOneOf_AutoOneOfTest_ArrayValue.string(string);
+    }
+
+    public static ArrayValue ofInts(int[] ints) {
+      return AutoOneOf_AutoOneOfTest_ArrayValue.ints(ints);
+    }
+  }
+
+  @Test
+  public void arrayValues() {
+    ArrayValue string = ArrayValue.ofString("foo");
+    ArrayValue ints1 = ArrayValue.ofInts(new int[] {17, 23});
+    ArrayValue ints2 = ArrayValue.ofInts(new int[] {17, 23});
+    new EqualsTester()
+        .addEqualityGroup(string)
+        .addEqualityGroup(ints1, ints2)
+        .testEquals();
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  public @interface CopyTest {
+    int value();
+  }
+
+  @AutoOneOf(AnnotationNotCopied.Kind.class)
+  @CopyTest(23)
+  public abstract static class AnnotationNotCopied {
+    public enum Kind {
+      ACE
+    }
+
+    public abstract Kind getKind();
+
+    public abstract String ace();
+
+    public static AnnotationNotCopied ace(String ace) {
+      return AutoOneOf_AutoOneOfTest_AnnotationNotCopied.ace(ace);
+    }
+  }
+
+  @Test
+  public void classAnnotationsNotCopiedByDefault() {
+    assertThat(AnnotationNotCopied.class.isAnnotationPresent(CopyTest.class)).isTrue();
+    AnnotationNotCopied ace = AnnotationNotCopied.ace("ace");
+    assertThat(ace.getClass().isAnnotationPresent(CopyTest.class)).isFalse();
+  }
+
+  @AutoOneOf(AnnotationCopied.Kind.class)
+  @CopyTest(23)
+  @AutoValue.CopyAnnotations
+  public abstract static class AnnotationCopied {
+    public enum Kind {
+      ACE
+    }
+
+    public abstract Kind getKind();
+
+    public abstract String ace();
+
+    public static AnnotationCopied ace(String ace) {
+      return AutoOneOf_AutoOneOfTest_AnnotationCopied.ace(ace);
+    }
+  }
+
+  @Test
+  public void classAnnotationsCopiedIfCopyAnnotations() {
+    assertThat(AnnotationCopied.class.isAnnotationPresent(CopyTest.class)).isTrue();
+    AnnotationCopied ace = AnnotationCopied.ace("ace");
+    assertThat(ace.getClass().isAnnotationPresent(CopyTest.class)).isTrue();
+    assertThat(ace.getClass().getAnnotation(CopyTest.class).value()).isEqualTo(23);
+  }
+
+  @AutoOneOf(MaybeEmpty.Kind.class)
+  public abstract static class MaybeEmpty implements Serializable {
+    public enum Kind {
+      EMPTY, STRING,
+    }
+
+    public abstract Kind getKind();
+
+    public abstract void empty();
+
+    public abstract String string();
+
+    public static MaybeEmpty ofEmpty() {
+      return AutoOneOf_AutoOneOfTest_MaybeEmpty.empty();
+    }
+
+    public static MaybeEmpty ofString(String s) {
+      return AutoOneOf_AutoOneOfTest_MaybeEmpty.string(s);
+    }
+  }
+
+  @Test
+  public void voidPropertyIsSingleton() {
+    MaybeEmpty empty1 = MaybeEmpty.ofEmpty();
+    MaybeEmpty empty2 = MaybeEmpty.ofEmpty();
+    assertThat(empty1).isSameInstanceAs(empty2);
+  }
+
+  @Test
+  public void voidPropertyRemainsSingletonWhenDeserialized() throws Exception {
+    MaybeEmpty empty1 = MaybeEmpty.ofEmpty();
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    // We're still compiling this with -source 6, so we can't use try-with-resources.
+    ObjectOutputStream dos = new ObjectOutputStream(baos);
+    dos.writeObject(empty1);
+    dos.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    ObjectInputStream ois = new ObjectInputStream(bais);
+    MaybeEmpty empty2 = (MaybeEmpty) ois.readObject();
+    assertThat(empty2).isSameInstanceAs(empty1);
+  }
+
+  @Test
+  public void voidPropertyToString() {
+    MaybeEmpty empty = MaybeEmpty.ofEmpty();
+    assertThat(empty.toString()).isEqualTo("MaybeEmpty{empty}");
+  }
+
+  @Test
+  public void voidPropertyHashCodeIsIdentity() {
+    MaybeEmpty empty = MaybeEmpty.ofEmpty();
+    assertThat(empty.hashCode()).isEqualTo(System.identityHashCode(empty));
+  }
+
+  @Test
+  public void voidPropertyGetterDoesNothing() {
+    MaybeEmpty empty = MaybeEmpty.ofEmpty();
+    empty.empty();
+  }
+
+  @Test
+  public void voidPropertyNotEqualToNonVoid() {
+    MaybeEmpty empty = MaybeEmpty.ofEmpty();
+    MaybeEmpty notEmpty = MaybeEmpty.ofString("foo");
+    assertThat(empty).isNotEqualTo(notEmpty);
+    assertThat(notEmpty).isNotEqualTo(empty);
+  }
+
+  @Test
+  public void voidPropertyWrongType() {
+    MaybeEmpty notEmpty = MaybeEmpty.ofString("foo");
+    try {
+      notEmpty.empty();
+      fail();
+    } catch (UnsupportedOperationException e) {
+      assertThat(e).hasMessageThat().containsMatch("(?i:string)");
+    }
+  }
+
+  @AutoOneOf(OneOfArray.Kind.class)
+  public abstract static class OneOfArray {
+    public enum Kind {
+      INTS
+    }
+
+    public abstract Kind getKind();
+
+    @SuppressWarnings("mutable")
+    public abstract int[] ints();
+
+    public static OneOfArray ofInts(int[] s) {
+      return AutoOneOf_AutoOneOfTest_OneOfArray.ints(s);
+    }
+  }
+
+  @Test
+  public void arrayToString() {
+    OneOfArray oneOfArray = OneOfArray.ofInts(new int[] {1, 2});
+    assertThat(oneOfArray.toString()).isEqualTo("OneOfArray{ints=[1, 2]}");
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java
new file mode 100644
index 0000000..27356c5
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java
@@ -0,0 +1,797 @@
+/*
+ * Copyright 2012 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.testing.EqualsTester;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.TypeVariable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for constructs new in Java 8, such as type annotations.
+ *
+ * @author Till Brychcy
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class AutoValueJava8Test {
+  private static boolean javacHandlesTypeAnnotationsCorrectly;
+
+  // This is appalling. Some versions of javac do not correctly report annotations on type uses in
+  // certain cases, for example on type variables or arrays. Since some of the tests here are for
+  // exactly that, we compile a test program with a test annotation processor to see whether we
+  // might be in the presence of such a javac, and if so we skip the tests that would fail because
+  // of the bug. This isn't completely sound because we can't be entirely sure that the javac that
+  // Compiler.javac() finds is the same as the javac that was used to build this test (and therefore
+  // run AutoValueProcessor), but it's better than just ignoring the tests outright.
+  @BeforeClass
+  public static void setUpClass() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "Test",
+            "import java.lang.annotation.ElementType;",
+            "import java.lang.annotation.Retention;",
+            "import java.lang.annotation.RetentionPolicy;",
+            "import java.lang.annotation.Target;",
+            "public abstract class Test<T> {",
+            "  @Retention(RetentionPolicy.RUNTIME)",
+            "  @Target(ElementType.TYPE_USE)",
+            "  public @interface Nullable {}",
+            "",
+            "  public abstract @Nullable T t();",
+            "}");
+    Compilation compilation =
+        Compiler.javac().withProcessors(new BugTestProcessor()).compile(javaFileObject);
+    if (compilation.errors().isEmpty()) {
+      javacHandlesTypeAnnotationsCorrectly = true;
+    } else {
+      assertThat(compilation).hadErrorCount(1);
+      assertThat(compilation).hadErrorContaining(JAVAC_HAS_BUG_ERROR);
+    }
+  }
+
+  private static final String JAVAC_HAS_BUG_ERROR = "javac has the type-annotation bug";
+
+  @SupportedAnnotationTypes("*")
+  @SupportedSourceVersion(SourceVersion.RELEASE_8)
+  private static class BugTestProcessor extends AbstractProcessor {
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      if (roundEnv.processingOver()) {
+        test();
+      }
+      return false;
+    }
+
+    private void test() {
+      TypeElement test = processingEnv.getElementUtils().getTypeElement("Test");
+      List<ExecutableElement> methods = ElementFilter.methodsIn(test.getEnclosedElements());
+      ExecutableElement t = Iterables.getOnlyElement(methods);
+      assertThat(t.getSimpleName().toString()).isEqualTo("t");
+      List<? extends AnnotationMirror> typeAnnotations = t.getReturnType().getAnnotationMirrors();
+      if (typeAnnotations.isEmpty()) {
+        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, JAVAC_HAS_BUG_ERROR);
+        return;
+      }
+      AnnotationMirror typeAnnotation = Iterables.getOnlyElement(typeAnnotations);
+      assertThat(typeAnnotation.getAnnotationType().toString()).contains("Nullable");
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE_USE)
+  public @interface Nullable {}
+
+  @AutoValue
+  abstract static class NullableProperties {
+    abstract @Nullable String nullableString();
+
+    abstract int randomInt();
+
+    static NullableProperties create(@Nullable String nullableString, int randomInt) {
+      return new AutoValue_AutoValueJava8Test_NullableProperties(nullableString, randomInt);
+    }
+  }
+
+  @Test
+  public void testNullablePropertiesCanBeNull() {
+    NullableProperties instance = NullableProperties.create(null, 23);
+    assertThat(instance.nullableString()).isNull();
+    assertThat(instance.randomInt()).isEqualTo(23);
+    assertThat(instance.toString())
+        .isEqualTo("NullableProperties{nullableString=null, randomInt=23}");
+  }
+
+  @AutoAnnotation
+  static Nullable nullable() {
+    return new AutoAnnotation_AutoValueJava8Test_nullable();
+  }
+
+  @Test
+  public void testNullablePropertyImplementationIsNullable() throws NoSuchMethodException {
+    Method method =
+        AutoValue_AutoValueJava8Test_NullableProperties.class.getDeclaredMethod("nullableString");
+    assertThat(method.getAnnotatedReturnType().getAnnotations())
+        .asList()
+        .contains(nullable());
+  }
+
+  @Test
+  public void testNullablePropertyConstructorParameterIsNullable() throws NoSuchMethodException {
+    Constructor<?> constructor =
+        AutoValue_AutoValueJava8Test_NullableProperties.class.getDeclaredConstructor(
+            String.class, int.class);
+    try {
+      assertThat(constructor.getAnnotatedParameterTypes()[0].getAnnotations())
+          .asList()
+          .contains(nullable());
+    } catch (AssertionError e) {
+      if (javacHandlesTypeAnnotationsCorrectly) {
+        throw e;
+      }
+    }
+  }
+
+  @AutoValue
+  abstract static class NullablePropertiesNotCopied {
+    @AutoValue.CopyAnnotations(exclude = Nullable.class)
+    abstract @Nullable String nullableString();
+
+    abstract int randomInt();
+
+    NullablePropertiesNotCopied create(String notNullableAfterAll, int randomInt) {
+      return new AutoValue_AutoValueJava8Test_NullablePropertiesNotCopied(
+          notNullableAfterAll, randomInt);
+    }
+  }
+
+  @Test
+  public void testExcludedNullablePropertyImplementation() throws NoSuchMethodException {
+    Method method = AutoValue_AutoValueJava8Test_NullablePropertiesNotCopied.class
+        .getDeclaredMethod("nullableString");
+    assertThat(method.getAnnotatedReturnType().getAnnotations())
+        .asList()
+        .doesNotContain(nullable());
+  }
+
+  @Test
+  public void testExcludedNullablePropertyConstructorParameter() throws NoSuchMethodException {
+    Constructor<?> constructor =
+        AutoValue_AutoValueJava8Test_NullablePropertiesNotCopied.class.getDeclaredConstructor(
+            String.class, int.class);
+    try {
+      assertThat(constructor.getAnnotatedParameterTypes()[0].getAnnotations())
+          .asList()
+          .doesNotContain(nullable());
+    } catch (AssertionError e) {
+      if (javacHandlesTypeAnnotationsCorrectly) {
+        throw e;
+      }
+    }
+  }
+
+  @AutoValue
+  abstract static class NullableNonNullable {
+    abstract @Nullable String nullableString();
+
+    abstract @Nullable String otherNullableString();
+
+    abstract String nonNullableString();
+
+    static NullableNonNullable create(
+        String nullableString, String otherNullableString, String nonNullableString) {
+      return new AutoValue_AutoValueJava8Test_NullableNonNullable(
+          nullableString, otherNullableString, nonNullableString);
+    }
+  }
+
+  @Test
+  public void testEqualsWithNullable() throws Exception {
+    NullableNonNullable everythingNull =
+        NullableNonNullable.create(null, null, "nonNullableString");
+    NullableNonNullable somethingNull =
+        NullableNonNullable.create(null, "otherNullableString", "nonNullableString");
+    NullableNonNullable nothingNull =
+        NullableNonNullable.create("nullableString", "otherNullableString", "nonNullableString");
+    NullableNonNullable nothingNullAgain =
+        NullableNonNullable.create("nullableString", "otherNullableString", "nonNullableString");
+    new EqualsTester()
+        .addEqualityGroup(everythingNull)
+        .addEqualityGroup(somethingNull)
+        .addEqualityGroup(nothingNull, nothingNullAgain)
+        .testEquals();
+  }
+
+  public static class Nested {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE_USE)
+  public @interface OtherTypeAnnotation {}
+
+  @AutoAnnotation
+  public static OtherTypeAnnotation otherTypeAnnotation() {
+    return new AutoAnnotation_AutoValueJava8Test_otherTypeAnnotation();
+  }
+
+  @AutoValue
+  abstract static class NestedNullableProperties {
+    abstract @Nullable @OtherTypeAnnotation Nested nullableThing();
+
+    abstract int randomInt();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueJava8Test_NestedNullableProperties.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder setNullableThing(@Nullable @OtherTypeAnnotation Nested thing);
+
+      abstract Builder setRandomInt(int x);
+
+      abstract NestedNullableProperties build();
+    }
+  }
+
+  @Test
+  public void testNestedNullablePropertiesCanBeNull() {
+    NestedNullableProperties instance = NestedNullableProperties.builder().setRandomInt(23).build();
+    assertThat(instance.nullableThing()).isNull();
+    assertThat(instance.randomInt()).isEqualTo(23);
+    assertThat(instance.toString())
+        .isEqualTo("NestedNullableProperties{nullableThing=null, randomInt=23}");
+  }
+
+  @Test
+  public void testNestedNullablePropertiesAreCopied() throws Exception {
+    try {
+      Method generatedGetter =
+          AutoValue_AutoValueJava8Test_NestedNullableProperties.class.getDeclaredMethod(
+              "nullableThing");
+      Annotation[] getterAnnotations = generatedGetter.getAnnotatedReturnType().getAnnotations();
+      assertThat(getterAnnotations).asList().containsAtLeast(nullable(), otherTypeAnnotation());
+
+      Method generatedSetter =
+          AutoValue_AutoValueJava8Test_NestedNullableProperties.Builder.class.getDeclaredMethod(
+              "setNullableThing", Nested.class);
+      Annotation[] setterAnnotations =
+          generatedSetter.getAnnotatedParameterTypes()[0].getAnnotations();
+      assertThat(setterAnnotations).asList().containsAtLeast(nullable(), otherTypeAnnotation());
+    } catch (AssertionError e) {
+      if (javacHandlesTypeAnnotationsCorrectly) {
+        throw e;
+      }
+    }
+  }
+
+  @AutoValue
+  @SuppressWarnings("AutoValueImmutableFields")
+  abstract static class PrimitiveArrays {
+    @SuppressWarnings("mutable")
+    abstract boolean[] booleans();
+
+    @SuppressWarnings("mutable")
+    abstract int @Nullable [] ints();
+
+    static PrimitiveArrays create(boolean[] booleans, int[] ints) {
+      // Real code would likely clone these parameters, but here we want to check that the
+      // generated constructor rejects a null value for booleans.
+      return new AutoValue_AutoValueJava8Test_PrimitiveArrays(booleans, ints);
+    }
+  }
+
+  @Test
+  public void testPrimitiveArrays() {
+    PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], new int[0]);
+    boolean[] booleans = {false, true, true, false};
+    int[] ints = {6, 28, 496, 8128, 33550336};
+    PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), ints.clone());
+    PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), ints.clone());
+    new EqualsTester().addEqualityGroup(object1, object2).addEqualityGroup(object0).testEquals();
+    // EqualsTester also exercises hashCode(). We clone the arrays above to ensure that using the
+    // default Object.hashCode() will fail.
+
+    String expectedString =
+        "PrimitiveArrays{booleans="
+            + Arrays.toString(booleans)
+            + ", "
+            + "ints="
+            + Arrays.toString(ints)
+            + "}";
+    assertThat(object1.toString()).isEqualTo(expectedString);
+
+    assertThat(object1.ints()).isSameInstanceAs(object1.ints());
+  }
+
+  @Test
+  public void testNullablePrimitiveArrays() {
+    assumeTrue(javacHandlesTypeAnnotationsCorrectly);
+    PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], null);
+    boolean[] booleans = {false, true, true, false};
+    PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), null);
+    PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), null);
+    new EqualsTester().addEqualityGroup(object1, object2).addEqualityGroup(object0).testEquals();
+
+    String expectedString =
+        "PrimitiveArrays{booleans=" + Arrays.toString(booleans) + ", " + "ints=null}";
+    assertThat(object1.toString()).isEqualTo(expectedString);
+
+    assertThat(object1.booleans()).isSameInstanceAs(object1.booleans());
+    assertThat(object1.booleans()).isEqualTo(booleans);
+    object1.booleans()[0] ^= true;
+    assertThat(object1.booleans()).isNotEqualTo(booleans);
+  }
+
+  @Test
+  public void testNotNullablePrimitiveArrays() {
+    try {
+      PrimitiveArrays.create(null, new int[0]);
+      fail("Construction with null value for non-@Nullable array should have failed");
+    } catch (NullPointerException e) {
+      assertThat(e.getMessage()).contains("booleans");
+    }
+  }
+
+  @AutoValue
+  public abstract static class NullablePropertyWithBuilder {
+    public abstract String notNullable();
+
+    public abstract @Nullable String nullable();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueJava8Test_NullablePropertyWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder notNullable(String s);
+
+      Builder nullable(@Nullable String s);
+
+      NullablePropertyWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testOmitNullableWithBuilder() {
+    NullablePropertyWithBuilder instance1 =
+        NullablePropertyWithBuilder.builder().notNullable("hello").build();
+    assertThat(instance1.notNullable()).isEqualTo("hello");
+    assertThat(instance1.nullable()).isNull();
+
+    NullablePropertyWithBuilder instance2 =
+        NullablePropertyWithBuilder.builder().notNullable("hello").nullable(null).build();
+    assertThat(instance2.notNullable()).isEqualTo("hello");
+    assertThat(instance2.nullable()).isNull();
+    assertThat(instance1).isEqualTo(instance2);
+
+    NullablePropertyWithBuilder instance3 =
+        NullablePropertyWithBuilder.builder().notNullable("hello").nullable("world").build();
+    assertThat(instance3.notNullable()).isEqualTo("hello");
+    assertThat(instance3.nullable()).isEqualTo("world");
+
+    try {
+      NullablePropertyWithBuilder.builder().build();
+      fail("Expected IllegalStateException for unset non-@Nullable property");
+    } catch (IllegalStateException e) {
+      assertThat(e.getMessage()).contains("notNullable");
+    }
+  }
+
+  @AutoValue
+  public abstract static class OptionalPropertyWithNullableBuilder {
+    public abstract String notOptional();
+
+    public abstract Optional<String> optional();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueJava8Test_OptionalPropertyWithNullableBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder notOptional(String s);
+
+      Builder optional(@Nullable String s);
+
+      OptionalPropertyWithNullableBuilder build();
+    }
+  }
+
+  @Test
+  public void testOmitOptionalWithNullableBuilder() {
+    OptionalPropertyWithNullableBuilder instance1 =
+        OptionalPropertyWithNullableBuilder.builder().notOptional("hello").build();
+    assertThat(instance1.notOptional()).isEqualTo("hello");
+    assertThat(instance1.optional()).isEmpty();
+
+    OptionalPropertyWithNullableBuilder instance2 =
+        OptionalPropertyWithNullableBuilder.builder().notOptional("hello").optional(null).build();
+    assertThat(instance2.notOptional()).isEqualTo("hello");
+    assertThat(instance2.optional()).isEmpty();
+    assertThat(instance1).isEqualTo(instance2);
+
+    OptionalPropertyWithNullableBuilder instance3 =
+        OptionalPropertyWithNullableBuilder.builder()
+            .notOptional("hello")
+            .optional("world")
+            .build();
+    assertThat(instance3.notOptional()).isEqualTo("hello");
+    assertThat(instance3.optional()).hasValue("world");
+
+    try {
+      OptionalPropertyWithNullableBuilder.builder().build();
+      fail("Expected IllegalStateException for unset non-Optional property");
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @AutoValue
+  @SuppressWarnings("AutoValueImmutableFields")
+  public abstract static class BuilderWithUnprefixedGetters<T extends Comparable<T>> {
+    public abstract ImmutableList<T> list();
+
+    public abstract @Nullable T t();
+
+    @SuppressWarnings("mutable")
+    public abstract int[] ints();
+
+    public abstract int noGetter();
+
+    public static <T extends Comparable<T>> Builder<T> builder() {
+      return new AutoValue_AutoValueJava8Test_BuilderWithUnprefixedGetters.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    public interface Builder<T extends Comparable<T>> {
+      Builder<T> setList(ImmutableList<T> list);
+
+      Builder<T> setT(T t);
+
+      Builder<T> setInts(int[] ints);
+
+      Builder<T> setNoGetter(int x);
+
+      ImmutableList<T> list();
+
+      T t();
+
+      int[] ints();
+
+      BuilderWithUnprefixedGetters<T> build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithUnprefixedGetter() {
+    assumeTrue(javacHandlesTypeAnnotationsCorrectly);
+    ImmutableList<String> names = ImmutableList.of("fred", "jim");
+    int[] ints = {6, 28, 496, 8128, 33550336};
+    int noGetter = -1;
+
+    BuilderWithUnprefixedGetters.Builder<String> builder = BuilderWithUnprefixedGetters.builder();
+    assertThat(builder.t()).isNull();
+    try {
+      builder.list();
+      fail("Attempt to retrieve unset list property should have failed");
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessageThat().isEqualTo("Property \"list\" has not been set");
+    }
+    try {
+      builder.ints();
+      fail("Attempt to retrieve unset ints property should have failed");
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessageThat().isEqualTo("Property \"ints\" has not been set");
+    }
+
+    builder.setList(names);
+    assertThat(builder.list()).isSameInstanceAs(names);
+    builder.setInts(ints);
+    assertThat(builder.ints()).isEqualTo(ints);
+    // The array is not cloned by the getter, so the client can modify it (but shouldn't).
+    ints[0] = 0;
+    assertThat(builder.ints()[0]).isEqualTo(0);
+    ints[0] = 6;
+
+    BuilderWithUnprefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
+    assertThat(instance.list()).isSameInstanceAs(names);
+    assertThat(instance.t()).isNull();
+    assertThat(instance.ints()).isEqualTo(ints);
+    assertThat(instance.noGetter()).isEqualTo(noGetter);
+  }
+
+  @AutoValue
+  @SuppressWarnings("AutoValueImmutableFields")
+  public abstract static class BuilderWithPrefixedGetters<T extends Comparable<T>> {
+    public abstract ImmutableList<T> getList();
+
+    public abstract T getT();
+
+    @SuppressWarnings("mutable")
+    public abstract int @Nullable [] getInts();
+
+    public abstract int getNoGetter();
+
+    public static <T extends Comparable<T>> Builder<T> builder() {
+      return new AutoValue_AutoValueJava8Test_BuilderWithPrefixedGetters.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder<T extends Comparable<T>> {
+      public abstract Builder<T> setList(ImmutableList<T> list);
+
+      public abstract Builder<T> setT(T t);
+
+      public abstract Builder<T> setInts(int[] ints);
+
+      public abstract Builder<T> setNoGetter(int x);
+
+      abstract ImmutableList<T> getList();
+
+      abstract T getT();
+
+      abstract int[] getInts();
+
+      public abstract BuilderWithPrefixedGetters<T> build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithPrefixedGetter() {
+    assumeTrue(javacHandlesTypeAnnotationsCorrectly);
+    ImmutableList<String> names = ImmutableList.of("fred", "jim");
+    String name = "sheila";
+    int noGetter = -1;
+
+    BuilderWithPrefixedGetters.Builder<String> builder = BuilderWithPrefixedGetters.builder();
+    assertThat(builder.getInts()).isNull();
+    try {
+      builder.getList();
+      fail("Attempt to retrieve unset list property should have failed");
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessageThat().isEqualTo("Property \"list\" has not been set");
+    }
+
+    builder.setList(names);
+    assertThat(builder.getList()).isSameInstanceAs(names);
+    builder.setT(name);
+    assertThat(builder.getInts()).isNull();
+
+    BuilderWithPrefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
+    assertThat(instance.getList()).isSameInstanceAs(names);
+    assertThat(instance.getT()).isEqualTo(name);
+    assertThat(instance.getInts()).isNull();
+    assertThat(instance.getNoGetter()).isEqualTo(noGetter);
+  }
+
+  // This class tests the case where an annotation is both a method annotation and a type
+  // annotation. If we weren't careful, we might emit it twice in the generated code.
+  @AutoValue
+  abstract static class FunkyNullable {
+    @Target({ElementType.METHOD, ElementType.TYPE_USE})
+    @interface Nullable {}
+
+    abstract @Nullable String foo();
+
+    abstract Optional<String> bar();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueJava8Test_FunkyNullable.Builder();
+    }
+
+    @AutoValue.Builder
+    interface Builder {
+      Builder setFoo(@Nullable String foo);
+
+      Builder setBar(@Nullable String bar);
+
+      FunkyNullable build();
+    }
+  }
+
+  @Test
+  public void testFunkyNullable() {
+    FunkyNullable explicitNull = FunkyNullable.builder().setFoo(null).setBar(null).build();
+    FunkyNullable implicitNull = FunkyNullable.builder().build();
+    assertThat(explicitNull).isEqualTo(implicitNull);
+  }
+
+  @AutoValue
+  abstract static class EqualsNullable {
+    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface Nullable {}
+
+    abstract String foo();
+
+    static EqualsNullable create(String foo) {
+      return new AutoValue_AutoValueJava8Test_EqualsNullable(foo);
+    }
+
+    @Override
+    public abstract boolean equals(@Nullable Object x);
+
+    @Override
+    public abstract int hashCode();
+  }
+
+  /**
+   * Tests that a type annotation on the parameter of {@code equals(Object)} is copied into the
+   * implementation class.
+   */
+  @Test
+  public void testEqualsNullable() throws ReflectiveOperationException {
+    EqualsNullable x = EqualsNullable.create("foo");
+    Class<? extends EqualsNullable> implClass = x.getClass();
+    Method equals = implClass.getDeclaredMethod("equals", Object.class);
+    AnnotatedType[] parameterTypes = equals.getAnnotatedParameterTypes();
+    assertThat(parameterTypes[0].isAnnotationPresent(EqualsNullable.Nullable.class)).isTrue();
+  }
+
+  @AutoValue
+  abstract static class AnnotatedTypeParameter<@Nullable T> {
+    abstract @Nullable T thing();
+
+    static <@Nullable T> AnnotatedTypeParameter<T> create(T thing) {
+      return new AutoValue_AutoValueJava8Test_AnnotatedTypeParameter<T>(thing);
+    }
+  }
+
+  /**
+   * Tests that an annotation on a type parameter of an {@code @AutoValue} class is copied to the
+   * implementation class.
+   */
+  @Test
+  public void testTypeAnnotationCopiedToImplementation() {
+    @Nullable String nullableString = "blibby";
+    AnnotatedTypeParameter<@Nullable String> x = AnnotatedTypeParameter.create(nullableString);
+    Class<?> c = x.getClass();
+    assertThat(c.getTypeParameters()).hasLength(1);
+    TypeVariable<?> typeParameter = c.getTypeParameters()[0];
+    assertWithMessage(typeParameter.toString())
+        .that(typeParameter.getAnnotations())
+        .asList()
+        .contains(nullable());
+  }
+
+  @AutoValue
+  abstract static class AnnotatedTypeParameterWithBuilder<@Nullable T> {
+    abstract @Nullable T thing();
+
+    static <@Nullable T> Builder<T> builder() {
+      return new AutoValue_AutoValueJava8Test_AnnotatedTypeParameterWithBuilder.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder<@Nullable T> {
+      abstract Builder<T> setThing(T thing);
+
+      abstract AnnotatedTypeParameterWithBuilder<T> build();
+    }
+  }
+
+  /**
+   * Tests that an annotation on a type parameter of an {@code @AutoValue} builder is copied to the
+   * implementation class.
+   */
+  @Test
+  public void testTypeAnnotationOnBuilderCopiedToImplementation() {
+    AnnotatedTypeParameterWithBuilder.Builder<@Nullable String> builder =
+        AnnotatedTypeParameterWithBuilder.builder();
+    Class<?> c = builder.getClass();
+    assertThat(c.getTypeParameters()).hasLength(1);
+    TypeVariable<?> typeParameter = c.getTypeParameters()[0];
+    assertWithMessage(typeParameter.toString())
+        .that(typeParameter.getAnnotations())
+        .asList()
+        .contains(nullable());
+  }
+
+  // b/127701294
+  @AutoValue
+  abstract static class OptionalOptional {
+    abstract Optional<Optional<String>> maybeJustMaybe();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueJava8Test_OptionalOptional.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder maybeJustMaybe(Optional<String> maybe);
+      abstract OptionalOptional build();
+    }
+  }
+
+  @Test
+  public void testOptionalOptional_empty() {
+    OptionalOptional empty = OptionalOptional.builder().build();
+    assertThat(empty.maybeJustMaybe()).isEmpty();
+  }
+
+  @Test
+  public void testOptionalOptional_ofEmpty() {
+    OptionalOptional ofEmpty = OptionalOptional.builder().maybeJustMaybe(Optional.empty()).build();
+    assertThat(ofEmpty.maybeJustMaybe()).hasValue(Optional.empty());
+  }
+
+  @Test
+  public void testOptionalOptional_ofSomething() {
+    OptionalOptional ofSomething =
+        OptionalOptional.builder().maybeJustMaybe(Optional.of("foo")).build();
+    assertThat(ofSomething.maybeJustMaybe()).hasValue(Optional.of("foo"));
+  }
+
+  @AutoValue
+  abstract static class OptionalExtends {
+    abstract Optional<? extends Predicate<? super Integer>> predicate();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueJava8Test_OptionalExtends.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder setPredicate(Predicate<? super Integer> predicate);
+      abstract OptionalExtends build();
+    }
+  }
+
+  @Test
+  public void testOptionalExtends() {
+    Predicate<Number> predicate = n -> n.toString().equals("0");
+    OptionalExtends t = OptionalExtends.builder().setPredicate(predicate).build();
+    assertThat(t.predicate()).hasValue(predicate);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueNotEclipseTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueNotEclipseTest.java
new file mode 100644
index 0000000..4d008c7
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueNotEclipseTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 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;
+
+import static com.google.common.truth.Truth8.assertThat;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Like {@link AutoValueTest}, but with code that doesn't build with at least some versions of
+ * Eclipse, and should therefore not be included in {@link CompileWithEclipseTest}. (The latter is
+ * not currently present in the open-source build.)
+ */
+@RunWith(JUnit4.class)
+public class AutoValueNotEclipseTest {
+  // This produced the following error with JDT 4.6:
+  // Internal compiler error: java.lang.Exception: java.lang.IllegalArgumentException: element
+  // public abstract B setOptional(T)  is not a member of the containing type
+  // com.google.auto.value.AutoValueTest.ConcreteOptional.Builder nor any of its superclasses at
+  // org.eclipse.jdt.internal.compiler.apt.dispatch.RoundDispatcher.handleProcessor(RoundDispatcher.java:169)
+  interface AbstractOptional<T> {
+    Optional<T> optional();
+
+    interface Builder<T, B extends Builder<T, B>> {
+      B setOptional(@Nullable T t);
+    }
+  }
+
+  @AutoValue
+  abstract static class ConcreteOptional implements AbstractOptional<String> {
+    static Builder builder() {
+      return new AutoValue_AutoValueNotEclipseTest_ConcreteOptional.Builder();
+    }
+
+    @AutoValue.Builder
+    interface Builder extends AbstractOptional.Builder<String, Builder> {
+      ConcreteOptional build();
+    }
+  }
+
+  @Test
+  public void genericOptionalOfNullable() {
+    ConcreteOptional empty = ConcreteOptional.builder().build();
+    assertThat(empty.optional()).isEmpty();
+    ConcreteOptional notEmpty = ConcreteOptional.builder().setOptional("foo").build();
+    assertThat(notEmpty.optional()).hasValue("foo");
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
new file mode 100644
index 0000000..346bc53
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
@@ -0,0 +1,3420 @@
+/*
+ * Copyright 2012 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ImmutableTable;
+import com.google.common.testing.EqualsTester;
+import com.google.common.testing.SerializableTester;
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.math.BigInteger;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.NavigableSet;
+import java.util.NoSuchElementException;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import javax.annotation.Nullable;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class AutoValueTest {
+  private static boolean omitIdentifiers;
+
+  @BeforeClass
+  public static void initOmitIdentifiers() {
+    omitIdentifiers = System.getProperty("OmitIdentifiers") != null;
+  }
+
+  @AutoValue
+  abstract static class Simple {
+    public abstract String publicString();
+
+    protected abstract int protectedInt();
+
+    abstract Map<String, Long> packageMap();
+
+    public static Simple create(String s, int i, Map<String, Long> m) {
+      return new AutoValue_AutoValueTest_Simple(s, i, m);
+    }
+  }
+
+  @Test
+  public void testSimple() throws Exception {
+    Simple instance1a = Simple.create("example", 23, ImmutableMap.of("twenty-three", 23L));
+    Simple instance1b = Simple.create("example", 23, ImmutableMap.of("twenty-three", 23L));
+    Simple instance2 = Simple.create("", 0, ImmutableMap.<String, Long>of());
+    assertEquals("example", instance1a.publicString());
+    assertEquals(23, instance1a.protectedInt());
+    assertEquals(ImmutableMap.of("twenty-three", 23L), instance1a.packageMap());
+    MoreObjects.ToStringHelper toStringHelper = MoreObjects.toStringHelper(Simple.class);
+    toStringHelper.add("publicString", "example");
+    toStringHelper.add("protectedInt", 23);
+    toStringHelper.add("packageMap", ImmutableMap.of("twenty-three", 23L));
+    String expectedString =
+        omitIdentifiers ? "{example, 23, {twenty-three=23}}" : toStringHelper.toString();
+    assertThat(instance1a.toString()).isEqualTo(expectedString);
+    new EqualsTester()
+        .addEqualityGroup(instance1a, instance1b)
+        .addEqualityGroup(instance2)
+        .testEquals();
+  }
+
+  @AutoValue
+  abstract static class Empty {
+    public static Empty create() {
+      return new AutoValue_AutoValueTest_Empty();
+    }
+  }
+
+  @Test
+  public void testEmpty() throws Exception {
+    Empty instance = Empty.create();
+    String expectedString = omitIdentifiers ? "{}" : "Empty{}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+    assertEquals(instance, instance);
+    assertEquals(instance, Empty.create());
+  }
+
+  @AutoValue
+  abstract static class SimpleWithGetters {
+    abstract int getFoo();
+
+    abstract boolean isBar();
+
+    abstract boolean getOtherBar();
+
+    abstract String getPackage(); // package is a reserved word
+
+    abstract String getPackage0();
+
+    abstract String getHTMLPage();
+
+    static SimpleWithGetters create(
+        int foo, boolean bar, boolean otherBar, String pkg, String pkg0, String htmlPage) {
+      return new AutoValue_AutoValueTest_SimpleWithGetters(foo, bar, otherBar, pkg, pkg0, htmlPage);
+    }
+  }
+
+  @Test
+  public void testGetters() {
+    SimpleWithGetters instance = SimpleWithGetters.create(23, true, false, "foo", "bar", "<html>");
+    String expectedString =
+        omitIdentifiers
+            ? "{23, true, false, foo, bar, <html>}"
+            : "SimpleWithGetters{"
+                + "foo=23, bar=true, otherBar=false, package=foo, package0=bar, HTMLPage=<html>}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+  }
+
+  @AutoValue
+  abstract static class NotAllGetters {
+    abstract int getFoo();
+
+    abstract boolean bar();
+
+    static NotAllGetters create(int foo, boolean bar) {
+      return new AutoValue_AutoValueTest_NotAllGetters(foo, bar);
+    }
+  }
+
+  @Test
+  public void testNotGetters() {
+    NotAllGetters instance = NotAllGetters.create(23, true);
+    String expectedString = omitIdentifiers ? "{23, true}" : "NotAllGetters{getFoo=23, bar=true}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+  }
+
+  @AutoValue
+  abstract static class StrangeGetters {
+    abstract int get1st();
+    abstract int get_1st(); // by default we'll use _1st where identifiers are needed, so foil that.
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder set1st(int x);
+      abstract Builder set_1st(int x);
+      abstract StrangeGetters build();
+    }
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_StrangeGetters.Builder();
+    }
+  }
+
+  @Test
+  public void testStrangeGetters() {
+    StrangeGetters instance = StrangeGetters.builder().set1st(17).set_1st(23).build();
+    String expectedString = omitIdentifiers ? "{17, 23}" : "StrangeGetters{1st=17, _1st=23}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+  }
+
+  @AutoValue
+  abstract static class GettersAndConcreteNonGetters {
+    abstract int getFoo();
+
+    @SuppressWarnings("mutable")
+    abstract byte[] getBytes();
+
+    boolean hasNoBytes() {
+      return getBytes().length == 0;
+    }
+
+    static GettersAndConcreteNonGetters create(int foo, byte[] bytes) {
+      return new AutoValue_AutoValueTest_GettersAndConcreteNonGetters(foo, bytes);
+    }
+  }
+
+  @Test
+  public void testGettersAndConcreteNonGetters() {
+    GettersAndConcreteNonGetters instance = GettersAndConcreteNonGetters.create(23, new byte[] {1});
+    assertFalse(instance.hasNoBytes());
+    String expectedString =
+        omitIdentifiers ? "{23, [1]}" : "GettersAndConcreteNonGetters{foo=23, bytes=[1]}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+  }
+
+  @AutoValue
+  abstract static class ClassProperty {
+    abstract Class<?> theClass();
+
+    static ClassProperty create(Class<?> theClass) {
+      return new AutoValue_AutoValueTest_ClassProperty(theClass);
+    }
+  }
+
+  @Test
+  public void testClassProperty() {
+    ClassProperty instance = ClassProperty.create(Thread.class);
+    assertThat(instance.theClass()).isEqualTo(Thread.class);
+
+    try {
+      ClassProperty.create(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  @AutoValue
+  abstract static class ClassPropertyWithBuilder {
+    abstract Class<? extends Number> numberClass();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_ClassPropertyWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder setNumberClass(Class<? extends Number> x);
+
+      abstract ClassPropertyWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testClassPropertyWithBuilder() {
+    ClassPropertyWithBuilder instance =
+        ClassPropertyWithBuilder.builder().setNumberClass(Integer.class).build();
+    assertThat(instance.numberClass()).isEqualTo(Integer.class);
+
+    try {
+      ClassPropertyWithBuilder.builder().build();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+
+    try {
+      ClassPropertyWithBuilder.builder().setNumberClass(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  @AutoValue
+  public abstract static class Serialize implements Serializable {
+    public abstract int integer();
+
+    public abstract String string();
+
+    public abstract BigInteger bigInteger();
+
+    public static Serialize create(int integer, String string, BigInteger bigInteger) {
+      return new AutoValue_AutoValueTest_Serialize(integer, string, bigInteger);
+    }
+  }
+
+  @Test
+  public void testSerialize() throws Exception {
+    Serialize instance = Serialize.create(23, "23", BigInteger.valueOf(23));
+    assertEquals(instance, SerializableTester.reserialize(instance));
+  }
+
+  @AutoValue
+  public abstract static class SerializeWithVersionUID implements Serializable {
+    private static final long serialVersionUID = 4294967297L;
+
+    public abstract int integer();
+
+    public abstract String string();
+
+    public static SerializeWithVersionUID create(int integer, String string) {
+      return new AutoValue_AutoValueTest_SerializeWithVersionUID(integer, string);
+    }
+  }
+
+  @Test
+  public void testSerializeWithVersionUID() throws Exception {
+    SerializeWithVersionUID instance = SerializeWithVersionUID.create(23, "23");
+    assertEquals(instance, SerializableTester.reserialize(instance));
+
+    long serialVersionUID =
+        ObjectStreamClass.lookup(AutoValue_AutoValueTest_SerializeWithVersionUID.class)
+            .getSerialVersionUID();
+    assertEquals(4294967297L, serialVersionUID);
+  }
+
+  @AutoValue
+  abstract static class LongProperty {
+    public abstract long longProperty();
+
+    public static LongProperty create(long longProperty) {
+      return new AutoValue_AutoValueTest_LongProperty(longProperty);
+    }
+  }
+
+  @Test
+  public void testLongHashCode() {
+    long longValue = 0x1234567887654321L;
+    LongProperty longProperty = LongProperty.create(longValue);
+    assertEquals(singlePropertyHash(longValue), longProperty.hashCode());
+  }
+
+  @AutoValue
+  abstract static class IntProperty {
+    public abstract int intProperty();
+
+    public static IntProperty create(int intProperty) {
+      return new AutoValue_AutoValueTest_IntProperty(intProperty);
+    }
+  }
+
+  @Test
+  public void testIntHashCode() {
+    int intValue = 0x12345678;
+    IntProperty intProperty = IntProperty.create(intValue);
+    assertEquals(singlePropertyHash(intValue), intProperty.hashCode());
+  }
+
+  @AutoValue
+  abstract static class ShortProperty {
+    public abstract short shortProperty();
+
+    public static ShortProperty create(short shortProperty) {
+      return new AutoValue_AutoValueTest_ShortProperty(shortProperty);
+    }
+  }
+
+  @Test
+  public void testShortHashCode() {
+    short shortValue = 0x1234;
+    ShortProperty shortProperty = ShortProperty.create(shortValue);
+    assertEquals(singlePropertyHash(shortValue), shortProperty.hashCode());
+  }
+
+  @AutoValue
+  abstract static class ByteProperty {
+    public abstract byte byteProperty();
+
+    public static ByteProperty create(byte byteProperty) {
+      return new AutoValue_AutoValueTest_ByteProperty(byteProperty);
+    }
+  }
+
+  @Test
+  public void testByteHashCode() {
+    byte byteValue = 123;
+    ByteProperty byteProperty = ByteProperty.create(byteValue);
+    assertEquals(singlePropertyHash(byteValue), byteProperty.hashCode());
+  }
+
+  @AutoValue
+  abstract static class CharProperty {
+    public abstract char charProperty();
+
+    public static CharProperty create(char charProperty) {
+      return new AutoValue_AutoValueTest_CharProperty(charProperty);
+    }
+  }
+
+  @Test
+  public void testCharHashCode() {
+    char charValue = 123;
+    CharProperty charProperty = CharProperty.create(charValue);
+    assertEquals(singlePropertyHash(charValue), charProperty.hashCode());
+  }
+
+  @AutoValue
+  abstract static class BooleanProperty {
+    public abstract boolean booleanProperty();
+
+    public static BooleanProperty create(boolean booleanProperty) {
+      return new AutoValue_AutoValueTest_BooleanProperty(booleanProperty);
+    }
+  }
+
+  @Test
+  public void testBooleanHashCode() {
+    for (boolean booleanValue : new boolean[] {false, true}) {
+      BooleanProperty booleanProperty = BooleanProperty.create(booleanValue);
+      assertEquals(singlePropertyHash(booleanValue), booleanProperty.hashCode());
+    }
+  }
+
+  @AutoValue
+  abstract static class FloatProperty {
+    public abstract float floatProperty();
+
+    public static FloatProperty create(float floatProperty) {
+      return new AutoValue_AutoValueTest_FloatProperty(floatProperty);
+    }
+  }
+
+  @Test
+  public void testFloatHashCode() {
+    float floatValue = 123456f;
+    FloatProperty floatProperty = FloatProperty.create(floatValue);
+    assertEquals(singlePropertyHash(floatValue), floatProperty.hashCode());
+  }
+
+  @AutoValue
+  abstract static class DoubleProperty {
+    public abstract double doubleProperty();
+
+    public static DoubleProperty create(double doubleProperty) {
+      return new AutoValue_AutoValueTest_DoubleProperty(doubleProperty);
+    }
+  }
+
+  @Test
+  public void testDoubleHashCode() {
+    double doubleValue = 1234567890123456d;
+    DoubleProperty doubleProperty = DoubleProperty.create(doubleValue);
+    assertEquals(singlePropertyHash(doubleValue), doubleProperty.hashCode());
+  }
+
+  @Test
+  public void testFloatingEquality() {
+    FloatProperty floatZero = FloatProperty.create(0.0f);
+    FloatProperty floatMinusZero = FloatProperty.create(-0.0f);
+    FloatProperty floatNaN = FloatProperty.create(Float.NaN);
+    DoubleProperty doubleZero = DoubleProperty.create(0.0);
+    DoubleProperty doubleMinusZero = DoubleProperty.create(-0.0);
+    DoubleProperty doubleNaN = DoubleProperty.create(Double.NaN);
+    new EqualsTester()
+        .addEqualityGroup(floatZero)
+        .addEqualityGroup(floatMinusZero)
+        .addEqualityGroup(floatNaN)
+        .addEqualityGroup(doubleZero)
+        .addEqualityGroup(doubleMinusZero)
+        .addEqualityGroup(doubleNaN)
+        .testEquals();
+  }
+
+  private static int singlePropertyHash(Object property) {
+    return 1000003 ^ property.hashCode();
+  }
+
+  abstract static class Super {
+    public abstract Object superObject();
+
+    public abstract boolean superBoolean();
+    // The above two are out of alphabetical order to test EclipseHack.
+  }
+
+  @AutoValue
+  public abstract static class Sub extends Super {
+    public abstract int subInt();
+
+    public static Sub create(Object superObject, boolean superBoolean, int subInt) {
+      return new AutoValue_AutoValueTest_Sub(superObject, superBoolean, subInt);
+    }
+  }
+
+  // The @AutoValue class can inherit abstract methods from its superclass.
+  @Test
+  public void testSuperclass() throws Exception {
+    Sub instance = Sub.create("blim", true, 1729);
+    assertEquals("blim", instance.superObject());
+    assertTrue(instance.superBoolean());
+    assertEquals(1729, instance.subInt());
+    assertEquals(instance, instance);
+    assertEqualsNullIsFalse(instance);
+  }
+
+  abstract static class NonPublicSuper {
+    abstract Object superObject();
+  }
+
+  // The properties in this subclass are not in alphabetical order, which enables us to test that
+  // everything works correctly when Eclipse sorts them into the order
+  // [superObject, subInt, subString], since it sorts per class.
+  @AutoValue
+  abstract static class NonPublicSub extends NonPublicSuper {
+    abstract String subString();
+
+    abstract int subInt();
+
+    static NonPublicSub create(Object superObject, String subString, int subInt) {
+      return new AutoValue_AutoValueTest_NonPublicSub(superObject, subString, subInt);
+    }
+  }
+
+  @Test
+  public void testNonPublicInheritedGetters() throws Exception {
+    NonPublicSub instance = NonPublicSub.create("blim", "blam", 1729);
+    assertEquals("blim", instance.superObject());
+    assertEquals("blam", instance.subString());
+    assertEquals(1729, instance.subInt());
+    assertEquals(instance, instance);
+    assertEqualsNullIsFalse(instance);
+  }
+
+  @SuppressWarnings("ObjectEqualsNull")
+  private void assertEqualsNullIsFalse(Object instance) {
+    assertFalse(instance.equals(null));
+  }
+
+  @AutoValue
+  abstract static class NullableProperties {
+    @Nullable
+    abstract String nullableString();
+
+    abstract int randomInt();
+
+    static NullableProperties create(@Nullable String nullableString, int randomInt) {
+      return new AutoValue_AutoValueTest_NullableProperties(nullableString, randomInt);
+    }
+  }
+
+  @Test
+  public void testNullablePropertiesCanBeNull() {
+    NullableProperties instance = NullableProperties.create(null, 23);
+    assertNull(instance.nullableString());
+    assertThat(instance.randomInt()).isEqualTo(23);
+    String expectedString =
+        omitIdentifiers ? "{null, 23}" : "NullableProperties{nullableString=null, randomInt=23}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+  }
+
+  @AutoAnnotation
+  static Nullable nullable() {
+    return new AutoAnnotation_AutoValueTest_nullable();
+  }
+
+  @Test
+  public void testNullablePropertyConstructorParameterIsNullable() throws NoSuchMethodException {
+    Constructor<?> constructor =
+        AutoValue_AutoValueTest_NullableProperties.class.getDeclaredConstructor(
+            String.class, int.class);
+    assertThat(constructor.getParameterAnnotations()[0]).asList().contains(nullable());
+  }
+
+  @AutoValue
+  abstract static class AlternativeNullableProperties {
+    @interface Nullable {}
+
+    @AlternativeNullableProperties.Nullable
+    abstract String nullableString();
+
+    abstract int randomInt();
+
+    static AlternativeNullableProperties create(@Nullable String nullableString, int randomInt) {
+      return new AutoValue_AutoValueTest_AlternativeNullableProperties(nullableString, randomInt);
+    }
+  }
+
+  @Test
+  public void testNullableCanBeFromElsewhere() throws Exception {
+    AlternativeNullableProperties instance = AlternativeNullableProperties.create(null, 23);
+    assertNull(instance.nullableString());
+    assertThat(instance.randomInt()).isEqualTo(23);
+    String expectedString =
+        omitIdentifiers
+            ? "{null, 23}"
+            : "AlternativeNullableProperties{nullableString=null, randomInt=23}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+  }
+
+  @AutoValue
+  abstract static class NonNullableProperties {
+    abstract String nonNullableString();
+
+    abstract int randomInt();
+
+    static NonNullableProperties create(String nonNullableString, int randomInt) {
+      return new AutoValue_AutoValueTest_NonNullableProperties(nonNullableString, randomInt);
+    }
+  }
+
+  @Test
+  public void testNonNullablePropertiesCannotBeNull() throws Exception {
+    try {
+      NonNullableProperties.create(null, 23);
+      fail("Object creation succeeded but should not have");
+    } catch (NullPointerException expected) {
+    }
+    NonNullableProperties instance = NonNullableProperties.create("nonnull", 23);
+    assertEquals("nonnull", instance.nonNullableString());
+    assertEquals(23, instance.randomInt());
+  }
+
+  @AutoValue
+  abstract static class NullableListProperties {
+    @Nullable
+    abstract ImmutableList<String> nullableStringList();
+
+    static NullableListProperties create(@Nullable ImmutableList<String> nullableStringList) {
+      return new AutoValue_AutoValueTest_NullableListProperties(nullableStringList);
+    }
+  }
+
+  @Test
+  public void testNullableListPropertiesCanBeNonNull() {
+    NullableListProperties instance = NullableListProperties.create(ImmutableList.of("foo", "bar"));
+    assertEquals(ImmutableList.of("foo", "bar"), instance.nullableStringList());
+  }
+
+  @Test
+  public void testNullableListPropertiesCanBeNull() {
+    NullableListProperties instance = NullableListProperties.create(null);
+    assertNull(instance.nullableStringList());
+  }
+
+  @AutoValue
+  abstract static class NullableListPropertiesWithBuilder {
+    @Nullable
+    abstract ImmutableList<String> nullableStringList();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_NullableListPropertiesWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    interface Builder {
+      Builder nullableStringList(List<String> nullableStringList);
+
+      NullableListPropertiesWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testNullableListPropertiesWithBuilderCanBeNonNull() {
+    NullableListPropertiesWithBuilder instance =
+        NullableListPropertiesWithBuilder.builder()
+            .nullableStringList(ImmutableList.of("foo", "bar"))
+            .build();
+    assertEquals(ImmutableList.of("foo", "bar"), instance.nullableStringList());
+  }
+
+  @Test
+  public void testNullableListPropertiesWithBuilderCanBeUnset() {
+    NullableListPropertiesWithBuilder instance =
+        NullableListPropertiesWithBuilder.builder().build();
+    assertNull(instance.nullableStringList());
+  }
+
+  @Test
+  public void testNullableListPropertiesWithBuilderCanBeNull() {
+    NullableListPropertiesWithBuilder instance =
+        NullableListPropertiesWithBuilder.builder().nullableStringList(null).build();
+    assertNull(instance.nullableStringList());
+  }
+
+  static class Nested {
+    @AutoValue
+    abstract static class Doubly {
+      @Nullable
+      abstract String nullableString();
+
+      abstract int randomInt();
+
+      static Doubly create(String nullableString, int randomInt) {
+        return new AutoValue_AutoValueTest_Nested_Doubly(nullableString, randomInt);
+      }
+    }
+  }
+
+  @Test
+  public void testDoublyNestedClass() throws Exception {
+    Nested.Doubly instance = Nested.Doubly.create(null, 23);
+    assertNull(instance.nullableString());
+    assertThat(instance.randomInt()).isEqualTo(23);
+    String expectedString =
+        omitIdentifiers ? "{null, 23}" : "Doubly{nullableString=null, randomInt=23}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+  }
+
+  static interface NestedInInterface {
+    @AutoValue
+    abstract class Doubly {
+      abstract String string();
+
+      abstract Map<String, Integer> map();
+
+      static Doubly create(String string, Map<String, Integer> map) {
+        return new AutoValue_AutoValueTest_NestedInInterface_Doubly(string, map);
+      }
+    }
+  }
+
+  @Test
+  public void testClassNestedInInterface() throws Exception {
+    Map<String, Integer> map = ImmutableMap.of("vingt-et-un", 21);
+    NestedInInterface.Doubly instance = NestedInInterface.Doubly.create("foo", map);
+    assertEquals("foo", instance.string());
+    assertEquals(map, instance.map());
+  }
+
+  @AutoValue
+  abstract static class NullableNonNullable {
+    @Nullable
+    abstract String nullableString();
+
+    @Nullable
+    abstract String otherNullableString();
+
+    abstract String nonNullableString();
+
+    static NullableNonNullable create(
+        String nullableString, String otherNullableString, String nonNullableString) {
+      return new AutoValue_AutoValueTest_NullableNonNullable(
+          nullableString, otherNullableString, nonNullableString);
+    }
+  }
+
+  @Test
+  public void testEqualsWithNullable() throws Exception {
+    NullableNonNullable everythingNull =
+        NullableNonNullable.create(null, null, "nonNullableString");
+    NullableNonNullable somethingNull =
+        NullableNonNullable.create(null, "otherNullableString", "nonNullableString");
+    NullableNonNullable nothingNull =
+        NullableNonNullable.create("nullableString", "otherNullableString", "nonNullableString");
+    NullableNonNullable nothingNullAgain =
+        NullableNonNullable.create("nullableString", "otherNullableString", "nonNullableString");
+    new EqualsTester()
+        .addEqualityGroup(everythingNull)
+        .addEqualityGroup(somethingNull)
+        .addEqualityGroup(nothingNull, nothingNullAgain)
+        .testEquals();
+  }
+
+  @AutoValue
+  abstract static class GenericProperties {
+    abstract Map<String, Integer> simpleMap();
+
+    abstract Map<String, Map<String, Integer>> hairyMap();
+
+    static GenericProperties create(
+        Map<String, Integer> simpleMap, Map<String, Map<String, Integer>> hairyMap) {
+      return new AutoValue_AutoValueTest_GenericProperties(simpleMap, hairyMap);
+    }
+  }
+
+  @Test
+  public void testGenericProperties() throws Exception {
+    GenericProperties instance1 =
+        GenericProperties.create(
+            ImmutableMap.of("twenty-three", 23),
+            ImmutableMap.of("very", (Map<String, Integer>) ImmutableMap.of("hairy", 17)));
+    GenericProperties instance2 =
+        GenericProperties.create(
+            ImmutableMap.of("seventeen", 17),
+            ImmutableMap.of("very", (Map<String, Integer>) ImmutableMap.of("hairy", 23)));
+    new EqualsTester().addEqualityGroup(instance1).addEqualityGroup(instance2).testEquals();
+    assertEquals(
+        ImmutableMap.of("very", (Map<String, Integer>) ImmutableMap.of("hairy", 23)),
+        instance2.hairyMap());
+  }
+
+  @AutoValue
+  abstract static class GenericClass<K, V> {
+    abstract K key();
+
+    abstract Map<K, V> map();
+
+    static <K, V> GenericClass<K, V> create(K key, Map<K, V> map) {
+      return new AutoValue_AutoValueTest_GenericClass<K, V>(key, map);
+    }
+  }
+
+  @Test
+  public void testGenericClass() throws Exception {
+    GenericClass<String, Boolean> instance =
+        GenericClass.create("whatever", ImmutableMap.of("no", false));
+    assertEquals(instance, instance);
+    assertEquals("whatever", instance.key());
+    assertEquals(ImmutableMap.of("no", false), instance.map());
+  }
+
+  @AutoValue
+  abstract static class GenericClassSimpleBounds<K extends Number, V extends K> {
+    abstract K key();
+
+    abstract Map<K, V> map();
+
+    static <K extends Number, V extends K> GenericClassSimpleBounds<K, V> create(
+        K key, Map<K, V> map) {
+      return new AutoValue_AutoValueTest_GenericClassSimpleBounds<K, V>(key, map);
+    }
+  }
+
+  @Test
+  public void testGenericClassWithSimpleBounds() throws Exception {
+    GenericClassSimpleBounds<Integer, Integer> instance =
+        GenericClassSimpleBounds.create(23, ImmutableMap.of(17, 23));
+    assertEquals(instance, instance);
+    assertEquals(23, (int) instance.key());
+    assertEquals(ImmutableMap.of(17, 23), instance.map());
+  }
+
+  @AutoValue
+  abstract static class GenericClassHairyBounds<K extends List<V> & Comparable<K>, V> {
+    abstract K key();
+
+    abstract Map<K, V> map();
+
+    static <K extends List<V> & Comparable<K>, V> GenericClassHairyBounds<K, V> create(
+        K key, Map<K, V> map) {
+      return new AutoValue_AutoValueTest_GenericClassHairyBounds<K, V>(key, map);
+    }
+  }
+
+  @Test
+  public void testGenericClassWithHairyBounds() throws Exception {
+    class ComparableList<E> extends ArrayList<E> implements Comparable<ComparableList<E>> {
+      @Override
+      public int compareTo(ComparableList<E> list) {
+        throw new UnsupportedOperationException();
+      }
+    }
+    ComparableList<String> emptyList = new ComparableList<String>();
+    GenericClassHairyBounds<ComparableList<String>, String> instance =
+        GenericClassHairyBounds.create(emptyList, ImmutableMap.of(emptyList, "23"));
+    assertEquals(instance, instance);
+    assertEquals(emptyList, instance.key());
+    assertEquals(ImmutableMap.of(emptyList, "23"), instance.map());
+  }
+
+  interface Mergeable<M extends Mergeable<M>> {
+    M merge(M other);
+  }
+
+  @AutoValue
+  abstract static class Delta<M extends Mergeable<M>> {
+    abstract M meta();
+
+    static <M extends Mergeable<M>> Delta<M> create(M meta) {
+      return new AutoValue_AutoValueTest_Delta<M>(meta);
+    }
+  }
+
+  @Test
+  public void testRecursiveGeneric() {
+    class MergeableImpl implements Mergeable<MergeableImpl> {
+      @Override
+      public MergeableImpl merge(MergeableImpl other) {
+        return this;
+      }
+    }
+    MergeableImpl mergeable = new MergeableImpl();
+    Delta<MergeableImpl> instance = Delta.create(mergeable);
+    assertSame(mergeable, instance.meta());
+  }
+
+  static class NodeType<O> {}
+
+  abstract static class NodeExpressionClass<O> {
+    abstract NodeType<O> getType();
+  }
+
+  @AutoValue
+  abstract static class NotNodeExpression extends NodeExpressionClass<Boolean> {
+    static NotNodeExpression create() {
+      return new AutoValue_AutoValueTest_NotNodeExpression(new NodeType<Boolean>());
+    }
+  }
+
+  interface NodeExpressionInterface<O> {
+    NodeType<O> getType();
+  }
+
+  @AutoValue
+  abstract static class NotNodeExpression2 implements NodeExpressionInterface<Boolean> {
+    static NotNodeExpression2 create() {
+      return new AutoValue_AutoValueTest_NotNodeExpression2(new NodeType<Boolean>());
+    }
+  }
+
+  @Test
+  public void testConcreteWithGenericParent() {
+    NotNodeExpression instance = NotNodeExpression.create();
+    assertThat(instance.getType()).isInstanceOf(NodeType.class);
+    NotNodeExpression2 instance2 = NotNodeExpression2.create();
+    assertThat(instance2.getType()).isInstanceOf(NodeType.class);
+  }
+
+  @AutoValue
+  abstract static class ExplicitToString {
+    abstract String string();
+
+    static ExplicitToString create(String string) {
+      return new AutoValue_AutoValueTest_ExplicitToString(string);
+    }
+
+    @Override
+    public String toString() {
+      return "Bazinga{" + string() + "}";
+    }
+  }
+
+  // We should not generate a toString() method if there already is a non-default one.
+  @Test
+  public void testExplicitToString() throws Exception {
+    ExplicitToString instance = ExplicitToString.create("foo");
+    assertEquals("Bazinga{foo}", instance.toString());
+  }
+
+  abstract static class NonAutoExplicitToString {
+    abstract String string();
+
+    @Override
+    public String toString() {
+      return "Bazinga{" + string() + "}";
+    }
+  }
+
+  @AutoValue
+  abstract static class InheritedExplicitToString extends NonAutoExplicitToString {
+    static InheritedExplicitToString create(String string) {
+      return new AutoValue_AutoValueTest_InheritedExplicitToString(string);
+    }
+  }
+
+  // We should not generate a toString() method if we already inherit a non-default one.
+  @Test
+  public void testInheritedExplicitToString() throws Exception {
+    InheritedExplicitToString instance = InheritedExplicitToString.create("foo");
+    assertEquals("Bazinga{foo}", instance.toString());
+  }
+
+  @AutoValue
+  abstract static class AbstractToString {
+    abstract String string();
+
+    static AbstractToString create(String string) {
+      return new AutoValue_AutoValueTest_AbstractToString(string);
+    }
+
+    @Override
+    public abstract String toString();
+  }
+
+  // We should generate a toString() method if the parent class has an abstract one.
+  // That allows users to cancel a toString() from a parent class if they want.
+  @Test
+  public void testAbstractToString() throws Exception {
+    AbstractToString instance = AbstractToString.create("foo");
+    String expectedString = omitIdentifiers ? "{foo}" : "AbstractToString{string=foo}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+  }
+
+  abstract static class NonAutoAbstractToString {
+    abstract String string();
+
+    @Override
+    public abstract String toString();
+  }
+
+  @AutoValue
+  abstract static class SubAbstractToString extends NonAutoAbstractToString {
+    static SubAbstractToString create(String string) {
+      return new AutoValue_AutoValueTest_SubAbstractToString(string);
+    }
+  }
+
+  // We should generate a toString() method if the parent class inherits an abstract one.
+  @Test
+  public void testInheritedAbstractToString() throws Exception {
+    SubAbstractToString instance = SubAbstractToString.create("foo");
+    String expectedString = omitIdentifiers ? "{foo}" : "SubAbstractToString{string=foo}";
+    assertThat(instance.toString()).isEqualTo(expectedString);
+  }
+
+  @AutoValue
+  abstract static class ExplicitHashCode {
+    abstract String string();
+
+    static ExplicitHashCode create(String string) {
+      return new AutoValue_AutoValueTest_ExplicitHashCode(string);
+    }
+
+    @Override
+    public int hashCode() {
+      return 1234;
+    }
+  }
+
+  @Test
+  public void testExplicitHashCode() throws Exception {
+    ExplicitHashCode instance = ExplicitHashCode.create("foo");
+    assertEquals(1234, instance.hashCode());
+  }
+
+  @AutoValue
+  @SuppressWarnings("EqualsHashCode")
+  abstract static class ExplicitEquals {
+    int equalsCount;
+
+    static ExplicitEquals create() {
+      return new AutoValue_AutoValueTest_ExplicitEquals();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      equalsCount++;
+      return super.equals(o);
+    }
+  }
+
+  @SuppressWarnings("SelfEquals")
+  @Test
+  public void testExplicitEquals() throws Exception {
+    ExplicitEquals instance = ExplicitEquals.create();
+    assertEquals(0, instance.equalsCount);
+    assertTrue(instance.equals(instance));
+    assertEquals(1, instance.equalsCount);
+    Method equals = instance.getClass().getMethod("equals", Object.class);
+    assertNotSame(ExplicitEquals.class, instance.getClass());
+    assertSame(ExplicitEquals.class, equals.getDeclaringClass());
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface MyAnnotation {
+    String value();
+  }
+
+  @AutoAnnotation
+  private static MyAnnotation myAnnotation(String value) {
+    return new AutoAnnotation_AutoValueTest_myAnnotation(value);
+  }
+
+  @AutoValue
+  abstract static class PrimitiveArrays {
+    @SuppressWarnings("mutable")
+    abstract boolean[] booleans();
+
+    @SuppressWarnings("mutable")
+    @Nullable
+    abstract int[] ints();
+
+    static PrimitiveArrays create(boolean[] booleans, int[] ints) {
+      // Real code would likely clone these parameters, but here we want to check that the
+      // generated constructor rejects a null value for booleans.
+      return new AutoValue_AutoValueTest_PrimitiveArrays(booleans, ints);
+    }
+  }
+
+  @Test
+  public void testPrimitiveArrays() {
+    PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], new int[0]);
+    boolean[] booleans = {false, true, true, false};
+    int[] ints = {6, 28, 496, 8128, 33550336};
+    PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), ints.clone());
+    PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), ints.clone());
+    new EqualsTester().addEqualityGroup(object1, object2).addEqualityGroup(object0).testEquals();
+    // EqualsTester also exercises hashCode(). We clone the arrays above to ensure that using the
+    // default Object.hashCode() will fail.
+
+    String expectedString =
+        omitIdentifiers
+            ? ("{" + Arrays.toString(booleans) + ", " + Arrays.toString(ints) + "}")
+            : ("PrimitiveArrays{booleans="
+                + Arrays.toString(booleans)
+                + ", "
+                + "ints="
+                + Arrays.toString(ints)
+                + "}");
+    assertThat(object1.toString()).isEqualTo(expectedString);
+    assertThat(object1.ints()).isSameInstanceAs(object1.ints());
+  }
+
+  @Test
+  public void testNullablePrimitiveArrays() {
+    PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], null);
+    boolean[] booleans = {false, true, true, false};
+    PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), null);
+    PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), null);
+    new EqualsTester().addEqualityGroup(object1, object2).addEqualityGroup(object0).testEquals();
+
+    String expectedString =
+        omitIdentifiers
+            ? ("{" + Arrays.toString(booleans) + ", null}")
+            : ("PrimitiveArrays{booleans=" + Arrays.toString(booleans) + ", " + "ints=null}");
+    assertThat(object1.toString()).isEqualTo(expectedString);
+
+    assertThat(object1.booleans()).isSameInstanceAs(object1.booleans());
+    assertThat(object1.booleans()).isEqualTo(booleans);
+    object1.booleans()[0] ^= true;
+    assertThat(object1.booleans()).isNotEqualTo(booleans);
+  }
+
+  @Test
+  public void testNotNullablePrimitiveArrays() {
+    try {
+      PrimitiveArrays.create(null, new int[0]);
+      fail("Construction with null value for non-@Nullable array should have failed");
+    } catch (NullPointerException e) {
+      if (omitIdentifiers) {
+        assertThat(e).hasMessageThat().isNull();
+      } else {
+        assertThat(e).hasMessageThat().contains("booleans");
+      }
+    }
+  }
+
+  // If users are mad enough to define their own Arrays class and have some properties of that
+  // class and others of primitive array type, then we can't import java.util.Arrays.
+  // This is unlikely.
+  @AutoValue
+  abstract static class AmbiguousArrays {
+    static class Arrays {}
+
+    abstract Arrays arrays();
+
+    @SuppressWarnings("mutable")
+    abstract int[] ints();
+
+    static AmbiguousArrays create(Arrays arrays, int[] ints) {
+      return new AutoValue_AutoValueTest_AmbiguousArrays(arrays, ints);
+    }
+  }
+
+  @Test
+  public void testAmbiguousArrays() {
+    // If this test compiles at all then we presumably don't have the import problem above.
+    AmbiguousArrays object1 = AmbiguousArrays.create(new AmbiguousArrays.Arrays(), new int[0]);
+    assertNotNull(object1.arrays());
+    assertEquals(0, object1.ints().length);
+  }
+
+  static final class HashCodeObserver {
+    int hashCodeCount;
+
+    @Override
+    public boolean equals(Object obj) {
+      return obj instanceof HashCodeObserver;
+    }
+
+    @Override
+    public int hashCode() {
+      hashCodeCount++;
+      return 23;
+    }
+  }
+
+  @AutoValue
+  abstract static class MaybeCachedHashCode {
+    abstract HashCodeObserver hashCodeObserver();
+
+    abstract int randomInt();
+
+    static MaybeCachedHashCode create(HashCodeObserver hashCodeObserver, int randomInt) {
+      return new AutoValue_AutoValueTest_MaybeCachedHashCode(hashCodeObserver, randomInt);
+    }
+  }
+
+  @Test
+  public void testHashCodeNotCached() {
+    HashCodeObserver observer = new HashCodeObserver();
+    MaybeCachedHashCode maybeCached = MaybeCachedHashCode.create(observer, 17);
+    int hash1 = maybeCached.hashCode();
+    int hash2 = maybeCached.hashCode();
+    assertEquals(hash1, hash2);
+    assertEquals(2, observer.hashCodeCount);
+  }
+
+  @AutoValue
+  abstract static class Version implements Comparable<Version> {
+    abstract int major();
+
+    abstract int minor();
+
+    static Version create(int major, int minor) {
+      return new AutoValue_AutoValueTest_Version(major, minor);
+    }
+
+    @Override
+    public int compareTo(Version that) {
+      return ComparisonChain.start()
+          .compare(this.major(), that.major())
+          .compare(this.minor(), that.minor())
+          .result();
+    }
+  }
+
+  @Test
+  public void testComparisonChain() {
+    assertEquals(Version.create(1, 2), Version.create(1, 2));
+    Version[] versions = {Version.create(1, 2), Version.create(1, 3), Version.create(2, 1)};
+    for (int i = 0; i < versions.length; i++) {
+      for (int j = 0; j < versions.length; j++) {
+        int actual = Integer.signum(versions[i].compareTo(versions[j]));
+        int expected = Integer.signum(i - j);
+        assertEquals(expected, actual);
+      }
+    }
+  }
+
+  abstract static class LukesBase {
+    interface LukesVisitor<T> {
+      T visit(LukesSub s);
+    }
+
+    abstract <T> T accept(LukesVisitor<T> visitor);
+
+    @AutoValue
+    abstract static class LukesSub extends LukesBase {
+      static LukesSub create() {
+        return new AutoValue_AutoValueTest_LukesBase_LukesSub();
+      }
+
+      @Override
+      <T> T accept(LukesVisitor<T> visitor) {
+        return visitor.visit(this);
+      }
+    }
+  }
+
+  @Test
+  public void testVisitor() {
+    LukesBase.LukesVisitor<String> visitor =
+        new LukesBase.LukesVisitor<String>() {
+          @Override
+          public String visit(LukesBase.LukesSub s) {
+            return s.toString();
+          }
+        };
+    LukesBase.LukesSub sub = LukesBase.LukesSub.create();
+    assertEquals(sub.toString(), sub.accept(visitor));
+  }
+
+  @AutoValue
+  public abstract static class ComplexInheritance extends AbstractBase implements A, B {
+    public static ComplexInheritance create(String name) {
+      return new AutoValue_AutoValueTest_ComplexInheritance(name);
+    }
+
+    abstract String name();
+  }
+
+  static class AbstractBase implements Base {
+    @Override
+    public int answer() {
+      return 42;
+    }
+  }
+
+  interface A extends Base {}
+
+  interface B extends Base {}
+
+  interface Base {
+    int answer();
+  }
+
+  @Test
+  public void testComplexInheritance() {
+    ComplexInheritance complex = ComplexInheritance.create("fred");
+    assertEquals("fred", complex.name());
+    assertEquals(42, complex.answer());
+  }
+
+  // This tests the case where we inherit abstract methods on more than one path. AbstractList
+  // extends AbstractCollection, which implements Collection; and AbstractList also implements List,
+  // which extends Collection. So the class here inherits the methods of Collection on more than
+  // one path. In an earlier version of the logic for handling inheritance, this confused us into
+  // thinking that the methods from Collection were still abstract and therefore candidates for
+  // implementation, even though we inherit concrete implementations of them from AbstractList.
+  @AutoValue
+  public static class MoreComplexInheritance extends AbstractList<String> {
+    @Override
+    public String get(int index) {
+      throw new NoSuchElementException(String.valueOf(index));
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    public static MoreComplexInheritance create() {
+      return new AutoValue_AutoValueTest_MoreComplexInheritance();
+    }
+  }
+
+  @Test
+  public void testMoreComplexInheritance() {
+    MoreComplexInheritance instance1 = MoreComplexInheritance.create();
+    MoreComplexInheritance instance2 = MoreComplexInheritance.create();
+    assertThat(instance1).isEqualTo(instance2);
+    assertThat(instance1).isNotSameInstanceAs(instance2);
+  }
+
+  // Test that we are not misled by the privateness of an ancestor into thinking that its methods
+  // are invisible to descendants.
+  public abstract static class PublicGrandparent {
+    public abstract String foo();
+  }
+
+  private static class PrivateParent extends PublicGrandparent {
+    @Override
+    public String foo() {
+      return "foo";
+    }
+  }
+
+  @AutoValue
+  static class EffectiveVisibility extends PrivateParent {
+    static EffectiveVisibility create() {
+      return new AutoValue_AutoValueTest_EffectiveVisibility();
+    }
+  }
+
+  @Test
+  public void testEffectiveVisibility() {
+    EffectiveVisibility instance1 = EffectiveVisibility.create();
+    EffectiveVisibility instance2 = EffectiveVisibility.create();
+    assertThat(instance1).isEqualTo(instance2);
+    assertThat(instance1).isNotSameInstanceAs(instance2);
+  }
+
+  @AutoValue
+  public abstract static class InheritTwice implements A, B {
+    public static InheritTwice create(int answer) {
+      return new AutoValue_AutoValueTest_InheritTwice(answer);
+    }
+  }
+
+  @Test
+  public void testInheritTwice() {
+    InheritTwice inheritTwice = InheritTwice.create(42);
+    assertEquals(42, inheritTwice.answer());
+  }
+
+  @AutoValue
+  public abstract static class Optional {
+    public abstract com.google.common.base.Optional<Object> getOptional();
+
+    public static Optional create(com.google.common.base.Optional<Object> opt) {
+      return new AutoValue_AutoValueTest_Optional(opt);
+    }
+  }
+
+  @Test
+  public void testAmbiguityFromAutoValueType() {
+    Optional autoOptional = Optional.create(com.google.common.base.Optional.absent());
+    assertEquals(com.google.common.base.Optional.absent(), autoOptional.getOptional());
+  }
+
+  static class BaseWithNestedType {
+    static class Optional {}
+  }
+
+  @AutoValue
+  public abstract static class InheritsNestedType extends BaseWithNestedType {
+    public abstract com.google.common.base.Optional<Object> getOptional();
+
+    public static InheritsNestedType create(com.google.common.base.Optional<Object> opt) {
+      return new AutoValue_AutoValueTest_InheritsNestedType(opt);
+    }
+  }
+
+  @Test
+  public void testAmbiguityFromInheritedType() {
+    InheritsNestedType inheritsNestedType =
+        InheritsNestedType.create(com.google.common.base.Optional.absent());
+    assertEquals(com.google.common.base.Optional.absent(), inheritsNestedType.getOptional());
+  }
+
+  abstract static class AbstractParent {
+    abstract int foo();
+  }
+
+  @AutoValue
+  abstract static class AbstractChild extends AbstractParent {
+    // The main point of this test is to ensure that we don't try to copy this @Override into the
+    // generated implementation alongside the @Override that we put on all implementation methods.
+    @Override
+    abstract int foo();
+
+    static AbstractChild create(int foo) {
+      return new AutoValue_AutoValueTest_AbstractChild(foo);
+    }
+  }
+
+  @Test
+  public void testOverrideNotDuplicated() {
+    AbstractChild instance = AbstractChild.create(23);
+    assertEquals(23, instance.foo());
+  }
+
+  @AutoValue
+  public abstract static class BasicWithBuilder {
+    public abstract int foo();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_BasicWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder foo(int foo);
+
+      BasicWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testBasicWithBuilder() {
+    BasicWithBuilder x = BasicWithBuilder.builder().foo(23).build();
+    assertEquals(23, x.foo());
+    try {
+      BasicWithBuilder.builder().build();
+      fail("Expected exception for missing property");
+    } catch (IllegalStateException e) {
+      if (omitIdentifiers) {
+        assertThat(e).hasMessageThat().isNull();
+      } else {
+        assertThat(e).hasMessageThat().contains("foo");
+      }
+    }
+  }
+
+  @Test
+  public void testBasicWithBuilderHasOnlyOneConstructor() throws Exception {
+    Class<?> builderClass = AutoValue_AutoValueTest_BasicWithBuilder.Builder.class;
+    Constructor<?>[] constructors = builderClass.getDeclaredConstructors();
+    assertThat(constructors).hasLength(1);
+    Constructor<?> constructor = constructors[0];
+    assertThat(constructor.getParameterTypes()).isEmpty();
+  }
+
+  @AutoValue
+  public abstract static class EmptyWithBuilder {
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_EmptyWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      EmptyWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testEmptyWithBuilder() {
+    EmptyWithBuilder x = EmptyWithBuilder.builder().build();
+    EmptyWithBuilder y = EmptyWithBuilder.builder().build();
+    assertEquals(x, y);
+  }
+
+  @AutoValue
+  public abstract static class TwoPropertiesWithBuilderClass {
+    public abstract String string();
+
+    public abstract int integer();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_TwoPropertiesWithBuilderClass.Builder();
+    }
+
+    public static Builder builder(String string) {
+      return new AutoValue_AutoValueTest_TwoPropertiesWithBuilderClass.Builder().string(string);
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+      public abstract Builder string(String x);
+
+      public abstract Builder integer(int x);
+
+      public abstract TwoPropertiesWithBuilderClass build();
+    }
+  }
+
+  @Test
+  public void testTwoPropertiesWithBuilderClass() {
+    TwoPropertiesWithBuilderClass a1 =
+        TwoPropertiesWithBuilderClass.builder().string("23").integer(17).build();
+    TwoPropertiesWithBuilderClass a2 =
+        TwoPropertiesWithBuilderClass.builder("23").integer(17).build();
+    TwoPropertiesWithBuilderClass a3 =
+        TwoPropertiesWithBuilderClass.builder().integer(17).string("23").build();
+    TwoPropertiesWithBuilderClass b =
+        TwoPropertiesWithBuilderClass.builder().string("17").integer(17).build();
+    new EqualsTester().addEqualityGroup(a1, a2, a3).addEqualityGroup(b).testEquals();
+
+    try {
+      TwoPropertiesWithBuilderClass.builder().string(null);
+      fail("Did not get expected exception");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  @AutoValue
+  public abstract static class NullablePropertyWithBuilder {
+    public abstract String notNullable();
+
+    @Nullable
+    public abstract String nullable();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_NullablePropertyWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder notNullable(String s);
+
+      Builder nullable(@Nullable String s);
+
+      NullablePropertyWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testOmitNullableWithBuilder() {
+    NullablePropertyWithBuilder instance1 =
+        NullablePropertyWithBuilder.builder().notNullable("hello").build();
+    assertThat(instance1.notNullable()).isEqualTo("hello");
+    assertThat(instance1.nullable()).isNull();
+
+    NullablePropertyWithBuilder instance2 =
+        NullablePropertyWithBuilder.builder().notNullable("hello").nullable(null).build();
+    assertThat(instance2.notNullable()).isEqualTo("hello");
+    assertThat(instance2.nullable()).isNull();
+    assertThat(instance1).isEqualTo(instance2);
+
+    NullablePropertyWithBuilder instance3 =
+        NullablePropertyWithBuilder.builder().notNullable("hello").nullable("world").build();
+    assertThat(instance3.notNullable()).isEqualTo("hello");
+    assertThat(instance3.nullable()).isEqualTo("world");
+
+    try {
+      NullablePropertyWithBuilder.builder().build();
+      fail("Expected IllegalStateException for unset non-@Nullable property");
+    } catch (IllegalStateException e) {
+      if (omitIdentifiers) {
+        assertThat(e).hasMessageThat().isNull();
+      } else {
+        assertThat(e).hasMessageThat().contains("notNullable");
+      }
+    }
+  }
+
+  @AutoValue
+  public abstract static class PrimitiveAndBoxed {
+    public abstract int anInt();
+
+    @Nullable
+    public abstract Integer aNullableInteger();
+
+    public abstract Integer aNonNullableInteger();
+
+    public abstract Builder toBuilder();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_PrimitiveAndBoxed.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder setAnInt(Integer x);
+
+      Builder setANullableInteger(int x);
+
+      Builder setANonNullableInteger(int x);
+
+      PrimitiveAndBoxed build();
+    }
+  }
+
+  @Test
+  public void testPrimitiveAndBoxed() {
+    PrimitiveAndBoxed instance1 =
+        PrimitiveAndBoxed.builder().setAnInt(17).setANonNullableInteger(23).build();
+    assertThat(instance1.anInt()).isEqualTo(17);
+    assertThat(instance1.aNullableInteger()).isNull();
+    assertThat(instance1.aNonNullableInteger()).isEqualTo(23);
+
+    PrimitiveAndBoxed instance2 = instance1.toBuilder().setANullableInteger(5).build();
+    assertThat(instance2.aNullableInteger()).isEqualTo(5);
+
+    try {
+      instance1.toBuilder().setAnInt(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  @AutoValue
+  public abstract static class OptionalPropertiesWithBuilder {
+    public abstract com.google.common.base.Optional<String> optionalString();
+
+    public abstract com.google.common.base.Optional<Integer> optionalInteger();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_OptionalPropertiesWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder setOptionalString(com.google.common.base.Optional<String> s);
+
+      Builder setOptionalString(String s);
+
+      Builder setOptionalInteger(com.google.common.base.Optional<Integer> i);
+
+      Builder setOptionalInteger(int i);
+
+      OptionalPropertiesWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testOmitOptionalWithBuilder() {
+    OptionalPropertiesWithBuilder omitted = OptionalPropertiesWithBuilder.builder().build();
+    assertThat(omitted.optionalString()).isAbsent();
+    assertThat(omitted.optionalInteger()).isAbsent();
+
+    OptionalPropertiesWithBuilder supplied =
+        OptionalPropertiesWithBuilder.builder()
+            .setOptionalString(com.google.common.base.Optional.of("foo"))
+            .build();
+    assertThat(supplied.optionalString()).hasValue("foo");
+    assertThat(omitted.optionalInteger()).isAbsent();
+
+    OptionalPropertiesWithBuilder suppliedDirectly =
+        OptionalPropertiesWithBuilder.builder()
+            .setOptionalString("foo")
+            .setOptionalInteger(23)
+            .build();
+    assertThat(suppliedDirectly.optionalString()).hasValue("foo");
+    assertThat(suppliedDirectly.optionalInteger()).hasValue(23);
+  }
+
+  @AutoValue
+  public abstract static class OptionalPropertyWithNullableBuilder {
+    public abstract String notOptional();
+
+    public abstract com.google.common.base.Optional<String> optional();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_OptionalPropertyWithNullableBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder notOptional(String s);
+
+      Builder optional(@Nullable String s);
+
+      OptionalPropertyWithNullableBuilder build();
+    }
+  }
+
+  @Test
+  public void testOmitOptionalWithNullableBuilder() {
+    OptionalPropertyWithNullableBuilder instance1 =
+        OptionalPropertyWithNullableBuilder.builder().notOptional("hello").build();
+    assertThat(instance1.notOptional()).isEqualTo("hello");
+    assertThat(instance1.optional()).isAbsent();
+
+    OptionalPropertyWithNullableBuilder instance2 =
+        OptionalPropertyWithNullableBuilder.builder().notOptional("hello").optional(null).build();
+    assertThat(instance2.notOptional()).isEqualTo("hello");
+    assertThat(instance2.optional()).isAbsent();
+    assertThat(instance1).isEqualTo(instance2);
+
+    OptionalPropertyWithNullableBuilder instance3 =
+        OptionalPropertyWithNullableBuilder.builder()
+            .notOptional("hello")
+            .optional("world")
+            .build();
+    assertThat(instance3.notOptional()).isEqualTo("hello");
+    assertThat(instance3.optional()).hasValue("world");
+
+    try {
+      OptionalPropertyWithNullableBuilder.builder().build();
+      fail("Expected IllegalStateException for unset non-Optional property");
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @AutoValue
+  public abstract static class NullableOptionalPropertiesWithBuilder {
+    @Nullable
+    public abstract com.google.common.base.Optional<String> optionalString();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_NullableOptionalPropertiesWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder setOptionalString(com.google.common.base.Optional<String> s);
+
+      NullableOptionalPropertiesWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testOmitNullableOptionalWithBuilder() {
+    NullableOptionalPropertiesWithBuilder omitted =
+        NullableOptionalPropertiesWithBuilder.builder().build();
+    assertThat(omitted.optionalString()).isNull();
+
+    NullableOptionalPropertiesWithBuilder supplied =
+        NullableOptionalPropertiesWithBuilder.builder()
+            .setOptionalString(com.google.common.base.Optional.of("foo"))
+            .build();
+    assertThat(supplied.optionalString()).hasValue("foo");
+  }
+
+  @AutoValue
+  public abstract static class OptionalPropertiesWithBuilderSimpleSetter {
+    public abstract com.google.common.base.Optional<String> optionalString();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_OptionalPropertiesWithBuilderSimpleSetter.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder setOptionalString(String s);
+
+      OptionalPropertiesWithBuilderSimpleSetter build();
+    }
+  }
+
+  @Test
+  public void testOptionalPropertySimpleSetter() {
+    OptionalPropertiesWithBuilderSimpleSetter omitted =
+        OptionalPropertiesWithBuilderSimpleSetter.builder().build();
+    assertThat(omitted.optionalString()).isAbsent();
+
+    OptionalPropertiesWithBuilderSimpleSetter supplied =
+        OptionalPropertiesWithBuilderSimpleSetter.builder().setOptionalString("foo").build();
+    assertThat(supplied.optionalString()).hasValue("foo");
+  }
+
+  @AutoValue
+  public abstract static class PropertyWithOptionalGetter {
+    public abstract String getString();
+
+    public abstract int getInt();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_PropertyWithOptionalGetter.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder setString(String s);
+
+      com.google.common.base.Optional<String> getString();
+
+      Builder setInt(int x);
+
+      com.google.common.base.Optional<Integer> getInt();
+
+      PropertyWithOptionalGetter build();
+    }
+  }
+
+  @Test
+  public void testOptionalGetter() {
+    PropertyWithOptionalGetter.Builder omitted = PropertyWithOptionalGetter.builder();
+    assertThat(omitted.getString()).isAbsent();
+    assertThat(omitted.getInt()).isAbsent();
+
+    PropertyWithOptionalGetter.Builder supplied =
+        PropertyWithOptionalGetter.builder().setString("foo").setInt(23);
+    assertThat(supplied.getString()).hasValue("foo");
+    assertThat(supplied.getInt()).hasValue(23);
+  }
+
+  @AutoValue
+  public abstract static class PropertyNamedMissing {
+    public abstract String missing();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_PropertyNamedMissing.Builder();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+      public abstract Builder setMissing(String x);
+
+      public abstract PropertyNamedMissing build();
+    }
+  }
+
+  // https://github.com/google/auto/issues/412
+  @Test
+  public void testPropertyNamedMissing() {
+    try {
+      PropertyNamedMissing.builder().build();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+    PropertyNamedMissing x = PropertyNamedMissing.builder().setMissing("foo").build();
+    assertThat(x.missing()).isEqualTo("foo");
+  }
+
+  @AutoValue
+  public abstract static class GenericsWithBuilder<T extends Number & Comparable<T>, U extends T> {
+    public abstract List<T> list();
+
+    public abstract U u();
+
+    public static <T extends Number & Comparable<T>, U extends T> Builder<T, U> builder() {
+      return new AutoValue_AutoValueTest_GenericsWithBuilder.Builder<T, U>();
+    }
+
+    public abstract Builder<T, U> toBuilderGenerated();
+
+    @AutoValue.Builder
+    public interface Builder<T extends Number & Comparable<T>, U extends T> {
+      Builder<T, U> list(List<T> list);
+
+      Builder<T, U> u(U u);
+
+      GenericsWithBuilder<T, U> build();
+    }
+  }
+
+  @Test
+  public void testBuilderGenerics() {
+    List<Integer> integers = ImmutableList.of(1, 2, 3);
+    GenericsWithBuilder<Integer, Integer> instance =
+        GenericsWithBuilder.<Integer, Integer>builder().list(integers).u(23).build();
+    assertEquals(integers, instance.list());
+    assertEquals((Integer) 23, instance.u());
+
+    GenericsWithBuilder<Integer, Integer> instance2 = instance.toBuilderGenerated().build();
+    assertEquals(instance, instance2);
+    assertNotSame(instance, instance2);
+
+    GenericsWithBuilder<Integer, Integer> instance3 = instance.toBuilderGenerated().u(17).build();
+    assertEquals(integers, instance3.list());
+    assertEquals((Integer) 17, instance3.u());
+  }
+
+  @AutoValue
+  public abstract static class BuilderWithSet<T extends Comparable<T>> {
+    public abstract List<T> list();
+
+    public abstract T t();
+
+    public static <T extends Comparable<T>> Builder<T> builder() {
+      return new AutoValue_AutoValueTest_BuilderWithSet.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    public interface Builder<T extends Comparable<T>> {
+      Builder<T> setList(List<T> list);
+
+      Builder<T> setT(T t);
+
+      BuilderWithSet<T> build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithSet() {
+    List<Integer> integers = ImmutableList.of(1, 2, 3);
+    BuilderWithSet<Integer> instance =
+        BuilderWithSet.<Integer>builder().setList(integers).setT(23).build();
+    assertEquals(integers, instance.list());
+    assertEquals((Integer) 23, instance.t());
+  }
+
+  @AutoValue
+  public abstract static class BuilderWithSetAndGet {
+    public abstract List<Integer> getAList();
+
+    public abstract int getAnInt();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_BuilderWithSetAndGet.Builder();
+    }
+
+    public abstract Builder toBuilder();
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder setAList(List<Integer> list);
+
+      Builder setAnInt(int i);
+
+      BuilderWithSetAndGet build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithSetAndGet() {
+    List<Integer> integers = ImmutableList.of(1, 2, 3);
+    BuilderWithSetAndGet instance =
+        BuilderWithSetAndGet.builder().setAList(integers).setAnInt(23).build();
+    assertEquals(integers, instance.getAList());
+    assertEquals(23, instance.getAnInt());
+
+    BuilderWithSetAndGet instance2 = instance.toBuilder().build();
+    assertEquals(instance, instance2);
+    assertNotSame(instance, instance2);
+
+    BuilderWithSetAndGet instance3 = instance.toBuilder().setAnInt(17).build();
+    assertEquals(integers, instance3.getAList());
+    assertEquals(17, instance3.getAnInt());
+  }
+
+  @AutoValue
+  public abstract static class BuilderWithUnprefixedGetters<T extends Comparable<T>> {
+    public abstract ImmutableList<T> list();
+
+    @Nullable
+    public abstract T t();
+
+    @SuppressWarnings("mutable")
+    public abstract int[] ints();
+
+    public abstract int noGetter();
+
+    public abstract String oAuth();
+
+    public abstract String oBrien();
+
+    public static <T extends Comparable<T>> Builder<T> builder() {
+      return new AutoValue_AutoValueTest_BuilderWithUnprefixedGetters.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    public interface Builder<T extends Comparable<T>> {
+      Builder<T> setList(ImmutableList<T> list);
+
+      Builder<T> setT(T t);
+
+      Builder<T> setInts(int[] ints);
+
+      Builder<T> setNoGetter(int x);
+
+      Builder<T> setoAuth(String x); // this ugly spelling is for compatibility
+
+      Builder<T> setOBrien(String x);
+
+      ImmutableList<T> list();
+
+      T t();
+
+      int[] ints();
+
+      String oAuth();
+
+      String oBrien();
+
+      BuilderWithUnprefixedGetters<T> build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithUnprefixedGetter() {
+    ImmutableList<String> names = ImmutableList.of("fred", "jim");
+    int[] ints = {6, 28, 496, 8128, 33550336};
+    int noGetter = -1;
+
+    BuilderWithUnprefixedGetters.Builder<String> builder = BuilderWithUnprefixedGetters.builder();
+    assertNull(builder.t());
+    try {
+      builder.list();
+      fail("Attempt to retrieve unset list property should have failed");
+    } catch (IllegalStateException e) {
+      if (omitIdentifiers) {
+        assertThat(e).hasMessageThat().isNull();
+      } else {
+        assertThat(e).hasMessageThat().isEqualTo("Property \"list\" has not been set");
+      }
+    }
+    try {
+      builder.ints();
+      fail("Attempt to retrieve unset ints property should have failed");
+    } catch (IllegalStateException e) {
+      if (omitIdentifiers) {
+        assertThat(e).hasMessageThat().isNull();
+      } else {
+        assertThat(e).hasMessageThat().isEqualTo("Property \"ints\" has not been set");
+      }
+    }
+
+    builder.setList(names);
+    assertThat(builder.list()).isSameInstanceAs(names);
+    builder.setInts(ints);
+    assertThat(builder.ints()).isEqualTo(ints);
+    builder.setoAuth("OAuth");
+    assertThat(builder.oAuth()).isEqualTo("OAuth");
+    builder.setOBrien("Flann");
+    assertThat(builder.oBrien()).isEqualTo("Flann");
+    // The array is not cloned by the getter, so the client can modify it (but shouldn't).
+    ints[0] = 0;
+    assertThat(builder.ints()[0]).isEqualTo(0);
+    ints[0] = 6;
+
+    BuilderWithUnprefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
+    assertThat(instance.list()).isSameInstanceAs(names);
+    assertThat(instance.t()).isNull();
+    assertThat(instance.ints()).isEqualTo(ints);
+    assertThat(instance.noGetter()).isEqualTo(noGetter);
+    assertThat(instance.oAuth()).isEqualTo("OAuth");
+    assertThat(instance.oBrien()).isEqualTo("Flann");
+  }
+
+  @AutoValue
+  public abstract static class BuilderWithPrefixedGetters<T extends Comparable<T>> {
+    public abstract ImmutableList<T> getList();
+
+    public abstract T getT();
+
+    @SuppressWarnings("mutable")
+    @Nullable
+    public abstract int[] getInts();
+
+    public abstract String getOAuth();
+
+    public abstract int getNoGetter();
+
+    public static <T extends Comparable<T>> Builder<T> builder() {
+      return new AutoValue_AutoValueTest_BuilderWithPrefixedGetters.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder<T extends Comparable<T>> {
+      public abstract Builder<T> setList(ImmutableList<T> list);
+
+      public abstract Builder<T> setT(T t);
+
+      public abstract Builder<T> setInts(int[] ints);
+
+      public abstract Builder<T> setNoGetter(int x);
+
+      public abstract Builder<T> setOAuth(String x);
+
+      abstract ImmutableList<T> getList();
+
+      abstract T getT();
+
+      abstract int[] getInts();
+
+      public abstract BuilderWithPrefixedGetters<T> build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithPrefixedGetter() {
+    ImmutableList<String> names = ImmutableList.of("fred", "jim");
+    String name = "sheila";
+    int noGetter = -1;
+
+    BuilderWithPrefixedGetters.Builder<String> builder = BuilderWithPrefixedGetters.builder();
+    assertThat(builder.getInts()).isNull();
+    try {
+      builder.getList();
+      fail("Attempt to retrieve unset list property should have failed");
+    } catch (IllegalStateException e) {
+      if (omitIdentifiers) {
+        assertThat(e).hasMessageThat().isNull();
+      } else {
+        assertThat(e).hasMessageThat().isEqualTo("Property \"list\" has not been set");
+      }
+    }
+
+    builder.setList(names);
+    assertThat(builder.getList()).isSameInstanceAs(names);
+    builder.setT(name);
+    assertThat(builder.getInts()).isNull();
+    builder.setOAuth("OAuth");
+
+    BuilderWithPrefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
+    assertThat(instance.getList()).isSameInstanceAs(names);
+    assertThat(instance.getT()).isEqualTo(name);
+    assertThat(instance.getInts()).isNull();
+    assertThat(instance.getNoGetter()).isEqualTo(noGetter);
+    assertThat(instance.getOAuth()).isEqualTo("OAuth");
+  }
+
+  @AutoValue
+  public abstract static class BuilderWithPropertyBuilders<FooT extends Comparable<FooT>> {
+    public abstract ImmutableList<FooT> getFoos();
+
+    public abstract ImmutableSet<String> getStrings();
+
+    public abstract BuilderWithPropertyBuilders.Builder<FooT> toBuilder();
+
+    public static <FooT extends Comparable<FooT>> Builder<FooT> builder() {
+      return new AutoValue_AutoValueTest_BuilderWithPropertyBuilders.Builder<FooT>();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder<FooT extends Comparable<FooT>> {
+      public abstract ImmutableList<FooT> getFoos();
+
+      public Builder<FooT> addFoos(Iterable<FooT> foos) {
+        foosBuilder().addAll(foos);
+        return this;
+      }
+
+      abstract ImmutableList.Builder<FooT> foosBuilder();
+
+      public Builder<FooT> addToTs(FooT element) {
+        foosBuilder().add(element);
+        return this;
+      }
+
+      abstract Builder<FooT> setStrings(ImmutableList<String> strings);
+
+      abstract ImmutableSet.Builder<String> stringsBuilder();
+
+      public Builder<FooT> addToStrings(String element) {
+        stringsBuilder().add(element);
+        return this;
+      }
+
+      public abstract BuilderWithPropertyBuilders<FooT> build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithPropertyBuilders() {
+    ImmutableList<Integer> numbers = ImmutableList.of(1, 1, 2, 6, 24);
+    ImmutableSet<String> names = ImmutableSet.of("one", "two", "six", "twenty-four");
+
+    BuilderWithPropertyBuilders<Integer> a =
+        BuilderWithPropertyBuilders.<Integer>builder()
+            .addFoos(numbers)
+            .addToStrings("one")
+            .addToStrings("two")
+            .addToStrings("six")
+            .addToStrings("twenty-four")
+            .build();
+
+    assertEquals(numbers, a.getFoos());
+    assertEquals(names, a.getStrings());
+
+    BuilderWithPropertyBuilders.Builder<Integer> bBuilder = BuilderWithPropertyBuilders.builder();
+    bBuilder.stringsBuilder().addAll(names);
+    bBuilder.foosBuilder().addAll(numbers);
+
+    assertEquals(numbers, bBuilder.getFoos());
+
+    BuilderWithPropertyBuilders<Integer> b = bBuilder.build();
+    assertEquals(a, b);
+
+    BuilderWithPropertyBuilders.Builder<Integer> cBuilder = a.toBuilder();
+    cBuilder.addToStrings("one hundred and twenty");
+    cBuilder.addToTs(120);
+    BuilderWithPropertyBuilders<Integer> c = cBuilder.build();
+    assertEquals(
+        ImmutableSet.of("one", "two", "six", "twenty-four", "one hundred and twenty"),
+        c.getStrings());
+    assertEquals(ImmutableList.of(1, 1, 2, 6, 24, 120), c.getFoos());
+
+    BuilderWithPropertyBuilders.Builder<Integer> dBuilder = a.toBuilder();
+    dBuilder.addFoos(ImmutableList.of(120, 720));
+    BuilderWithPropertyBuilders<Integer> d = dBuilder.build();
+    assertEquals(ImmutableList.of(1, 1, 2, 6, 24, 120, 720), d.getFoos());
+    assertEquals(names, d.getStrings());
+
+    BuilderWithPropertyBuilders<Integer> empty =
+        BuilderWithPropertyBuilders.<Integer>builder().build();
+    assertEquals(ImmutableList.of(), empty.getFoos());
+    assertEquals(ImmutableSet.of(), empty.getStrings());
+
+    try {
+      BuilderWithPropertyBuilders.<Integer>builder().setStrings(null).build();
+      fail("Did not get expected exception");
+    } catch (RuntimeException expected) {
+      // We don't specify whether you get the exception on setStrings(null) or on build(), nor
+      // which exception it is exactly.
+    }
+  }
+
+  interface ImmutableListOf<T> {
+    ImmutableList<T> list();
+  }
+
+  @AutoValue
+  abstract static class PropertyBuilderInheritsType implements ImmutableListOf<String> {
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_PropertyBuilderInheritsType.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract ImmutableList.Builder<String> listBuilder();
+      abstract PropertyBuilderInheritsType build();
+    }
+  }
+
+  @Test
+  public void propertyBuilderInheritsType() {
+    PropertyBuilderInheritsType.Builder builder = PropertyBuilderInheritsType.builder();
+    builder.listBuilder().add("foo", "bar");
+    PropertyBuilderInheritsType x = builder.build();
+    assertThat(x.list()).containsExactly("foo", "bar").inOrder();
+  }
+
+  @AutoValue
+  public abstract static class BuilderWithExoticPropertyBuilders<
+      K extends Number, V extends Comparable<K>> {
+    public abstract ImmutableMap<String, V> map();
+
+    public abstract ImmutableTable<String, K, V> table();
+
+    public static <K extends Number, V extends Comparable<K>> Builder<K, V> builder() {
+      return new AutoValue_AutoValueTest_BuilderWithExoticPropertyBuilders.Builder<K, V>();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder<K extends Number, V extends Comparable<K>> {
+      public Builder<K, V> putAll(Map<String, V> map) {
+        mapBuilder().putAll(map);
+        return this;
+      }
+
+      public abstract ImmutableMap.Builder<String, V> mapBuilder();
+
+      public Builder<K, V> putAll(ImmutableTable<String, K, V> table) {
+        tableBuilder().putAll(table);
+        return this;
+      }
+
+      public abstract ImmutableTable.Builder<String, K, V> tableBuilder();
+
+      public abstract BuilderWithExoticPropertyBuilders<K, V> build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithExoticPropertyBuilders() {
+    ImmutableMap<String, Integer> map = ImmutableMap.of("one", 1);
+    ImmutableTable<String, Integer, Integer> table = ImmutableTable.of("one", 1, -1);
+
+    BuilderWithExoticPropertyBuilders<Integer, Integer> a =
+        BuilderWithExoticPropertyBuilders.<Integer, Integer>builder()
+            .putAll(map)
+            .putAll(table)
+            .build();
+    assertEquals(map, a.map());
+    assertEquals(table, a.table());
+
+    BuilderWithExoticPropertyBuilders.Builder<Integer, Integer> bBuilder =
+        BuilderWithExoticPropertyBuilders.builder();
+    bBuilder.mapBuilder().putAll(map);
+    bBuilder.tableBuilder().putAll(table);
+    BuilderWithExoticPropertyBuilders<Integer, Integer> b = bBuilder.build();
+    assertEquals(a, b);
+
+    BuilderWithExoticPropertyBuilders<Integer, Integer> empty =
+        BuilderWithExoticPropertyBuilders.<Integer, Integer>builder().build();
+    assertEquals(ImmutableMap.of(), empty.map());
+    assertEquals(ImmutableTable.of(), empty.table());
+  }
+
+  @AutoValue
+  public abstract static class BuilderWithCopyingSetters<T extends Number> {
+    public abstract ImmutableSet<? extends T> things();
+
+    public abstract ImmutableList<Number> numbers();
+
+    public abstract ImmutableMap<String, T> map();
+
+    public static <T extends Number> Builder<T> builder(T value) {
+      return new AutoValue_AutoValueTest_BuilderWithCopyingSetters.Builder<T>()
+          .setNumbers(ImmutableSet.of(17, 23.0))
+          .setMap(Collections.singletonMap("foo", value));
+    }
+
+    @AutoValue.Builder
+    public interface Builder<T extends Number> {
+      Builder<T> setThings(ImmutableSet<T> things);
+
+      Builder<T> setThings(Iterable<? extends T> things);
+
+      Builder<T> setThings(T... things);
+
+      Builder<T> setNumbers(Collection<? extends Number> strings);
+
+      Builder<T> setMap(Map<String, T> map);
+
+      BuilderWithCopyingSetters<T> build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithCopyingSetters() {
+    BuilderWithCopyingSetters.Builder<Integer> builder = BuilderWithCopyingSetters.builder(23);
+
+    BuilderWithCopyingSetters<Integer> a = builder.setThings(ImmutableSet.of(1, 2)).build();
+    assertThat(a.things()).containsExactly(1, 2);
+    assertThat(a.numbers()).containsExactly(17, 23.0).inOrder();
+    assertThat(a.map()).containsExactly("foo", 23);
+
+    BuilderWithCopyingSetters<Integer> b = builder.setThings(Arrays.asList(1, 2)).build();
+    assertThat(b).isEqualTo(a);
+
+    BuilderWithCopyingSetters<Integer> c = builder.setThings(1, 2).build();
+    assertThat(c).isEqualTo(a);
+  }
+
+  @AutoValue
+  public abstract static class BuilderWithImmutableSorted<T extends Comparable<T>> {
+    public abstract ImmutableSortedSet<T> sortedSet();
+
+    public abstract ImmutableSortedMap<T, Integer> sortedMap();
+
+    public static <T extends Comparable<T>> Builder<T> builder() {
+      return new AutoValue_AutoValueTest_BuilderWithImmutableSorted.Builder<T>()
+          .setSortedSet(new TreeSet<T>())
+          .setSortedMap(new TreeMap<T, Integer>());
+    }
+
+    @AutoValue.Builder
+    public interface Builder<T extends Comparable<T>> {
+      @SuppressWarnings("unchecked")
+      Builder<T> setSortedSet(T... x);
+
+      Builder<T> setSortedSet(NavigableSet<T> x);
+
+      ImmutableSortedSet.Builder<T> sortedSetBuilder();
+
+      Builder<T> setSortedMap(SortedMap<T, Integer> x);
+
+      Builder<T> setSortedMap(NavigableMap<T, Integer> x);
+
+      ImmutableSortedMap.Builder<T, Integer> sortedMapBuilder();
+
+      BuilderWithImmutableSorted<T> build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithImmutableSorted_Varargs() {
+    BuilderWithImmutableSorted<String> x =
+        BuilderWithImmutableSorted.<String>builder().setSortedSet("foo", "bar", "baz").build();
+    assertThat(x.sortedSet()).containsExactly("bar", "baz", "foo").inOrder();
+  }
+
+  @Test
+  public void testBuilderWithImmutableSorted_SetSet() {
+    BuilderWithImmutableSorted<String> x =
+        BuilderWithImmutableSorted.<String>builder()
+            .setSortedSet(new TreeSet<String>(String.CASE_INSENSITIVE_ORDER))
+            .build();
+    assertThat(x.sortedSet().comparator()).isEqualTo(String.CASE_INSENSITIVE_ORDER);
+  }
+
+  @Test
+  public void testBuilderWithImmutableSorted_SetMap() {
+    BuilderWithImmutableSorted<String> x =
+        BuilderWithImmutableSorted.<String>builder()
+            .setSortedMap(new TreeMap<String, Integer>(String.CASE_INSENSITIVE_ORDER))
+            .build();
+    assertThat(x.sortedMap().comparator()).isEqualTo(String.CASE_INSENSITIVE_ORDER);
+  }
+
+  @Test
+  public void testBuilderWithImmutableSorted_SetCollectionBuilder() {
+    BuilderWithImmutableSorted.Builder<String> builder =
+        BuilderWithImmutableSorted.<String>builder();
+    builder.sortedSetBuilder().add("is", "ea", "id");
+    BuilderWithImmutableSorted<String> x = builder.build();
+    assertThat(x.sortedSet()).containsExactly("ea", "id", "is").inOrder();
+  }
+
+  @Test
+  public void testBuilderWithImmutableSorted_MapCollectionBuilder() {
+    BuilderWithImmutableSorted.Builder<String> builder =
+        BuilderWithImmutableSorted.<String>builder();
+    builder.sortedMapBuilder().put("two", 2).put("one", 1);
+    BuilderWithImmutableSorted<String> x = builder.build();
+    assertThat(x.sortedMap()).containsExactly("one", 1, "two", 2).inOrder();
+  }
+
+  @AutoValue
+  public abstract static class BuilderWithCollectionBuilderAndSetter<T extends Number> {
+    public abstract ImmutableList<T> things();
+
+    public static <T extends Number> Builder<T> builder() {
+      return new AutoValue_AutoValueTest_BuilderWithCollectionBuilderAndSetter.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    public interface Builder<T extends Number> {
+      Builder<T> setThings(List<T> things);
+
+      ImmutableList<T> things();
+
+      ImmutableList.Builder<T> thingsBuilder();
+
+      BuilderWithCollectionBuilderAndSetter<T> build();
+    }
+  }
+
+  @Test
+  public void testBuilderAndSetterDefaultsEmpty() {
+    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
+        BuilderWithCollectionBuilderAndSetter.<Integer>builder();
+    assertThat(builder.things()).isEmpty();
+    assertThat(builder.build().things()).isEmpty();
+  }
+
+  @Test
+  public void testBuilderAndSetterUsingBuilder() {
+    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
+        BuilderWithCollectionBuilderAndSetter.builder();
+    builder.thingsBuilder().add(17, 23);
+    BuilderWithCollectionBuilderAndSetter<Integer> x = builder.build();
+    assertThat(x.things()).isEqualTo(ImmutableList.of(17, 23));
+  }
+
+  @Test
+  public void testBuilderAndSetterUsingSetter() {
+    ImmutableList<Integer> things = ImmutableList.of(17, 23);
+    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
+        BuilderWithCollectionBuilderAndSetter.<Integer>builder().setThings(things);
+    assertThat(builder.things()).isSameInstanceAs(things);
+    assertThat(builder.build().things()).isSameInstanceAs(things);
+
+    List<Integer> moreThings = Arrays.asList(5, 17, 23);
+    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder2 =
+        BuilderWithCollectionBuilderAndSetter.<Integer>builder().setThings(moreThings);
+    assertThat(builder2.things()).isEqualTo(moreThings);
+    assertThat(builder2.build().things()).isEqualTo(moreThings);
+  }
+
+  @Test
+  public void testBuilderAndSetterUsingSetterThenBuilder() {
+    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
+        BuilderWithCollectionBuilderAndSetter.builder();
+    builder.setThings(ImmutableList.of(5));
+    builder.thingsBuilder().add(17, 23);
+    List<Integer> expectedThings = ImmutableList.of(5, 17, 23);
+    assertThat(builder.things()).isEqualTo(expectedThings);
+    assertThat(builder.build().things()).isEqualTo(expectedThings);
+  }
+
+  @Test
+  public void testBuilderAndSetterCannotSetAfterBuilder() {
+    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
+        BuilderWithCollectionBuilderAndSetter.builder();
+    builder.setThings(ImmutableList.of(5));
+    builder.thingsBuilder().add(17, 23);
+    try {
+      builder.setThings(ImmutableList.of(1729));
+      fail("Setting list after retrieving builder should provoke an exception");
+    } catch (IllegalStateException e) {
+      if (omitIdentifiers) {
+        assertThat(e).hasMessageThat().isNull();
+      } else {
+        assertThat(e).hasMessageThat().isEqualTo("Cannot set things after calling thingsBuilder()");
+      }
+    }
+  }
+
+  abstract static class AbstractParentWithBuilder {
+    abstract String foo();
+
+    abstract static class Builder<B extends Builder<B>> {
+      abstract B foo(String s);
+    }
+  }
+
+  @AutoValue
+  abstract static class ChildWithBuilder extends AbstractParentWithBuilder {
+    abstract String bar();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_ChildWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder extends AbstractParentWithBuilder.Builder<Builder> {
+      abstract Builder bar(String s);
+
+      abstract ChildWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testInheritedBuilder() {
+    ChildWithBuilder x = ChildWithBuilder.builder().foo("foo").bar("bar").build();
+    assertThat(x.foo()).isEqualTo("foo");
+    assertThat(x.bar()).isEqualTo("bar");
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface GwtCompatible {
+    boolean funky() default false;
+  }
+
+  @AutoValue
+  @GwtCompatible(funky = true)
+  abstract static class GwtCompatibleTest {
+    abstract int foo();
+
+    static GwtCompatibleTest create(int foo) {
+      return new AutoValue_AutoValueTest_GwtCompatibleTest(foo);
+    }
+  }
+
+  @AutoValue
+  @GwtCompatible
+  abstract static class GwtCompatibleTestNoArgs {
+    abstract String bar();
+
+    static GwtCompatibleTestNoArgs create(String bar) {
+      return new AutoValue_AutoValueTest_GwtCompatibleTestNoArgs(bar);
+    }
+  }
+
+  @Test
+  public void testGwtCompatibleInherited() {
+    GwtCompatibleTest test = GwtCompatibleTest.create(23);
+    GwtCompatible gwtCompatible = test.getClass().getAnnotation(GwtCompatible.class);
+    assertNotNull(gwtCompatible);
+    assertTrue(gwtCompatible.funky());
+
+    GwtCompatibleTestNoArgs testNoArgs = GwtCompatibleTestNoArgs.create("23");
+    GwtCompatible gwtCompatibleNoArgs = testNoArgs.getClass().getAnnotation(GwtCompatible.class);
+    assertNotNull(gwtCompatibleNoArgs);
+    assertFalse(gwtCompatibleNoArgs.funky());
+  }
+
+  @interface NestedAnnotation {
+    int anInt();
+
+    Class<?>[] aClassArray();
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface HairyAnnotation {
+    String aString();
+
+    Class<? extends Number> aClass();
+
+    RetentionPolicy anEnum();
+
+    NestedAnnotation anAnnotation();
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface CopiedAnnotation {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface ExcludedAnnotation {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Inherited
+  @interface InheritedAnnotation {}
+
+  @CopiedAnnotation
+  @ExcludedAnnotation
+  @InheritedAnnotation
+  @AutoValue
+  @AutoValue.CopyAnnotations(exclude = {ExcludedAnnotation.class})
+  abstract static class CopyAnnotation {
+    @HairyAnnotation(
+        aString = "hello",
+        aClass = Integer.class,
+        anEnum = RetentionPolicy.RUNTIME,
+        anAnnotation =
+            @NestedAnnotation(
+                anInt = 73,
+                aClassArray = {String.class, Object.class}))
+    abstract String field1();
+
+    @CopiedAnnotation
+    @ExcludedAnnotation
+    @InheritedAnnotation
+    @AutoValue.CopyAnnotations(exclude = {ExcludedAnnotation.class})
+    abstract String field2();
+
+    static CopyAnnotation create() {
+      return new AutoValue_AutoValueTest_CopyAnnotation("field1", "field2");
+    }
+  }
+
+  @Test
+  public void testCopyClassAnnotations() throws Exception {
+    CopyAnnotation x = CopyAnnotation.create();
+    Class<?> c = x.getClass();
+    assertNotSame(CopyAnnotation.class, c);
+
+    // Sanity check: if these don't appear on CopyAnnotation, it makes no sense to assert that they
+    // don't appear on the AutoValue_ subclass.
+    {
+      List<Class<? extends Annotation>> annotationsOnSuperclass =
+          new ArrayList<Class<? extends Annotation>>();
+      for (Annotation annotation : CopyAnnotation.class.getDeclaredAnnotations()) {
+        annotationsOnSuperclass.add(annotation.annotationType());
+      }
+      assertThat(annotationsOnSuperclass)
+          .containsAtLeast(
+              CopiedAnnotation.class, ExcludedAnnotation.class, InheritedAnnotation.class);
+    }
+
+    {
+      List<Class<? extends Annotation>> annotationsOnSubclass =
+          new ArrayList<Class<? extends Annotation>>();
+      for (Annotation annotation : c.getDeclaredAnnotations()) {
+        annotationsOnSubclass.add(annotation.annotationType());
+      }
+      assertThat(annotationsOnSubclass).containsExactly(CopiedAnnotation.class);
+    }
+  }
+
+  @Test
+  public void testCopyMethodAnnotations() throws Exception {
+    CopyAnnotation x = CopyAnnotation.create();
+    Class<?> c = x.getClass();
+    assertNotSame(CopyAnnotation.class, c);
+
+    Method methodInSubclass = c.getDeclaredMethod("field2");
+    Method methodInSuperclass = CopyAnnotation.class.getDeclaredMethod("field2");
+
+    // Sanity check: if these don't appear on CopyAnnotation, it makes no sense to assert that they
+    // don't appear on the AutoValue_ subclass.
+    assertThat(methodInSuperclass.isAnnotationPresent(CopiedAnnotation.class)).isTrue();
+    assertThat(methodInSuperclass.isAnnotationPresent(ExcludedAnnotation.class)).isTrue();
+    assertThat(methodInSuperclass.isAnnotationPresent(InheritedAnnotation.class)).isTrue();
+
+    assertThat(methodInSubclass.isAnnotationPresent(CopiedAnnotation.class)).isTrue();
+    assertThat(methodInSubclass.isAnnotationPresent(ExcludedAnnotation.class)).isFalse();
+    assertThat(methodInSubclass.isAnnotationPresent(InheritedAnnotation.class)).isTrue();
+  }
+
+  @Test
+  public void testCopyMethodAnnotationsByDefault() throws Exception {
+    CopyAnnotation x = CopyAnnotation.create();
+    Class<?> c = x.getClass();
+    assertNotSame(CopyAnnotation.class, c);
+    Method methodInSubclass = c.getDeclaredMethod("field1");
+    Method methodInSuperclass = CopyAnnotation.class.getDeclaredMethod("field1");
+    assertNotSame(methodInSuperclass, methodInSubclass);
+    HairyAnnotation annotationInSubclass = methodInSubclass.getAnnotation(HairyAnnotation.class);
+    HairyAnnotation annotationInSuperclass =
+        methodInSuperclass.getAnnotation(HairyAnnotation.class);
+    assertEquals(annotationInSuperclass, annotationInSubclass);
+  }
+
+  @AutoValue
+  abstract static class HProperty {
+    public abstract Object h();
+
+    public static HProperty create(Object h) {
+      return new AutoValue_AutoValueTest_HProperty(h);
+    }
+  }
+
+  @Test
+  public void testHProperty() throws Exception {
+    // Checks that we can have a property called `h`. The generated hashCode() method has
+    // a local variable of that name and can cause the error `int cannot be dereferenced`
+    HProperty.create(new Object());
+  }
+
+  interface Parent1 {
+    int something();
+  }
+
+  interface Parent2 {
+    int something();
+  }
+
+  @AutoValue
+  abstract static class InheritSameMethodTwice implements Parent1, Parent2 {
+    static InheritSameMethodTwice create(int something) {
+      return new AutoValue_AutoValueTest_InheritSameMethodTwice(something);
+    }
+  }
+
+  @Test
+  public void testInheritSameMethodTwice() {
+    InheritSameMethodTwice x = InheritSameMethodTwice.create(23);
+    assertThat(x.something()).isEqualTo(23);
+  }
+
+  // Make sure we behave correctly when we inherit the same method definition from more than
+  // one parent interface. We expect methods to appear in the order they are seen, with parents
+  // preceding children, the superclass of a class preceding interfaces that class implements,
+  // and an interface mentioned earlier in the "implements" clause preceding one mentioned later.
+  // https://github.com/google/auto/issues/372
+  interface OneTwoThreeFour {
+    String one();
+
+    String two();
+
+    boolean three();
+
+    long four();
+  }
+
+  interface TwoFour {
+    String two();
+
+    long four();
+  }
+
+  @AutoValue
+  abstract static class OneTwoThreeFourImpl implements OneTwoThreeFour, TwoFour {
+    static OneTwoThreeFourImpl create(String one, String two, boolean three, long four) {
+      return new AutoValue_AutoValueTest_OneTwoThreeFourImpl(one, two, three, four);
+    }
+  }
+
+  @Test
+  public void testOneTwoThreeFour() {
+    OneTwoThreeFour x = OneTwoThreeFourImpl.create("one", "two", false, 4);
+    String expectedString =
+        omitIdentifiers
+            ? "{one, two, false, 4}"
+            : "OneTwoThreeFourImpl{one=one, two=two, three=false, four=4}";
+    assertThat(x.toString()).isEqualTo(expectedString);
+  }
+
+  @AutoValue
+  abstract static class OuterWithBuilder {
+    abstract String foo();
+
+    abstract InnerWithBuilder inner();
+
+    abstract Builder toBuilder();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_OuterWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder foo(String x);
+
+      abstract Builder inner(InnerWithBuilder x);
+
+      abstract InnerWithBuilder.Builder innerBuilder();
+
+      abstract OuterWithBuilder build();
+    }
+  }
+
+  @AutoValue
+  abstract static class InnerWithBuilder {
+    abstract int bar();
+
+    abstract Builder toBuilder();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_InnerWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder setBar(int x);
+
+      abstract InnerWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithinBuilder() {
+    OuterWithBuilder x =
+        OuterWithBuilder.builder()
+            .inner(InnerWithBuilder.builder().setBar(23).build())
+            .foo("yes")
+            .build();
+    String expectedStringX =
+        omitIdentifiers
+            ? "{yes, {23}}"
+            : "OuterWithBuilder{foo=yes, inner=InnerWithBuilder{bar=23}}";
+    assertThat(x.toString()).isEqualTo(expectedStringX);
+
+    OuterWithBuilder.Builder xBuilder = x.toBuilder();
+    xBuilder.innerBuilder().setBar(17);
+    OuterWithBuilder y = xBuilder.build();
+    String expectedStringY =
+        omitIdentifiers
+            ? "{yes, {17}}"
+            : "OuterWithBuilder{foo=yes, inner=InnerWithBuilder{bar=17}}";
+    assertThat(y.toString()).isEqualTo(expectedStringY);
+  }
+
+  public static class MyMap<K, V> extends HashMap<K, V> {
+    public MyMap() {}
+
+    public MyMap(Map<K, V> map) {
+      super(map);
+    }
+  }
+
+  public static class MyMapBuilder<K, V> extends LinkedHashMap<K, V> {
+    public MyMapBuilder() {}
+
+    public MyMapBuilder(Map<K, V> map) {
+      super(map);
+    }
+
+    public MyMap<K, V> build() {
+      return new MyMap<K, V>(this);
+    }
+  }
+
+  @AutoValue
+  abstract static class BuildMyMap<K, V> {
+    abstract MyMap<K, V> map();
+
+    abstract Builder<K, V> toBuilder();
+
+    static <K, V> Builder<K, V> builder() {
+      return new AutoValue_AutoValueTest_BuildMyMap.Builder<K, V>();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder<K, V> {
+      abstract MyMapBuilder<K, V> mapBuilder();
+
+      abstract BuildMyMap<K, V> build();
+    }
+  }
+
+  @Test
+  public void testMyMapBuilder() {
+    BuildMyMap.Builder<String, Integer> builder = BuildMyMap.builder();
+    MyMapBuilder<String, Integer> mapBuilder = builder.mapBuilder();
+    mapBuilder.put("23", 23);
+    BuildMyMap<String, Integer> built = builder.build();
+    assertThat(built.map()).containsExactly("23", 23);
+
+    BuildMyMap.Builder<String, Integer> builder2 = built.toBuilder();
+    MyMapBuilder<String, Integer> mapBuilder2 = builder2.mapBuilder();
+    mapBuilder2.put("17", 17);
+    BuildMyMap<String, Integer> built2 = builder2.build();
+    assertThat(built2.map()).containsExactly("23", 23, "17", 17);
+  }
+
+  public static class MyStringMap<V> extends MyMap<String, V> {
+    public MyStringMap() {}
+
+    public MyStringMap(Map<String, V> map) {
+      super(map);
+    }
+
+    public MyStringMapBuilder<V> toBuilder() {
+      return new MyStringMapBuilder<V>(this);
+    }
+  }
+
+  public static class MyStringMapBuilder<V> extends MyMapBuilder<String, V> {
+    public MyStringMapBuilder() {}
+
+    public MyStringMapBuilder(Map<String, V> map) {
+      super(map);
+    }
+
+    @Override
+    public MyStringMap<V> build() {
+      return new MyStringMap<V>(this);
+    }
+  }
+
+  @AutoValue
+  abstract static class BuildMyStringMap<V> {
+    abstract MyStringMap<V> map();
+
+    abstract Builder<V> toBuilder();
+
+    static <V> Builder<V> builder() {
+      return new AutoValue_AutoValueTest_BuildMyStringMap.Builder<V>();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder<V> {
+      abstract MyStringMapBuilder<V> mapBuilder();
+
+      abstract BuildMyStringMap<V> build();
+    }
+  }
+
+  @Test
+  public void testMyStringMapBuilder() {
+    BuildMyStringMap.Builder<Integer> builder = BuildMyStringMap.builder();
+    MyStringMapBuilder<Integer> mapBuilder = builder.mapBuilder();
+    mapBuilder.put("23", 23);
+    BuildMyStringMap<Integer> built = builder.build();
+    assertThat(built.map()).containsExactly("23", 23);
+
+    BuildMyStringMap.Builder<Integer> builder2 = built.toBuilder();
+    MyStringMapBuilder<Integer> mapBuilder2 = builder2.mapBuilder();
+    mapBuilder2.put("17", 17);
+    BuildMyStringMap<Integer> built2 = builder2.build();
+    assertThat(built2.map()).containsExactly("17", 17, "23", 23);
+  }
+
+  @AutoValue
+  abstract static class BuilderOfManyAccessLevels {
+    public abstract int publicGetterProtectedBuilderGetterPackageProtectedSetterInt();
+
+    protected abstract int protectedGetterPackageProtectedBuilderGetterPublicSetterInt();
+
+    abstract int packageProtectedGetterPublicBuilderGetterProtectedSetterInt();
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+      protected abstract int publicGetterProtectedBuilderGetterPackageProtectedSetterInt();
+
+      abstract int protectedGetterPackageProtectedBuilderGetterPublicSetterInt();
+
+      public abstract int packageProtectedGetterPublicBuilderGetterProtectedSetterInt();
+
+      abstract Builder setPublicGetterProtectedBuilderGetterPackageProtectedSetterInt(int x);
+
+      public abstract Builder setProtectedGetterPackageProtectedBuilderGetterPublicSetterInt(int x);
+
+      protected abstract Builder setPackageProtectedGetterPublicBuilderGetterProtectedSetterInt(
+          int x);
+
+      public abstract BuilderOfManyAccessLevels build();
+    }
+  }
+
+  @Test
+  public void testBuilderOfManyAccessLevels_accessLevels() throws NoSuchMethodException {
+    Class<?> builderClass = AutoValue_AutoValueTest_BuilderOfManyAccessLevels.Builder.class;
+
+    testMethodAccess(
+        Access.PROTECTED,
+        builderClass,
+        "publicGetterProtectedBuilderGetterPackageProtectedSetterInt");
+    testMethodAccess(
+        Access.PACKAGE,
+        builderClass,
+        "protectedGetterPackageProtectedBuilderGetterPublicSetterInt");
+    testMethodAccess(
+        Access.PUBLIC, builderClass, "packageProtectedGetterPublicBuilderGetterProtectedSetterInt");
+
+    testMethodAccess(
+        Access.PACKAGE,
+        builderClass,
+        "setPublicGetterProtectedBuilderGetterPackageProtectedSetterInt",
+        int.class);
+    testMethodAccess(
+        Access.PUBLIC,
+        builderClass,
+        "setProtectedGetterPackageProtectedBuilderGetterPublicSetterInt",
+        int.class);
+    testMethodAccess(
+        Access.PROTECTED,
+        builderClass,
+        "setPackageProtectedGetterPublicBuilderGetterProtectedSetterInt",
+        int.class);
+  }
+
+  private enum Access {
+    PRIVATE,
+    PACKAGE,
+    PROTECTED,
+    PUBLIC
+  }
+
+  private static final ImmutableMap<Integer, Access> MODIFIER_BITS_TO_ACCESS =
+      ImmutableMap.of(
+          Modifier.PUBLIC,
+          Access.PUBLIC,
+          Modifier.PROTECTED,
+          Access.PROTECTED,
+          Modifier.PRIVATE,
+          Access.PRIVATE,
+          0,
+          Access.PACKAGE);
+
+  private static void testMethodAccess(
+      Access expectedAccess, Class<?> clazz, String methodName, Class<?>... parameterTypes)
+      throws NoSuchMethodException {
+    Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
+    int modBits = method.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE);
+    Access actualAccess = MODIFIER_BITS_TO_ACCESS.get(modBits);
+    assertWithMessage("Wrong access for %s", methodName)
+        .that(actualAccess)
+        .isEqualTo(expectedAccess);
+  }
+
+  static class VersionId {}
+
+  static class ItemVersionId extends VersionId {}
+
+  interface VersionedPersistent {
+    VersionId getVersionId();
+  }
+
+  interface Item extends VersionedPersistent {
+    @Override
+    ItemVersionId getVersionId();
+  }
+
+  @AutoValue
+  abstract static class FakeItem implements Item {
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_FakeItem.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder setVersionId(ItemVersionId x);
+
+      abstract FakeItem build();
+    }
+  }
+
+  @Test
+  public void testParentInterfaceOverridesGrandparent() {
+    ItemVersionId version = new ItemVersionId();
+    FakeItem fakeItem = FakeItem.builder().setVersionId(version).build();
+    assertThat(fakeItem.getVersionId()).isSameInstanceAs(version);
+  }
+
+  /** Fake ApkVersionCode class. */
+  public static class ApkVersionCode {}
+
+  /**
+   * Illustrates a potential problem that showed up while generalizing builders. If our imports are
+   * not accurate we may end up importing ImmutableList.Builder, which won't work because the
+   * generated Builder subclass of ReleaseInfoBuilder will supersede it. Normally we wouldn't import
+   * ImmutableList.Builder because the nested Builder class in the {@code @AutoValue} class would
+   * prevent us trying. But in this case the nested class is called ReleaseInfoBuilder so we might
+   * import anyway if we're not careful. This is one reason why we moved away from importing nested
+   * classes to only importing top-level classes.
+   */
+  @AutoValue
+  public abstract static class ReleaseInfo {
+    public static ReleaseInfoBuilder newBuilder() {
+      return new AutoValue_AutoValueTest_ReleaseInfo.Builder();
+    }
+
+    public abstract ImmutableList<ApkVersionCode> apkVersionCodes();
+
+    ReleaseInfo() {}
+
+    /** Notice that this is called ReleaseInfoBuilder and not Builder. */
+    @AutoValue.Builder
+    public abstract static class ReleaseInfoBuilder {
+      public ReleaseInfoBuilder addApkVersionCode(ApkVersionCode code) {
+        apkVersionCodesBuilder().add(code);
+        return this;
+      }
+
+      abstract ImmutableList.Builder<ApkVersionCode> apkVersionCodesBuilder();
+
+      public abstract ReleaseInfo build();
+    }
+  }
+
+  @Test
+  public void testUnusualBuilderName() {
+    ApkVersionCode apkVersionCode = new ApkVersionCode();
+    ReleaseInfo x = ReleaseInfo.newBuilder().addApkVersionCode(apkVersionCode).build();
+    assertThat(x.apkVersionCodes()).containsExactly(apkVersionCode);
+  }
+
+  @AutoValue
+  public abstract static class OuterWithDefaultableInner {
+    public abstract ImmutableList<String> names();
+
+    public abstract DefaultableInner inner();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_OuterWithDefaultableInner.Builder();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+      public abstract ImmutableList<String> names();
+
+      public abstract ImmutableList.Builder<String> namesBuilder();
+
+      public abstract DefaultableInner inner();
+
+      public abstract DefaultableInner.Builder innerBuilder();
+
+      public abstract OuterWithDefaultableInner build();
+    }
+  }
+
+  @AutoValue
+  public abstract static class DefaultableInner {
+    public abstract int bar();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_DefaultableInner.Builder().setBar(23);
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+      public abstract Builder setBar(int x);
+
+      public abstract DefaultableInner build();
+    }
+  }
+
+  @Test
+  public void testOuterWithDefaultableInner_Defaults() {
+    DefaultableInner defaultInner = DefaultableInner.builder().build();
+    OuterWithDefaultableInner x = OuterWithDefaultableInner.builder().build();
+    assertThat(x.names()).isEmpty();
+    assertThat(x.inner()).isEqualTo(defaultInner);
+  }
+
+  @Test
+  public void testOuterWithDefaultableInner_Getters() {
+    DefaultableInner defaultInner = DefaultableInner.builder().build();
+
+    OuterWithDefaultableInner.Builder builder = OuterWithDefaultableInner.builder();
+    assertThat(builder.names()).isEmpty();
+    assertThat(builder.inner()).isEqualTo(defaultInner);
+
+    OuterWithDefaultableInner x1 = builder.build();
+    assertThat(x1.names()).isEmpty();
+    assertThat(x1.inner()).isEqualTo(defaultInner);
+
+    builder.namesBuilder().add("Fred");
+    builder.innerBuilder().setBar(17);
+    OuterWithDefaultableInner x2 = builder.build();
+    assertThat(x2.names()).containsExactly("Fred");
+    assertThat(x2.inner().bar()).isEqualTo(17);
+  }
+
+  @AutoValue
+  public abstract static class OuterWithNonDefaultableInner<T> {
+    public abstract int foo();
+
+    public abstract NonDefaultableInner<T> inner();
+
+    public static <T> Builder<T> builder() {
+      return new AutoValue_AutoValueTest_OuterWithNonDefaultableInner.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder<T> {
+      public abstract Builder<T> setFoo(int x);
+
+      public abstract NonDefaultableInner.Builder<T> innerBuilder();
+
+      public abstract OuterWithNonDefaultableInner<T> build();
+    }
+  }
+
+  @AutoValue
+  public abstract static class NonDefaultableInner<E> {
+    public abstract E bar();
+
+    public static <E> Builder<E> builder() {
+      return new AutoValue_AutoValueTest_NonDefaultableInner.Builder<E>();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder<E> {
+      public abstract Builder<E> setBar(E x);
+
+      public abstract NonDefaultableInner<E> build();
+    }
+  }
+
+  @Test
+  public void testOuterWithNonDefaultableInner() {
+    OuterWithNonDefaultableInner.Builder<String> builder = OuterWithNonDefaultableInner.builder();
+    builder.setFoo(23);
+    try {
+      builder.build();
+      fail("Did not get expected exception for unbuilt inner instance");
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @SuppressWarnings("JavaLangClash")
+  @AutoValue
+  public abstract static class RedeclareJavaLangClasses {
+    // If you really really want to do this, we have you covered.
+
+    public static class Object {}
+
+    public static class String {}
+
+    public abstract Object alienObject();
+
+    public abstract String alienString();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_RedeclareJavaLangClasses.Builder();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+      public abstract Builder setAlienObject(Object x);
+
+      public abstract Builder setAlienString(String x);
+
+      public abstract RedeclareJavaLangClasses build();
+    }
+  }
+
+  @Test
+  public void testRedeclareJavaLangClasses() {
+    RedeclareJavaLangClasses x =
+        RedeclareJavaLangClasses.builder()
+            .setAlienObject(new RedeclareJavaLangClasses.Object())
+            .setAlienString(new RedeclareJavaLangClasses.String())
+            .build();
+    assertThat(x).isNotNull();
+  }
+
+  // b/28382293
+  @AutoValue
+  abstract static class GenericExtends {
+    abstract ImmutableSet<Number> metrics();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_GenericExtends.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder setMetrics(ImmutableSet<? extends Number> metrics);
+      abstract GenericExtends build();
+    }
+  }
+
+  @Test
+  public void testGenericExtends() {
+    ImmutableSet<Integer> ints = ImmutableSet.of(1, 2, 3);
+    GenericExtends g = GenericExtends.builder().setMetrics(ints).build();
+    assertThat(g.metrics()).isEqualTo(ints);
+  }
+
+  abstract static class Parent<T> {
+    abstract List<T> getList();
+  }
+
+  @AutoValue
+  abstract static class Child extends Parent<String> {
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_Child.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder setList(List<String> list);
+      abstract Child build();
+    }
+  }
+
+  @Test
+  public void nonGenericExtendsGeneric() {
+    List<String> list = ImmutableList.of("foo", "bar", "baz");
+    Child child = Child.builder().setList(list).build();
+    assertThat(child.getList()).containsExactlyElementsIn(list).inOrder();
+  }
+
+  abstract static class AbstractGenericParentWithBuilder<T> {
+    abstract T foo();
+
+    abstract static class Builder<T, B extends Builder<T, B>> {
+      abstract B foo(T s);
+    }
+  }
+
+  @AutoValue
+  abstract static class ChildOfAbstractGenericParentWithBuilder<T>
+      extends AbstractGenericParentWithBuilder<T> {
+    static <T> Builder<T> builder() {
+      return new AutoValue_AutoValueTest_ChildOfAbstractGenericParentWithBuilder.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder<T>
+        extends AbstractGenericParentWithBuilder.Builder<T, Builder<T>> {
+      abstract ChildOfAbstractGenericParentWithBuilder<T> build();
+    }
+  }
+
+  @Test
+  public void genericExtendsGeneric() {
+    ChildOfAbstractGenericParentWithBuilder<String> child =
+        ChildOfAbstractGenericParentWithBuilder.<String>builder().foo("foo").build();
+    assertThat(child.foo()).isEqualTo("foo");
+  }
+
+  @SuppressWarnings("ClassCanBeStatic")
+  static class OuterWithTypeParam<T extends Number> {
+    class InnerWithTypeParam<U> {}
+    class InnerWithoutTypeParam {}
+    static class Nested {}
+  }
+
+  @AutoValue
+  abstract static class Nesty {
+    abstract OuterWithTypeParam<Double>.InnerWithTypeParam<String> innerWithTypeParam();
+    abstract OuterWithTypeParam<Double>.InnerWithoutTypeParam innerWithoutTypeParam();
+    abstract OuterWithTypeParam.Nested nested();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_Nesty.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract Builder setInnerWithTypeParam(
+          OuterWithTypeParam<Double>.InnerWithTypeParam<String> x);
+      abstract Builder setInnerWithoutTypeParam(OuterWithTypeParam<Double>.InnerWithoutTypeParam x);
+      abstract Builder setNested(OuterWithTypeParam.Nested x);
+      abstract Nesty build();
+    }
+  }
+
+  @Test
+  public void outerWithTypeParam() throws ReflectiveOperationException {
+    @SuppressWarnings("UseDiamond") // Currently we compile this with -source 6 in the Eclipse test.
+    OuterWithTypeParam<Double> outer = new OuterWithTypeParam<Double>();
+    Nesty nesty = Nesty.builder()
+        .setInnerWithTypeParam(outer.new InnerWithTypeParam<String>())
+        .setInnerWithoutTypeParam(outer.new InnerWithoutTypeParam())
+        .setNested(new OuterWithTypeParam.Nested())
+        .build();
+    Type originalReturnType =
+        Nesty.class.getDeclaredMethod("innerWithTypeParam").getGenericReturnType();
+    Type generatedReturnType =
+        nesty.getClass().getDeclaredMethod("innerWithTypeParam").getGenericReturnType();
+    assertThat(generatedReturnType).isEqualTo(originalReturnType);
+    Type generatedBuilderParamType =
+        Nesty.builder()
+            .getClass()
+            .getDeclaredMethod("setInnerWithTypeParam", OuterWithTypeParam.InnerWithTypeParam.class)
+            .getGenericParameterTypes()[0];
+    assertThat(generatedBuilderParamType).isEqualTo(originalReturnType);
+  }
+
+  @AutoValue
+  abstract static class BuilderAnnotationsNotCopied {
+    abstract String foo();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_BuilderAnnotationsNotCopied.Builder();
+    }
+
+    @AutoValue.Builder
+    @MyAnnotation("thing")
+    abstract static class Builder {
+      abstract Builder setFoo(String x);
+      abstract BuilderAnnotationsNotCopied build();
+    }
+  }
+
+  @Test
+  public void builderAnnotationsNotCopiedByDefault() {
+    BuilderAnnotationsNotCopied.Builder builder = BuilderAnnotationsNotCopied.builder();
+    assertThat(builder.getClass().getAnnotations()).isEmpty();
+    assertThat(builder.setFoo("foo").build().foo()).isEqualTo("foo");
+  }
+
+  @AutoValue
+  abstract static class BuilderAnnotationsCopied {
+    abstract String foo();
+
+    static Builder builder() {
+      return new AutoValue_AutoValueTest_BuilderAnnotationsCopied.Builder();
+    }
+
+    @AutoValue.Builder
+    @AutoValue.CopyAnnotations
+    @MyAnnotation("thing")
+    abstract static class Builder {
+      abstract Builder setFoo(String x);
+      abstract BuilderAnnotationsCopied build();
+    }
+  }
+
+  @Test
+  public void builderAnnotationsCopiedIfRequested() {
+    BuilderAnnotationsCopied.Builder builder = BuilderAnnotationsCopied.builder();
+    assertThat(builder.getClass().getAnnotations()).asList().containsExactly(myAnnotation("thing"));
+    assertThat(builder.setFoo("foo").build().foo()).isEqualTo("foo");
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java
new file mode 100644
index 0000000..1518827
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2014 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;
+
+import static com.google.common.base.StandardSystemProperty.JAVA_HOME;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.auto.value.processor.AutoAnnotationProcessor;
+import com.google.auto.value.processor.AutoOneOfProcessor;
+import com.google.auto.value.processor.AutoValueProcessor;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import javax.annotation.processing.Processor;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests that we can compile our AutoValue tests using the Eclipse batch compiler. Since the tests
+ * exercise many AutoValue subtleties, the ability to compile them all is a good indication of
+ * Eclipse support.
+ */
+@RunWith(JUnit4.class)
+public class CompileWithEclipseTest {
+  private static final String SOURCE_ROOT = System.getProperty("basedir");
+
+  @BeforeClass
+  public static void setSourceRoot() {
+    assertWithMessage("basedir property must be set - test must be run from Maven")
+        .that(SOURCE_ROOT).isNotNull();
+  }
+
+  public @Rule TemporaryFolder tmp = new TemporaryFolder();
+
+  private static final ImmutableSet<String> IGNORED_TEST_FILES =
+      ImmutableSet.of("AutoValueNotEclipseTest.java", "CompileWithEclipseTest.java");
+
+  private static final Predicate<File> JAVA_FILE =
+      f -> f.getName().endsWith(".java") && !IGNORED_TEST_FILES.contains(f.getName());
+
+  private static final Predicate<File> JAVA8_TEST =
+      f -> f.getName().equals("AutoValueJava8Test.java")
+          || f.getName().equals("AutoOneOfJava8Test.java")
+          || f.getName().equals("EmptyExtension.java");
+
+  @Test
+  public void compileWithEclipseJava6() throws Exception {
+    compileWithEclipse("6", JAVA_FILE.and(JAVA8_TEST.negate()));
+  }
+
+  @Test
+  public void compileWithEclipseJava8() throws Exception {
+    compileWithEclipse("8", JAVA_FILE);
+  }
+
+  private void compileWithEclipse(String version, Predicate<File> predicate) throws IOException {
+    File sourceRootFile = new File(SOURCE_ROOT);
+    File javaDir = new File(sourceRootFile, "src/main/java");
+    File javatestsDir = new File(sourceRootFile, "src/test/java");
+    Set<File> sources =
+        new ImmutableSet.Builder<File>()
+            .addAll(filesUnderDirectory(javaDir, predicate))
+            .addAll(filesUnderDirectory(javatestsDir, predicate))
+            .build();
+    assertThat(sources).isNotEmpty();
+    JavaCompiler compiler = new EclipseCompiler();
+    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
+    // This hack is only needed in a Google-internal Java 8 environment where symbolic links make it
+    // hard for ecj to find the boot class path. Elsewhere it is unnecessary but harmless. Notably,
+    // on Java 9+ there is no rt.jar. There, fileManager.getLocation(PLATFORM_CLASS_PATH) returns
+    // null, because the relevant classes are in modules inside
+    // fileManager.getLocation(SYSTEM_MODULES).
+    File rtJar = new File(JAVA_HOME.value() + "/lib/rt.jar");
+    if (rtJar.exists()) {
+      List<File> bootClassPath = ImmutableList.<File>builder()
+          .add(rtJar)
+          .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH))
+          .build();
+      fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath);
+    }
+    Iterable<? extends JavaFileObject> sourceFileObjects =
+        fileManager.getJavaFileObjectsFromFiles(sources);
+    String outputDir = tmp.getRoot().toString();
+    ImmutableList<String> options =
+        ImmutableList.of("-d", outputDir, "-s", outputDir, "-source", version, "-target", version);
+    JavaCompiler.CompilationTask task =
+        compiler.getTask(null, fileManager, null, options, null, sourceFileObjects);
+    // Explicitly supply an empty list of extensions for AutoValueProcessor, because otherwise this
+    // test will pick up a test one and get confused.
+    AutoValueProcessor autoValueProcessor = new AutoValueProcessor(ImmutableList.of());
+    ImmutableList<? extends Processor> processors =
+        ImmutableList.of(
+            autoValueProcessor, new AutoOneOfProcessor(), new AutoAnnotationProcessor());
+    task.setProcessors(processors);
+    assertWithMessage("Compilation should succeed").that(task.call()).isTrue();
+  }
+
+  private static ImmutableSet<File> filesUnderDirectory(File dir, Predicate<File> predicate)
+      throws IOException {
+    assertWithMessage(dir.toString()).that(dir.isDirectory()).isTrue();
+    try (Stream<Path> paths = Files.walk(dir.toPath())) {
+      return paths.map(Path::toFile).filter(predicate).collect(toImmutableSet());
+    }
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/SimpleValueTypeTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/SimpleValueTypeTest.java
new file mode 100644
index 0000000..de70dc2
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/SimpleValueTypeTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.NullPointerTester;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class SimpleValueTypeTest {
+  @Test
+  public void testSimpleValueType() {
+    final String happy = "happy";
+    final int testInt = 23;
+    final Map<String, Long> testMap = ImmutableMap.of("happy", 23L);
+    SimpleValueType simple = SimpleValueType.create(happy, testInt, testMap);
+    assertSame(happy, simple.string());
+    assertEquals(testInt, simple.integer());
+    assertSame(testMap, simple.map());
+    assertEquals("SimpleValueType{string=happy, integer=23, map={happy=23}}", simple.toString());
+    int expectedHashCode = 1;
+    expectedHashCode = (expectedHashCode * 1000003) ^ happy.hashCode();
+    expectedHashCode = (expectedHashCode * 1000003) ^ ((Object) testInt).hashCode();
+    expectedHashCode = (expectedHashCode * 1000003) ^ testMap.hashCode();
+    assertEquals(expectedHashCode, simple.hashCode());
+  }
+
+  @Test
+  public void testNestedValueType() {
+    ImmutableMap<Integer, String> numberNames = ImmutableMap.of(1, "un", 2, "deux");
+    NestedValueType.Nested nested = NestedValueType.Nested.create(numberNames);
+    assertEquals(numberNames, nested.numberNames());
+  }
+
+  @Test
+  public void testNull() {
+    NullPointerTester tester = new NullPointerTester();
+    tester.testAllPublicStaticMethods(SimpleValueType.class);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/annotations/Empty.java b/value/src/it/functional/src/test/java/com/google/auto/value/annotations/Empty.java
new file mode 100644
index 0000000..cff5f13
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/annotations/Empty.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014 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.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An annotation with no members, for tests, and runtime retention so it can be accessed through
+ * reflection.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Empty {}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/annotations/GwtArrays.java b/value/src/it/functional/src/test/java/com/google/auto/value/annotations/GwtArrays.java
new file mode 100644
index 0000000..3fbc9bf
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/annotations/GwtArrays.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 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.annotations;
+
+import com.google.common.annotations.GwtCompatible;
+
+/**
+ * An annotation that is marked {@code @GwtCompatible} and that contains an array member.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@GwtCompatible
+public @interface GwtArrays {
+  String[] strings();
+
+  int[] ints();
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/annotations/StringValues.java b/value/src/it/functional/src/test/java/com/google/auto/value/annotations/StringValues.java
new file mode 100644
index 0000000..9f41ff0
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/annotations/StringValues.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2014 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.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An annotation with one member, an array of strings with the default name {@code value}, and
+ * runtime retention so it can be accessed through reflection.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface StringValues {
+  String[] value();
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/enums/MyEnum.java b/value/src/it/functional/src/test/java/com/google/auto/value/enums/MyEnum.java
new file mode 100644
index 0000000..f4c2307
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/enums/MyEnum.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2014 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.enums;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+public enum MyEnum {
+  ONE,
+  TWO,
+  BUCKLE_MY_SHOE
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/CustomFieldSerializerTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/CustomFieldSerializerTest.java
new file mode 100644
index 0000000..fa1bbfb
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/CustomFieldSerializerTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2014 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.gwt;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.GwtCompatible;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests that the generated GWT serializer for GwtValueType serializes fields in the expected way.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class CustomFieldSerializerTest {
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class ValueType implements Serializable {
+    abstract String string();
+
+    abstract int integer();
+
+    @Nullable
+    abstract ValueType other();
+
+    abstract List<ValueType> others();
+
+    static ValueType create(String string, int integer, @Nullable ValueType other) {
+      return create(string, integer, other, Collections.<ValueType>emptyList());
+    }
+
+    static ValueType create(
+        String string, int integer, @Nullable ValueType other, List<ValueType> others) {
+      return new AutoValue_CustomFieldSerializerTest_ValueType(string, integer, other, others);
+    }
+  }
+
+  private static final ValueType SIMPLE = ValueType.create("anotherstring", 1729, null);
+  private static final ValueType CONS = ValueType.create("whatever", 1296, SIMPLE);
+  private static final ValueType WITH_LIST =
+      ValueType.create("blim", 11881376, SIMPLE, ImmutableList.of(SIMPLE, CONS));
+
+  @Mock SerializationStreamWriter streamWriter;
+
+  @Test
+  public void testCustomFieldSerializer() throws SerializationException {
+    AutoValue_CustomFieldSerializerTest_ValueType withList =
+        (AutoValue_CustomFieldSerializerTest_ValueType) WITH_LIST;
+    AutoValue_CustomFieldSerializerTest_ValueType_CustomFieldSerializer.serialize(
+        streamWriter, withList);
+    verify(streamWriter).writeString("blim");
+    verify(streamWriter).writeInt(11881376);
+    verify(streamWriter).writeObject(SIMPLE);
+    verify(streamWriter).writeObject(ImmutableList.of(SIMPLE, CONS));
+    verifyNoMoreInteractions(streamWriter);
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class ValueTypeWithGetters implements Serializable {
+    abstract String getPackage();
+
+    abstract boolean isDefault();
+
+    static ValueTypeWithGetters create(String pkg, boolean dflt) {
+      return new AutoValue_CustomFieldSerializerTest_ValueTypeWithGetters(pkg, dflt);
+    }
+  }
+
+  @Test
+  public void testCustomFieldSerializerWithGetters() throws SerializationException {
+    AutoValue_CustomFieldSerializerTest_ValueTypeWithGetters instance =
+        (AutoValue_CustomFieldSerializerTest_ValueTypeWithGetters)
+            ValueTypeWithGetters.create("package", true);
+    AutoValue_CustomFieldSerializerTest_ValueTypeWithGetters_CustomFieldSerializer.serialize(
+        streamWriter, instance);
+    verify(streamWriter).writeString("package");
+    verify(streamWriter).writeBoolean(true);
+    verifyNoMoreInteractions(streamWriter);
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class GenericValueType<K extends Comparable<K>, V extends K>
+      implements Serializable {
+    abstract Map<K, V> map();
+
+    static <K extends Comparable<K>, V extends K> GenericValueType<K, V> create(Map<K, V> map) {
+      return new AutoValue_CustomFieldSerializerTest_GenericValueType<K, V>(map);
+    }
+  }
+
+  @Test
+  public void testCustomFieldSerializerGeneric() throws SerializationException {
+    Map<Integer, Integer> map = ImmutableMap.of(2, 2);
+    AutoValue_CustomFieldSerializerTest_GenericValueType<Integer, Integer> instance =
+        (AutoValue_CustomFieldSerializerTest_GenericValueType<Integer, Integer>)
+            GenericValueType.create(map);
+    AutoValue_CustomFieldSerializerTest_GenericValueType_CustomFieldSerializer.serialize(
+        streamWriter, instance);
+    verify(streamWriter).writeObject(map);
+    verifyNoMoreInteractions(streamWriter);
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class ValueTypeWithBuilder implements Serializable {
+    abstract String string();
+
+    abstract ImmutableList<String> strings();
+
+    static Builder builder() {
+      return new AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    interface Builder {
+      Builder string(String x);
+
+      Builder strings(ImmutableList<String> x);
+
+      ValueTypeWithBuilder build();
+    }
+  }
+
+  @Test
+  public void testCustomFieldSerializerWithBuilder() throws SerializationException {
+    AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilder instance =
+        (AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilder)
+            ValueTypeWithBuilder.builder().string("s").strings(ImmutableList.of("a", "b")).build();
+    AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilder_CustomFieldSerializer.serialize(
+        streamWriter, instance);
+    verify(streamWriter).writeString("s");
+    verify(streamWriter).writeObject(ImmutableList.of("a", "b"));
+    verifyNoMoreInteractions(streamWriter);
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class ValueTypeWithBuilderAndGetters implements Serializable {
+    abstract String getPackage();
+
+    abstract boolean isDefault();
+
+    static Builder builder() {
+      return new AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilderAndGetters.Builder();
+    }
+
+    @AutoValue.Builder
+    interface Builder {
+      Builder setPackage(String x);
+
+      Builder setDefault(boolean x);
+
+      ValueTypeWithBuilderAndGetters build();
+    }
+  }
+
+  @Test
+  public void testCustomFieldSerializerWithBuilderAndGetters() throws SerializationException {
+    AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilderAndGetters instance =
+        (AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilderAndGetters)
+            ValueTypeWithBuilderAndGetters.builder().setPackage("s").setDefault(false).build();
+    AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilderAndGetters_CustomFieldSerializer
+        .serialize(streamWriter, instance);
+    verify(streamWriter).writeString("s");
+    verify(streamWriter).writeBoolean(false);
+    verifyNoMoreInteractions(streamWriter);
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class GenericValueTypeWithBuilder<K extends Comparable<K>, V extends K>
+      implements Serializable {
+    abstract Map<K, V> map();
+
+    static <K extends Comparable<K>, V extends K> Builder<K, V> builder() {
+      return new AutoValue_CustomFieldSerializerTest_GenericValueTypeWithBuilder.Builder<K, V>();
+    }
+
+    @AutoValue.Builder
+    interface Builder<K extends Comparable<K>, V extends K> {
+      Builder<K, V> map(Map<K, V> map);
+
+      GenericValueTypeWithBuilder<K, V> build();
+    }
+  }
+
+  @Test
+  public void testCustomFieldSerializerGenericWithBuilder() throws SerializationException {
+    Map<Integer, Integer> map = ImmutableMap.of(2, 2);
+    AutoValue_CustomFieldSerializerTest_GenericValueTypeWithBuilder<Integer, Integer> instance =
+        (AutoValue_CustomFieldSerializerTest_GenericValueTypeWithBuilder<Integer, Integer>)
+            GenericValueTypeWithBuilder.<Integer, Integer>builder().map(map).build();
+    AutoValue_CustomFieldSerializerTest_GenericValueTypeWithBuilder_CustomFieldSerializer.serialize(
+        streamWriter, instance);
+    verify(streamWriter).writeObject(map);
+    verifyNoMoreInteractions(streamWriter);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java
new file mode 100644
index 0000000..0476906
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2018 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.gwt;
+
+import static java.util.stream.Collectors.joining;
+
+import com.google.auto.service.AutoService;
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.escapevelocity.Template;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * An AutoValue extension that generates a subclass that does nothing useful.
+ */
+@AutoService(AutoValueExtension.class)
+public class EmptyExtension extends AutoValueExtension {
+  // TODO(emcmanus): it is way too difficult to write a trivial extension. Problems we have here:
+  //   (1) We have to generate a constructor that calls the superclass constructor, which means
+  //       declaring the appropriate constructor parameters and then forwarding them to a super
+  //       call.
+  //   (2) We have to avoid generating variable names that are keywords (we append $ here
+  //       to avoid that).
+  //   (3) We have to concoct appropriate type parameter strings, for example
+  //       final class AutoValue_Foo<K extends Comparable<K>, V> extends $AutoValue_Foo<K, V>.
+  //   These problems show up with the template approach here, but also using JavaPoet as the
+  //   Memoize extension does.
+  private static final ImmutableList<String> TEMPLATE_LINES =
+      ImmutableList.of(
+          "package $package;",
+          "\n",
+          "#if ($isFinal) final #end class ${className}${formalTypes}"
+              + " extends ${classToExtend}${actualTypes} {\n",
+          "  ${className}(",
+          "    #foreach ($property in $propertyTypes.keySet())",
+          "    $propertyTypes[$property] ${property}$ #if ($foreach.hasNext) , #end",
+          "    #end",
+          "  ) {",
+          "    super(",
+          "      #foreach ($property in $propertyTypes.keySet())",
+          "      ${property}$ #if ($foreach.hasNext) , #end",
+          "      #end",
+          "    );",
+          "  }",
+          "}");
+
+  @Override
+  public boolean applicable(Context context) {
+    return true;
+  }
+
+  @Override
+  public String generateClass(
+      Context context, String className, String classToExtend, boolean isFinal) {
+    String templateString = Joiner.on('\n').join(TEMPLATE_LINES);
+    StringReader templateReader = new StringReader(templateString);
+    Template template;
+    try {
+      template = Template.parseFrom(templateReader);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    TypeElement autoValueClass = context.autoValueClass();
+    ImmutableMap<String, Object> vars =
+        ImmutableMap.<String, Object>builder()
+            .put("package", context.packageName())
+            .put("className", className)
+            .put("classToExtend", classToExtend)
+            .put("isFinal", isFinal)
+            .put("propertyTypes", context.propertyTypes())
+            .put("formalTypes", formalTypeParametersString(autoValueClass))
+            .put("actualTypes", actualTypeParametersString(autoValueClass))
+            .build();
+    return template.evaluate(vars);
+  }
+
+  private static String actualTypeParametersString(TypeElement type) {
+    List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
+    if (typeParameters.isEmpty()) {
+      return "";
+    }
+    return typeParameters
+        .stream()
+        .map(e -> e.getSimpleName().toString())
+        .collect(joining(", ", "<", ">"));
+  }
+
+  private static String formalTypeParametersString(TypeElement type) {
+    List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
+    if (typeParameters.isEmpty()) {
+      return "";
+    }
+    StringBuilder sb = new StringBuilder("<");
+    String sep = "";
+    for (TypeParameterElement typeParameter : typeParameters) {
+      sb.append(sep);
+      sep = ", ";
+      appendTypeParameterWithBounds(typeParameter, sb);
+    }
+    return sb.append(">").toString();
+  }
+
+  private static void appendTypeParameterWithBounds(
+      TypeParameterElement typeParameter, StringBuilder sb) {
+    sb.append(typeParameter.getSimpleName());
+    String sep = " extends ";
+    for (TypeMirror bound : typeParameter.getBounds()) {
+      if (!bound.toString().equals("java.lang.Object")) {
+        sb.append(sep);
+        sep = " & ";
+        sb.append(bound);
+      }
+    }
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtCompilationTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtCompilationTest.java
new file mode 100644
index 0000000..6a48b35
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtCompilationTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2014 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.gwt;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.auto.value.processor.AutoValueProcessor;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.lang.model.SourceVersion;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test of generated source for GWT serialization classes.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class GwtCompilationTest {
+  private static final JavaFileObject GWT_COMPATIBLE =
+      JavaFileObjects.forSourceLines(
+          "com.google.annotations.GwtCompatible",
+          "package com.google.annotations;",
+          "",
+          "public @interface GwtCompatible {",
+          "  boolean serializable() default false;",
+          "}");
+
+  /**
+   * Test where the serialized properties don't include generics, so no {@code @SuppressWarnings}
+   * annotation is needed. We explicitly check that one is not included anyway, because Eclipse for
+   * example can be configured to complain about unnecessary warning suppression.
+   */
+  @Test
+  public void testBasic() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.annotations.GwtCompatible;",
+            "",
+            "@AutoValue",
+            "@GwtCompatible(serializable = true)",
+            "public abstract class Baz {",
+            "  public abstract int buh();",
+            "",
+            "  public static Baz create(int buh) {",
+            "    return new AutoValue_Baz(buh);",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoValue_Baz_CustomFieldSerializer",
+            "package foo.bar;",
+            "",
+            "import com.google.gwt.user.client.rpc.CustomFieldSerializer;",
+            "import com.google.gwt.user.client.rpc.SerializationException;",
+            "import com.google.gwt.user.client.rpc.SerializationStreamReader;",
+            "import com.google.gwt.user.client.rpc.SerializationStreamWriter;",
+            "import " + generatedAnnotationType() + ";",
+            "",
+            "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")",
+            "public final class AutoValue_Baz_CustomFieldSerializer"
+                + " extends CustomFieldSerializer<AutoValue_Baz> {",
+            "",
+            "  public static AutoValue_Baz instantiate(",
+            "      SerializationStreamReader streamReader) throws SerializationException {",
+            "    int buh = streamReader.readInt();",
+            "    return new AutoValue_Baz(buh);",
+            "  }",
+            "",
+            "  public static void serialize(",
+            "      SerializationStreamWriter streamWriter,",
+            "      AutoValue_Baz instance) throws SerializationException {",
+            "    streamWriter.writeInt(instance.buh());",
+            "  }",
+            "",
+            "  public static void deserialize(",
+            "      @SuppressWarnings(\"unused\") SerializationStreamReader streamReader,",
+            "      @SuppressWarnings(\"unused\") AutoValue_Baz instance) {",
+            "  }",
+            "",
+            "  @SuppressWarnings(\"unused\") private int dummy_3f8e1b04;",
+            "",
+            "  @Override",
+            "  public void deserializeInstance(",
+            "      SerializationStreamReader streamReader,",
+            "      AutoValue_Baz instance) {",
+            "    deserialize(streamReader, instance);",
+            "  }",
+            "",
+            "  @Override",
+            "  public boolean hasCustomInstantiateInstance() {",
+            "    return true;",
+            "  }",
+            "",
+            "  @Override",
+            "  public AutoValue_Baz instantiateInstance(",
+            "      SerializationStreamReader streamReader) throws SerializationException {",
+            "    return instantiate(streamReader);",
+            "  }",
+            "",
+            "  @Override",
+            "  public void serializeInstance(",
+            "    SerializationStreamWriter streamWriter,",
+            "    AutoValue_Baz instance) throws SerializationException {",
+            "    serialize(streamWriter, instance);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject, GWT_COMPATIBLE);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz_CustomFieldSerializer")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  /**
+   * Test where the serialized properties don't include generics, so a {@code @SuppressWarnings}
+   * annotation is needed.
+   */
+  @Test
+  public void testSuppressWarnings() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.annotations.GwtCompatible;",
+            "",
+            "import java.util.List;",
+            "",
+            "@AutoValue",
+            "@GwtCompatible(serializable = true)",
+            "public abstract class Baz {",
+            "  public abstract List<String> buh();",
+            "",
+            "  public static Baz create(List<String> buh) {",
+            "    return new AutoValue_Baz(buh);",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoValue_Baz_CustomFieldSerializer",
+            "package foo.bar;",
+            "",
+            "import com.google.gwt.user.client.rpc.CustomFieldSerializer;",
+            "import com.google.gwt.user.client.rpc.SerializationException;",
+            "import com.google.gwt.user.client.rpc.SerializationStreamReader;",
+            "import com.google.gwt.user.client.rpc.SerializationStreamWriter;",
+            "import java.util.List;",
+            "import " + generatedAnnotationType() + ";",
+            "",
+            "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")",
+            "public final class AutoValue_Baz_CustomFieldSerializer"
+                + " extends CustomFieldSerializer<AutoValue_Baz> {",
+            "",
+            "  public static AutoValue_Baz instantiate(",
+            "      SerializationStreamReader streamReader) throws SerializationException {",
+            "    @SuppressWarnings(\"unchecked\")",
+            "    List<String> buh = (List<String>) streamReader.readObject();",
+            "    return new AutoValue_Baz(buh);",
+            "  }",
+            "",
+            "  public static void serialize(",
+            "      SerializationStreamWriter streamWriter,",
+            "      AutoValue_Baz instance) throws SerializationException {",
+            "    streamWriter.writeObject(instance.buh());",
+            "  }",
+            "",
+            "  public static void deserialize(",
+            "      @SuppressWarnings(\"unused\") SerializationStreamReader streamReader,",
+            "      @SuppressWarnings(\"unused\") AutoValue_Baz instance) {",
+            "  }",
+            "",
+            "  @SuppressWarnings(\"unused\") private int dummy_949e312e;",
+            "",
+            "  @Override",
+            "  public void deserializeInstance(",
+            "      SerializationStreamReader streamReader,",
+            "      AutoValue_Baz instance) {",
+            "    deserialize(streamReader, instance);",
+            "  }",
+            "",
+            "  @Override",
+            "  public boolean hasCustomInstantiateInstance() {",
+            "    return true;",
+            "  }",
+            "",
+            "  @Override",
+            "  public AutoValue_Baz instantiateInstance(",
+            "      SerializationStreamReader streamReader) throws SerializationException {",
+            "    return instantiate(streamReader);",
+            "  }",
+            "",
+            "  @Override",
+            "  public void serializeInstance(",
+            "    SerializationStreamWriter streamWriter,",
+            "    AutoValue_Baz instance) throws SerializationException {",
+            "    serialize(streamWriter, instance);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject, GWT_COMPATIBLE);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz_CustomFieldSerializer")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  /**
+   * Test builders and classes that are generic (as opposed to just containing properties with
+   * generics).
+   */
+  @Test
+  public void testBuildersAndGenerics() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.annotations.GwtCompatible;",
+            "import com.google.common.collect.ImmutableMap;",
+            "import java.util.Map;",
+            "",
+            "@AutoValue",
+            "@GwtCompatible(serializable = true)",
+            "public abstract class Baz<K extends Comparable<K>, V extends K> {",
+            "  public abstract Map<K, V> map();",
+            "  public abstract ImmutableMap<K, V> immutableMap();",
+            "",
+            "  public static <K extends Comparable<K>, V extends K> Builder<K, V> builder() {",
+            "    return new AutoValue_Baz.Builder<K, V>();",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<K extends Comparable<K>, V extends K> {",
+            "    Builder<K, V> map(Map<K, V> map);",
+            "    ImmutableMap.Builder<K, V> immutableMapBuilder();",
+            "    Baz<K, V> build();",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoValue_Baz_CustomFieldSerializer",
+            "package foo.bar;",
+            "",
+            "import com.google.common.collect.ImmutableMap;",
+            "import com.google.gwt.user.client.rpc.CustomFieldSerializer;",
+            "import com.google.gwt.user.client.rpc.SerializationException;",
+            "import com.google.gwt.user.client.rpc.SerializationStreamReader;",
+            "import com.google.gwt.user.client.rpc.SerializationStreamWriter;",
+            "import java.util.Map;",
+            "import " + generatedAnnotationType() + ";",
+            "",
+            "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")",
+            "public final class AutoValue_Baz_CustomFieldSerializer"
+                + "<K extends Comparable<K>, V extends K>"
+                + " extends CustomFieldSerializer<AutoValue_Baz<K, V>> {",
+            "",
+            "  public static <K extends Comparable<K>, V extends K> AutoValue_Baz<K, V>"
+                + " instantiate(",
+            "      SerializationStreamReader streamReader) throws SerializationException {",
+            "    @SuppressWarnings(\"unchecked\")",
+            "    Map<K, V> map = (Map<K, V>) streamReader.readObject();",
+            "    @SuppressWarnings(\"unchecked\")",
+            "    ImmutableMap<K, V> immutableMap = (ImmutableMap<K, V>) streamReader.readObject();",
+            "    AutoValue_Baz.Builder<K, V> builder$ = new AutoValue_Baz.Builder<K, V>();",
+            "    builder$.map(map);",
+            "    builder$.immutableMapBuilder().putAll(immutableMap);",
+            "    return (AutoValue_Baz<K, V>) builder$.build();",
+            "  }",
+            "",
+            "  public static <K extends Comparable<K>, V extends K> void serialize(",
+            "      SerializationStreamWriter streamWriter,",
+            "      AutoValue_Baz<K, V> instance) throws SerializationException {",
+            "    streamWriter.writeObject(instance.map());",
+            "    streamWriter.writeObject(instance.immutableMap());",
+            "  }",
+            "",
+            "  public static <K extends Comparable<K>, V extends K> void deserialize(",
+            "      @SuppressWarnings(\"unused\") SerializationStreamReader streamReader,",
+            "      @SuppressWarnings(\"unused\") AutoValue_Baz<K, V> instance) {",
+            "  }",
+            "",
+            "  @SuppressWarnings(\"unused\")",
+            "  private int dummy_2865d9ec;",
+            "",
+            "  @Override",
+            "  public void deserializeInstance(",
+            "      SerializationStreamReader streamReader,",
+            "      AutoValue_Baz<K, V> instance) {",
+            "    deserialize(streamReader, instance);",
+            "  }",
+            "",
+            "  @Override",
+            "  public boolean hasCustomInstantiateInstance() {",
+            "    return true;",
+            "  }",
+            "",
+            "  @Override",
+            "  public AutoValue_Baz<K, V> instantiateInstance(",
+            "      SerializationStreamReader streamReader) throws SerializationException {",
+            "    return instantiate(streamReader);",
+            "  }",
+            "",
+            "  @Override",
+            "  public void serializeInstance(",
+            "    SerializationStreamWriter streamWriter,",
+            "    AutoValue_Baz<K, V> instance) throws SerializationException {",
+            "    serialize(streamWriter, instance);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject, GWT_COMPATIBLE);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz_CustomFieldSerializer")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  private String generatedAnnotationType() {
+    return isJavaxAnnotationProcessingGeneratedAvailable()
+        ? "javax.annotation.processing.Generated"
+        : "javax.annotation.Generated";
+  }
+
+  private boolean isJavaxAnnotationProcessingGeneratedAvailable() {
+    return SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0;
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtValueType.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtValueType.java
new file mode 100644
index 0000000..b015ac9
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtValueType.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 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.gwt;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.GwtCompatible;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * A class that is serializable with both Java and GWT serialization.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@AutoValue
+@GwtCompatible(serializable = true)
+abstract class GwtValueType implements Serializable {
+  abstract String string();
+
+  abstract int integer();
+
+  @Nullable
+  abstract GwtValueType other();
+
+  abstract List<GwtValueType> others();
+
+  static GwtValueType create(String string, int integer, @Nullable GwtValueType other) {
+    return create(string, integer, other, Collections.<GwtValueType>emptyList());
+  }
+
+  static GwtValueType create(
+      String string, int integer, @Nullable GwtValueType other, List<GwtValueType> others) {
+    return new AutoValue_GwtValueType(string, integer, other, others);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtValueTypeWithBuilder.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtValueTypeWithBuilder.java
new file mode 100644
index 0000000..781e537
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtValueTypeWithBuilder.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 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.gwt;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.GwtCompatible;
+import com.google.common.collect.ImmutableList;
+import java.io.Serializable;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * A class that is serializable with both Java and GWT serialization.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@AutoValue
+@GwtCompatible(serializable = true)
+abstract class GwtValueTypeWithBuilder<T> implements Serializable {
+  abstract String string();
+
+  abstract int integer();
+
+  @Nullable
+  abstract GwtValueTypeWithBuilder<T> other();
+
+  abstract List<GwtValueTypeWithBuilder<T>> others();
+
+  abstract ImmutableList<T> list();
+
+  abstract ImmutableList<T> otherList();
+
+  abstract ImmutableList<String> listWithBuilder();
+
+  static <T> Builder<T> builder() {
+    return new AutoValue_GwtValueTypeWithBuilder.Builder<T>();
+  }
+
+  @AutoValue.Builder
+  interface Builder<T> {
+    Builder<T> string(String x);
+
+    Builder<T> integer(int x);
+
+    Builder<T> other(@Nullable GwtValueTypeWithBuilder<T> x);
+
+    Builder<T> others(List<GwtValueTypeWithBuilder<T>> x);
+
+    Builder<T> list(ImmutableList<T> x);
+
+    Builder<T> otherList(List<T> x);
+
+    ImmutableList.Builder<String> listWithBuilderBuilder();
+
+    GwtValueTypeWithBuilder<T> build();
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/NonSerializableGwtValueType.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/NonSerializableGwtValueType.java
new file mode 100644
index 0000000..565b1d4
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/NonSerializableGwtValueType.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 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.gwt;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.GwtCompatible;
+import java.io.Serializable;
+
+/**
+ * A class that is serializable with native Java serialization but not with GWT serialization.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@AutoValue
+@GwtCompatible
+abstract class NonSerializableGwtValueType implements Serializable {
+  abstract String string();
+
+  abstract int integer();
+
+  static NonSerializableGwtValueType create(String string, int integer) {
+    return new AutoValue_NonSerializableGwtValueType(string, integer);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/SerialSignatureTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/SerialSignatureTest.java
new file mode 100644
index 0000000..65067fb
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/SerialSignatureTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2014 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.gwt;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.GwtCompatible;
+import java.lang.reflect.Field;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests that the generated serializer class for a GWT-serializable class contains a dummy field to
+ * influence the signature, and that different classes have different signatures.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class SerialSignatureTest {
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class One {
+    abstract int foo();
+
+    static One create(int foo) {
+      return new AutoValue_SerialSignatureTest_One(foo);
+    }
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class Two {
+    abstract int foo();
+
+    static Two create(int foo) {
+      return new AutoValue_SerialSignatureTest_Two(foo);
+    }
+  }
+
+  @Test
+  public void testSerialSignatures() {
+    Class<?> serializerOne = AutoValue_SerialSignatureTest_One_CustomFieldSerializer.class;
+    Class<?> serializerTwo = AutoValue_SerialSignatureTest_Two_CustomFieldSerializer.class;
+    String fieldNameOne = dummySignatureFieldName(serializerOne);
+    String fieldNameTwo = dummySignatureFieldName(serializerTwo);
+    assertFalse(fieldNameOne.equals(fieldNameTwo));
+  }
+
+  private static String dummySignatureFieldName(Class<?> c) {
+    String name = null;
+    for (Field f : c.getDeclaredFields()) {
+      if (f.getName().startsWith("dummy_")) {
+        assertNull("More than one field begins with dummy_: " + name + ", " + f.getName(), name);
+      }
+      name = f.getName();
+    }
+    assertNotNull("No field begins with dummy_", name);
+    return name;
+  }
+}
diff --git a/value/src/it/gwtserializer/invoker.properties b/value/src/it/gwtserializer/invoker.properties
new file mode 100644
index 0000000..e2a44e3
--- /dev/null
+++ b/value/src/it/gwtserializer/invoker.properties
@@ -0,0 +1 @@
+invoker.goals = clean verify
diff --git a/value/src/it/gwtserializer/pom.xml b/value/src/it/gwtserializer/pom.xml
new file mode 100644
index 0000000..cd56484
--- /dev/null
+++ b/value/src/it/gwtserializer/pom.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2015 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.
+-->
+<!-- TODO(gak): see if we can manage these dependencies any better -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.google.auto.value</groupId>
+    <artifactId>auto-value-parent</artifactId>
+    <version>HEAD-SNAPSHOT</version>
+    <relativePath>../../../pom.xml</relativePath>
+  </parent>
+  <url>https://github.com/google/auto/tree/master/value</url>
+
+  <groupId>com.google.auto.value.it.gwtserializer</groupId>
+  <artifactId>gwtserializer</artifactId>
+  <version>HEAD-SNAPSHOT</version>
+  <name>Auto-Value GWT-RPC Serialization Integration Test</name>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>com.google.gwt</groupId>
+        <artifactId>gwt</artifactId>
+        <version>2.8.2</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <dependencies>
+    <dependency>
+      <groupId>com.google.auto.value</groupId>
+      <artifactId>auto-value-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto.value</groupId>
+      <artifactId>auto-value</artifactId>
+      <version>${project.version}</version>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>com.google.gwt</groupId>
+      <artifactId>gwt-user</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.gwt</groupId>
+      <artifactId>gwt-dev</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava-gwt</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <testResources>
+      <testResource>
+        <directory>src/test/java</directory>
+      </testResource>
+      <testResource>
+        <directory>${project.build.directory}/generated-test-sources/test-annotations</directory>
+      </testResource>
+    </testResources>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.7.0</version>
+        <dependencies>
+          <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-java</artifactId>
+            <version>0.9.4</version>
+          </dependency>
+        </dependencies>
+        <configuration>
+          <source>1.7</source>
+          <target>1.7</target>
+          <compilerArgument>-Xlint:all</compilerArgument>
+          <showWarnings>true</showWarnings>
+          <showDeprecation>true</showDeprecation>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <skipTests>true</skipTests>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-resources-plugin</artifactId>
+        <version>3.0.2</version>
+        <executions>
+          <execution>
+            <!-- postpone resources:testResources until after compiler:testCompile to get generated sources -->
+            <id>default-testResources</id>
+            <phase>process-test-classes</phase>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>net.ltgt.gwt.maven</groupId>
+        <artifactId>gwt-maven-plugin</artifactId>
+        <version>1.0-rc-6</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>test</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <version>2.7</version>
+        <configuration>
+          <!-- Build/test, but don't deploy -->
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/value/src/it/gwtserializer/src/test/java/com/google/auto/value/GwtSerializerSuite.gwt.xml b/value/src/it/gwtserializer/src/test/java/com/google/auto/value/GwtSerializerSuite.gwt.xml
new file mode 100644
index 0000000..e556933
--- /dev/null
+++ b/value/src/it/gwtserializer/src/test/java/com/google/auto/value/GwtSerializerSuite.gwt.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2015 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.
+-->
+<module>
+  <inherits name="com.google.gwt.user.User" />
+
+  <servlet path="/test" class="com.google.auto.value.client.GwtSerializerTest$TestServiceImpl" />
+</module>
diff --git a/value/src/it/gwtserializer/src/test/java/com/google/auto/value/GwtSerializerSuite.java b/value/src/it/gwtserializer/src/test/java/com/google/auto/value/GwtSerializerSuite.java
new file mode 100644
index 0000000..f0b4087
--- /dev/null
+++ b/value/src/it/gwtserializer/src/test/java/com/google/auto/value/GwtSerializerSuite.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 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;
+
+import com.google.auto.value.client.GwtSerializerTest;
+import com.google.gwt.junit.tools.GWTTestSuite;
+import junit.framework.Test;
+
+public class GwtSerializerSuite {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite();
+
+    suite.addTestSuite(GwtSerializerTest.class);
+
+    return suite;
+  }
+}
diff --git a/value/src/it/gwtserializer/src/test/java/com/google/auto/value/client/GwtSerializerTest.java b/value/src/it/gwtserializer/src/test/java/com/google/auto/value/client/GwtSerializerTest.java
new file mode 100644
index 0000000..ca2be17
--- /dev/null
+++ b/value/src/it/gwtserializer/src/test/java/com/google/auto/value/client/GwtSerializerTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2015 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.client;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.GwtCompatible;
+import com.google.common.annotations.GwtIncompatible;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.rpc.RemoteService;
+import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
+import com.google.gwt.user.server.rpc.RemoteServiceServlet;
+
+public class GwtSerializerTest extends GWTTestCase {
+
+  @RemoteServiceRelativePath("test")
+  public interface TestService extends RemoteService {
+    Simple echo(Simple simple);
+
+    SimpleWithBuilder echo(SimpleWithBuilder simple);
+
+    Nested echo(Nested nested);
+
+    NestedWithBuilder echo(NestedWithBuilder nested);
+
+    Generics<Simple> echo(Generics<Simple> generics);
+
+    GenericsWithBuilder<SimpleWithBuilder> echo(GenericsWithBuilder<SimpleWithBuilder> generics);
+  }
+
+  interface TestServiceAsync {
+    void echo(Simple simple, AsyncCallback<Simple> callback);
+
+    void echo(SimpleWithBuilder simple, AsyncCallback<SimpleWithBuilder> callback);
+
+    void echo(Nested nested, AsyncCallback<Nested> callback);
+
+    void echo(NestedWithBuilder nested, AsyncCallback<NestedWithBuilder> callback);
+
+    void echo(Generics<Simple> generics, AsyncCallback<Generics<Simple>> callback);
+
+    void echo(
+        GenericsWithBuilder<SimpleWithBuilder> generics,
+        AsyncCallback<GenericsWithBuilder<SimpleWithBuilder>> callback);
+  }
+
+  class AssertEqualsCallback<T> implements AsyncCallback<T> {
+    private final T expected;
+
+    AssertEqualsCallback(T expected) {
+      this.expected = expected;
+    }
+
+    @Override
+    public void onSuccess(T actual) {
+      assertEquals(expected, actual);
+      finishTest();
+    }
+
+    @Override
+    public void onFailure(Throwable caught) {
+      fail();
+    }
+  }
+
+  @GwtIncompatible("RemoteServiceServlet")
+  @SuppressWarnings("serial")
+  public static class TestServiceImpl extends RemoteServiceServlet implements TestService {
+    @Override
+    public Simple echo(Simple simple) {
+      return Simple.create(simple.message());
+    }
+
+    @Override
+    public SimpleWithBuilder echo(SimpleWithBuilder simple) {
+      return SimpleWithBuilder.builder().message(simple.message()).build();
+    }
+
+    @Override
+    public Nested echo(Nested nested) {
+      return Nested.create(nested.message(), echo(nested.simple()));
+    }
+
+    @Override
+    public NestedWithBuilder echo(NestedWithBuilder nested) {
+      return NestedWithBuilder.builder()
+          .message(nested.message())
+          .simple(echo(nested.simple()))
+          .build();
+    }
+
+    @Override
+    public Generics<Simple> echo(Generics<Simple> generics) {
+      return Generics.create(echo(generics.simple()));
+    }
+
+    @Override
+    public GenericsWithBuilder<SimpleWithBuilder> echo(
+        GenericsWithBuilder<SimpleWithBuilder> generics) {
+      return GenericsWithBuilder.<SimpleWithBuilder>builder()
+          .simple(echo(generics.simple()))
+          .build();
+    }
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class Simple {
+    public abstract String message();
+
+    public static Simple create(String message) {
+      return new AutoValue_GwtSerializerTest_Simple(message);
+    }
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class SimpleWithBuilder {
+    public abstract String message();
+
+    public static Builder builder() {
+      return new AutoValue_GwtSerializerTest_SimpleWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder message(String message);
+
+      SimpleWithBuilder build();
+    }
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class Nested {
+    public abstract String message();
+
+    public abstract Simple simple();
+
+    public static Nested create(String message, Simple simple) {
+      return new AutoValue_GwtSerializerTest_Nested(message, simple);
+    }
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class NestedWithBuilder {
+    public abstract String message();
+
+    public abstract SimpleWithBuilder simple();
+
+    public static Builder builder() {
+      return new AutoValue_GwtSerializerTest_NestedWithBuilder.Builder();
+    }
+
+    @AutoValue.Builder
+    public interface Builder {
+      Builder message(String message);
+
+      Builder simple(SimpleWithBuilder simple);
+
+      NestedWithBuilder build();
+    }
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class Generics<T> {
+    public abstract T simple();
+
+    public static <T> Generics<T> create(T simple) {
+      return new AutoValue_GwtSerializerTest_Generics<T>(simple);
+    }
+  }
+
+  @AutoValue
+  @GwtCompatible(serializable = true)
+  abstract static class GenericsWithBuilder<T> {
+    public abstract T simple();
+
+    public static <T> Builder<T> builder() {
+      return new AutoValue_GwtSerializerTest_GenericsWithBuilder.Builder<T>();
+    }
+
+    @AutoValue.Builder
+    public interface Builder<T> {
+      Builder<T> simple(T simple);
+
+      GenericsWithBuilder<T> build();
+    }
+  }
+
+  private TestServiceAsync testService;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.auto.value.GwtSerializerSuite";
+  }
+
+  @Override
+  public void gwtSetUp() {
+    testService = GWT.create(TestService.class);
+  }
+
+  public void testSimple() {
+    delayTestFinish(2000);
+    Simple simple = Simple.create("able");
+    testService.echo(simple, new AssertEqualsCallback<Simple>(simple));
+  }
+
+  public void testSimpleWithBuilder() {
+    delayTestFinish(2000);
+    SimpleWithBuilder simple = SimpleWithBuilder.builder().message("able").build();
+    testService.echo(simple, new AssertEqualsCallback<SimpleWithBuilder>(simple));
+  }
+
+  public void testNested() {
+    delayTestFinish(2000);
+    Nested nested = Nested.create("able", Simple.create("baker"));
+    testService.echo(nested, new AssertEqualsCallback<Nested>(nested));
+  }
+
+  public void testNestedWithBuilder() {
+    delayTestFinish(2000);
+    NestedWithBuilder nested =
+        NestedWithBuilder.builder()
+            .message("able")
+            .simple(SimpleWithBuilder.builder().message("baker").build())
+            .build();
+    testService.echo(nested, new AssertEqualsCallback<NestedWithBuilder>(nested));
+  }
+
+  public void testGenerics() {
+    delayTestFinish(2000);
+    Generics<Simple> generics = Generics.create(Simple.create("able"));
+    testService.echo(generics, new AssertEqualsCallback<Generics<Simple>>(generics));
+  }
+
+  public void testGenericsWithBuilder() {
+    delayTestFinish(2000);
+    GenericsWithBuilder<SimpleWithBuilder> generics =
+        GenericsWithBuilder.<SimpleWithBuilder>builder()
+            .simple(SimpleWithBuilder.builder().message("able").build())
+            .build();
+    testService.echo(
+        generics, new AssertEqualsCallback<GenericsWithBuilder<SimpleWithBuilder>>(generics));
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/AutoAnnotation.java b/value/src/main/java/com/google/auto/value/AutoAnnotation.java
new file mode 100644
index 0000000..d36d8e2
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/AutoAnnotation.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2014 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;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedElement;
+
+/**
+ * Annotation that causes an implementation of an annotation interface to be generated. The
+ * annotation is applied to a method whose return type is an annotation interface. The method can
+ * then create and return an instance of the generated class that conforms to the specification of
+ * {@link Annotation}, in particular as regards {@link Annotation#equals equals} and {@link
+ * Annotation#hashCode hashCode}. These instances behave essentially the same as instances returned
+ * by {@link AnnotatedElement#getAnnotation}.
+ *
+ * <p>For example, suppose you have an annotation like this:
+ *
+ * <pre>
+ * package com.google.inject.name;
+ *
+ * public &#64;interface Named {
+ *   String value();
+ * }</pre>
+ *
+ * <p>You could write a method like this to construct implementations of the interface:
+ *
+ * <pre>
+ * package com.example;
+ *
+ * public class Names {
+ *   &#64;AutoAnnotation public static Named named(String value) {
+ *     return new AutoAnnotation_Names_named(value);
+ *   }
+ * }</pre>
+ *
+ * <p>Because the annotated method is called {@code Names.named}, the generated class is called
+ * {@code AutoAnnotation_Names_named} in the same package. If the annotated method were in a nested
+ * class, for example {@code Outer.Names.named}, then the generated class would be called {@code
+ * AutoAnnotation_Outer_Names_named}. The generated class is package-private and it is not expected
+ * that it will be referenced outside the {@code @AutoAnnotation} method.
+ *
+ * <p>The names and types of the parameters in the annotated method must be the same as the names
+ * and types of the annotation elements, except that elements which have default values can be
+ * omitted. The parameters do not need to be in any particular order.
+ *
+ * <p>The annotated method does not need to be public. It can have any visibility, including
+ * private. This means the method can be a private implementation called by a public method with a
+ * different API, for example using a builder.
+ *
+ * <p>It is a compile-time error if more than one method with the same name in the same class is
+ * annotated {@code @AutoAnnotation}.
+ *
+ * <p>The constructor of the generated class has the same parameters as the {@code @AutoAnnotation}
+ * method. It will throw {@code NullPointerException} if any parameter is null. In order to
+ * guarantee that the constructed object is immutable, the constructor will clone each array
+ * parameter corresponding to an array-valued annotation member, and the implementation of each such
+ * member will also return a clone of the array.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.SOURCE)
+public @interface AutoAnnotation {}
diff --git a/value/src/main/java/com/google/auto/value/AutoOneOf.java b/value/src/main/java/com/google/auto/value/AutoOneOf.java
new file mode 100644
index 0000000..064b777
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/AutoOneOf.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies that the annotated class is a <em>one-of</em> class, also known as a <a
+ * href="https://en.wikipedia.org/wiki/Tagged_union"><em>tagged union</em></a>. An
+ * {@code @AutoOneOf} class is very similar to an {@link AutoValue @AutoValue} class, in that its
+ * abstract methods define a set of properties. But unlike {@code @AutoValue}, only one of those
+ * properties is defined in any given instance.
+ *
+ * <pre>{@code @AutoOneOf(StringOrInteger.Kind.class)
+ * public abstract class StringOrInteger {
+ *   public enum Kind {STRING, INTEGER}
+ *
+ *   public abstract Kind getKind();
+ *
+ *   public abstract String string();
+ *   public abstract int integer();
+ *
+ *   public static StringOrInteger ofString(String s) {
+ *     return AutoOneOf_StringOrInteger.string(s);
+ *   }
+ *
+ *   public static StringOrInteger ofInteger(int i) {
+ *     return AutoOneOf_StringOrInteger.integer(i);
+ *   }
+ * }
+ *
+ * String client(StringOrInteger stringOrInteger) {
+ *   switch (stringOrInteger.getKind()) {
+ *     case STRING:
+ *       return "the string '" + stringOrInteger.string() + "'";
+ *     case INTEGER:
+ *       return "the integer " + stringOrInteger.integer();
+ *   }
+ *   throw new AssertionError();
+ * }}</pre>
+ *
+ * <p>{@code @AutoOneOf} is explained in more detail in the <a
+ * href="https://github.com/google/auto/blob/master/value/userguide/howto.md#oneof">user guide</a>.
+ *
+ * @author Chris Nokleberg
+ * @author Éamonn McManus
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.TYPE)
+public @interface AutoOneOf {
+  /** Specifies an enum that has one entry per variant in the one-of. */
+  Class<? extends Enum<?>> value();
+}
diff --git a/value/src/main/java/com/google/auto/value/AutoValue.java b/value/src/main/java/com/google/auto/value/AutoValue.java
new file mode 100644
index 0000000..45a677c
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/AutoValue.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2012 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;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies that <a href="https://github.com/google/auto/tree/master/value">AutoValue</a> should
+ * generate an implementation class for the annotated abstract class, implementing the standard
+ * {@link Object} methods like {@link Object#equals equals} to have conventional value semantics. A
+ * simple example:
+ *
+ * <pre>
+ *
+ *   {@code @}AutoValue
+ *   abstract class Person {
+ *     static Person create(String name, int id) {
+ *       return new AutoValue_Person(name, id);
+ *     }
+ *
+ *     abstract String name();
+ *     abstract int id();
+ *   }</pre>
+ *
+ * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
+ * @author Éamonn McManus
+ * @author Kevin Bourrillion
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.TYPE)
+public @interface AutoValue {
+
+  /**
+   * Specifies that AutoValue should generate an implementation of the annotated class or interface,
+   * to serve as a <i>builder</i> for the value-type class it is nested within. As a simple example,
+   * here is an alternative way to write the {@code Person} class mentioned in the {@link AutoValue}
+   * example:
+   *
+   * <pre>
+   *
+   *   {@code @}AutoValue
+   *   abstract class Person {
+   *     static Builder builder() {
+   *       return new AutoValue_Person.Builder();
+   *     }
+   *
+   *     abstract String name();
+   *     abstract int id();
+   *
+   *     {@code @}AutoValue.Builder
+   *     interface Builder {
+   *       Builder name(String x);
+   *       Builder id(int x);
+   *       Person build();
+   *     }
+   *   }</pre>
+   *
+   * @author Éamonn McManus
+   */
+  @Retention(RetentionPolicy.CLASS)
+  @Target(ElementType.TYPE)
+  public @interface Builder {}
+
+  /**
+   * Specifies that AutoValue should copy any annotations from the annotated element to the
+   * generated class. This annotation supports classes and methods.
+   *
+   * <p>The following annotations are excluded:
+   *
+   * <ol>
+   *   <li>AutoValue and its nested annotations;
+   *   <li>any annotation appearing in the {@link AutoValue.CopyAnnotations#exclude} field;
+   *   <li>any class annotation which is itself annotated with the {@link
+   *       java.lang.annotation.Inherited} meta-annotation.
+   * </ol>
+   *
+   * <p>For historical reasons, annotations are always copied from an {@code @AutoValue} property
+   * method to its implementation, unless {@code @CopyAnnotations} is present and explicitly
+   * {@linkplain CopyAnnotations#exclude excludes} that annotation. But annotations are not copied
+   * from the {@code @AutoValue} class itself to its implementation unless {@code @CopyAnnotations}
+   * is present.
+   *
+   * <p>If you want to copy annotations from your {@literal @}AutoValue-annotated class's methods to
+   * the generated fields in the AutoValue_... implementation, annotate your method
+   * with {@literal @}AutoValue.CopyAnnotations. For example, if Example.java is:<pre>
+
+   *   {@code @}Immutable
+   *   {@code @}AutoValue
+   *   abstract class Example {
+   *     {@code @}CopyAnnotations
+   *     {@code @}SuppressWarnings("Immutable") // justification ...
+   *     abstract Object getObject();
+   *     // other details ...
+   *   }</pre>
+   *
+   * <p>Then AutoValue will generate the following AutoValue_Example.java:<pre>
+   *
+   *   final class AutoValue_Example extends Example {
+   *     {@code @}SuppressWarnings("Immutable")
+   *     private final Object object;
+   *
+   *     {@code @}SuppressWarnings("Immutable")
+   *     {@code @}Override
+   *     Object getObject() {
+   *       return object;
+   *     }
+   *
+   *     // other details ...
+   *   }</pre>
+   *
+   * <p>When the <i>type</i> of an {@code @AutoValue} property method has annotations, those are
+   * part of the type, so by default they are copied to the implementation of the method. But if
+   * a type annotation is mentioned in {@code exclude} then it is not copied.
+   *
+   * <p>For example, suppose {@code @Confidential} is a
+   * {@link java.lang.annotation.ElementType#TYPE_USE TYPE_USE} annotation:
+   *
+   * <pre>
+   *
+   *   {@code @}AutoValue
+   *   abstract class Person {
+   *     static Person create({@code @}Confidential String name, int id) {
+   *       return new AutoValue_Person(name, id);
+   *     }
+   *
+   *     abstract {@code @}Confidential String name();
+   *     abstract int id();
+   *   }</pre>
+   *
+   * Then the implementation of the {@code name()} method will also have return type
+   * {@code @Confidential String}. But if {@code name()} were written like this...
+   *
+   * <pre>
+   *
+   *     {@code @AutoValue.CopyAnnotations(exclude = Confidential.class)}
+   *     abstract {@code @}Confidential String name();</pre>
+   *
+   * <p>...then the implementation of {@code name()} would have return type {@code String} without
+   * the annotation.
+   *
+   * @author Carmi Grushko
+   */
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.TYPE, ElementType.METHOD})
+  public @interface CopyAnnotations {
+    Class<? extends Annotation>[] exclude() default {};
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
new file mode 100644
index 0000000..2c8a3fb
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2015 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;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.SupportedOptions;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * An AutoValueExtension allows for extra functionality to be created during the generation of an
+ * AutoValue class.
+ *
+ * <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.AutoValueExtension} in a jar that is on the
+ * compiler's {@code -classpath} or {@code -processorpath}.
+ *
+ * <p>When the AutoValue processor runs for a class {@code Foo}, it will ask each Extension whether
+ * it is {@linkplain #applicable applicable}. Suppose two Extensions reply that they are. Then
+ * the processor will generate the AutoValue logic in a direct subclass of {@code Foo}, and it
+ * will ask the first Extension to generate a subclass of that, and the second Extension to generate
+ * a subclass of the subclass. So we might have this hierarchy:
+ *
+ * <pre>
+ * &#64;AutoValue abstract class Foo {...}                          // the hand-written class
+ * abstract class $$AutoValue_Foo extends Foo {...}             // generated by AutoValue processor
+ * abstract class $AutoValue_Foo extends $$AutoValue_Foo {...}  // generated by first Extension
+ * final class AutoValue_Foo extends $AutoValue_Foo {...}       // generated by second Extension
+ * </pre>
+ *
+ * <p>(The exact naming scheme illustrated here is not fixed and should not be relied on.)
+ *
+ * <p>If an Extension needs its generated class to be the final class in the inheritance hierarchy,
+ * its {@link #mustBeFinal(Context)} method returns true. Only one Extension can return true for a
+ * given context. Only generated classes that will be the final class in the inheritance hierarchy
+ * can be declared final. All others should be declared abstract.
+ *
+ * <p>The first generated class in the hierarchy will always be the one generated by the AutoValue
+ * processor and the last one will always be the one generated by the Extension that {@code
+ * mustBeFinal}, if any. Other than that, the order of the classes in the hierarchy is unspecified.
+ * The last class in the hierarchy is {@code AutoValue_Foo} and that is the one that the
+ * {@code Foo} class will reference, for example with {@code new AutoValue_Foo(...)}.
+ *
+ * <p>Each Extension must also be sure to generate a constructor with arguments corresponding to all
+ * properties in {@link com.google.auto.value.extension.AutoValueExtension.Context#propertyTypes()},
+ * in order, and to call the superclass constructor with the same arguments. This constructor must
+ * have at least package visibility.
+ *
+ * <p>Because the class generated by the AutoValue processor is at the top of the generated
+ * hierarchy, Extensions can override its methods, for example {@code hashCode()},
+ * {@code toString()}, or the implementations of the various {@code bar()} property methods.
+ */
+public abstract class AutoValueExtension {
+
+  /** The context of the generation cycle. */
+  public interface Context {
+
+    /**
+     * Returns the processing environment of this generation cycle. This can be used, among other
+     * things, to produce compilation warnings or errors, using {@link
+     * ProcessingEnvironment#getMessager()}.
+     */
+    ProcessingEnvironment processingEnvironment();
+
+    /** Returns the package name of the classes to be generated. */
+    String packageName();
+
+    /**
+     * Returns the annotated class that this generation cycle is based on.
+     *
+     * <p>Given {@code @AutoValue public class Foo {...}}, this will be {@code Foo}.
+     */
+    TypeElement autoValueClass();
+
+    /**
+     * The fully-qualified name of the last class in the {@code AutoValue} hierarchy. For an
+     * {@code @AutoValue} class {@code foo.bar.Baz}, this will be {@code foo.bar.AutoValue_Baz}.
+     * The class may be generated by an extension, which will be the current extension if the
+     * {@code isFinal} parameter to {@link AutoValueExtension#generateClass} is true and the
+     * returned string is not {@code null}.
+     *
+     * <p>For compatibility reasons, this method has a default implementation that throws an
+     * exception. The AutoValue processor supplies an implementation that behaves as documented.
+     */
+    default String finalAutoValueClassName() {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns the ordered collection of properties to be generated by AutoValue. Each key is a
+     * property name, and the corresponding value is the getter method for that property. For
+     * example, if property {@code bar} is defined by {@code abstract String getBar()} then this map
+     * will have an entry mapping {@code "bar"} to the {@code ExecutableElement} for {@code
+     * getBar()}.
+     *
+     * <p>To determine the type of a property, it is best to use {@link #propertyTypes()} rather
+     * than looking at the return type of the {@link ExecutableElement} in this map. The reason is
+     * that the final type of the property might be different because of type variables. For
+     * example, if you have...
+     *
+     * <pre>
+     *   interface Parent<T> {
+     *     T bar();
+     *   }
+     *
+     *  {@code @AutoValue abstract class Foo implements Parent<String> {...}}
+     * </pre>
+     *
+     * ...then the type of the {@code bar} property in {@code Foo} is actually {@code String}, but
+     * the {@code ExecutableElement} will be the the method in {@code Parent}, whose return type is
+     * {@code T}.
+     */
+    Map<String, ExecutableElement> properties();
+
+    /**
+     * Returns the properties to be generated by AutoValue, with their types. Each key is a property
+     * name, and the corresponding value is the type of that property. The order of the map entries
+     * is the same as the order of the {@code @AutoValue} properties.
+     *
+     * <p>For example, if property {@code bar} is defined by {@code abstract String getBar()} then
+     * this map will have an entry mapping {@code "bar"} to the {@code TypeMirror} for {@code
+     * String}.
+     *
+     * <p>For compatibility reasons, this method has a default implementation that throws an
+     * exception. The AutoValue processor supplies an implementation that behaves as documented.
+     */
+    default Map<String, TypeMirror> propertyTypes() {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns the complete set of abstract methods defined in or inherited by the
+     * {@code @AutoValue} class. This includes all methods that define properties (like {@code
+     * abstract String getBar()}), any abstract {@code toBuilder()} method, and any other abstract
+     * method even if it has been consumed by this or another Extension.
+     */
+    Set<ExecutableElement> abstractMethods();
+
+    /**
+     * Returns a representation of the {@code Builder} associated with the {@code @AutoValue} class,
+     * if there is one.
+     *
+     * <p>This method returns {@link Optional#empty()} if called from within the {@link #applicable}
+     * method. If an Extension needs {@code Builder} information to decide whether it is applicable,
+     * it should return {@code true} from the {@link #applicable} method and then return {@code
+     * null} from the {@link #generateClass} method if it does not need to generate a class after
+     * all.
+     *
+     * <p>The default implementation of this method returns {@link Optional#empty()} for
+     * compatibility with extensions which may have implemented this interface themselves.
+     */
+    default Optional<BuilderContext> builder() {
+      return Optional.empty();
+    }
+  }
+
+  /**
+   * Represents a {@code Builder} associated with an {@code @AutoValue} class.
+   */
+  public interface BuilderContext {
+    /**
+     * Returns the {@code @AutoValue.Builder} interface or abstract class that this object
+     * represents.
+     */
+    TypeElement builderType();
+
+    /**
+     * Returns abstract no-argument methods in the {@code @AutoValue} class that return the builder
+     * type.
+     *
+     * <p>Consider a class like this:
+     * <pre>
+     *   {@code @AutoValue} abstract class Foo {
+     *     abstract String bar();
+     *
+     *     abstract Builder toBuilder();
+     *
+     *     ...
+     *     {@code @AutoValue.Builder}
+     *     abstract static class Builder {...}
+     *   }
+     * </pre>
+     *
+     * <p>Here {@code toBuilderMethods()} will return a set containing the method
+     * {@code Foo.toBuilder()}.
+     */
+    Set<ExecutableElement> toBuilderMethods();
+
+    /**
+     * Returns static no-argument methods in the {@code @AutoValue} class that return the builder
+     * type.
+     *
+     * <p>Consider a class like this:
+     * <pre>
+     *   {@code @AutoValue} abstract class Foo {
+     *     abstract String bar();
+     *
+     *     static Builder builder() {
+     *       return new AutoValue_Foo.Builder()
+     *           .setBar("default bar");
+     *     }
+     *
+     *     {@code @AutoValue.Builder}
+     *     abstract class Builder {
+     *       abstract Builder setBar(String x);
+     *       abstract Foo build();
+     *     }
+     *   }
+     * </pre>
+     *
+     * <p>Here {@code builderMethods()} will return a set containing the method
+     * {@code Foo.builder()}. Generated code should usually call this method in preference to
+     * constructing {@code AutoValue_Foo.Builder()} directly, because this method can establish
+     * default values for properties, as it does here.
+     */
+    Set<ExecutableElement> builderMethods();
+
+    /**
+     * Returns the method {@code build()} in the builder class, if it exists and returns the
+     * {@code @AutoValue} type. This is the method that generated code for
+     * {@code @AutoValue class Foo} should call in order to get an instance of {@code Foo} from its
+     * builder. The returned method is called {@code build()}; if the builder uses some other name
+     * then extensions have no good way to guess how they should build.
+     *
+     * <p>A common convention is for {@code build()} to be a concrete method in the
+     * {@code @AutoValue.Builder} class, which calls an abstract method {@code autoBuild()} that is
+     * implemented in the generated subclass. The {@code build()} method can then do validation,
+     * defaulting, and so on.
+     */
+    Optional<ExecutableElement> buildMethod();
+
+    /**
+     * Returns the abstract build method. If the {@code @AutoValue} class is {@code Foo}, this is an
+     * abstract no-argument method in the builder class that returns {@code Foo}. This might be
+     * called {@code build()}, or, following a common convention, it might be called
+     * {@code autoBuild()} and used in the implementation of a {@code build()} method that is
+     * defined in the builder class.
+     *
+     * <p>Extensions should call the {@code build()} method in preference to this one. But they
+     * should override this one if they want to customize build-time behaviour.
+     */
+    ExecutableElement autoBuildMethod();
+
+    /**
+     * Returns a map from property names to the corresponding setters. A property may have more than
+     * one setter. For example, an {@code ImmutableList<String>} might be set by
+     * {@code setFoo(ImmutableList<String>)} and {@code setFoo(String[])}.
+     */
+    Map<String, Set<ExecutableElement>> setters();
+
+    /**
+     * Returns a map from property names to property builders. For example, if there is a property
+     * {@code foo} defined by {@code abstract ImmutableList<String> foo();} or
+     * {@code abstract ImmutableList<String> getFoo();} in the {@code @AutoValue} class,
+     * then there can potentially be a builder defined by
+     * {@code abstract ImmutableList.Builder<String> fooBuilder();} in the
+     * {@code @AutoValue.Builder} class. This map would then map {@code "foo"} to the
+     * {@link ExecutableElement} representing {@code fooBuilder()}.
+     */
+    Map<String, ExecutableElement> propertyBuilders();
+  }
+
+  /**
+   * Indicates to an annotation processor environment supporting incremental annotation processing
+   * (currently a feature specific to Gradle starting with version 4.8) the incremental type of an
+   * Extension.
+   *
+   * <p>The constants for this enum are ordered by increasing performance (but also constraints).
+   *
+   * @see <a
+   *     href="https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing">Gradle
+   *     documentation of its incremental annotation processing</a>
+   */
+  public enum IncrementalExtensionType {
+    /**
+     * The incrementality of this extension is unknown, or it is neither aggregating nor isolating.
+     */
+    UNKNOWN,
+
+    /**
+     * This extension is <i>aggregating</i>, meaning that it may generate outputs based on several
+     * annotated input classes and it respects the constraints imposed on aggregating processors.
+     * It is unusual for AutoValue extensions to be aggregating.
+     *
+     * @see <a
+     *     href="https://docs.gradle.org/current/userguide/java_plugin.html#aggregating_annotation_processors">Gradle
+     *     definition of aggregating processors</a>
+     */
+    AGGREGATING,
+
+    /**
+     * This extension is <i>isolating</i>, meaning roughly that its output depends on the
+     * {@code @AutoValue} class and its dependencies, but not on other {@code @AutoValue} classes
+     * that might be compiled at the same time. The constraints that an isolating extension must
+     * respect are the same as those that Gradle imposes on an isolating annotation processor.
+     *
+     * @see <a
+     *     href="https://docs.gradle.org/current/userguide/java_plugin.html#isolating_annotation_processors">Gradle
+     *     definition of isolating processors</a>
+     */
+    ISOLATING
+  }
+
+  /**
+   * Determines the incremental type of this Extension.
+   *
+   * <p>The {@link ProcessingEnvironment} can be used, among other things, to obtain the processor
+   * options, using {@link ProcessingEnvironment#getOptions()}.
+   *
+   * <p>The actual incremental type of the AutoValue processor as a whole will be the loosest
+   * incremental types of the Extensions present in the annotation processor path. The default
+   * returned value is {@link IncrementalExtensionType#UNKNOWN}, which will disable incremental
+   * annotation processing entirely.
+   */
+  public IncrementalExtensionType incrementalType(ProcessingEnvironment processingEnvironment) {
+    return IncrementalExtensionType.UNKNOWN;
+  }
+
+  /**
+   * Analogous to {@link Processor#getSupportedOptions()}, here to allow extensions to report their
+   * own.
+   *
+   * <p>By default, if the extension class is annotated with {@link SupportedOptions}, this will
+   * return a set with the strings in the annotation. If the class is not so annotated, an empty set
+   * is returned.
+   *
+   * @return the set of options recognized by this extension or an empty set if none
+   * @see SupportedOptions
+   */
+  public Set<String> getSupportedOptions() {
+    SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
+    if (so == null) {
+      return ImmutableSet.of();
+    } else {
+      return ImmutableSet.copyOf(so.value());
+    }
+  }
+
+  /**
+   * Determines whether this Extension applies to the given context. If an Extension returns {@code
+   * false} for a given class, it will not be called again during the processing of that class. An
+   * Extension can return {@code true} and still choose not to generate any code for the class, by
+   * returning {@code null} from {@link #generateClass}. That is often a more flexible approach.
+   *
+   * @param context The Context of the code generation for this class.
+   */
+  public boolean applicable(Context context) {
+    return false;
+  }
+
+  /**
+   * Denotes that the class generated by this Extension must be the final class in the inheritance
+   * hierarchy. Only one Extension may be the final class, so this should be used sparingly.
+   *
+   * @param context the Context of the code generation for this class.
+   */
+  public boolean mustBeFinal(Context context) {
+    return false;
+  }
+
+  /**
+   * Returns a possibly empty set of property names that this Extension intends to implement. This
+   * will prevent AutoValue from generating an implementation, and remove the supplied properties
+   * from builders, constructors, {@code toString}, {@code equals}, and {@code hashCode}. The
+   * default set returned by this method is empty.
+   *
+   * <p>Each returned string must be one of the property names in {@link Context#properties()}.
+   *
+   * <p>Returning a property name from this method is equivalent to returning the property's getter
+   * method from {@link #consumeMethods}.
+   *
+   * <p>For example, Android's {@code Parcelable} interface includes a <a
+   * href="http://developer.android.com/reference/android/os/Parcelable.html#describeContents()">method</a>
+   * {@code int describeContents()}. Since this is an abstract method with no parameters, by default
+   * AutoValue will consider that it defines an {@code int} property called {@code
+   * describeContents}. If an {@code @AutoValue} class implements {@code Parcelable} and does not
+   * provide an implementation of this method, by default its implementation will include {@code
+   * describeContents} in builders, constructors, and so on. But an {@code AutoValueExtension} that
+   * understands {@code Parcelable} can instead provide a useful implementation and return a set
+   * containing {@code "describeContents"}. Then {@code describeContents} will be omitted from
+   * builders and the rest.
+   *
+   * @param context the Context of the code generation for this class.
+   */
+  public Set<String> consumeProperties(Context context) {
+    return ImmutableSet.of();
+  }
+
+  /**
+   * Returns a possible empty set of abstract methods that this Extension intends to implement. This
+   * will prevent AutoValue from generating an implementation, in cases where it would have, and it
+   * will also avoid warnings about abstract methods that AutoValue doesn't expect. The default set
+   * returned by this method is empty.
+   *
+   * <p>Each returned method must be one of the abstract methods in {@link
+   * Context#abstractMethods()}.
+   *
+   * <p>For example, Android's {@code Parcelable} interface includes a <a
+   * href="http://developer.android.com/reference/android/os/Parcelable.html#writeToParcel(android.os.Parcel,
+   * int)">method</a> {@code void writeToParcel(Parcel, int)}. Normally AutoValue would not know
+   * what to do with that abstract method. But an {@code AutoValueExtension} that understands {@code
+   * Parcelable} can provide a useful implementation and return the {@code writeToParcel} method
+   * here. That will prevent a warning about the method from AutoValue.
+   *
+   * @param context the Context of the code generation for this class.
+   */
+  public Set<ExecutableElement> consumeMethods(Context context) {
+    return ImmutableSet.of();
+  }
+
+  /**
+   * Returns the generated source code of the class named {@code className} to extend {@code
+   * classToExtend}, or {@code null} if this extension does not generate a class in the hierarchy.
+   * If there is a generated class, it should be final if {@code isFinal} is true; otherwise it
+   * should be abstract. The returned string should be a complete Java class definition of the class
+   * {@code className} in the package {@link Context#packageName() context.packageName()}.
+   *
+   * <p>The returned string will typically look like this:
+   *
+   * <pre>{@code
+   * package <package>;
+   * ...
+   * <finalOrAbstract> class <className> extends <classToExtend> {
+   *   // Constructor
+   *   <className>(<constructorParameters>) {
+   *     super(<constructorParameterNames>);
+   *     ...
+   *   }
+   *   ...
+   * }}</pre>
+   *
+   * <p>Here, {@code <package>} is {@link Context#packageName()}; {@code <finalOrAbstract>} is the
+   * keyword {@code final} if {@code isFinal} is true or {@code abstract} otherwise; and {@code
+   * <className>} and {@code <classToExtend>} are the values of this method's parameters of the same
+   * name. The {@code <constructorParameters>} and {@code <constructorParameterNames>} are typically
+   * derived from {@link Context#propertyTypes()}.
+   *
+   * @param context The {@link Context} of the code generation for this class.
+   * @param className The simple name of the resulting class. The returned code will be written to a
+   *     file named accordingly.
+   * @param classToExtend The simple name of the direct parent of the generated class. This could be
+   *     the AutoValue generated class, or a class generated as the result of another Extension.
+   * @param isFinal True if this class is the last class in the chain, meaning it should be marked
+   *     as final. Otherwise it should be marked as abstract.
+   * @return The source code of the generated class, or {@code null} if this extension does not
+   *     generate a class in the hierarchy.
+   */
+  public abstract String generateClass(
+      Context context, String className, String classToExtend, boolean isFinal);
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java b/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
new file mode 100644
index 0000000..ef16135
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016 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.memoized;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates methods in {@link com.google.auto.value.AutoValue @AutoValue} classes for which the
+ * generated subclass will <a href="https://en.wikipedia.org/wiki/Memoization">memoize</a> the
+ * returned value.
+ *
+ * <p>Methods annotated with {@code @Memoized} cannot:
+ *
+ * <ul>
+ *   <li>be {@code abstract} (except for {@link #hashCode()} and {@link #toString()}), {@code
+ *       private}, {@code final}, or {@code static}
+ *   <li>return {@code void}
+ *   <li>have any parameters
+ * </ul>
+ *
+ * <p>If you want to memoize {@link #hashCode()} or {@link #toString()}, you can redeclare them,
+ * keeping them {@code abstract}, and annotate them with {@code @Memoize}.
+ *
+ * <p>If a {@code @Memoized} method is annotated with an annotation whose simple name is {@code
+ * Nullable}, then {@code null} values will also be memoized. Otherwise, if the method returns
+ * {@code null}, the overriding method will throw a {@link NullPointerException}.
+ *
+ * <p>The overriding method uses <a
+ * href="https://errorprone.info/bugpattern/DoubleCheckedLocking">double-checked locking</a> to
+ * ensure that the annotated method is called at most once.
+ *
+ * <h3>Example</h3>
+ *
+ * <pre>
+ *   {@code @AutoValue}
+ *   abstract class Value {
+ *     abstract String stringProperty();
+ *
+ *     {@code @Memoized}
+ *     String derivedProperty() {
+ *       return someCalculationOn(stringProperty());
+ *     }
+ *   }
+ *
+ *   {@code @Generated}
+ *   class AutoValue_Value {
+ *     // …
+ *
+ *     private volatile String derivedProperty;
+ *
+ *     {@code Override}
+ *     String derivedProperty() {
+ *       if (derivedProperty == null) {
+ *         synchronized (this) {
+ *           if (derivedProperty == null) {
+ *             derivedProperty = super.derivedProperty();
+ *           }
+ *         }
+ *       }
+ *       return derivedProperty;
+ *     }
+ *   }</pre>
+ */
+@Documented
+@Retention(CLASS)
+@Target(METHOD)
+public @interface Memoized {}
diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/processor/ClassNames.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/ClassNames.java
new file mode 100644
index 0000000..83794f6
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/ClassNames.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 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.memoized.processor;
+
+/** Names of classes that are referenced in the processor/extension. */
+final class ClassNames {
+  static final String MEMOIZED_NAME = "com.google.auto.value.extension.memoized.Memoized";
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java
new file mode 100644
index 0000000..db2f221
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java
@@ -0,0 +1,599 @@
+/*
+ * Copyright 2016 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.memoized.processor;
+
+import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
+import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec;
+import static com.google.auto.common.MoreElements.getPackage;
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.auto.value.extension.memoized.processor.ClassNames.MEMOIZED_NAME;
+import static com.google.auto.value.extension.memoized.processor.MemoizedValidator.getAnnotationMirror;
+import static com.google.common.base.Predicates.equalTo;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.collect.Sets.union;
+import static com.squareup.javapoet.MethodSpec.constructorBuilder;
+import static com.squareup.javapoet.MethodSpec.methodBuilder;
+import static com.squareup.javapoet.TypeSpec.classBuilder;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.element.Modifier.STATIC;
+import static javax.lang.model.element.Modifier.TRANSIENT;
+import static javax.lang.model.element.Modifier.VOLATILE;
+import static javax.lang.model.type.TypeKind.VOID;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.common.Visibility;
+import com.google.auto.service.AutoService;
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.FormatMethod;
+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.lang.annotation.Inherited;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.QualifiedNameable;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic.Kind;
+
+/**
+ * An extension that implements the {@link com.google.auto.value.extension.memoized.Memoized}
+ * contract.
+ */
+@AutoService(AutoValueExtension.class)
+public final class MemoizeExtension extends AutoValueExtension {
+  private static final ImmutableSet<String> DO_NOT_PULL_DOWN_ANNOTATIONS =
+      ImmutableSet.of(Override.class.getCanonicalName(), MEMOIZED_NAME);
+
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private static final String AUTO_VALUE_PACKAGE_NAME = "com.google.auto.value.";
+  private static final String AUTO_VALUE_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoValue";
+  private static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations";
+
+  private static final ClassName LAZY_INIT =
+      ClassName.get("com.google.errorprone.annotations.concurrent", "LazyInit");
+
+  private static final AnnotationSpec SUPPRESS_WARNINGS =
+      AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "$S", "Immutable").build();
+
+  @Override
+  public IncrementalExtensionType incrementalType(ProcessingEnvironment processingEnvironment) {
+    return IncrementalExtensionType.ISOLATING;
+  }
+
+  @Override
+  public boolean applicable(Context context) {
+    return !memoizedMethods(context).isEmpty();
+  }
+
+  @Override
+  public String generateClass(
+      Context context, String className, String classToExtend, boolean isFinal) {
+    return new Generator(context, className, classToExtend, isFinal).generate();
+  }
+
+  private static ImmutableSet<ExecutableElement> memoizedMethods(Context context) {
+    ImmutableSet.Builder<ExecutableElement> memoizedMethods = ImmutableSet.builder();
+    for (ExecutableElement method : methodsIn(context.autoValueClass().getEnclosedElements())) {
+      if (getAnnotationMirror(method, MEMOIZED_NAME).isPresent()) {
+        memoizedMethods.add(method);
+      }
+    }
+    return memoizedMethods.build();
+  }
+
+  static final class Generator {
+    private final Context context;
+    private final String className;
+    private final String classToExtend;
+    private final boolean isFinal;
+    private final Elements elements;
+    private final Types types;
+    private final SourceVersion sourceVersion;
+    private final Messager messager;
+    private final Optional<AnnotationSpec> lazyInitAnnotation;
+    private boolean hasErrors;
+
+    Generator(Context context, String className, String classToExtend, boolean isFinal) {
+      this.context = context;
+      this.className = className;
+      this.classToExtend = classToExtend;
+      this.isFinal = isFinal;
+      this.elements = context.processingEnvironment().getElementUtils();
+      this.types = context.processingEnvironment().getTypeUtils();
+      this.sourceVersion = context.processingEnvironment().getSourceVersion();
+      this.messager = context.processingEnvironment().getMessager();
+      this.lazyInitAnnotation = getLazyInitAnnotation(elements);
+    }
+
+    String generate() {
+      TypeSpec.Builder generated =
+          classBuilder(className)
+              .superclass(superType())
+              .addAnnotations(copiedClassAnnotations(context.autoValueClass()))
+              .addTypeVariables(typeVariableNames())
+              .addModifiers(isFinal ? FINAL : ABSTRACT)
+              .addMethod(constructor());
+      generatedAnnotationSpec(elements, sourceVersion, MemoizeExtension.class)
+          .ifPresent(generated::addAnnotation);
+      for (ExecutableElement method : memoizedMethods(context)) {
+        MethodOverrider methodOverrider = new MethodOverrider(method);
+        generated.addFields(methodOverrider.fields());
+        generated.addMethod(methodOverrider.method());
+      }
+      if (isHashCodeMemoized() && !isEqualsFinal()) {
+        generated.addMethod(equalsWithHashCodeCheck());
+      }
+      if (hasErrors) {
+        return null;
+      }
+      return JavaFile.builder(context.packageName(), generated.build()).build().toString();
+    }
+
+    private TypeName superType() {
+      ClassName superType = ClassName.get(context.packageName(), classToExtend);
+      ImmutableList<TypeVariableName> typeVariableNames = typeVariableNames();
+
+      return typeVariableNames.isEmpty()
+          ? superType
+          : ParameterizedTypeName.get(superType, typeVariableNames.toArray(new TypeName[] {}));
+    }
+
+    private ImmutableList<TypeVariableName> typeVariableNames() {
+      ImmutableList.Builder<TypeVariableName> typeVariableNamesBuilder = ImmutableList.builder();
+      for (TypeParameterElement typeParameter : context.autoValueClass().getTypeParameters()) {
+        typeVariableNamesBuilder.add(TypeVariableName.get(typeParameter));
+      }
+      return typeVariableNamesBuilder.build();
+    }
+
+    private MethodSpec constructor() {
+      MethodSpec.Builder constructor = constructorBuilder();
+      for (Map.Entry<String, TypeMirror> property : context.propertyTypes().entrySet()) {
+        constructor.addParameter(annotatedType(property.getValue()), property.getKey() + "$");
+      }
+      List<String> namesWithDollars = new ArrayList<String>();
+      for (String property : context.properties().keySet()) {
+        namesWithDollars.add(property + "$");
+      }
+      constructor.addStatement("super($L)", Joiner.on(", ").join(namesWithDollars));
+      return constructor.build();
+    }
+
+    private boolean isHashCodeMemoized() {
+      return memoizedMethods(context).stream()
+          .anyMatch(method -> method.getSimpleName().contentEquals("hashCode"));
+    }
+
+    private boolean isEqualsFinal() {
+      TypeMirror objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType();
+      ExecutableElement equals =
+          MoreElements.getLocalAndInheritedMethods(context.autoValueClass(), types, elements)
+              .stream()
+              .filter(method -> method.getSimpleName().contentEquals("equals"))
+              .filter(method -> method.getParameters().size() == 1)
+              .filter(
+                  method ->
+                      types.isSameType(getOnlyElement(method.getParameters()).asType(), objectType))
+              .findFirst()
+              .get();
+      return equals.getModifiers().contains(FINAL);
+    }
+
+    private MethodSpec equalsWithHashCodeCheck() {
+      return methodBuilder("equals")
+          .addModifiers(PUBLIC)
+          .returns(TypeName.BOOLEAN)
+          .addAnnotation(Override.class)
+          .addParameter(TypeName.OBJECT, "that")
+          .beginControlFlow("if (this == that)")
+          .addStatement("return true")
+          .endControlFlow()
+          .addStatement(
+              "return that instanceof $N "
+                  + "&& this.hashCode() == that.hashCode() "
+                  + "&& super.equals(that)",
+              className)
+          .build();
+    }
+
+    /**
+     * True if the given class name is in the com.google.auto.value package or a subpackage. False
+     * if the class name contains {@code Test}, since many AutoValue tests under
+     * com.google.auto.value define their own annotations.
+     */
+    // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+    private boolean isInAutoValuePackage(String className) {
+      return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test");
+    }
+
+    /**
+     * Returns the fully-qualified name of an annotation-mirror, e.g.
+     * "com.google.auto.value.AutoValue".
+     */
+    // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+    private static String getAnnotationFqName(AnnotationMirror annotation) {
+      return ((QualifiedNameable) annotation.getAnnotationType().asElement())
+          .getQualifiedName()
+          .toString();
+    }
+
+    // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+    private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) {
+      Element annotationElement = annotation.getAnnotationType().asElement();
+      Visibility visibility = Visibility.effectiveVisibilityOfElement(annotationElement);
+      switch (visibility) {
+        case PUBLIC:
+          return true;
+        case PROTECTED:
+          // If the annotation is protected, it must be inside another class, call it C. If our
+          // @AutoValue class is Foo then, for the annotation to be visible, either Foo must be in
+          // the same package as C or Foo must be a subclass of C. If the annotation is visible from
+          // Foo then it is also visible from our generated subclass AutoValue_Foo.
+          // The protected case only applies to method annotations. An annotation on the
+          // AutoValue_Foo class itself can't be protected, even if AutoValue_Foo ultimately
+          // inherits from the class that defines the annotation. The JLS says "Access is permitted
+          // only within the body of a subclass":
+          // https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.6.2.1
+          // AutoValue_Foo is a top-level class, so an annotation on it cannot be in the body of a
+          // subclass of anything.
+          return getPackage(annotationElement).equals(getPackage(from))
+              || types.isSubtype(from.asType(), annotationElement.getEnclosingElement().asType());
+        case DEFAULT:
+          return getPackage(annotationElement).equals(getPackage(from));
+        default:
+          return false;
+      }
+    }
+
+    /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
+    // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+    private ImmutableList<AnnotationMirror> annotationsToCopy(
+        Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
+      ImmutableList.Builder<AnnotationMirror> result = ImmutableList.builder();
+      for (AnnotationMirror annotation : typeOrMethod.getAnnotationMirrors()) {
+        String annotationFqName = getAnnotationFqName(annotation);
+        // To be included, the annotation should not be in com.google.auto.value,
+        // and it should not be in the excludedAnnotations set.
+        if (!isInAutoValuePackage(annotationFqName)
+            && !excludedAnnotations.contains(annotationFqName)
+            && annotationVisibleFrom(annotation, autoValueType)) {
+          result.add(annotation);
+        }
+      }
+
+      return result.build();
+    }
+
+    /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
+    // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+    private ImmutableList<AnnotationSpec> copyAnnotations(
+        Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
+      ImmutableList<AnnotationMirror> annotationsToCopy =
+          annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations);
+      return annotationsToCopy.stream().map(AnnotationSpec::get).collect(toImmutableList());
+    }
+
+    // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+    private static boolean hasAnnotationMirror(Element element, String annotationName) {
+      return getAnnotationMirror(element, annotationName).isPresent();
+    }
+
+    /**
+     * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
+     * {@code TypeMirror} where each type is an annotation type.
+     */
+    // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+    private ImmutableSet<TypeMirror> getExcludedAnnotationTypes(Element element) {
+      Optional<AnnotationMirror> maybeAnnotation =
+          getAnnotationMirror(element, COPY_ANNOTATIONS_NAME);
+      if (!maybeAnnotation.isPresent()) {
+        return ImmutableSet.of();
+      }
+
+      @SuppressWarnings("unchecked")
+      List<AnnotationValue> excludedClasses =
+          (List<AnnotationValue>) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue();
+      return excludedClasses.stream()
+          .map(
+              annotationValue ->
+                  MoreTypes.equivalence().wrap((TypeMirror) annotationValue.getValue()))
+          // TODO(b/122509249): Move TypeMirrorSet to common package instead of doing this.
+          .distinct()
+          .map(Wrapper::get)
+          .collect(toImmutableSet());
+    }
+
+    /**
+     * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
+     * strings that are fully-qualified class names.
+     */
+    // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+    private Set<String> getExcludedAnnotationClassNames(Element element) {
+      return getExcludedAnnotationTypes(element).stream()
+          .map(MoreTypes::asTypeElement)
+          .map(typeElement -> typeElement.getQualifiedName().toString())
+          .collect(toSet());
+    }
+
+    // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+    private static Set<String> getAnnotationsMarkedWithInherited(Element element) {
+      return element.getAnnotationMirrors().stream()
+          .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class))
+          .map(Generator::getAnnotationFqName)
+          .collect(toSet());
+    }
+
+    private ImmutableList<AnnotationSpec> copiedClassAnnotations(TypeElement type) {
+      // Only copy annotations from a class if it has @AutoValue.CopyAnnotations.
+      if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) {
+        Set<String> excludedAnnotations =
+            union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type));
+
+        return copyAnnotations(type, type, excludedAnnotations);
+      } else {
+        return ImmutableList.of();
+      }
+    }
+
+    /**
+     * Determines the required fields and overriding method for a {@link
+     * com.google.auto.value.extension.memoized.Memoized @Memoized} method.
+     */
+    private final class MethodOverrider {
+      private final ExecutableElement method;
+      private final MethodSpec.Builder override;
+      private final FieldSpec cacheField;
+      private final ImmutableList.Builder<FieldSpec> fields = ImmutableList.builder();
+
+      MethodOverrider(ExecutableElement method) {
+        this.method = method;
+        validate();
+        cacheField =
+            buildCacheField(
+                annotatedType(method.getReturnType()), method.getSimpleName().toString());
+        fields.add(cacheField);
+        override =
+            methodBuilder(method.getSimpleName().toString())
+                .addAnnotation(Override.class)
+                .returns(cacheField.type)
+                .addExceptions(
+                    method.getThrownTypes().stream().map(TypeName::get).collect(toList()))
+                .addModifiers(filter(method.getModifiers(), not(equalTo(ABSTRACT))));
+        for (AnnotationMirror annotation : method.getAnnotationMirrors()) {
+          AnnotationSpec annotationSpec = AnnotationSpec.get(annotation);
+          if (pullDownMethodAnnotation(annotation)) {
+            override.addAnnotation(annotationSpec);
+          }
+        }
+
+        InitializationStrategy checkStrategy = strategy();
+        fields.addAll(checkStrategy.additionalFields());
+        override
+            .beginControlFlow("if ($L)", checkStrategy.checkMemoized())
+            .beginControlFlow("synchronized (this)")
+            .beginControlFlow("if ($L)", checkStrategy.checkMemoized())
+            .addStatement("$N = super.$L()", cacheField, method.getSimpleName())
+            .addCode(checkStrategy.setMemoized())
+            .endControlFlow()
+            .endControlFlow()
+            .endControlFlow()
+            .addStatement("return $N", cacheField);
+      }
+
+      /** The fields that should be added to the subclass. */
+      Iterable<FieldSpec> fields() {
+        return fields.build();
+      }
+
+      /** The overriding method that should be added to the subclass. */
+      MethodSpec method() {
+        return override.build();
+      }
+
+      private void validate() {
+        if (method.getReturnType().getKind().equals(VOID)) {
+          printMessage(ERROR, "@Memoized methods cannot be void");
+        }
+        if (!method.getParameters().isEmpty()) {
+          printMessage(ERROR, "@Memoized methods cannot have parameters");
+        }
+        checkIllegalModifier(PRIVATE);
+        checkIllegalModifier(FINAL);
+        checkIllegalModifier(STATIC);
+
+        if (!overridesObjectMethod("hashCode") && !overridesObjectMethod("toString")) {
+          checkIllegalModifier(ABSTRACT);
+        }
+      }
+
+      private void checkIllegalModifier(Modifier modifier) {
+        if (method.getModifiers().contains(modifier)) {
+          printMessage(ERROR, "@Memoized methods cannot be %s", modifier.toString());
+        }
+      }
+
+      @FormatMethod
+      private void printMessage(Kind kind, String format, Object... args) {
+        if (kind.equals(ERROR)) {
+          hasErrors = true;
+        }
+        messager.printMessage(kind, String.format(format, args), method);
+      }
+
+      private boolean overridesObjectMethod(String methodName) {
+        return elements.overrides(method, objectMethod(methodName), context.autoValueClass());
+      }
+
+      private ExecutableElement objectMethod(final String methodName) {
+        TypeElement object = elements.getTypeElement(Object.class.getName());
+        for (ExecutableElement method : methodsIn(object.getEnclosedElements())) {
+          if (method.getSimpleName().contentEquals(methodName)) {
+            return method;
+          }
+        }
+        throw new IllegalArgumentException(
+            String.format("No method in Object named \"%s\"", methodName));
+      }
+
+      private boolean pullDownMethodAnnotation(AnnotationMirror annotation) {
+        return !DO_NOT_PULL_DOWN_ANNOTATIONS.contains(
+            MoreElements.asType(annotation.getAnnotationType().asElement())
+                .getQualifiedName()
+                .toString());
+      }
+
+      /**
+       * Builds a {@link FieldSpec} for use in property caching. Field will be {@code private
+       * transient volatile} and have the given type and name. If the @LazyInit annotation is
+       * available it is added as well.
+       */
+      private FieldSpec buildCacheField(TypeName type, String name) {
+        FieldSpec.Builder builder = FieldSpec.builder(type, name, PRIVATE, TRANSIENT, VOLATILE);
+        if (lazyInitAnnotation.isPresent()) {
+          builder.addAnnotation(lazyInitAnnotation.get());
+          builder.addAnnotation(SUPPRESS_WARNINGS);
+        }
+        return builder.build();
+      }
+
+      InitializationStrategy strategy() {
+        if (method.getReturnType().getKind().isPrimitive()) {
+          return new CheckBooleanField();
+        }
+        if (containsNullable(method.getAnnotationMirrors())
+            || containsNullable(method.getReturnType().getAnnotationMirrors())) {
+          return new CheckBooleanField();
+        }
+        return new NullMeansUninitialized();
+      }
+
+      private abstract class InitializationStrategy {
+
+        abstract Iterable<FieldSpec> additionalFields();
+
+        abstract CodeBlock checkMemoized();
+
+        abstract CodeBlock setMemoized();
+      }
+
+      private final class NullMeansUninitialized extends InitializationStrategy {
+        @Override
+        Iterable<FieldSpec> additionalFields() {
+          return ImmutableList.of();
+        }
+
+        @Override
+        CodeBlock checkMemoized() {
+          return CodeBlock.of("$N == null", cacheField);
+        }
+
+        @Override
+        CodeBlock setMemoized() {
+          return CodeBlock.builder()
+              .beginControlFlow("if ($N == null)", cacheField)
+              .addStatement(
+                  "throw new NullPointerException($S)",
+                  method.getSimpleName() + "() cannot return null")
+              .endControlFlow()
+              .build();
+        }
+      }
+
+      private final class CheckBooleanField extends InitializationStrategy {
+
+        private final FieldSpec field =
+            buildCacheField(TypeName.BOOLEAN, method.getSimpleName() + "$Memoized");
+
+        @Override
+        Iterable<FieldSpec> additionalFields() {
+          return ImmutableList.of(field);
+        }
+
+        @Override
+        CodeBlock checkMemoized() {
+          return CodeBlock.of("!$N", field);
+        }
+
+        @Override
+        CodeBlock setMemoized() {
+          return CodeBlock.builder().addStatement("$N = true", field).build();
+        }
+      }
+    }
+  }
+
+  /** Returns the errorprone {@code @LazyInit} annotation if it is found on the classpath. */
+  private static Optional<AnnotationSpec> getLazyInitAnnotation(Elements elements) {
+    if (elements.getTypeElement(LAZY_INIT.toString()) == null) {
+      return Optional.empty();
+    }
+    return Optional.of(AnnotationSpec.builder(LAZY_INIT).build());
+  }
+
+  /** True if one of the given annotations is {@code @Nullable} in any package. */
+  private static boolean containsNullable(List<? extends AnnotationMirror> annotations) {
+    return annotations.stream()
+        .map(a -> a.getAnnotationType().asElement().getSimpleName())
+        .anyMatch(n -> n.contentEquals("Nullable"));
+  }
+
+  /** Translate a {@link TypeMirror} into a {@link TypeName}, including type annotations. */
+  private static TypeName annotatedType(TypeMirror type) {
+    List<AnnotationSpec> annotations =
+        type.getAnnotationMirrors().stream()
+            .map(AnnotationSpec::get)
+            .collect(toList());
+    return TypeName.get(type).annotated(annotations);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java
new file mode 100644
index 0000000..5a77050
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 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.memoized.processor;
+
+import static com.google.auto.value.extension.memoized.processor.ClassNames.MEMOIZED_NAME;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+
+/**
+ * An annotation {@link Processor} that reports errors for {@link Memoized @Memoized} methods that
+ * are not inside {@code AutoValue}-annotated classes.
+ */
+@AutoService(Processor.class)
+@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
+@SupportedAnnotationTypes(MEMOIZED_NAME)
+public final class MemoizedValidator extends AbstractProcessor {
+  @Override
+  public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    Messager messager = processingEnv.getMessager();
+    TypeElement memoized = processingEnv.getElementUtils().getTypeElement(MEMOIZED_NAME);
+    for (ExecutableElement method : methodsIn(roundEnv.getElementsAnnotatedWith(memoized))) {
+      if (!isAutoValue(method.getEnclosingElement())) {
+        messager.printMessage(
+            ERROR,
+            "@Memoized methods must be declared only in @AutoValue classes",
+            method,
+            getAnnotationMirror(method, MEMOIZED_NAME).get());
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latestSupported();
+  }
+
+  private static boolean isAutoValue(Element element) {
+    return element
+        .getAnnotationMirrors()
+        .stream()
+        .map(annotation -> MoreTypes.asTypeElement(annotation.getAnnotationType()))
+        .anyMatch(type -> type.getQualifiedName().contentEquals("com.google.auto.value.AutoValue"));
+  }
+
+  static Optional<AnnotationMirror> getAnnotationMirror(Element element, String annotationName) {
+    for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
+      TypeElement annotationElement = MoreTypes.asTypeElement(annotation.getAnnotationType());
+      if (annotationElement.getQualifiedName().contentEquals(annotationName)) {
+        return Optional.of(annotation);
+      }
+    }
+    return Optional.empty();
+  }
+}
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..d3265d0
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java
@@ -0,0 +1,322 @@
+/*
+ * 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.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.base.Equivalence;
+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 java.util.function.Function;
+import javax.annotation.processing.ProcessingEnvironment;
+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 IncrementalExtensionType incrementalType(ProcessingEnvironment processingEnvironment) {
+    return IncrementalExtensionType.ISOLATING;
+  }
+
+  @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<Equivalence.Wrapper<TypeMirror>, Serializer> buildSerializersMap() {
+      SerializerFactory factory =
+          SerializerFactoryLoader.getFactory(context.processingEnvironment());
+      return propertyMirrors.stream()
+          .map(PropertyMirror::getType)
+          .map(MoreTypes.equivalence()::wrap)
+          .distinct()
+          .collect(
+              toImmutableMap(
+                  Function.identity(), equivalence -> factory.getSerializer(equivalence.get())));
+    }
+
+    /** 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<Equivalence.Wrapper<TypeMirror>, Serializer> serializersMap;
+
+    ProxyGenerator(
+        TypeName outerClassTypeName,
+        ImmutableList<TypeVariableName> typeVariableNames,
+        ImmutableList<PropertyMirror> propertyMirrors,
+        ImmutableMap<Equivalence.Wrapper<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(MoreTypes.equivalence().wrap(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(MoreTypes.equivalence().wrap(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(MoreTypes.equivalence().wrap(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/extension/serializable/userguide/index.md b/value/src/main/java/com/google/auto/value/extension/serializable/userguide/index.md
new file mode 100644
index 0000000..e766183
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/userguide/index.md
@@ -0,0 +1,103 @@
+# Serializable AutoValue Extension
+
+
+An [`AutoValue`] extension that enables `@AutoValue` classes with
+un-serializable properties to be serializable.
+
+
+## Usage
+
+To use the [`SerializableAutoValueExtension`] with your `AutoValue` class, the
+`AutoValue` class must:
+
+1.  Implement `java.io.Serializable`.
+2.  Be annotated with `@SerializableAutoValue`.
+
+## Example
+
+The following `AutoValue` class is un-serializable:
+
+```java
+@AutoValue
+public abstract class Foo implements Serializable {
+
+  public static Foo create(Optional<String> x) {
+    return new AutoValue_Foo(x);
+  }
+
+  // java.util.Optional is not serializable.
+  abstract Optional<String> x;
+}
+```
+
+This is because `java.util.Optional` is un-serializable. We can make `Foo`
+serializable by using the [`SerializableAutoValueExtension`].
+
+```java
+@SerializableAutoValue  // This annotation activates the extension.
+@AutoValue
+public abstract class Foo implements Serializable {
+  ...
+}
+```
+
+## Details
+
+For the example class `Foo` above, `SerializableAutoValueExtension` will
+generate the following code:
+
+```java
+@Generated("SerializableAutoValueExtension")
+final class AutoValue_Foo extends $AutoValue_Foo {
+
+  // Instead of serializing AutoValue_Foo, we delegate serialization to a
+  // proxy object.
+  Object writeReplace() throws ObjectStreamException {
+    return new Proxy$(this.x);
+  }
+
+  // When serializing, AutoValue_Foo's values are written to Proxy$.
+  // When de-serializing, Proxy$'s values used to create a new instance of
+  // AutoValue_Foo
+  static class Proxy$ implements Serializable {
+
+    private String x;
+
+    // During serialization, un-wrap the Optional field.
+    Proxy$(Optional<String> x) {
+      this.x = x.orElse(null);
+    }
+
+    // During de-serialization, re-create AutoValue_Foo.
+    Object readResolve() throws ObjectStreamException {
+      return new AutoValue_Foo(Optional.ofNullable(x));
+    }
+  }
+}
+```
+
+`SerializableAutoValueExtension` delegates the serialization of `Foo` to a proxy
+object `Proxy$` where `Foo`'s data is unwrapped.
+
+## Supported Types
+
+`SerializableAutoValueExtension` currently supports the following types:
+
+*   `java.util.Optional`
+*   `com.google.common.collect.ImmutableList`
+    *   Enables `ImmutableList<T>`, where `T` is an un-serializable but
+        supported type, to be serializable.
+*   `com.google.common.collect.ImmutableMap`
+    *   Enables `ImmutableMap<K, V>`, where `K` and/or `V` are un-serializable
+        but supported types, to be serializable.
+
+### Extensions
+
+`SerializableAutoValueExtension` can be extended to support additional
+un-serializable types with [SerializerExtensions].
+
+[`AutoValue`]: https://github.com/google/auto/tree/master/value
+[`SerializableAutoValue`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java
+[`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
+[SerializerExtensions]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md
+
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md b/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md
new file mode 100644
index 0000000..edfa3ca
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md
@@ -0,0 +1,246 @@
+# Serializer Extensions
+
+
+[`SerializableAutoValueExtension`] can be extended to support additional
+un-serializable types. Extensions are created by implementing
+[`SerializerExtension`].
+
+[TOC]
+
+## Using SerializerExtensions
+
+To use an extension that's not bundled with [`SerializableAutoValueExtension`],
+include it in your `AutoValue` class's dependencies.
+
+## Writing a SerializerExtension
+
+To add serialization support to a new type, write a class that extends
+[`SerializerExtension`]. Put that class on the `processorpath` along with
+`SerializerExtension`.
+
+See [`AutoService`] for Java extension loading information.
+
+### How SerializerExtension Works
+
+`SerializableAutoValueExtension` iterates through each property of the
+`@AutoValue` class and tries to look up a `SerializerExtension` for the
+property's type. If a `SerializerExtension` is available, a [`Serializer`] is
+returned. The `Serializer` does three things:
+
+1.  From the original property's type, determine what a suitable serializable
+    type is.
+2.  Generate an expression that maps the original property's value to the proxy
+    value.
+3.  Generate an expression that maps a proxy value back to the original
+    property's value.
+
+### Example
+
+We have a `Foo` AutoValue class that we would like to serialize:
+
+```java
+@SerializableAutoValue
+@AutoValue
+public abstract class Foo implements Serializable {
+
+  public static Foo create(Bar bar) {
+    return new AutoValue_Foo(bar);
+  }
+
+  // Bar is unserializable.
+  abstract Bar bar();
+}
+
+// An un-serializable class.
+public final class Bar {
+
+  public int x;
+
+  public Bar(int x) {
+    this.x = x;
+  }
+}
+```
+
+Unfortunately, `Bar` is un-serializable, which makes the entire class
+un-serializable. We can extend `SerializableAutoValue` to support `Bar` by
+implementing a `SerializerExtension` for `Bar`.
+
+```java
+// Use AutoService to make BarSerializerExtension discoverable by java.util.ServiceLoader.
+@AutoService(SerializerExtension.class)
+public final class BarSerializerExtension implements SerializerExtension {
+
+  // Service providers must have a public constructor with no formal parameters.
+  public BarSerializerExtension() {}
+
+  // This method is called for each property in an AutoValue.
+  // When this extension can handle a type (i.e. Bar), we return a Serializer.
+  called on all SerializerExtensions.
+  @Override
+  public Optional<Serializer> getSerializer(
+      TypeMirror type,
+      SerializerFactory factory,
+      ProcessingEnvironment env) {
+    // BarSerializerExtension only handles Bar types.
+    if (!isBar(type)) {
+      return Optional.empty();
+    }
+
+    return Optional.of(new BarSerializer(env));
+  }
+
+  // Our implementation of how Bar should be serialized + de-serialized.
+  private static class BarSerializer implements Serializer {
+
+    private final ProcessingEnvironment env;
+
+    BarSerializer(ProcessingEnvironment env) {
+      this.env = env;
+    }
+
+    // One way to serialize Bar is to just serialize Bar.x.
+    // We can do that by mapping Bar to an int.
+    @Override
+    public TypeMirror proxyFieldType() {
+      return Types.getPrimitiveType(TypeKind.INT);
+    }
+
+    // We need to map Bar to the type we declared in {@link #proxyFieldType}.
+    // Note that {@code expression} is a variable of type Bar.
+    @Override
+    public CodeBlock toProxy(CodeBlock expression) {
+      return CodeBlock.of("$L.x", expression);
+    }
+
+    // We need to map the integer back to a Bar.
+    @Override
+    public CodeBlock fromProxy(CodeBlock expression) {
+      return CodeBlock.of("new $T($L)", Bar.class, expression);
+    }
+  }
+}
+```
+
+`BarSerializerExtension` enables AutoValue classes with `Bar` properties to be
+serialized. For our example class `Foo`, it would help `SerializableAutoValue`
+generate the following code:
+
+```java
+@Generated("SerializableAutoValueExtension")
+final class AutoValue_Foo extends $AutoValue_Foo {
+
+  Object writeReplace() throws ObjectStreamException {
+    return new Proxy$(this.x);
+  }
+
+  static class Proxy$ implements Serializable {
+
+    // The type is generated by {@code BarSerializer#proxyFieldType}.
+    private int bar;
+
+    Proxy$(Bar bar) {
+      // The assignment expression is generated by {@code BarSerializer#toProxy}.
+      this.bar = bar.x;
+    }
+
+    Object readResolve() throws ObjectStreamException {
+      // The reverse mapping expression is generated by {@code BarSerializer#fromProxy}.
+      return new AutoValue_Foo(new Bar(bar));
+    }
+  }
+}
+```
+
+### Type Parameters
+
+Objects with type parameters are also supported by `SerializerExtension`.
+
+For example:
+
+```java
+// A potentially un-serializable class, depending on the actual type of T.
+public final class Baz<T> implements Serializable {
+
+  public T x;
+
+  public Baz(int x) {
+    this.x = x;
+  }
+}
+```
+
+`Baz`'s type argument `T` may not be serializable, but we could create a
+`SerializerExtension` that supports `Baz` by asking for a `SerializerExtension`
+for `T`.
+
+```java
+@AutoService(SerializerExtension.class)
+public final class BazSerializerExtension implements SerializerExtension {
+
+  public BazSerializerExtension() {}
+
+  @Override
+  public Optional<Serializer> getSerializer(
+        TypeMirror type,
+        SerializerFactory factory,
+        ProcessingEnvironment env) {
+    if (!isBaz(type)) {
+      return Optional.empty();
+    }
+
+    // Extract the T of Baz<T>.
+    TypeMirror containedType = getContainedType(type);
+
+    // Look up a serializer for the contained type T.
+    Serializer containedTypeSerializer = factory.getSerializer(containedType);
+
+    // If the serializer for the contained type T is an identity function, it
+    // means the contained type is either serializable or unsupported.
+    // Either way, nothing needs to be done. Baz can be serialized as is.
+    if (containedTypeSerializer.isIdentity()) {
+      return Optional.empty();
+    }
+
+    // Make Baz serializable by using the contained type T serializer.
+    return Optional.of(new BazSerializer(containedTypeSerializer));
+  }
+
+  private static class BazSerializer implements Serializer {
+
+    private Serializer serializer;
+
+    BazSerializer(Serializer serialize) {
+      this.serializer = serializer;
+    }
+
+    @Override
+    public TypeMirror proxyFieldType() {
+      // Since the contained type "T" is Baz's only field, we map Baz to "T"'s
+      // proxy type.
+      return serializer.proxyFieldType();
+    }
+
+    @Override
+    public CodeBlock toProxy(CodeBlock expression) {
+      return serializer.toProxy(expression);
+    }
+
+    @Override
+    public CodeBlock fromProxy(CodeBlock expression) {
+      return serializer.fromProxy(expression);
+    }
+  }
+}
+```
+
+This implementation uses `SerializerFactory` to find a `Serializer` for `T`. If
+a `Serializer` is available, we use it to map `Baz` to a serializable type. If
+no `Serializer` is available, we can do nothing and let `Baz` be serialized
+as-is.
+
+[AutoService]: https://github.com/google/auto/tree/master/service
+[`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
+[`SerializerExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java
+[`Serializer`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java
+
diff --git a/value/src/main/java/com/google/auto/value/processor/AbortProcessingException.java b/value/src/main/java/com/google/auto/value/processor/AbortProcessingException.java
new file mode 100644
index 0000000..c234f22
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AbortProcessingException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 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.processor;
+
+/**
+ * Exception thrown when annotation processing should be aborted for the current class. Processing
+ * can continue on other classes. Throwing this exception does not cause a compiler error, so either
+ * one should explicitly be emitted or it should be clear that the compiler will be producing its
+ * own error for other reasons.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@SuppressWarnings("serial")
+class AbortProcessingException extends RuntimeException {}
diff --git a/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java
new file mode 100644
index 0000000..ed986ab
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2014 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.processor;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+import javax.tools.Diagnostic;
+
+/**
+ * Handling of default values for annotation members.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+final class AnnotationOutput {
+  private AnnotationOutput() {} // There are no instances of this class.
+
+  /**
+   * Visitor that produces a string representation of an annotation value, suitable for inclusion in
+   * a Java source file as an annotation member or as the initializer of a variable of the
+   * appropriate type. The syntax for the two is the same except for annotation members that are
+   * themselves annotations. Within an annotation, an annotation member can be written as
+   * {@code @NestedAnnotation(...)}, while in an initializer it must be written as an object, for
+   * example the construction of an {@code @AutoAnnotation} class. That's why we have this abstract
+   * class and two concrete subclasses.
+   */
+  private abstract static class SourceFormVisitor
+      extends SimpleAnnotationValueVisitor8<Void, StringBuilder> {
+    @Override
+    protected Void defaultAction(Object value, StringBuilder sb) {
+      sb.append(value);
+      return null;
+    }
+
+    @Override
+    public Void visitArray(List<? extends AnnotationValue> values, StringBuilder sb) {
+      sb.append('{');
+      String sep = "";
+      for (AnnotationValue value : values) {
+        sb.append(sep);
+        visit(value, sb);
+        sep = ", ";
+      }
+      sb.append('}');
+      return null;
+    }
+
+    @Override
+    public Void visitChar(char c, StringBuilder sb) {
+      appendQuoted(sb, c);
+      return null;
+    }
+
+    @Override
+    public Void visitLong(long i, StringBuilder sb) {
+      sb.append(i).append('L');
+      return null;
+    }
+
+    @Override
+    public Void visitDouble(double d, StringBuilder sb) {
+      if (Double.isNaN(d)) {
+        sb.append("Double.NaN");
+      } else if (d == Double.POSITIVE_INFINITY) {
+        sb.append("Double.POSITIVE_INFINITY");
+      } else if (d == Double.NEGATIVE_INFINITY) {
+        sb.append("Double.NEGATIVE_INFINITY");
+      } else {
+        sb.append(d);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitFloat(float f, StringBuilder sb) {
+      if (Float.isNaN(f)) {
+        sb.append("Float.NaN");
+      } else if (f == Float.POSITIVE_INFINITY) {
+        sb.append("Float.POSITIVE_INFINITY");
+      } else if (f == Float.NEGATIVE_INFINITY) {
+        sb.append("Float.NEGATIVE_INFINITY");
+      } else {
+        sb.append(f).append('F');
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitEnumConstant(VariableElement c, StringBuilder sb) {
+      sb.append(TypeEncoder.encode(c.asType())).append('.').append(c.getSimpleName());
+      return null;
+    }
+
+    @Override
+    public Void visitString(String s, StringBuilder sb) {
+      appendQuoted(sb, s);
+      return null;
+    }
+
+    @Override
+    public Void visitType(TypeMirror classConstant, StringBuilder sb) {
+      sb.append(TypeEncoder.encode(classConstant)).append(".class");
+      return null;
+    }
+  }
+
+  private static class InitializerSourceFormVisitor extends SourceFormVisitor {
+    private final ProcessingEnvironment processingEnv;
+    private final String memberName;
+    private final Element context;
+
+    InitializerSourceFormVisitor(
+        ProcessingEnvironment processingEnv, String memberName, Element context) {
+      this.processingEnv = processingEnv;
+      this.memberName = memberName;
+      this.context = context;
+    }
+
+    @Override
+    public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) {
+      processingEnv
+          .getMessager()
+          .printMessage(
+              Diagnostic.Kind.ERROR,
+              "@AutoAnnotation cannot yet supply a default value for annotation-valued member '"
+                  + memberName
+                  + "'",
+              context);
+      sb.append("null");
+      return null;
+    }
+  }
+
+  private static class AnnotationSourceFormVisitor extends SourceFormVisitor {
+    @Override
+    public Void visitArray(List<? extends AnnotationValue> values, StringBuilder sb) {
+      if (values.size() == 1) {
+        // We can shorten @Foo(a = {23}) to @Foo(a = 23). For the specific case where `a` is
+        // actually `value`, we'll already have shortened that in visitAnnotation, so effectively we
+        // go from @Foo(value = {23}) to @Foo({23}) to @Foo(23).
+        visit(values.get(0), sb);
+        return null;
+      }
+      return super.visitArray(values, sb);
+    }
+
+    @Override
+    public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) {
+      sb.append('@').append(TypeEncoder.encode(a.getAnnotationType()));
+      ImmutableMap<ExecutableElement, AnnotationValue> map =
+          ImmutableMap.copyOf(a.getElementValues());
+      if (!map.isEmpty()) {
+        sb.append('(');
+        Optional<AnnotationValue> shortForm = shortForm(map);
+        if (shortForm.isPresent()) {
+          this.visit(shortForm.get(), sb);
+        } else {
+          String sep = "";
+          for (Map.Entry<ExecutableElement, AnnotationValue> entry : map.entrySet()) {
+            sb.append(sep).append(entry.getKey().getSimpleName()).append(" = ");
+            sep = ", ";
+            this.visit(entry.getValue(), sb);
+          }
+        }
+        sb.append(')');
+      }
+      return null;
+    }
+
+    // We can shorten @Annot(value = 23) to @Annot(23).
+    private static Optional<AnnotationValue> shortForm(
+        Map<ExecutableElement, AnnotationValue> values) {
+      if (values.size() == 1
+          && Iterables.getOnlyElement(values.keySet()).getSimpleName().contentEquals("value")) {
+        return Optional.of(Iterables.getOnlyElement(values.values()));
+      }
+      return Optional.empty();
+    }
+  }
+
+  /**
+   * Returns a string representation of the given annotation value, suitable for inclusion in a Java
+   * source file as the initializer of a variable of the appropriate type.
+   */
+  static String sourceFormForInitializer(
+      AnnotationValue annotationValue,
+      ProcessingEnvironment processingEnv,
+      String memberName,
+      Element context) {
+    SourceFormVisitor visitor =
+        new InitializerSourceFormVisitor(processingEnv, memberName, context);
+    StringBuilder sb = new StringBuilder();
+    visitor.visit(annotationValue, sb);
+    return sb.toString();
+  }
+
+  /**
+   * Returns a string representation of the given annotation mirror, suitable for inclusion in a
+   * Java source file to reproduce the annotation in source form.
+   */
+  static String sourceFormForAnnotation(AnnotationMirror annotationMirror) {
+    StringBuilder sb = new StringBuilder();
+    new AnnotationSourceFormVisitor().visitAnnotation(annotationMirror, sb);
+    return sb.toString();
+  }
+
+  private static StringBuilder appendQuoted(StringBuilder sb, String s) {
+    sb.append('"');
+    for (int i = 0; i < s.length(); i++) {
+      appendEscaped(sb, s.charAt(i));
+    }
+    return sb.append('"');
+  }
+
+  private static StringBuilder appendQuoted(StringBuilder sb, char c) {
+    sb.append('\'');
+    appendEscaped(sb, c);
+    return sb.append('\'');
+  }
+
+  private static void appendEscaped(StringBuilder sb, char c) {
+    switch (c) {
+      case '\\':
+      case '"':
+      case '\'':
+        sb.append('\\').append(c);
+        break;
+      case '\n':
+        sb.append("\\n");
+        break;
+      case '\r':
+        sb.append("\\r");
+        break;
+      case '\t':
+        sb.append("\\t");
+        break;
+      default:
+        if (c < 0x20) {
+          sb.append(String.format("\\%03o", (int) c));
+        } else if (c < 0x7f || Character.isLetter(c)) {
+          sb.append(c);
+        } else {
+          sb.append(String.format("\\u%04x", (int) c));
+        }
+        break;
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
new file mode 100644
index 0000000..d3cd9bd
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation;
+import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.SuperficialValidation;
+import com.google.auto.service.AutoService;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.primitives.Primitives;
+import com.google.errorprone.annotations.FormatMethod;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+
+/**
+ * Javac annotation processor (compiler plugin) to generate annotation implementations. User code
+ * never references this class.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@AutoService(Processor.class)
+@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
+@SupportedAnnotationTypes(AUTO_ANNOTATION_NAME)
+public class AutoAnnotationProcessor extends AbstractProcessor {
+  public AutoAnnotationProcessor() {}
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latestSupported();
+  }
+
+  /**
+   * Issue a compilation error. This method does not throw an exception, since we want to continue
+   * processing and perhaps report other errors.
+   */
+  @FormatMethod
+  private void reportError(Element e, String msg, Object... msgParams) {
+    String formattedMessage = String.format(msg, msgParams);
+    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, formattedMessage, e);
+  }
+
+  /**
+   * Issue a compilation error and return an exception that, when thrown, will cause the processing
+   * of this class to be abandoned. This does not prevent the processing of other classes.
+   */
+  @FormatMethod
+  private AbortProcessingException abortWithError(Element e, String msg, Object... msgParams) {
+    reportError(e, msg, msgParams);
+    return new AbortProcessingException();
+  }
+
+  private Elements elementUtils;
+  private Types typeUtils;
+
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    elementUtils = processingEnv.getElementUtils();
+    typeUtils = processingEnv.getTypeUtils();
+    boolean claimed =
+        (annotations.size() == 1
+            && annotations
+                .iterator()
+                .next()
+                .getQualifiedName()
+                .contentEquals(AUTO_ANNOTATION_NAME));
+    if (claimed) {
+      process(roundEnv);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  private void process(RoundEnvironment roundEnv) {
+    TypeElement autoAnnotation = elementUtils.getTypeElement(AUTO_ANNOTATION_NAME);
+    Collection<? extends Element> annotatedElements =
+        roundEnv.getElementsAnnotatedWith(autoAnnotation);
+    List<ExecutableElement> methods = ElementFilter.methodsIn(annotatedElements);
+    if (!SuperficialValidation.validateElements(methods) || methodsAreOverloaded(methods)) {
+      return;
+    }
+    for (ExecutableElement method : methods) {
+      try {
+        processMethod(method);
+      } catch (AbortProcessingException e) {
+        // We abandoned this type, but continue with the next.
+      } catch (RuntimeException e) {
+        String trace = Throwables.getStackTraceAsString(e);
+        reportError(method, "@AutoAnnotation processor threw an exception: %s", trace);
+        throw e;
+      }
+    }
+  }
+
+  private void processMethod(ExecutableElement method) {
+    if (!method.getModifiers().contains(Modifier.STATIC)) {
+      throw abortWithError(method, "@AutoAnnotation method must be static");
+    }
+
+    TypeElement annotationElement = getAnnotationReturnType(method);
+
+    Set<Class<?>> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method);
+
+    ImmutableMap<String, ExecutableElement> memberMethods = getMemberMethods(annotationElement);
+    TypeElement methodClass = (TypeElement) method.getEnclosingElement();
+    String pkg = TypeSimplifier.packageNameOf(methodClass);
+
+    ImmutableMap<String, AnnotationValue> defaultValues = getDefaultValues(annotationElement);
+    ImmutableMap<String, Member> members = getMembers(method, memberMethods);
+    ImmutableMap<String, Parameter> parameters = getParameters(annotationElement, method, members);
+    validateParameters(annotationElement, method, members, parameters, defaultValues);
+
+    String generatedClassName = generatedClassName(method);
+
+    AutoAnnotationTemplateVars vars = new AutoAnnotationTemplateVars();
+    vars.annotationFullName = annotationElement.toString();
+    vars.annotationName = TypeEncoder.encode(annotationElement.asType());
+    vars.className = generatedClassName;
+    vars.generated = getGeneratedTypeName();
+    vars.members = members;
+    vars.params = parameters;
+    vars.pkg = pkg;
+    vars.wrapperTypesUsedInCollections = wrapperTypesUsedInCollections;
+    vars.gwtCompatible = isGwtCompatible(annotationElement);
+    ImmutableMap<String, Integer> invariableHashes = invariableHashes(members, parameters.keySet());
+    vars.invariableHashSum = 0;
+    for (int h : invariableHashes.values()) {
+      vars.invariableHashSum += h;
+    }
+    vars.invariableHashes = invariableHashes.keySet();
+    String text = vars.toText();
+    text = TypeEncoder.decode(text, processingEnv, pkg, annotationElement.asType());
+    text = Reformatter.fixup(text);
+    String fullName = fullyQualifiedName(pkg, generatedClassName);
+    writeSourceFile(fullName, text, methodClass);
+  }
+
+  private String getGeneratedTypeName() {
+    return generatedAnnotation(elementUtils, processingEnv.getSourceVersion())
+        .map(generatedAnnotation -> TypeEncoder.encode(generatedAnnotation.asType()))
+        .orElse("");
+  }
+
+  /**
+   * Returns the hashCode of the given AnnotationValue, if that hashCode is guaranteed to be always
+   * the same. The hashCode of a String or primitive type never changes. The hashCode of a Class or
+   * an enum constant does potentially change in different runs of the same program. The hashCode of
+   * an array doesn't change if the hashCodes of its elements don't. Although we could have a
+   * similar rule for nested annotation values, we currently don't.
+   */
+  private static Optional<Integer> invariableHash(AnnotationValue annotationValue) {
+    Object value = annotationValue.getValue();
+    if (value instanceof String || Primitives.isWrapperType(value.getClass())) {
+      return Optional.of(value.hashCode());
+    } else if (value instanceof List<?>) {
+      @SuppressWarnings("unchecked") // by specification
+      List<? extends AnnotationValue> list = (List<? extends AnnotationValue>) value;
+      return invariableHash(list);
+    } else {
+      return Optional.empty();
+    }
+  }
+
+  private static Optional<Integer> invariableHash(
+      List<? extends AnnotationValue> annotationValues) {
+    int h = 1;
+    for (AnnotationValue annotationValue : annotationValues) {
+      Optional<Integer> maybeHash = invariableHash(annotationValue);
+      if (!maybeHash.isPresent()) {
+        return Optional.empty();
+      }
+      h = h * 31 + maybeHash.get();
+    }
+    return Optional.of(h);
+  }
+
+  /**
+   * Returns a map from the names of members with invariable hashCodes to the values of those
+   * hashCodes.
+   */
+  private static ImmutableMap<String, Integer> invariableHashes(
+      ImmutableMap<String, Member> members, ImmutableSet<String> parameters) {
+    ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
+    for (String element : members.keySet()) {
+      if (!parameters.contains(element)) {
+        Member member = members.get(element);
+        AnnotationValue annotationValue = member.method.getDefaultValue();
+        Optional<Integer> invariableHash = invariableHash(annotationValue);
+        if (invariableHash.isPresent()) {
+          builder.put(element, (element.hashCode() * 127) ^ invariableHash.get());
+        }
+      }
+    }
+    return builder.build();
+  }
+
+  private boolean methodsAreOverloaded(List<ExecutableElement> methods) {
+    boolean overloaded = false;
+    Set<String> classNames = new HashSet<String>();
+    for (ExecutableElement method : methods) {
+      String qualifiedClassName =
+          fullyQualifiedName(
+              MoreElements.getPackage(method).getQualifiedName().toString(),
+              generatedClassName(method));
+      if (!classNames.add(qualifiedClassName)) {
+        overloaded = true;
+        reportError(method, "@AutoAnnotation methods cannot be overloaded");
+      }
+    }
+    return overloaded;
+  }
+
+  private String generatedClassName(ExecutableElement method) {
+    TypeElement type = (TypeElement) method.getEnclosingElement();
+    String name = type.getSimpleName().toString();
+    while (type.getEnclosingElement() instanceof TypeElement) {
+      type = (TypeElement) type.getEnclosingElement();
+      name = type.getSimpleName() + "_" + name;
+    }
+    return "AutoAnnotation_" + name + "_" + method.getSimpleName();
+  }
+
+  private TypeElement getAnnotationReturnType(ExecutableElement method) {
+    TypeMirror returnTypeMirror = method.getReturnType();
+    if (returnTypeMirror.getKind() == TypeKind.DECLARED) {
+      Element returnTypeElement = typeUtils.asElement(method.getReturnType());
+      if (returnTypeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
+        return (TypeElement) returnTypeElement;
+      }
+    }
+    throw abortWithError(
+        method,
+        "Return type of @AutoAnnotation method must be an annotation type, not %s",
+        returnTypeMirror);
+  }
+
+  private ImmutableMap<String, ExecutableElement> getMemberMethods(TypeElement annotationElement) {
+    ImmutableMap.Builder<String, ExecutableElement> members = ImmutableMap.builder();
+    for (ExecutableElement member :
+        ElementFilter.methodsIn(annotationElement.getEnclosedElements())) {
+      String name = member.getSimpleName().toString();
+      members.put(name, member);
+    }
+    return members.build();
+  }
+
+  private ImmutableMap<String, Member> getMembers(
+      Element context, ImmutableMap<String, ExecutableElement> memberMethods) {
+    ImmutableMap.Builder<String, Member> members = ImmutableMap.builder();
+    for (Map.Entry<String, ExecutableElement> entry : memberMethods.entrySet()) {
+      ExecutableElement memberMethod = entry.getValue();
+      String name = memberMethod.getSimpleName().toString();
+      members.put(name, new Member(processingEnv, context, memberMethod));
+    }
+    return members.build();
+  }
+
+  private ImmutableMap<String, AnnotationValue> getDefaultValues(TypeElement annotationElement) {
+    ImmutableMap.Builder<String, AnnotationValue> defaultValues = ImmutableMap.builder();
+    for (ExecutableElement member :
+        ElementFilter.methodsIn(annotationElement.getEnclosedElements())) {
+      String name = member.getSimpleName().toString();
+      AnnotationValue defaultValue = member.getDefaultValue();
+      if (defaultValue != null) {
+        defaultValues.put(name, defaultValue);
+      }
+    }
+    return defaultValues.build();
+  }
+
+  private ImmutableMap<String, Parameter> getParameters(
+      TypeElement annotationElement, ExecutableElement method, Map<String, Member> members) {
+    ImmutableMap.Builder<String, Parameter> parameters = ImmutableMap.builder();
+    boolean error = false;
+    for (VariableElement parameter : method.getParameters()) {
+      String name = parameter.getSimpleName().toString();
+      Member member = members.get(name);
+      if (member == null) {
+        reportError(
+            parameter,
+            "@AutoAnnotation method parameter '%s' must have the same name as a member of %s",
+            name,
+            annotationElement);
+        error = true;
+      } else {
+        TypeMirror parameterType = parameter.asType();
+        TypeMirror memberType = member.getTypeMirror();
+        if (compatibleTypes(parameterType, memberType)) {
+          parameters.put(name, new Parameter(parameterType));
+        } else {
+          reportError(
+              parameter,
+              "@AutoAnnotation method parameter '%s' has type %s but %s.%s has type %s",
+              name,
+              parameterType,
+              annotationElement,
+              name,
+              memberType);
+          error = true;
+        }
+      }
+    }
+    if (error) {
+      throw new AbortProcessingException();
+    }
+    return parameters.build();
+  }
+
+  private void validateParameters(
+      TypeElement annotationElement,
+      ExecutableElement method,
+      ImmutableMap<String, Member> members,
+      ImmutableMap<String, Parameter> parameters,
+      ImmutableMap<String, AnnotationValue> defaultValues) {
+    boolean error = false;
+    for (String memberName : members.keySet()) {
+      if (!parameters.containsKey(memberName) && !defaultValues.containsKey(memberName)) {
+        reportError(
+            method,
+            "@AutoAnnotation method needs a parameter with name '%s' and type %s"
+                + " corresponding to %s.%s, which has no default value",
+            memberName,
+            members.get(memberName).getType(),
+            annotationElement,
+            memberName);
+        error = true;
+      }
+    }
+    if (error) {
+      throw new AbortProcessingException();
+    }
+  }
+
+  /**
+   * Returns true if {@code parameterType} can be used to provide the value of an annotation member
+   * of type {@code memberType}. They must either be the same type, or the member type must be an
+   * array and the parameter type must be a collection of a compatible type.
+   */
+  private boolean compatibleTypes(TypeMirror parameterType, TypeMirror memberType) {
+    if (typeUtils.isAssignable(parameterType, memberType)) {
+      // parameterType assignable to memberType, which in the restricted world of annotations
+      // means they are the same type, or maybe memberType is an annotation type and parameterType
+      // is a subtype of that annotation interface (why would you do that?).
+      return true;
+    }
+    // They're not the same, but we could still consider them compatible if for example
+    // parameterType is List<Integer> and memberType is int[]. We accept any type that is assignable
+    // to Collection<Integer> (in this example).
+    if (memberType.getKind() != TypeKind.ARRAY) {
+      return false;
+    }
+    TypeMirror arrayElementType = ((ArrayType) memberType).getComponentType();
+    TypeMirror wrappedArrayElementType =
+        arrayElementType.getKind().isPrimitive()
+            ? typeUtils.boxedClass((PrimitiveType) arrayElementType).asType()
+            : arrayElementType;
+    TypeElement javaUtilCollection =
+        elementUtils.getTypeElement(Collection.class.getCanonicalName());
+    DeclaredType collectionOfElement =
+        typeUtils.getDeclaredType(javaUtilCollection, wrappedArrayElementType);
+    return typeUtils.isAssignable(parameterType, collectionOfElement);
+  }
+
+  /**
+   * Returns the wrapper types ({@code Integer.class} etc) that are used in collection parameters
+   * like {@code List<Integer>}. This is needed because we will emit a helper method for each such
+   * type, for example to convert {@code Collection<Integer>} into {@code int[]}.
+   */
+  private Set<Class<?>> wrapperTypesUsedInCollections(ExecutableElement method) {
+    TypeElement javaUtilCollection = elementUtils.getTypeElement(Collection.class.getName());
+    ImmutableSet.Builder<Class<?>> usedInCollections = ImmutableSet.builder();
+    for (Class<?> wrapper : Primitives.allWrapperTypes()) {
+      DeclaredType collectionOfWrapper =
+          typeUtils.getDeclaredType(javaUtilCollection, getTypeMirror(wrapper));
+      for (VariableElement parameter : method.getParameters()) {
+        if (typeUtils.isAssignable(parameter.asType(), collectionOfWrapper)) {
+          usedInCollections.add(wrapper);
+          break;
+        }
+      }
+    }
+    return usedInCollections.build();
+  }
+
+  private TypeMirror getTypeMirror(Class<?> c) {
+    return elementUtils.getTypeElement(c.getName()).asType();
+  }
+
+  private static boolean isGwtCompatible(TypeElement annotationElement) {
+    return annotationElement
+        .getAnnotationMirrors()
+        .stream()
+        .map(mirror -> mirror.getAnnotationType().asElement())
+        .anyMatch(element -> element.getSimpleName().contentEquals("GwtCompatible"));
+  }
+
+  private static String fullyQualifiedName(String pkg, String cls) {
+    return pkg.isEmpty() ? cls : pkg + "." + cls;
+  }
+
+  private void writeSourceFile(String className, String text, TypeElement originatingType) {
+    try {
+      JavaFileObject sourceFile =
+          processingEnv.getFiler().createSourceFile(className, originatingType);
+      try (Writer writer = sourceFile.openWriter()) {
+        writer.write(text);
+      }
+    } catch (IOException e) {
+      // This should really be an error, but we make it a warning in the hope of resisting Eclipse
+      // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599. If that bug manifests, we may get
+      // invoked more than once for the same file, so ignoring the ability to overwrite it is the
+      // right thing to do. If we are unable to write for some other reason, we should get a compile
+      // error later because user code will have a reference to the code we were supposed to
+      // generate (new AutoValue_Foo() or whatever) and that reference will be undefined.
+      processingEnv
+          .getMessager()
+          .printMessage(
+              Diagnostic.Kind.WARNING, "Could not write generated class " + className + ": " + e);
+    }
+  }
+
+  public static class Member {
+    private final ProcessingEnvironment processingEnv;
+    private final Element context;
+    private final ExecutableElement method;
+
+    Member(ProcessingEnvironment processingEnv, Element context, ExecutableElement method) {
+      this.processingEnv = processingEnv;
+      this.context = context;
+      this.method = method;
+    }
+
+    @Override
+    public String toString() {
+      return method.getSimpleName().toString();
+    }
+
+    public String getType() {
+      return TypeEncoder.encode(getTypeMirror());
+    }
+
+    public String getComponentType() {
+      Preconditions.checkState(getTypeMirror().getKind() == TypeKind.ARRAY);
+      ArrayType arrayType = (ArrayType) getTypeMirror();
+      return TypeEncoder.encode(arrayType.getComponentType());
+    }
+
+    public TypeMirror getTypeMirror() {
+      return method.getReturnType();
+    }
+
+    public TypeKind getKind() {
+      return getTypeMirror().getKind();
+    }
+
+    // Used as part of the hashCode() computation.
+    // See https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Annotation.html#hashCode--
+    public int getNameHash() {
+      return 127 * toString().hashCode();
+    }
+
+    public boolean isArrayOfClassWithBounds() {
+      if (getTypeMirror().getKind() != TypeKind.ARRAY) {
+        return false;
+      }
+      TypeMirror componentType = ((ArrayType) getTypeMirror()).getComponentType();
+      if (componentType.getKind() != TypeKind.DECLARED) {
+        return false;
+      }
+      DeclaredType declared = (DeclaredType) componentType;
+      if (!((TypeElement) processingEnv.getTypeUtils().asElement(componentType))
+          .getQualifiedName()
+          .contentEquals("java.lang.Class")) {
+        return false;
+      }
+      if (declared.getTypeArguments().size() != 1) {
+        return false;
+      }
+      TypeMirror parameter = declared.getTypeArguments().get(0);
+      if (parameter.getKind() != TypeKind.WILDCARD) {
+        return true; // for Class<Foo>
+      }
+      WildcardType wildcard = (WildcardType) parameter;
+      // In theory, we should check if getExtendsBound() != Object, since '?' is equivalent to
+      // '? extends Object', but, experimentally, neither javac or ecj will sets getExtendsBound()
+      // to 'Object', so there isn't a point in checking.
+      return wildcard.getSuperBound() != null || wildcard.getExtendsBound() != null;
+    }
+
+    public String getDefaultValue() {
+      AnnotationValue defaultValue = method.getDefaultValue();
+      if (defaultValue == null) {
+        return null;
+      } else {
+        return AnnotationOutput.sourceFormForInitializer(
+            defaultValue, processingEnv, method.getSimpleName().toString(), context);
+      }
+    }
+  }
+
+  public static class Parameter {
+    private final String typeName;
+    private final TypeKind kind;
+
+    Parameter(TypeMirror type) {
+      this.typeName = TypeEncoder.encode(type);
+      this.kind = type.getKind();
+    }
+
+    public String getType() {
+      return typeName;
+    }
+
+    public TypeKind getKind() {
+      return kind;
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java
new file mode 100644
index 0000000..4db59c7
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014 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.processor;
+
+import com.google.escapevelocity.Template;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The variables to substitute into the autoannotation.vm template.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@SuppressWarnings("unused") // the fields in this class are only read via reflection
+class AutoAnnotationTemplateVars extends TemplateVars {
+  /** The members of the annotation being implemented. */
+  Map<String, AutoAnnotationProcessor.Member> members;
+
+  /**
+   * The parameters in the {@code @AutoAnnotation} method, which are also the constructor parameters
+   * in the generated class.
+   */
+  Map<String, AutoAnnotationProcessor.Parameter> params;
+
+  /** The encoded form of the {@code Generated} class, or empty if it is not available. */
+  String generated;
+
+  /**
+   * The package of the class containing the {@code @AutoAnnotation} annotation, which is also the
+   * package where the annotation implementation will be generated.
+   */
+  String pkg;
+
+  /** The simple name of the generated class, like {@code AutoAnnotation_Foo_bar}. */
+  String className;
+
+  /** The name of the annotation interface as it can be referenced in the generated code. */
+  String annotationName;
+
+  /** The fully-qualified name of the annotation interface. */
+  String annotationFullName;
+
+  /**
+   * The wrapper types (like {@code Integer.class}) that are referenced in collection parameters
+   * (like {@code List<Integer>}).
+   */
+  Set<Class<?>> wrapperTypesUsedInCollections;
+
+  /**
+   * True if this annotation is marked {@code @GwtCompatible}. That means that we can't use {@code
+   * clone()} to make a copy of an array.
+   */
+  Boolean gwtCompatible;
+
+  /**
+   * The names of members that are defaulted (not mentioned) in this {@code @AutoAnnotation}, and
+   * whose hash codes are invariable.
+   */
+  Set<String> invariableHashes;
+
+  /** The sum of the hash code contributions from the members in {@link #invariableHashes}. */
+  Integer invariableHashSum;
+
+  private static final Template TEMPLATE = parsedTemplateForResource("autoannotation.vm");
+
+  @Override
+  Template parsedTemplate() {
+    return TEMPLATE;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
new file mode 100644
index 0000000..d05ed43
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2018 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.processor;
+
+import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
+import static com.google.auto.value.processor.ClassNames.AUTO_ONE_OF_NAME;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+
+import com.google.auto.common.AnnotationMirrors;
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+
+/**
+ * Javac annotation processor (compiler plugin) for {@linkplain com.google.auto.value.AutoOneOf
+ * one-of} types; user code never references this class.
+ *
+ * @author Éamonn McManus
+ * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
+ */
+@AutoService(Processor.class)
+@SupportedAnnotationTypes(AUTO_ONE_OF_NAME)
+@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
+public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor {
+  public AutoOneOfProcessor() {
+    super(AUTO_ONE_OF_NAME);
+  }
+
+  @Override
+  boolean propertiesCanBeVoid() {
+    return true;
+  }
+
+  @Override
+  void processType(TypeElement autoOneOfType) {
+    if (autoOneOfType.getKind() != ElementKind.CLASS) {
+      errorReporter()
+          .abortWithError(autoOneOfType, "@" + AUTO_ONE_OF_NAME + " only applies to classes");
+    }
+    checkModifiersIfNested(autoOneOfType);
+    DeclaredType kindMirror = mirrorForKindType(autoOneOfType);
+
+    // We are going to classify the methods of the @AutoOneOf class into several categories.
+    // This covers the methods in the class itself and the ones it inherits from supertypes.
+    // First, the only concrete (non-abstract) methods we are interested in are overrides of
+    // Object methods (equals, hashCode, toString), which signal that we should not generate
+    // an implementation of those methods.
+    // Then, each abstract method is one of the following:
+    // (1) A property getter, like "abstract String foo()" or "abstract String getFoo()".
+    // (2) A kind getter, which is a method that returns the enum in @AutoOneOf. For
+    //     example if we have @AutoOneOf(PetKind.class), this would be a method that returns
+    //     PetKind.
+    // If there are abstract methods that don't fit any of the categories above, that is an error
+    // which we signal explicitly to avoid confusion.
+
+    ImmutableSet<ExecutableElement> methods =
+        getLocalAndInheritedMethods(
+            autoOneOfType, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
+    ImmutableSet<ExecutableElement> abstractMethods = abstractMethodsIn(methods);
+    ExecutableElement kindGetter =
+        findKindGetterOrAbort(autoOneOfType, kindMirror, abstractMethods);
+    Set<ExecutableElement> otherMethods = new LinkedHashSet<>(abstractMethods);
+    otherMethods.remove(kindGetter);
+
+    ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes =
+        propertyMethodsIn(otherMethods, autoOneOfType);
+    ImmutableBiMap<String, ExecutableElement> properties =
+        propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
+    validateMethods(autoOneOfType, abstractMethods, propertyMethodsAndTypes.keySet(), kindGetter);
+    ImmutableMap<String, String> propertyToKind =
+        propertyToKindMap(kindMirror, properties.keySet());
+
+    String subclass = generatedClassName(autoOneOfType, "AutoOneOf_");
+    AutoOneOfTemplateVars vars = new AutoOneOfTemplateVars();
+    vars.generatedClass = TypeSimplifier.simpleNameOf(subclass);
+    vars.propertyToKind = propertyToKind;
+    defineSharedVarsForType(autoOneOfType, methods, vars);
+    defineVarsForType(autoOneOfType, vars, propertyMethodsAndTypes, kindGetter);
+
+    String text = vars.toText();
+    text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoOneOfType.asType());
+    text = Reformatter.fixup(text);
+    writeSourceFile(subclass, text, autoOneOfType);
+  }
+
+  private DeclaredType mirrorForKindType(TypeElement autoOneOfType) {
+    Optional<AnnotationMirror> oneOfAnnotation =
+        getAnnotationMirror(autoOneOfType, AUTO_ONE_OF_NAME);
+    if (!oneOfAnnotation.isPresent()) {
+      // This shouldn't happen unless the compilation environment is buggy,
+      // but it has happened in the past and can crash the compiler.
+      errorReporter()
+          .abortWithError(
+              autoOneOfType,
+              "annotation processor for @AutoOneOf was invoked with a type"
+                  + " that does not have that annotation; this is probably a compiler bug");
+    }
+    AnnotationValue kindValue =
+        AnnotationMirrors.getAnnotationValue(oneOfAnnotation.get(), "value");
+    Object value = kindValue.getValue();
+    if (value instanceof TypeMirror) {
+      TypeMirror kindType = (TypeMirror) value;
+      switch (kindType.getKind()) {
+        case DECLARED:
+          return MoreTypes.asDeclared(kindType);
+        case ERROR:
+          throw new MissingTypeException(MoreTypes.asError(kindType));
+        default:
+          break;
+      }
+    }
+    throw new MissingTypeException(null);
+  }
+
+  private ImmutableMap<String, String> propertyToKindMap(
+      DeclaredType kindMirror, ImmutableSet<String> propertyNames) {
+    // We require a one-to-one correspondence between the property names and the enum constants.
+    // We must have transformName(propertyName) = transformName(constantName) for each one.
+    // So we build two maps, transformName(propertyName) → propertyName and
+    // transformName(constantName) → constant. The key sets of the two maps must match, and we
+    // can then join them to make propertyName → constantName.
+    TypeElement kindElement = MoreElements.asType(kindMirror.asElement());
+    Map<String, String> transformedPropertyNames =
+        propertyNames.stream().collect(toMap(this::transformName, s -> s));
+    Map<String, Element> transformedEnumConstants =
+        kindElement
+            .getEnclosedElements()
+            .stream()
+            .filter(e -> e.getKind().equals(ElementKind.ENUM_CONSTANT))
+            .collect(toMap(e -> transformName(e.getSimpleName().toString()), e -> e));
+
+    if (transformedPropertyNames.keySet().equals(transformedEnumConstants.keySet())) {
+      ImmutableMap.Builder<String, String> mapBuilder = ImmutableMap.builder();
+      for (String transformed : transformedPropertyNames.keySet()) {
+        mapBuilder.put(
+            transformedPropertyNames.get(transformed),
+            transformedEnumConstants.get(transformed).getSimpleName().toString());
+      }
+      return mapBuilder.build();
+    }
+
+    // The names don't match. Emit errors for the differences.
+    // Properties that have no enum constant
+    transformedPropertyNames.forEach(
+        (transformed, property) -> {
+          if (!transformedEnumConstants.containsKey(transformed)) {
+            errorReporter()
+                .reportError(
+                    kindElement,
+                    "Enum has no constant with name corresponding to property '%s'",
+                    property);
+          }
+        });
+    // Enum constants that have no property
+    transformedEnumConstants.forEach(
+        (transformed, constant) -> {
+          if (!transformedPropertyNames.containsKey(transformed)) {
+            errorReporter()
+                .reportError(
+                    constant,
+                    "Name of enum constant '%s' does not correspond to any property name",
+                    constant.getSimpleName());
+          }
+        });
+    throw new AbortProcessingException();
+  }
+
+  private String transformName(String s) {
+    return s.toLowerCase(Locale.ROOT).replace("_", "");
+  }
+
+  private ExecutableElement findKindGetterOrAbort(
+      TypeElement autoOneOfType,
+      TypeMirror kindMirror,
+      ImmutableSet<ExecutableElement> abstractMethods) {
+    Set<ExecutableElement> kindGetters =
+        abstractMethods
+            .stream()
+            .filter(e -> sameType(kindMirror, e.getReturnType()))
+            .filter(e -> e.getParameters().isEmpty())
+            .collect(toSet());
+    switch (kindGetters.size()) {
+      case 0:
+        errorReporter()
+            .reportError(
+                autoOneOfType,
+                "%s must have a no-arg abstract method returning %s",
+                autoOneOfType,
+                kindMirror);
+        break;
+      case 1:
+        return Iterables.getOnlyElement(kindGetters);
+      default:
+        for (ExecutableElement getter : kindGetters) {
+          errorReporter()
+              .reportError(getter, "More than one abstract method returns %s", kindMirror);
+        }
+    }
+    throw new AbortProcessingException();
+  }
+
+  private void validateMethods(
+      TypeElement type,
+      ImmutableSet<ExecutableElement> abstractMethods,
+      ImmutableSet<ExecutableElement> propertyMethods,
+      ExecutableElement kindGetter) {
+    for (ExecutableElement method : abstractMethods) {
+      if (propertyMethods.contains(method)) {
+        checkReturnType(type, method);
+      } else if (!method.equals(kindGetter)
+          && objectMethodToOverride(method) == ObjectMethod.NONE) {
+        // This could reasonably be an error, were it not for an Eclipse bug in
+        // ElementUtils.override that sometimes fails to recognize that one method overrides
+        // another, and therefore leaves us with both an abstract method and the subclass method
+        // that overrides it. This shows up in AutoValueTest.LukesBase for example.
+        // The compilation will fail anyway because the generated concrete classes won't
+        // implement this alien method.
+        errorReporter()
+            .reportWarning(
+                method, "Abstract methods in @AutoOneOf classes must have no parameters");
+      }
+    }
+    errorReporter().abortIfAnyError();
+  }
+
+  private void defineVarsForType(
+      TypeElement type,
+      AutoOneOfTemplateVars vars,
+      ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
+      ExecutableElement kindGetter) {
+    vars.props = propertySet(
+        propertyMethodsAndTypes, ImmutableListMultimap.of(), ImmutableListMultimap.of());
+    vars.kindGetter = kindGetter.getSimpleName().toString();
+    vars.kindType = TypeEncoder.encode(kindGetter.getReturnType());
+    TypeElement javaIoSerializable = elementUtils().getTypeElement("java.io.Serializable");
+    vars.serializable =
+        javaIoSerializable != null  // just in case
+        && typeUtils().isAssignable(type.asType(), javaIoSerializable.asType());
+  }
+
+  @Override
+  Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) {
+    if (nullableAnnotationFor(propertyMethod, propertyMethod.getReturnType()).isPresent()) {
+      errorReporter().reportError(propertyMethod, "@AutoOneOf properties cannot be @Nullable");
+    }
+    return Optional.empty();
+  }
+
+  private static boolean sameType(TypeMirror t1, TypeMirror t2) {
+    return MoreTypes.equivalence().equivalent(t1, t2);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java
new file mode 100644
index 0000000..1e15771
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 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.processor;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.escapevelocity.Template;
+import java.util.Map;
+
+/**
+ * The variables to substitute into the autooneof.vm template.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@SuppressWarnings("unused") // the fields in this class are only read via reflection
+class AutoOneOfTemplateVars extends AutoValueOrOneOfTemplateVars {
+  /**
+   * The properties defined by the parent class's abstract methods. The elements of this set are in
+   * the same order as the original abstract method declarations in the AutoOneOf class.
+   */
+  ImmutableSet<AutoOneOfProcessor.Property> props;
+
+  /** The simple name of the generated class. */
+  String generatedClass;
+
+  /** The encoded name of the "kind" enum class. */
+  String kindType;
+
+  /** The name of the method that gets the kind of the current {@code @AutoOneOf} instance. */
+  String kindGetter;
+
+  /** Maps property names like {@code dog} to enum constants like {@code DOG}. */
+  Map<String, String> propertyToKind;
+
+  /** True if this {@code @AutoOneOf} class is Serializable. */
+  Boolean serializable;
+
+  private static final Template TEMPLATE = parsedTemplateForResource("autooneof.vm");
+
+  @Override
+  Template parsedTemplate() {
+    return TEMPLATE;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java
new file mode 100644
index 0000000..62dfeb3
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.hasAnnotationMirror;
+import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME;
+import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME;
+
+import com.google.auto.common.SuperficialValidation;
+import com.google.auto.service.AutoService;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+
+/**
+ * Annotation processor that checks that the type that {@code AutoValue.Builder} is applied to is
+ * nested inside an {@code @AutoValue} class. The actual code generation for builders is done in
+ * {@link AutoValueProcessor}.
+ *
+ * @author Éamonn McManus
+ */
+@AutoService(Processor.class)
+@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
+@SupportedAnnotationTypes(AUTO_VALUE_BUILDER_NAME)
+public class AutoValueBuilderProcessor extends AbstractProcessor {
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latest();
+  }
+
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    TypeElement autoValueBuilder =
+        processingEnv.getElementUtils().getTypeElement(AUTO_VALUE_BUILDER_NAME);
+    Set<? extends Element> builderTypes = roundEnv.getElementsAnnotatedWith(autoValueBuilder);
+    if (!SuperficialValidation.validateElements(builderTypes)) {
+      return false;
+    }
+    for (Element annotatedType : builderTypes) {
+      // Double-check that the annotation is there. Sometimes the compiler gets confused in case of
+      // erroneous source code. SuperficialValidation should protect us against this but it doesn't
+      // cost anything to check again.
+      if (hasAnnotationMirror(annotatedType, AUTO_VALUE_BUILDER_NAME)) {
+        validate(
+            annotatedType,
+            "@AutoValue.Builder can only be applied to a class or interface inside an"
+                + " @AutoValue class");
+      }
+    }
+    return false;
+  }
+
+  private void validate(Element annotatedType, String errorMessage) {
+    Element container = annotatedType.getEnclosingElement();
+    if (!hasAnnotationMirror(container, AUTO_VALUE_NAME)) {
+      processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, errorMessage, annotatedType);
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java
new file mode 100644
index 0000000..4214c12
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java
@@ -0,0 +1,1108 @@
+/*
+ * Copyright 2018 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.processor;
+
+import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
+import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation;
+import static com.google.auto.common.MoreElements.getPackage;
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_PACKAGE_NAME;
+import static com.google.auto.value.processor.ClassNames.COPY_ANNOTATIONS_NAME;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.collect.Sets.union;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.common.Visibility;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.function.Predicate;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.QualifiedNameable;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+/**
+ * Shared code between AutoValueProcessor and AutoOneOfProcessor.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+abstract class AutoValueOrOneOfProcessor extends AbstractProcessor {
+  private final String annotationClassName;
+
+  /**
+   * Qualified names of {@code @AutoValue} or {@code AutoOneOf} classes that we attempted to process
+   * but had to abandon because we needed other types that they referenced and those other types
+   * were missing.
+   */
+  private final List<String> deferredTypeNames = new ArrayList<>();
+
+  AutoValueOrOneOfProcessor(String annotationClassName) {
+    this.annotationClassName = annotationClassName;
+  }
+
+  /** The annotation we are processing, {@code AutoValue} or {@code AutoOneOf}. */
+  private TypeElement annotationType;
+  /** The simple name of {@link #annotationType}. */
+  private String simpleAnnotationName;
+
+  private ErrorReporter errorReporter;
+
+  @Override
+  public synchronized void init(ProcessingEnvironment processingEnv) {
+    super.init(processingEnv);
+    errorReporter = new ErrorReporter(processingEnv);
+  }
+
+  final ErrorReporter errorReporter() {
+    return errorReporter;
+  }
+
+  final Types typeUtils() {
+    return processingEnv.getTypeUtils();
+  }
+
+  final Elements elementUtils() {
+    return processingEnv.getElementUtils();
+  }
+
+  /**
+   * Qualified names of {@code @AutoValue} or {@code AutoOneOf} classes that we attempted to process
+   * but had to abandon because we needed other types that they referenced and those other types
+   * were missing. This is used by tests.
+   */
+  final ImmutableList<String> deferredTypeNames() {
+    return ImmutableList.copyOf(deferredTypeNames);
+  }
+
+  @Override
+  public final SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latestSupported();
+  }
+
+  /**
+   * A property of an {@code @AutoValue} or {@code @AutoOneOf} class, defined by one of its abstract
+   * methods. An instance of this class is made available to the Velocity template engine for each
+   * property. The public methods of this class define JavaBeans-style properties that are
+   * accessible from templates. For example {@link #getType()} means we can write {@code $p.type}
+   * for a Velocity variable {@code $p} that is a {@code Property}.
+   */
+  public static class Property {
+    private final String name;
+    private final String identifier;
+    private final ExecutableElement method;
+    private final String type;
+    private final ImmutableList<String> fieldAnnotations;
+    private final ImmutableList<String> methodAnnotations;
+    private final Optional<String> nullableAnnotation;
+    private final Optionalish optional;
+
+    Property(
+        String name,
+        String identifier,
+        ExecutableElement method,
+        String type,
+        ImmutableList<String> fieldAnnotations,
+        ImmutableList<String> methodAnnotations,
+        Optional<String> nullableAnnotation) {
+      this.name = name;
+      this.identifier = identifier;
+      this.method = method;
+      this.type = type;
+      this.fieldAnnotations = fieldAnnotations;
+      this.methodAnnotations = methodAnnotations;
+      this.nullableAnnotation = nullableAnnotation;
+      TypeMirror propertyType = method.getReturnType();
+      this.optional = Optionalish.createIfOptional(propertyType);
+    }
+
+    /**
+     * Returns the name of the property as it should be used when declaring identifiers (fields and
+     * parameters). If the original getter method was {@code foo()} then this will be {@code foo}.
+     * If it was {@code getFoo()} then it will be {@code foo}. If it was {@code getPackage()} then
+     * it will be something like {@code package0}, since {@code package} is a reserved word.
+     */
+    @Override
+    public String toString() {
+      return identifier;
+    }
+
+    /**
+     * Returns the name of the property as it should be used in strings visible to users. This is
+     * usually the same as {@code toString()}, except that if we had to use an identifier like
+     * "package0" because "package" is a reserved word, the name here will be the original
+     * "package".
+     */
+    public String getName() {
+      return name;
+    }
+
+    /**
+     * Returns the name of the getter method for this property as defined by the {@code @AutoValue}
+     * class. For property {@code foo}, this will be {@code foo} or {@code getFoo} or {@code isFoo}.
+     */
+    public String getGetter() {
+      return method.getSimpleName().toString();
+    }
+
+    public TypeMirror getTypeMirror() {
+      return method.getReturnType();
+    }
+
+    public String getType() {
+      return type;
+    }
+
+    public TypeKind getKind() {
+      return method.getReturnType().getKind();
+    }
+
+    /**
+     * Returns the annotations (in string form) that should be applied to the property's field
+     * declaration.
+     */
+    public List<String> getFieldAnnotations() {
+      return fieldAnnotations;
+    }
+
+    /**
+     * Returns the annotations (in string form) that should be applied to the property's method
+     * implementation.
+     */
+    public List<String> getMethodAnnotations() {
+      return methodAnnotations;
+    }
+
+    /**
+     * Returns an {@link Optionalish} representing the kind of Optional that this property's type
+     * is, or null if the type is not an Optional of any kind.
+     */
+    public Optionalish getOptional() {
+      return optional;
+    }
+
+    /**
+     * Returns the string to use as a method annotation to indicate the nullability of this
+     * property. It is either the empty string, if the property is not nullable, or an annotation
+     * string with a trailing space, such as {@code "@`javax.annotation.Nullable` "}, where the
+     * {@code ``} is the encoding used by {@link TypeEncoder}. If the property is nullable by virtue
+     * of its <i>type</i> rather than its method being {@code @Nullable}, this method returns the
+     * empty string, because the {@code @Nullable} will appear when the type is spelled out. In this
+     * case, {@link #nullableAnnotation} is present but empty.
+     */
+    public final String getNullableAnnotation() {
+      return nullableAnnotation.orElse("");
+    }
+
+    public boolean isNullable() {
+      return nullableAnnotation.isPresent();
+    }
+
+    public String getAccess() {
+      return SimpleMethod.access(method);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return obj instanceof Property && ((Property) obj).method.equals(method);
+    }
+
+    @Override
+    public int hashCode() {
+      return method.hashCode();
+    }
+  }
+
+  @Override
+  public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    annotationType = elementUtils().getTypeElement(annotationClassName);
+    if (annotationType == null) {
+      // This should not happen. If the annotation type is not found, how did the processor get
+      // triggered?
+      processingEnv
+          .getMessager()
+          .printMessage(
+              Diagnostic.Kind.ERROR,
+              "Did not process @"
+                  + annotationClassName
+                  + " because the annotation class was not found");
+      return false;
+    }
+    simpleAnnotationName = annotationType.getSimpleName().toString();
+    List<TypeElement> deferredTypes =
+        deferredTypeNames
+            .stream()
+            .map(name -> elementUtils().getTypeElement(name))
+            .collect(toList());
+    if (roundEnv.processingOver()) {
+      // This means that the previous round didn't generate any new sources, so we can't have found
+      // any new instances of @AutoValue; and we can't have any new types that are the reason a type
+      // was in deferredTypes.
+      for (TypeElement type : deferredTypes) {
+        errorReporter.reportError(
+            type,
+            "Did not generate @%s class for %s because it references undefined types",
+            simpleAnnotationName,
+            type.getQualifiedName());
+      }
+      return false;
+    }
+    Collection<? extends Element> annotatedElements =
+        roundEnv.getElementsAnnotatedWith(annotationType);
+    List<TypeElement> types =
+        new ImmutableList.Builder<TypeElement>()
+            .addAll(deferredTypes)
+            .addAll(ElementFilter.typesIn(annotatedElements))
+            .build();
+    deferredTypeNames.clear();
+    for (TypeElement type : types) {
+      try {
+        processType(type);
+      } catch (AbortProcessingException e) {
+        // We abandoned this type; continue with the next.
+      } catch (MissingTypeException e) {
+        // We abandoned this type, but only because we needed another type that it references and
+        // that other type was missing. It is possible that the missing type will be generated by
+        // further annotation processing, so we will try again on the next round (perhaps failing
+        // again and adding it back to the list). We save the name of the @AutoValue type rather
+        // than its TypeElement because it is not guaranteed that it will be represented by
+        // the same TypeElement on the next round.
+        deferredTypeNames.add(type.getQualifiedName().toString());
+      } catch (RuntimeException e) {
+        String trace = Throwables.getStackTraceAsString(e);
+        errorReporter.reportError(
+            type, "@%s processor threw an exception: %s", simpleAnnotationName, trace);
+        throw e;
+      }
+    }
+    return false; // never claim annotation, because who knows what other processors want?
+  }
+
+  /**
+   * Analyzes a single {@code @AutoValue} or {@code @AutoOneOf} class, and outputs the corresponding
+   * implementation class or classes.
+   *
+   * @param type the class with the {@code @AutoValue} or {@code @AutoOneOf} annotation.
+   */
+  abstract void processType(TypeElement type);
+
+  /**
+   * Returns the appropriate {@code @Nullable} annotation to put on the implementation of the given
+   * property method, and indicates whether the property is in fact nullable. The annotation in
+   * question is on the method, not its return type. If instead the return type is
+   * {@code @Nullable}, this method returns {@code Optional.of("")}, to indicate that the property
+   * is nullable but the <i>method</i> isn't. The {@code @Nullable} annotation will instead appear
+   * when the return type of the method is spelled out in the implementation.
+   */
+  abstract Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod);
+
+  /**
+   * Returns the ordered set of {@link Property} definitions for the given {@code @AutoValue} or
+   * {@code AutoOneOf} type.
+   *
+   * @param annotatedPropertyMethods a map from property methods to the method annotations that
+   *     should go on the implementation of those methods. These annotations are method annotations
+   *     specifically. Type annotations do not appear because they are considered part of the return
+   *     type and will appear when that is spelled out. Annotations that are excluded by {@code
+   *     AutoValue.CopyAnnotations} also do not appear here.
+   */
+  final ImmutableSet<Property> propertySet(
+      ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
+      ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields,
+      ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods) {
+    ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
+        propertyNameToMethodMap(propertyMethodsAndTypes.keySet()).inverse();
+    Map<ExecutableElement, String> methodToIdentifier = new LinkedHashMap<>(methodToPropertyName);
+    fixReservedIdentifiers(methodToIdentifier);
+
+    ImmutableSet.Builder<Property> props = ImmutableSet.builder();
+    propertyMethodsAndTypes.forEach(
+        (propertyMethod, returnType) -> {
+          String propertyType = TypeEncoder.encodeWithAnnotations(
+              returnType, getExcludedAnnotationTypes(propertyMethod));
+          String propertyName = methodToPropertyName.get(propertyMethod);
+          String identifier = methodToIdentifier.get(propertyMethod);
+          ImmutableList<String> fieldAnnotations =
+              annotationStrings(annotatedPropertyFields.get(propertyMethod));
+          ImmutableList<AnnotationMirror> methodAnnotationMirrors =
+              annotatedPropertyMethods.get(propertyMethod);
+          ImmutableList<String> methodAnnotations = annotationStrings(methodAnnotationMirrors);
+          Optional<String> nullableAnnotation = nullableAnnotationForMethod(propertyMethod);
+          Property p =
+              new Property(
+                  propertyName,
+                  identifier,
+                  propertyMethod,
+                  propertyType,
+                  fieldAnnotations,
+                  methodAnnotations,
+                  nullableAnnotation);
+          props.add(p);
+          if (p.isNullable() && returnType.getKind().isPrimitive()) {
+            errorReporter().reportError(propertyMethod, "Primitive types cannot be @Nullable");
+          }
+        });
+    return props.build();
+  }
+
+  /** Defines the template variables that are shared by AutoValue and AutoOneOf. */
+  final void defineSharedVarsForType(
+      TypeElement type,
+      ImmutableSet<ExecutableElement> methods,
+      AutoValueOrOneOfTemplateVars vars) {
+    vars.pkg = TypeSimplifier.packageNameOf(type);
+    vars.origClass = TypeSimplifier.classNameOf(type);
+    vars.simpleClassName = TypeSimplifier.simpleNameOf(vars.origClass);
+    vars.generated =
+        generatedAnnotation(elementUtils(), processingEnv.getSourceVersion())
+            .map(annotation -> TypeEncoder.encode(annotation.asType()))
+            .orElse("");
+    vars.formalTypes = TypeEncoder.formalTypeParametersString(type);
+    vars.actualTypes = TypeSimplifier.actualTypeParametersString(type);
+    vars.wildcardTypes = wildcardTypeParametersString(type);
+    vars.annotations = copiedClassAnnotations(type);
+    Map<ObjectMethod, ExecutableElement> methodsToGenerate =
+        determineObjectMethodsToGenerate(methods);
+    vars.toString = methodsToGenerate.containsKey(ObjectMethod.TO_STRING);
+    vars.equals = methodsToGenerate.containsKey(ObjectMethod.EQUALS);
+    vars.hashCode = methodsToGenerate.containsKey(ObjectMethod.HASH_CODE);
+    vars.equalsParameterType = equalsParameterType(methodsToGenerate);
+  }
+
+  /** Returns the spelling to be used in the generated code for the given list of annotations. */
+  static ImmutableList<String> annotationStrings(List<? extends AnnotationMirror> annotations) {
+    // TODO(b/68008628): use ImmutableList.toImmutableList() when that works.
+    return ImmutableList.copyOf(
+        annotations.stream().map(AnnotationOutput::sourceFormForAnnotation).collect(toList()));
+  }
+
+  /**
+   * Returns the name of the generated {@code @AutoValue} or {@code @AutoOneOf} class, for example
+   * {@code AutoOneOf_TaskResult} or {@code $$AutoValue_SimpleMethod}.
+   *
+   * @param type the name of the type bearing the {@code @AutoValue} or {@code @AutoOneOf}
+   *     annotation.
+   * @param prefix the prefix to use in the generated class. This may start with one or more dollar
+   *     signs, for an {@code @AutoValue} implementation where there are AutoValue extensions.
+   */
+  static String generatedClassName(TypeElement type, String prefix) {
+    String name = type.getSimpleName().toString();
+    while (type.getEnclosingElement() instanceof TypeElement) {
+      type = (TypeElement) type.getEnclosingElement();
+      name = type.getSimpleName() + "_" + name;
+    }
+    String pkg = TypeSimplifier.packageNameOf(type);
+    String dot = pkg.isEmpty() ? "" : ".";
+    return pkg + dot + prefix + name;
+  }
+
+  private static boolean isJavaLangObject(TypeElement type) {
+    return type.getSuperclass().getKind() == TypeKind.NONE && type.getKind() == ElementKind.CLASS;
+  }
+
+  enum ObjectMethod {
+    NONE,
+    TO_STRING,
+    EQUALS,
+    HASH_CODE
+  }
+
+  /**
+   * Determines which of the three public non-final methods from {@code java.lang.Object}, if any,
+   * is overridden by the given method.
+   */
+  static ObjectMethod objectMethodToOverride(ExecutableElement method) {
+    String name = method.getSimpleName().toString();
+    switch (method.getParameters().size()) {
+      case 0:
+        if (name.equals("toString")) {
+          return ObjectMethod.TO_STRING;
+        } else if (name.equals("hashCode")) {
+          return ObjectMethod.HASH_CODE;
+        }
+        break;
+      case 1:
+        if (name.equals("equals")) {
+          TypeMirror param = getOnlyElement(method.getParameters()).asType();
+          if (param.getKind().equals(TypeKind.DECLARED)) {
+            TypeElement paramType = MoreTypes.asTypeElement(param);
+            if (paramType.getQualifiedName().contentEquals("java.lang.Object")) {
+              return ObjectMethod.EQUALS;
+            }
+          }
+        }
+        break;
+      default:
+        // No relevant Object methods have more than one parameter.
+    }
+    return ObjectMethod.NONE;
+  }
+
+  /** Returns a bi-map between property names and the corresponding abstract property methods. */
+  final ImmutableBiMap<String, ExecutableElement> propertyNameToMethodMap(
+      Set<ExecutableElement> propertyMethods) {
+    Map<String, ExecutableElement> map = new LinkedHashMap<>();
+    Set<String> reportedDups = new HashSet<>();
+    boolean allPrefixed = gettersAllPrefixed(propertyMethods);
+    for (ExecutableElement method : propertyMethods) {
+      String methodName = method.getSimpleName().toString();
+      String name = allPrefixed ? nameWithoutPrefix(methodName) : methodName;
+      ExecutableElement old = map.put(name, method);
+      if (old != null) {
+        List<ExecutableElement> contexts = new ArrayList<>(Arrays.asList(method));
+        if (reportedDups.add(name)) {
+          contexts.add(old);
+        }
+        // Report the error for both of the methods. If this is a third or further occurrence,
+        // reportedDups prevents us from reporting more than one error for the same method.
+        for (ExecutableElement context : contexts) {
+          errorReporter.reportError(
+              context, "More than one @%s property called %s", simpleAnnotationName, name);
+        }
+      }
+    }
+    return ImmutableBiMap.copyOf(map);
+  }
+
+  private static boolean gettersAllPrefixed(Set<ExecutableElement> methods) {
+    return prefixedGettersIn(methods).size() == methods.size();
+  }
+
+  /**
+   * Returns an appropriate annotation spelling to indicate the nullability of an element. If the
+   * return value is a non-empty Optional, that indicates that the element is nullable, and the
+   * string should be used to annotate it. If the return value is an empty Optional, the element is
+   * not nullable. The return value can be {@code Optional.of("")}, which indicates that the element
+   * is nullable but that the nullability comes from a type annotation. In this case, the annotation
+   * will appear when the type is written, and must not be specified again. If the Optional contains
+   * a present non-empty string then that string will end with a space.
+   *
+   * @param element the element that might be {@code @Nullable}, either a method or a parameter.
+   * @param elementType the relevant type of the element: the return type for a method, or the
+   *     parameter type for a parameter.
+   */
+  static Optional<String> nullableAnnotationFor(Element element, TypeMirror elementType) {
+    List<? extends AnnotationMirror> typeAnnotations = elementType.getAnnotationMirrors();
+    if (nullableAnnotationIndex(typeAnnotations).isPresent()) {
+      return Optional.of("");
+    }
+    List<? extends AnnotationMirror> elementAnnotations = element.getAnnotationMirrors();
+    OptionalInt nullableAnnotationIndex = nullableAnnotationIndex(elementAnnotations);
+    if (nullableAnnotationIndex.isPresent()) {
+      ImmutableList<String> annotations = annotationStrings(elementAnnotations);
+      return Optional.of(annotations.get(nullableAnnotationIndex.getAsInt()) + " ");
+    } else {
+      return Optional.empty();
+    }
+  }
+
+  private static OptionalInt nullableAnnotationIndex(List<? extends AnnotationMirror> annotations) {
+    for (int i = 0; i < annotations.size(); i++) {
+      if (isNullable(annotations.get(i))) {
+        return OptionalInt.of(i);
+      }
+    }
+    return OptionalInt.empty();
+  }
+
+  private static boolean isNullable(AnnotationMirror annotation) {
+    return annotation.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable");
+  }
+
+  /**
+   * Returns the subset of the given zero-arg methods whose names begin with {@code get}. Also
+   * includes {@code isFoo} methods if they return {@code boolean}. This corresponds to JavaBeans
+   * conventions.
+   */
+  static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) {
+    ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder();
+    for (ExecutableElement method : methods) {
+      String name = method.getSimpleName().toString();
+      // Note that getfoo() (without a capital) is still a getter.
+      boolean get = name.startsWith("get") && !name.equals("get");
+      boolean is =
+          name.startsWith("is")
+              && !name.equals("is")
+              && method.getReturnType().getKind() == TypeKind.BOOLEAN;
+      if (get || is) {
+        getters.add(method);
+      }
+    }
+    return getters.build();
+  }
+
+  /**
+   * Returns the name of the property defined by the given getter. A getter called {@code getFoo()}
+   * or {@code isFoo()} defines a property called {@code foo}. For consistency with JavaBeans, a
+   * getter called {@code getHTMLPage()} defines a property called {@code HTMLPage}. The <a
+   * href="https://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize-java.lang.String-">
+   * rule</a> is: the name of the property is the part after {@code get} or {@code is}, with the
+   * first letter lowercased <i>unless</i> the first two letters are uppercase. This works well for
+   * the {@code HTMLPage} example, but in these more enlightened times we use {@code HtmlPage}
+   * anyway, so the special behaviour is not useful, and of course it behaves poorly with examples
+   * like {@code OAuth}.
+   */
+  private static String nameWithoutPrefix(String name) {
+    if (name.startsWith("get")) {
+      name = name.substring(3);
+    } else {
+      assert name.startsWith("is");
+      name = name.substring(2);
+    }
+    return PropertyNames.decapitalizeLikeJavaBeans(name);
+  }
+
+  /**
+   * Checks that, if the given {@code @AutoValue} or {@code @AutoOneOf} class is nested, it is
+   * static and not private. This check is not necessary for correctness, since the generated code
+   * would not compile if the check fails, but it produces better error messages for the user.
+   */
+  final void checkModifiersIfNested(TypeElement type) {
+    ElementKind enclosingKind = type.getEnclosingElement().getKind();
+    if (enclosingKind.isClass() || enclosingKind.isInterface()) {
+      if (type.getModifiers().contains(Modifier.PRIVATE)) {
+        errorReporter.abortWithError(
+            type, "@%s class must not be private", simpleAnnotationName);
+      } else if (Visibility.effectiveVisibilityOfElement(type).equals(Visibility.PRIVATE)) {
+        // The previous case, where the class itself is private, is much commoner so it deserves
+        // its own error message, even though it would be caught by the test here too.
+        errorReporter.abortWithError(
+            type, "@%s class must not be nested in a private class", simpleAnnotationName);
+      }
+      if (!type.getModifiers().contains(Modifier.STATIC)) {
+        errorReporter.abortWithError(
+            type, "Nested @%s class must be static", simpleAnnotationName);
+      }
+    }
+    // In principle type.getEnclosingElement() could be an ExecutableElement (for a class
+    // declared inside a method), but since RoundEnvironment.getElementsAnnotatedWith doesn't
+    // return such classes we won't see them here.
+  }
+
+  /**
+   * Modifies the values of the given map to avoid reserved words. If we have a getter called {@code
+   * getPackage()} then we can't use the identifier {@code package} to represent its value since
+   * that's a reserved word.
+   */
+  static void fixReservedIdentifiers(Map<?, String> methodToIdentifier) {
+    for (Map.Entry<?, String> entry : methodToIdentifier.entrySet()) {
+      String name = entry.getValue();
+      if (SourceVersion.isKeyword(name) || !Character.isJavaIdentifierStart(name.codePointAt(0))) {
+        entry.setValue(disambiguate(name, methodToIdentifier.values()));
+      }
+    }
+  }
+
+  private static String disambiguate(String name, Collection<String> existingNames) {
+    if (!Character.isJavaIdentifierStart(name.codePointAt(0))) {
+      // You've defined a getter called get1st(). What were you thinking?
+      name = "_" + name;
+      if (!existingNames.contains(name)) {
+        return name;
+      }
+    }
+    for (int i = 0; ; i++) {
+      String candidate = name + i;
+      if (!existingNames.contains(candidate)) {
+        return candidate;
+      }
+    }
+  }
+
+  /**
+   * Given a list of all methods defined in or inherited by a class, returns a map indicating which
+   * of equals, hashCode, and toString should be generated. Each value in the map is the method that
+   * will be overridden by the generated method, which might be a method in {@code Object} or an
+   * abstract method in the {@code @AutoValue} class or an ancestor.
+   */
+  private static Map<ObjectMethod, ExecutableElement> determineObjectMethodsToGenerate(
+      Set<ExecutableElement> methods) {
+    Map<ObjectMethod, ExecutableElement> methodsToGenerate = new EnumMap<>(ObjectMethod.class);
+    for (ExecutableElement method : methods) {
+      ObjectMethod override = objectMethodToOverride(method);
+      boolean canGenerate =
+          method.getModifiers().contains(Modifier.ABSTRACT)
+              || isJavaLangObject((TypeElement) method.getEnclosingElement());
+      if (!override.equals(ObjectMethod.NONE) && canGenerate) {
+        methodsToGenerate.put(override, method);
+      }
+    }
+    return methodsToGenerate;
+  }
+
+  /**
+   * Returns the encoded parameter type of the {@code equals(Object)} method that is to be
+   * generated, or an empty string if the method is not being generated. The parameter type includes
+   * any type annotations, for example {@code @Nullable}.
+   */
+  static String equalsParameterType(Map<ObjectMethod, ExecutableElement> methodsToGenerate) {
+    ExecutableElement equals = methodsToGenerate.get(ObjectMethod.EQUALS);
+    if (equals == null) {
+      return ""; // this will not be referenced because no equals method will be generated
+    }
+    TypeMirror parameterType = equals.getParameters().get(0).asType();
+    return TypeEncoder.encodeWithAnnotations(parameterType);
+  }
+
+  /**
+   * Returns the subset of all abstract methods in the given set of methods. A given method
+   * signature is only mentioned once, even if it is inherited on more than one path. If any of the
+   * abstract methods has a return type or parameter type that is not currently defined then this
+   * method will throw an exception that will cause us to defer processing of the current class
+   * until a later annotation-processing round.
+   */
+  static ImmutableSet<ExecutableElement> abstractMethodsIn(Iterable<ExecutableElement> methods) {
+    Set<Name> noArgMethods = new HashSet<>();
+    ImmutableSet.Builder<ExecutableElement> abstracts = ImmutableSet.builder();
+    for (ExecutableElement method : methods) {
+      if (method.getModifiers().contains(Modifier.ABSTRACT)) {
+        MissingTypes.deferIfMissingTypesIn(method);
+        boolean hasArgs = !method.getParameters().isEmpty();
+        if (hasArgs || noArgMethods.add(method.getSimpleName())) {
+          // If an abstract method with the same signature is inherited on more than one path,
+          // we only add it once. At the moment we only do this check for no-arg methods. All
+          // methods that AutoValue will implement are either no-arg methods or equals(Object).
+          // The former is covered by this check and the latter will lead to vars.equals being
+          // set to true, regardless of how many times it appears. So the only case that is
+          // covered imperfectly here is that of a method that is inherited on more than one path
+          // and that will be consumed by an extension. We could check parameters as well, but that
+          // can be a bit tricky if any of the parameters are generic.
+          abstracts.add(method);
+        }
+      }
+    }
+    return abstracts.build();
+  }
+
+  /**
+   * Returns the subset of property methods in the given set of abstract methods, with their actual
+   * return types. A property method has no arguments, is not void, and is not {@code hashCode()} or
+   * {@code toString()}.
+   */
+  ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsIn(
+      Set<ExecutableElement> abstractMethods,
+      TypeElement autoValueOrOneOfType) {
+    DeclaredType declaredType = MoreTypes.asDeclared(autoValueOrOneOfType.asType());
+    ImmutableSet.Builder<ExecutableElement> properties = ImmutableSet.builder();
+    for (ExecutableElement method : abstractMethods) {
+      if (method.getParameters().isEmpty()
+          && (method.getReturnType().getKind() != TypeKind.VOID || propertiesCanBeVoid())
+          && objectMethodToOverride(method) == ObjectMethod.NONE) {
+        properties.add(method);
+      }
+    }
+    return new EclipseHack(processingEnv).methodReturnTypes(properties.build(), declaredType);
+  }
+
+  /** True if void properties are allowed. */
+  boolean propertiesCanBeVoid() {
+    return false;
+  }
+
+  /**
+   * Checks that the return type of the given property method is allowed. Currently, this means that
+   * it cannot be an array, unless it is a primitive array.
+   */
+  final void checkReturnType(TypeElement autoValueClass, ExecutableElement getter) {
+    TypeMirror type = getter.getReturnType();
+    if (type.getKind() == TypeKind.ARRAY) {
+      TypeMirror componentType = ((ArrayType) type).getComponentType();
+      if (componentType.getKind().isPrimitive()) {
+        warnAboutPrimitiveArrays(autoValueClass, getter);
+      } else {
+        errorReporter.reportError(
+            getter,
+            "An @%s class cannot define an array-valued property unless it is a primitive array",
+            simpleAnnotationName);
+      }
+    }
+  }
+
+  private void warnAboutPrimitiveArrays(TypeElement autoValueClass, ExecutableElement getter) {
+    boolean suppressed = false;
+    Optional<AnnotationMirror> maybeAnnotation =
+        getAnnotationMirror(getter, "java.lang.SuppressWarnings");
+    if (maybeAnnotation.isPresent()) {
+      AnnotationValue listValue = getAnnotationValue(maybeAnnotation.get(), "value");
+      suppressed = listValue.accept(new ContainsMutableVisitor(), null);
+    }
+    if (!suppressed) {
+      // If the primitive-array property method is defined directly inside the @AutoValue class,
+      // then our error message should point directly to it. But if it is inherited, we don't
+      // want to try to make the error message point to the inherited definition, since that would
+      // be confusing (there is nothing wrong with the definition itself), and won't work if the
+      // inherited class is not being recompiled. Instead, in this case we point to the @AutoValue
+      // class itself, and we include extra text in the error message that shows the full name of
+      // the inherited method.
+      boolean sameClass = getter.getEnclosingElement().equals(autoValueClass);
+      Element element = sameClass ? getter : autoValueClass;
+      String context = sameClass ? "" : (" Method: " + getter.getEnclosingElement() + "." + getter);
+      errorReporter.reportWarning(
+          element,
+          "An @%s property that is a primitive array returns the original array, which can"
+              + " therefore be modified by the caller. If this is OK, you can suppress this warning"
+              + " with @SuppressWarnings(\"mutable\"). Otherwise, you should replace the property"
+              + " with an immutable type, perhaps a simple wrapper around the original array.%s",
+          simpleAnnotationName,
+          context);
+    }
+  }
+
+  // Detects whether the visited AnnotationValue is an array that contains the string "mutable".
+  // The simpler approach using Element.getAnnotation(SuppressWarnings.class) doesn't work if
+  // the annotation has an undefined reference, like @SuppressWarnings(UNDEFINED).
+  // TODO(emcmanus): replace with a method from auto-common when that is available.
+  private static class ContainsMutableVisitor extends SimpleAnnotationValueVisitor8<Boolean, Void> {
+    @Override
+    public Boolean visitArray(List<? extends AnnotationValue> list, Void p) {
+      return list.stream().map(av -> av.getValue()).anyMatch("mutable"::equals);
+    }
+  }
+
+  /**
+   * Returns a string like {@code "1234L"} if {@code type instanceof Serializable} and defines
+   * {@code serialVersionUID = 1234L}; otherwise {@code ""}.
+   */
+  final String getSerialVersionUID(TypeElement type) {
+    TypeMirror serializable = elementUtils().getTypeElement(Serializable.class.getName()).asType();
+    if (typeUtils().isAssignable(type.asType(), serializable)) {
+      List<VariableElement> fields = ElementFilter.fieldsIn(type.getEnclosedElements());
+      for (VariableElement field : fields) {
+        if (field.getSimpleName().contentEquals("serialVersionUID")) {
+          Object value = field.getConstantValue();
+          if (field.getModifiers().containsAll(Arrays.asList(Modifier.STATIC, Modifier.FINAL))
+              && field.asType().getKind() == TypeKind.LONG
+              && value != null) {
+            return value + "L";
+          } else {
+            errorReporter.reportError(
+                field, "serialVersionUID must be a static final long compile-time constant");
+            break;
+          }
+        }
+      }
+    }
+    return "";
+  }
+
+  /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
+  ImmutableList<AnnotationMirror> annotationsToCopy(
+      Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
+    ImmutableList.Builder<AnnotationMirror> result = ImmutableList.builder();
+    for (AnnotationMirror annotation : typeOrMethod.getAnnotationMirrors()) {
+      String annotationFqName = getAnnotationFqName(annotation);
+      // To be included, the annotation should not be in com.google.auto.value,
+      // and it should not be in the excludedAnnotations set.
+      if (!isInAutoValuePackage(annotationFqName)
+          && !excludedAnnotations.contains(annotationFqName)
+          && annotationVisibleFrom(annotation, autoValueType)) {
+        result.add(annotation);
+      }
+    }
+
+    return result.build();
+  }
+
+  /**
+   * True if the given class name is in the com.google.auto.value package or a subpackage. False if
+   * the class name contains {@code Test}, since many AutoValue tests under com.google.auto.value
+   * define their own annotations.
+   */
+  private boolean isInAutoValuePackage(String className) {
+    return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test");
+  }
+
+  ImmutableList<String> copiedClassAnnotations(TypeElement type) {
+    // Only copy annotations from a class if it has @AutoValue.CopyAnnotations.
+    if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) {
+      Set<String> excludedAnnotations =
+          union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type));
+
+      return copyAnnotations(type, type, excludedAnnotations);
+    } else {
+      return ImmutableList.of();
+    }
+  }
+
+  /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
+  private ImmutableList<String> copyAnnotations(
+      Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
+    ImmutableList<AnnotationMirror> annotationsToCopy =
+        annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations);
+    return annotationStrings(annotationsToCopy);
+  }
+
+  /**
+   * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
+   * {@code TypeMirror} where each type is an annotation type.
+   */
+  private Set<TypeMirror> getExcludedAnnotationTypes(Element element) {
+    Optional<AnnotationMirror> maybeAnnotation =
+        getAnnotationMirror(element, COPY_ANNOTATIONS_NAME);
+    if (!maybeAnnotation.isPresent()) {
+      return ImmutableSet.of();
+    }
+
+    @SuppressWarnings("unchecked")
+    List<AnnotationValue> excludedClasses =
+        (List<AnnotationValue>) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue();
+    return excludedClasses
+        .stream()
+        .map(annotationValue -> (DeclaredType) annotationValue.getValue())
+        .collect(toCollection(TypeMirrorSet::new));
+  }
+
+  /**
+   * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
+   * strings that are fully-qualified class names.
+   */
+  private Set<String> getExcludedAnnotationClassNames(Element element) {
+    return getExcludedAnnotationTypes(element)
+        .stream()
+        .map(MoreTypes::asTypeElement)
+        .map(typeElement -> typeElement.getQualifiedName().toString())
+        .collect(toSet());
+  }
+
+  private static Set<String> getAnnotationsMarkedWithInherited(Element element) {
+    return element
+        .getAnnotationMirrors()
+        .stream()
+        .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class))
+        .map(a -> getAnnotationFqName(a))
+        .collect(toSet());
+  }
+
+  /**
+   * Returns the fully-qualified name of an annotation-mirror, e.g.
+   * "com.google.auto.value.AutoValue".
+   */
+  private static String getAnnotationFqName(AnnotationMirror annotation) {
+    return ((QualifiedNameable) annotation.getAnnotationType().asElement())
+        .getQualifiedName()
+        .toString();
+  }
+
+  final ImmutableListMultimap<ExecutableElement, AnnotationMirror> propertyMethodAnnotationMap(
+      TypeElement type, ImmutableSet<ExecutableElement> propertyMethods) {
+    ImmutableListMultimap.Builder<ExecutableElement, AnnotationMirror> builder =
+        ImmutableListMultimap.builder();
+    for (ExecutableElement propertyMethod : propertyMethods) {
+      builder.putAll(propertyMethod, propertyMethodAnnotations(type, propertyMethod));
+    }
+    return builder.build();
+  }
+
+  private ImmutableList<AnnotationMirror> propertyMethodAnnotations(
+      TypeElement type, ExecutableElement method) {
+    ImmutableSet<String> excludedAnnotations =
+        ImmutableSet.<String>builder()
+            .addAll(getExcludedAnnotationClassNames(method))
+            .add(Override.class.getCanonicalName())
+            .build();
+
+    // We need to exclude type annotations from the ones being output on the method, since
+    // they will be output as part of the method's return type.
+    Set<String> returnTypeAnnotations = getReturnTypeAnnotations(method, a -> true);
+    Set<String> excluded = union(excludedAnnotations, returnTypeAnnotations);
+    return annotationsToCopy(type, method, excluded);
+  }
+
+  final ImmutableListMultimap<ExecutableElement, AnnotationMirror> propertyFieldAnnotationMap(
+      TypeElement type, ImmutableSet<ExecutableElement> propertyMethods) {
+    ImmutableListMultimap.Builder<ExecutableElement, AnnotationMirror> builder =
+        ImmutableListMultimap.builder();
+    for (ExecutableElement propertyMethod : propertyMethods) {
+      builder.putAll(propertyMethod, propertyFieldAnnotations(type, propertyMethod));
+    }
+    return builder.build();
+  }
+
+  private ImmutableList<AnnotationMirror> propertyFieldAnnotations(
+      TypeElement type, ExecutableElement method) {
+    if (!hasAnnotationMirror(method, COPY_ANNOTATIONS_NAME)) {
+      return ImmutableList.of();
+    }
+    ImmutableSet<String> excludedAnnotations =
+        ImmutableSet.<String>builder()
+            .addAll(getExcludedAnnotationClassNames(method))
+            .add(Override.class.getCanonicalName())
+            .build();
+
+    // We need to exclude type annotations from the ones being output on the method, since
+    // they will be output as part of the field's type.
+    Set<String> returnTypeAnnotations =
+        getReturnTypeAnnotations(method, this::annotationAppliesToFields);
+    Set<String> nonFieldAnnotations =
+        method
+            .getAnnotationMirrors()
+            .stream()
+            .map(a -> a.getAnnotationType().asElement())
+            .map(MoreElements::asType)
+            .filter(a -> !annotationAppliesToFields(a))
+            .map(e -> e.getQualifiedName().toString())
+            .collect(toSet());
+
+    Set<String> excluded =
+        ImmutableSet.<String>builder()
+            .addAll(excludedAnnotations)
+            .addAll(returnTypeAnnotations)
+            .addAll(nonFieldAnnotations)
+            .build();
+    return annotationsToCopy(type, method, excluded);
+  }
+
+  private Set<String> getReturnTypeAnnotations(
+      ExecutableElement method, Predicate<TypeElement> typeFilter) {
+    return method
+        .getReturnType()
+        .getAnnotationMirrors()
+        .stream()
+        .map(a -> a.getAnnotationType().asElement())
+        .map(MoreElements::asType)
+        .filter(typeFilter)
+        .map(e -> e.getQualifiedName().toString())
+        .collect(toSet());
+  }
+
+  private boolean annotationAppliesToFields(TypeElement annotation) {
+    Target target = annotation.getAnnotation(Target.class);
+    return target == null || Arrays.asList(target.value()).contains(ElementType.FIELD);
+  }
+
+  private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) {
+    Element annotationElement = annotation.getAnnotationType().asElement();
+    Visibility visibility = Visibility.effectiveVisibilityOfElement(annotationElement);
+    switch (visibility) {
+      case PUBLIC:
+        return true;
+      case PROTECTED:
+        // If the annotation is protected, it must be inside another class, call it C. If our
+        // @AutoValue class is Foo then, for the annotation to be visible, either Foo must be in the
+        // same package as C or Foo must be a subclass of C. If the annotation is visible from Foo
+        // then it is also visible from our generated subclass AutoValue_Foo.
+        // The protected case only applies to method annotations. An annotation on the AutoValue_Foo
+        // class itself can't be protected, even if AutoValue_Foo ultimately inherits from the
+        // class that defines the annotation. The JLS says "Access is permitted only within the
+        // body of a subclass":
+        // https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.6.2.1
+        // AutoValue_Foo is a top-level class, so an annotation on it cannot be in the body of a
+        // subclass of anything.
+        return getPackage(annotationElement).equals(getPackage(from))
+            || typeUtils()
+                .isSubtype(from.asType(), annotationElement.getEnclosingElement().asType());
+      case DEFAULT:
+        return getPackage(annotationElement).equals(getPackage(from));
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Returns the {@code @AutoValue} or {@code @AutoOneOf} type parameters, with a ? for every type.
+   * If we have {@code @AutoValue abstract class Foo<T extends Something>} then this method will
+   * return just {@code <?>}.
+   */
+  private static String wildcardTypeParametersString(TypeElement type) {
+    List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
+    if (typeParameters.isEmpty()) {
+      return "";
+    } else {
+      return typeParameters.stream().map(e -> "?").collect(joining(", ", "<", ">"));
+    }
+  }
+
+  // TODO(emcmanus,ronshapiro): move to auto-common
+  static Optional<AnnotationMirror> getAnnotationMirror(Element element, String annotationName) {
+    for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
+      TypeElement annotationElement = MoreTypes.asTypeElement(annotation.getAnnotationType());
+      if (annotationElement.getQualifiedName().contentEquals(annotationName)) {
+        return Optional.of(annotation);
+      }
+    }
+    return Optional.empty();
+  }
+
+  static boolean hasAnnotationMirror(Element element, String annotationName) {
+    return getAnnotationMirror(element, annotationName).isPresent();
+  }
+
+  final void writeSourceFile(String className, String text, TypeElement originatingType) {
+    try {
+      JavaFileObject sourceFile =
+          processingEnv.getFiler().createSourceFile(className, originatingType);
+      try (Writer writer = sourceFile.openWriter()) {
+        writer.write(text);
+      }
+    } catch (IOException e) {
+      // This should really be an error, but we make it a warning in the hope of resisting Eclipse
+      // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599. If that bug manifests, we may get
+      // invoked more than once for the same file, so ignoring the ability to overwrite it is the
+      // right thing to do. If we are unable to write for some other reason, we should get a compile
+      // error later because user code will have a reference to the code we were supposed to
+      // generate (new AutoValue_Foo() or whatever) and that reference will be undefined.
+      errorReporter.reportWarning(
+          originatingType, "Could not write generated class %s: %s", className, e);
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfTemplateVars.java
new file mode 100644
index 0000000..9fdbea4
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfTemplateVars.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018 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.processor;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * The variables to substitute into the autovalue.vm or autooneof.vm template.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@SuppressWarnings("unused") // the fields in this class are only read via reflection
+abstract class AutoValueOrOneOfTemplateVars extends TemplateVars {
+  /** Whether to generate an equals(Object) method. */
+  Boolean equals;
+
+  /** Whether to generate a hashCode() method. */
+  Boolean hashCode;
+
+  /** Whether to generate a toString() method. */
+  Boolean toString;
+
+  /**
+   * A string representing the parameter type declaration of the equals(Object) method, including
+   * any annotations. If {@link #equals} is false, this field is ignored (but it must still be
+   * non-null).
+   */
+  String equalsParameterType;
+
+  /** The encoding of the {@code Generated} class. Empty if the class is not available. */
+  String generated;
+
+  /** The package of the class with the {@code @AutoValue} annotation and its generated subclass. */
+  String pkg;
+
+  /**
+   * The name of the class with the {@code @AutoValue} annotation, including containing classes but
+   * not including the package name.
+   */
+  String origClass;
+
+  /** The simple name of the class with the {@code @AutoValue} annotation. */
+  String simpleClassName;
+
+  /**
+   * The full spelling of any annotations to add to this class, or an empty list if there are none.
+   * A non-empty value might look something like {@code
+   * "@`com.google.common.annotations.GwtCompatible`(serializable = true)"}. The {@code ``} marks
+   * are explained in {@link TypeEncoder}.
+   */
+  ImmutableList<String> annotations;
+
+  /**
+   * The formal generic signature of the class with the {@code @AutoValue} or {@code AutoOneOf}
+   * annotation and any generated subclass. This is empty, or contains type variables with optional
+   * bounds, for example {@code <K, V extends K>}.
+   */
+  String formalTypes;
+
+  /**
+   * The generic signature used by any generated subclass for its superclass reference. This is
+   * empty, or contains only type variables with no bounds, for example {@code <K, V>}.
+   */
+  String actualTypes;
+
+  /**
+   * The generic signature in {@link #actualTypes} where every variable has been replaced by a
+   * wildcard, for example {@code <?, ?>}.
+   */
+  String wildcardTypes;
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
new file mode 100644
index 0000000..f3b396c
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright 2012 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.processor;
+
+import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
+import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME;
+import static com.google.common.collect.Sets.difference;
+import static com.google.common.collect.Sets.intersection;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+
+import com.google.auto.service.AutoService;
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.ServiceConfigurationError;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+
+/**
+ * Javac annotation processor (compiler plugin) for value types; user code never references this
+ * class.
+ *
+ * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
+ * @author Éamonn McManus
+ */
+@AutoService(Processor.class)
+@SupportedAnnotationTypes(AUTO_VALUE_NAME)
+@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
+public class AutoValueProcessor extends AutoValueOrOneOfProcessor {
+  private static final String OMIT_IDENTIFIERS_OPTION = "com.google.auto.value.OmitIdentifiers";
+
+  // We moved MemoizeExtension to a different package, which had an unexpected effect:
+  // now if an old version of AutoValue is in the class path, ServiceLoader can pick up both the
+  // old and the new versions of MemoizeExtension. So we exclude the old version if we see it.
+  // The new version will be bundled with this processor so we should always find it.
+  private static final String OLD_MEMOIZE_EXTENSION =
+      "com.google.auto.value.extension.memoized.MemoizeExtension";
+
+  public AutoValueProcessor() {
+    this(AutoValueProcessor.class.getClassLoader());
+  }
+
+  @VisibleForTesting
+  AutoValueProcessor(ClassLoader loaderForExtensions) {
+    super(AUTO_VALUE_NAME);
+    this.extensions = null;
+    this.loaderForExtensions = loaderForExtensions;
+  }
+
+  @VisibleForTesting
+  public AutoValueProcessor(Iterable<? extends AutoValueExtension> extensions) {
+    super(AUTO_VALUE_NAME);
+    this.extensions = ImmutableList.copyOf(extensions);
+    this.loaderForExtensions = null;
+  }
+
+  // Depending on how this AutoValueProcessor was constructed, we might already have a list of
+  // extensions when init() is run, or, if `extensions` is null, we have a ClassLoader that will be
+  // used to get the list using the ServiceLoader API.
+  private ImmutableList<AutoValueExtension> extensions;
+  private final ClassLoader loaderForExtensions;
+
+  @VisibleForTesting
+  static ImmutableList<AutoValueExtension> extensionsFromLoader(ClassLoader loader) {
+    return ImmutableList.copyOf(
+        Iterables.filter(
+            SimpleServiceLoader.load(AutoValueExtension.class, loader),
+            ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION)));
+  }
+
+  @Override
+  public synchronized void init(ProcessingEnvironment processingEnv) {
+    super.init(processingEnv);
+
+    if (extensions == null) {
+      try {
+        extensions = extensionsFromLoader(loaderForExtensions);
+      } catch (RuntimeException | Error e) {
+        String explain =
+            (e instanceof ServiceConfigurationError)
+                ? " This may be due to a corrupt jar file in the compiler's classpath."
+                : "";
+        errorReporter()
+            .reportWarning(
+                null,
+                "An exception occurred while looking for AutoValue extensions."
+                    + " No extensions will function.%s\n%s",
+                explain,
+                Throwables.getStackTraceAsString(e));
+        extensions = ImmutableList.of();
+      }
+    }
+  }
+
+  @Override
+  public Set<String> getSupportedOptions() {
+    ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+    AutoValueExtension.IncrementalExtensionType incrementalType =
+        extensions.stream()
+            .map(e -> e.incrementalType(processingEnv))
+            .min(Comparator.naturalOrder())
+            .orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING);
+    builder.add(OMIT_IDENTIFIERS_OPTION).addAll(optionsFor(incrementalType));
+    for (AutoValueExtension extension : extensions) {
+      builder.addAll(extension.getSupportedOptions());
+    }
+    return builder.build();
+  }
+
+  private static ImmutableSet<String> optionsFor(
+      AutoValueExtension.IncrementalExtensionType incrementalType) {
+    switch (incrementalType) {
+      case ISOLATING:
+        return ImmutableSet.of(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption());
+      case AGGREGATING:
+        return ImmutableSet.of(IncrementalAnnotationProcessorType.AGGREGATING.getProcessorOption());
+      case UNKNOWN:
+        return ImmutableSet.of();
+    }
+    throw new AssertionError(incrementalType);
+  }
+
+  static String generatedSubclassName(TypeElement type, int depth) {
+    return generatedClassName(type, Strings.repeat("$", depth) + "AutoValue_");
+  }
+
+  @Override
+  void processType(TypeElement type) {
+    if (!hasAnnotationMirror(type, AUTO_VALUE_NAME)) {
+      // This shouldn't happen unless the compilation environment is buggy,
+      // but it has happened in the past and can crash the compiler.
+      errorReporter()
+          .abortWithError(
+              type,
+              "annotation processor for @AutoValue was invoked with a type"
+                  + " that does not have that annotation; this is probably a compiler bug");
+    }
+    if (type.getKind() != ElementKind.CLASS) {
+      errorReporter().abortWithError(type, "@AutoValue only applies to classes");
+    }
+    if (ancestorIsAutoValue(type)) {
+      errorReporter().abortWithError(type, "One @AutoValue class may not extend another");
+    }
+    if (implementsAnnotation(type)) {
+      errorReporter()
+          .abortWithError(
+              type, "@AutoValue may not be used to implement an annotation"
+                  + " interface; try using @AutoAnnotation instead");
+    }
+    checkModifiersIfNested(type);
+
+    // We are going to classify the methods of the @AutoValue class into several categories.
+    // This covers the methods in the class itself and the ones it inherits from supertypes.
+    // First, the only concrete (non-abstract) methods we are interested in are overrides of
+    // Object methods (equals, hashCode, toString), which signal that we should not generate
+    // an implementation of those methods.
+    // Then, each abstract method is one of the following:
+    // (1) A property getter, like "abstract String foo()" or "abstract String getFoo()".
+    // (2) A toBuilder() method, which is any abstract no-arg method returning the Builder for
+    //     this @AutoValue class.
+    // (3) An abstract method that will be consumed by an extension, such as
+    //     Parcelable.describeContents() or Parcelable.writeToParcel(Parcel, int).
+    // The describeContents() example shows a quirk here: initially we will identify it as a
+    // property, which means that we need to reconstruct the list of properties after allowing
+    // extensions to consume abstract methods.
+    // If there are abstract methods that don't fit any of the categories above, that is an error
+    // which we signal explicitly to avoid confusion.
+
+    ImmutableSet<ExecutableElement> methods =
+        getLocalAndInheritedMethods(
+            type, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
+    ImmutableSet<ExecutableElement> abstractMethods = abstractMethodsIn(methods);
+
+    BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter());
+    Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder();
+    ImmutableSet<ExecutableElement> toBuilderMethods;
+    if (builder.isPresent()) {
+      toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), abstractMethods);
+    } else {
+      toBuilderMethods = ImmutableSet.of();
+    }
+
+    ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes =
+        propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type);
+    ImmutableMap<String, ExecutableElement> properties =
+        propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
+
+    ExtensionContext context = new ExtensionContext(
+        processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods);
+    ImmutableList<AutoValueExtension> applicableExtensions = applicableExtensions(type, context);
+    ImmutableSet<ExecutableElement> consumedMethods =
+        methodsConsumedByExtensions(
+            type, applicableExtensions, context, abstractMethods, properties);
+
+    if (!consumedMethods.isEmpty()) {
+      ImmutableSet<ExecutableElement> allAbstractMethods = abstractMethods;
+      abstractMethods = immutableSetDifference(abstractMethods, consumedMethods);
+      toBuilderMethods = immutableSetDifference(toBuilderMethods, consumedMethods);
+      propertyMethodsAndTypes =
+          propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type);
+      properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
+      context = new ExtensionContext(
+          processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods);
+    }
+
+    ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet();
+    boolean extensionsPresent = !applicableExtensions.isEmpty();
+    validateMethods(type, abstractMethods, toBuilderMethods, propertyMethods, extensionsPresent);
+
+    String finalSubclass = generatedSubclassName(type, 0);
+    AutoValueTemplateVars vars = new AutoValueTemplateVars();
+    vars.finalSubclass = TypeSimplifier.simpleNameOf(finalSubclass);
+    vars.types = processingEnv.getTypeUtils();
+    vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
+    defineSharedVarsForType(type, methods, vars);
+    defineVarsForType(type, vars, toBuilderMethods, propertyMethodsAndTypes, builder);
+
+    // If we've encountered problems then we might end up invoking extensions with inconsistent
+    // state. Anyway we probably don't want to generate code which is likely to provoke further
+    // compile errors to add to the ones we've already seen.
+    errorReporter().abortIfAnyError();
+
+    GwtCompatibility gwtCompatibility = new GwtCompatibility(type);
+    vars.gwtCompatibleAnnotation = gwtCompatibility.gwtCompatibleAnnotationString();
+
+    builder.ifPresent(context::setBuilderContext);
+    int subclassDepth = writeExtensions(type, context, applicableExtensions);
+    String subclass = generatedSubclassName(type, subclassDepth);
+    vars.subclass = TypeSimplifier.simpleNameOf(subclass);
+    vars.isFinal = (subclassDepth == 0);
+    vars.modifiers = vars.isFinal ? "final " : "abstract ";
+
+    String text = vars.toText();
+    text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType());
+    text = Reformatter.fixup(text);
+    writeSourceFile(subclass, text, type);
+    GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type);
+    gwtSerialization.maybeWriteGwtSerializer(vars);
+  }
+
+  // Invokes each of the given extensions to generate its subclass, and returns the number of
+  // hierarchy classes that extensions generated. This number is then the number of $ characters
+  // that should precede the name of the AutoValue implementation class.
+  // Assume the @AutoValue class is com.example.Foo.Bar. Then if there are no
+  // extensions the returned value will be 0, so the AutoValue implementation will be
+  // com.example.AutoValue_Foo_Bar. If there is one extension, it will be asked to
+  // generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar. If it does so (returns
+  // non-null) then the returned value will be 1, so the AutoValue implementation will be
+  // com.example.$AutoValue_Foo_Bar. Otherwise, the returned value will still be 0. Likewise,
+  // if there is a second extension and both extensions return non-null, the first one will
+  // generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar, the second will generate
+  // $AutoValue_Foo_Bar with parent $$AutoValue_Foo_Bar, and the returned value will be 2 for
+  // com.example.$$AutoValue_Foo_Bar.
+  private int writeExtensions(
+      TypeElement type,
+      ExtensionContext context,
+      ImmutableList<AutoValueExtension> applicableExtensions) {
+    int writtenSoFar = 0;
+    for (AutoValueExtension extension : applicableExtensions) {
+      String parentFqName = generatedSubclassName(type, writtenSoFar + 1);
+      String parentSimpleName = TypeSimplifier.simpleNameOf(parentFqName);
+      String classFqName = generatedSubclassName(type, writtenSoFar);
+      String classSimpleName = TypeSimplifier.simpleNameOf(classFqName);
+      boolean isFinal = (writtenSoFar == 0);
+      String source = extension.generateClass(context, classSimpleName, parentSimpleName, isFinal);
+      if (source != null) {
+        source = Reformatter.fixup(source);
+        writeSourceFile(classFqName, source, type);
+        writtenSoFar++;
+      }
+    }
+    return writtenSoFar;
+  }
+
+  private ImmutableList<AutoValueExtension> applicableExtensions(
+      TypeElement type, ExtensionContext context) {
+    List<AutoValueExtension> applicableExtensions = new ArrayList<>();
+    List<AutoValueExtension> finalExtensions = new ArrayList<>();
+    for (AutoValueExtension extension : extensions) {
+      if (extension.applicable(context)) {
+        if (extension.mustBeFinal(context)) {
+          finalExtensions.add(extension);
+        } else {
+          applicableExtensions.add(extension);
+        }
+      }
+    }
+    switch (finalExtensions.size()) {
+      case 0:
+        break;
+      case 1:
+        applicableExtensions.add(0, finalExtensions.get(0));
+        break;
+      default:
+        errorReporter()
+            .reportError(
+                type,
+                "More than one extension wants to generate the final class: %s",
+                finalExtensions.stream().map(this::extensionName).collect(joining(", ")));
+        break;
+    }
+    return ImmutableList.copyOf(applicableExtensions);
+  }
+
+  private ImmutableSet<ExecutableElement> methodsConsumedByExtensions(
+      TypeElement type,
+      ImmutableList<AutoValueExtension> applicableExtensions,
+      ExtensionContext context,
+      ImmutableSet<ExecutableElement> abstractMethods,
+      ImmutableMap<String, ExecutableElement> properties) {
+    Set<ExecutableElement> consumed = new HashSet<>();
+    for (AutoValueExtension extension : applicableExtensions) {
+      Set<ExecutableElement> consumedHere = new HashSet<>();
+      for (String consumedProperty : extension.consumeProperties(context)) {
+        ExecutableElement propertyMethod = properties.get(consumedProperty);
+        if (propertyMethod == null) {
+          errorReporter()
+              .reportError(
+                  type,
+                  "Extension %s wants to consume a property that does not exist: %s",
+                  extensionName(extension),
+                  consumedProperty);
+        } else {
+          consumedHere.add(propertyMethod);
+        }
+      }
+      for (ExecutableElement consumedMethod : extension.consumeMethods(context)) {
+        if (!abstractMethods.contains(consumedMethod)) {
+          errorReporter()
+              .reportError(
+                  type,
+                  "Extension %s wants to consume a method that is not one of the abstract methods"
+                      + " in this class: %s",
+                  extensionName(extension),
+                  consumedMethod);
+        } else {
+          consumedHere.add(consumedMethod);
+        }
+      }
+      for (ExecutableElement repeat : intersection(consumed, consumedHere)) {
+        errorReporter()
+            .reportError(
+                repeat,
+                "Extension %s wants to consume a method that was already consumed by another"
+                    + " extension",
+                extensionName(extension));
+      }
+      consumed.addAll(consumedHere);
+    }
+    return ImmutableSet.copyOf(consumed);
+  }
+
+  private void validateMethods(
+      TypeElement type,
+      ImmutableSet<ExecutableElement> abstractMethods,
+      ImmutableSet<ExecutableElement> toBuilderMethods,
+      ImmutableSet<ExecutableElement> propertyMethods,
+      boolean extensionsPresent) {
+    for (ExecutableElement method : abstractMethods) {
+      if (propertyMethods.contains(method)) {
+        checkReturnType(type, method);
+      } else if (!toBuilderMethods.contains(method)
+          && objectMethodToOverride(method) == ObjectMethod.NONE) {
+        // This could reasonably be an error, were it not for an Eclipse bug in
+        // ElementUtils.override that sometimes fails to recognize that one method overrides
+        // another, and therefore leaves us with both an abstract method and the subclass method
+        // that overrides it. This shows up in AutoValueTest.LukesBase for example.
+        String extensionMessage = extensionsPresent ? ", and no extension consumed it" : "";
+        errorReporter().reportWarning(
+            method,
+            "Abstract method is neither a property getter nor a Builder converter%s",
+            extensionMessage);
+      }
+    }
+    errorReporter().abortIfAnyError();
+  }
+
+  private String extensionName(AutoValueExtension extension) {
+    return extension.getClass().getName();
+  }
+
+  private void defineVarsForType(
+      TypeElement type,
+      AutoValueTemplateVars vars,
+      ImmutableSet<ExecutableElement> toBuilderMethods,
+      ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
+      Optional<BuilderSpec.Builder> maybeBuilder) {
+    ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet();
+    // We can't use ImmutableList.toImmutableList() for obscure Google-internal reasons.
+    vars.toBuilderMethods =
+        ImmutableList.copyOf(toBuilderMethods.stream().map(SimpleMethod::new).collect(toList()));
+    ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields =
+        propertyFieldAnnotationMap(type, propertyMethods);
+    ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods =
+        propertyMethodAnnotationMap(type, propertyMethods);
+    vars.props =
+        propertySet(propertyMethodsAndTypes, annotatedPropertyFields, annotatedPropertyMethods);
+    vars.serialVersionUID = getSerialVersionUID(type);
+    // Check for @AutoValue.Builder and add appropriate variables if it is present.
+    maybeBuilder.ifPresent(
+        builder -> {
+          ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
+              propertyNameToMethodMap(propertyMethods).inverse();
+          builder.defineVars(vars, methodToPropertyName);
+          vars.builderAnnotations = copiedClassAnnotations(builder.builderType());
+        });
+  }
+
+  @Override
+  Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) {
+    return nullableAnnotationFor(propertyMethod, propertyMethod.getReturnType());
+  }
+
+  static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) {
+    ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder();
+    for (ExecutableElement method : methods) {
+      String name = method.getSimpleName().toString();
+      // TODO(emcmanus): decide whether getfoo() (without a capital) is a getter. Currently it is.
+      boolean get = name.startsWith("get") && !name.equals("get");
+      boolean is =
+          name.startsWith("is")
+              && !name.equals("is")
+              && method.getReturnType().getKind() == TypeKind.BOOLEAN;
+      if (get || is) {
+        getters.add(method);
+      }
+    }
+    return getters.build();
+  }
+
+  private boolean ancestorIsAutoValue(TypeElement type) {
+    while (true) {
+      TypeMirror parentMirror = type.getSuperclass();
+      if (parentMirror.getKind() == TypeKind.NONE) {
+        return false;
+      }
+      TypeElement parentElement = (TypeElement) typeUtils().asElement(parentMirror);
+      if (hasAnnotationMirror(parentElement, AUTO_VALUE_NAME)) {
+        return true;
+      }
+      type = parentElement;
+    }
+  }
+
+  private boolean implementsAnnotation(TypeElement type) {
+    return typeUtils().isAssignable(type.asType(), getTypeMirror(Annotation.class));
+  }
+
+  private TypeMirror getTypeMirror(Class<?> c) {
+    return processingEnv.getElementUtils().getTypeElement(c.getName()).asType();
+  }
+
+  private static <E> ImmutableSet<E> immutableSetDifference(ImmutableSet<E> a, ImmutableSet<E> b) {
+    if (Collections.disjoint(a, b)) {
+      return a;
+    } else {
+      return ImmutableSet.copyOf(difference(a, b));
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java
new file mode 100644
index 0000000..8f855bd
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2012 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.processor;
+
+import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.escapevelocity.Template;
+import java.util.Optional;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.util.Types;
+
+/**
+ * The variables to substitute into the autovalue.vm template.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@SuppressWarnings("unused") // the fields in this class are only read via reflection
+class AutoValueTemplateVars extends AutoValueOrOneOfTemplateVars {
+  /**
+   * The properties defined by the parent class's abstract methods. The elements of this set are in
+   * the same order as the original abstract method declarations in the AutoValue class.
+   */
+  ImmutableSet<AutoValueOrOneOfProcessor.Property> props;
+
+  /**
+   * Whether to include identifiers in strings in the generated code. If false, exception messages
+   * will not mention properties by name, and {@code toString()} will include neither property names
+   * nor the name of the {@code @AutoValue} class.
+   */
+  Boolean identifiers;
+
+  /** The type utilities returned by {@link ProcessingEnvironment#getTypeUtils()}. */
+  Types types;
+
+  /**
+   * The encoding of the {@code @GwtCompatible} annotation to add to this class, or an empty string
+   * if there is none. A non-empty value will look something like {@code
+   * "@`com.google.common.annotations.GwtCompatible`(serializable = true)"}, where the {@code ``}
+   * represent the encoding used by {@link TypeEncoder}.
+   */
+  String gwtCompatibleAnnotation;
+
+  /** The text of the serialVersionUID constant, or empty if there is none. */
+  String serialVersionUID;
+
+  /** The simple name of the generated subclass. */
+  String subclass;
+  /**
+   * The simple name of the final generated subclass. For {@code @AutoValue public static class Foo
+   * {}} this should always be "AutoValue_Foo".
+   */
+  String finalSubclass;
+
+  /**
+   * True if the generated class should be final (there are no extensions that will generate
+   * subclasses)
+   */
+  Boolean isFinal = false;
+
+  /**
+   * The modifiers (for example {@code final} or {@code abstract}) for the generated subclass,
+   * followed by a space if they are not empty.
+   */
+  String modifiers;
+
+  /**
+   * The name of the builder type as it should appear in source code, or empty if there is no
+   * builder type. If class {@code Address} contains {@code @AutoValue.Builder} class Builder then
+   * this will typically be {@code "Address.Builder"}.
+   */
+  String builderTypeName = "";
+
+  /**
+   * The formal generic signature of the {@code AutoValue.Builder} class. This is empty, or contains
+   * type variables with optional bounds, for example {@code <K, V extends K>}.
+   */
+  String builderFormalTypes = "";
+  /**
+   * The generic signature used by the generated builder subclass for its superclass reference. This
+   * is empty, or contains only type variables with no bounds, for example {@code <K, V>}.
+   */
+  String builderActualTypes = "";
+
+  /** True if the builder being implemented is an interface, false if it is an abstract class. */
+  Boolean builderIsInterface = false;
+
+  /**
+   * The full spelling of any annotations to add to the generated builder subclass, or an empty list
+   * if there are none. A non-empty value might look something like {@code
+   * @`java.lang.SuppressWarnings`("Immutable")}. The {@code ``} marks are explained in
+   * {@link TypeEncoder}.
+   */
+  ImmutableList<String> builderAnnotations = ImmutableList.of();
+
+  /** The builder's build method, often {@code "build"}. */
+  Optional<SimpleMethod> buildMethod = Optional.empty();
+
+  /**
+   * A multimap from property names (like foo) to the corresponding setters. The same property may
+   * be set by more than one setter. For example, an ImmutableList might be set by {@code
+   * setFoo(ImmutableList<String>)} and {@code setFoo(String[])}.
+   */
+  ImmutableMultimap<String, BuilderSpec.PropertySetter> builderSetters = ImmutableMultimap.of();
+
+  /**
+   * A map from property names to information about the associated property builder. A property
+   * called foo (defined by a method foo() or getFoo()) can have a property builder called
+   * fooBuilder(). The type of foo must be a type that has an associated builder following certain
+   * conventions. Guava immutable types such as ImmutableList follow those conventions, as do many
+   * {@code @AutoValue} types.
+   */
+  ImmutableMap<String, PropertyBuilder> builderPropertyBuilders = ImmutableMap.of();
+
+  /**
+   * Properties that are required to be set. A property must be set explicitly except in the
+   * following cases:
+   *
+   * <ul>
+   *   <li>it is {@code @Nullable} (in which case it defaults to null);
+   *   <li>it is {@code Optional} (in which case it defaults to empty);
+   *   <li>it has a property-builder method (in which case it defaults to empty).
+   * </ul>
+   */
+  ImmutableSet<AutoValueProcessor.Property> builderRequiredProperties = ImmutableSet.of();
+
+  /**
+   * A map from property names to information about the associated property getter. A property
+   * called foo (defined by a method foo() or getFoo()) can have a property getter method with the
+   * same name (foo() or getFoo()) and either the same return type or an Optional (or OptionalInt,
+   * etc) wrapping it.
+   */
+  ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters = ImmutableMap.of();
+
+  /** Any {@code toBuilder()} methods, that is methods that return the builder type. */
+  ImmutableList<SimpleMethod> toBuilderMethods;
+
+  private static final Template TEMPLATE = parsedTemplateForResource("autovalue.vm");
+
+  @Override
+  Template parsedTemplate() {
+    return TEMPLATE;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
new file mode 100644
index 0000000..724a322
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
@@ -0,0 +1,619 @@
+/*
+ * Copyright 2015 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.processor;
+
+import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.nullableAnnotationFor;
+import static com.google.common.collect.Sets.difference;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.processor.BuilderSpec.PropertySetter;
+import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
+import com.google.common.base.Equivalence;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+/**
+ * Classifies methods inside builder types, based on their names and parameter and return types.
+ *
+ * @author Éamonn McManus
+ */
+class BuilderMethodClassifier {
+  private static final Equivalence<TypeMirror> TYPE_EQUIVALENCE = MoreTypes.equivalence();
+
+  private final ErrorReporter errorReporter;
+  private final Types typeUtils;
+  private final Elements elementUtils;
+  private final TypeElement autoValueClass;
+  private final TypeElement builderType;
+  private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName;
+  private final ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType;
+  private final ImmutableMap<String, ExecutableElement> getterNameToGetter;
+
+  private final Set<ExecutableElement> buildMethods = new LinkedHashSet<>();
+  private final Map<String, BuilderSpec.PropertyGetter> builderGetters = new LinkedHashMap<>();
+  private final Map<String, PropertyBuilder> propertyNameToPropertyBuilder = new LinkedHashMap<>();
+  private final Multimap<String, PropertySetter> propertyNameToPrefixedSetters =
+      LinkedListMultimap.create();
+  private final Multimap<String, PropertySetter> propertyNameToUnprefixedSetters =
+      LinkedListMultimap.create();
+  private final EclipseHack eclipseHack;
+
+  private boolean settersPrefixed;
+
+  private BuilderMethodClassifier(
+      ErrorReporter errorReporter,
+      ProcessingEnvironment processingEnv,
+      TypeElement autoValueClass,
+      TypeElement builderType,
+      ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
+      ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType) {
+    this.errorReporter = errorReporter;
+    this.typeUtils = processingEnv.getTypeUtils();
+    this.elementUtils = processingEnv.getElementUtils();
+    this.autoValueClass = autoValueClass;
+    this.builderType = builderType;
+    this.getterToPropertyName = getterToPropertyName;
+    this.getterToPropertyType = getterToPropertyType;
+    ImmutableMap.Builder<String, ExecutableElement> getterToPropertyNameBuilder =
+        ImmutableMap.builder();
+    for (ExecutableElement getter : getterToPropertyName.keySet()) {
+      getterToPropertyNameBuilder.put(getter.getSimpleName().toString(), getter);
+    }
+    this.getterNameToGetter = getterToPropertyNameBuilder.build();
+    this.eclipseHack = new EclipseHack(processingEnv);
+  }
+
+  /**
+   * Classifies the given methods from a builder type and its ancestors.
+   *
+   * @param methods the abstract methods in {@code builderType} and its ancestors.
+   * @param errorReporter where to report errors.
+   * @param processingEnv the ProcessingEnvironment for annotation processing.
+   * @param autoValueClass the {@code AutoValue} class containing the builder.
+   * @param builderType the builder class or interface within {@code autoValueClass}.
+   * @param getterToPropertyName a map from getter methods to the properties they get.
+   * @param getterToPropertyType a map from getter methods to their return types. The return types
+   *     here use type parameters from the builder class (if any) rather than from the {@code
+   *     AutoValue} class, even though the getter methods are in the latter.
+   * @param autoValueHasToBuilder true if the containing {@code @AutoValue} class has a {@code
+   *     toBuilder()} method.
+   * @return an {@code Optional} that contains the results of the classification if it was
+   *     successful or nothing if it was not.
+   */
+  static Optional<BuilderMethodClassifier> classify(
+      Iterable<ExecutableElement> methods,
+      ErrorReporter errorReporter,
+      ProcessingEnvironment processingEnv,
+      TypeElement autoValueClass,
+      TypeElement builderType,
+      ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
+      ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType,
+      boolean autoValueHasToBuilder) {
+    BuilderMethodClassifier classifier =
+        new BuilderMethodClassifier(
+            errorReporter,
+            processingEnv,
+            autoValueClass,
+            builderType,
+            getterToPropertyName,
+            getterToPropertyType);
+    if (classifier.classifyMethods(methods, autoValueHasToBuilder)) {
+      return Optional.of(classifier);
+    } else {
+      return Optional.empty();
+    }
+  }
+
+  /**
+   * Returns a multimap from the name of a property to the methods that set it. If the property is
+   * defined by an abstract method in the {@code @AutoValue} class called {@code foo()} or {@code
+   * getFoo()} then the name of the property is {@code foo} and there will be an entry in the map
+   * where the key is {@code "foo"} and the value describes a method in the builder called
+   * {@code foo} or {@code setFoo}.
+   */
+  ImmutableMultimap<String, PropertySetter> propertyNameToSetters() {
+    return ImmutableMultimap.copyOf(
+        settersPrefixed ? propertyNameToPrefixedSetters : propertyNameToUnprefixedSetters);
+  }
+
+  Map<String, PropertyBuilder> propertyNameToPropertyBuilder() {
+    return propertyNameToPropertyBuilder;
+  }
+
+  /**
+   * Returns the set of properties that have getters in the builder. If a property is defined by an
+   * abstract method in the {@code @AutoValue} class called {@code foo()} or {@code getFoo()} then
+   * the name of the property is {@code foo}, If the builder also has a method of the same name
+   * ({@code foo()} or {@code getFoo()}) then the set returned here will contain {@code foo}.
+   */
+  ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters() {
+    return ImmutableMap.copyOf(builderGetters);
+  }
+
+  /**
+   * Returns the methods that were identified as {@code build()} methods. These are methods that
+   * have no parameters and return the {@code @AutoValue} type, conventionally called {@code
+   * build()}.
+   */
+  Set<ExecutableElement> buildMethods() {
+    return ImmutableSet.copyOf(buildMethods);
+  }
+
+  /** Classifies the given methods and sets the state of this object based on what is found. */
+  private boolean classifyMethods(
+      Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) {
+    int startErrorCount = errorReporter.errorCount();
+    for (ExecutableElement method : methods) {
+      classifyMethod(method);
+    }
+    if (errorReporter.errorCount() > startErrorCount) {
+      return false;
+    }
+    Multimap<String, PropertySetter> propertyNameToSetter;
+    if (propertyNameToPrefixedSetters.isEmpty()) {
+      propertyNameToSetter = propertyNameToUnprefixedSetters;
+      this.settersPrefixed = false;
+    } else if (propertyNameToUnprefixedSetters.isEmpty()) {
+      propertyNameToSetter = propertyNameToPrefixedSetters;
+      this.settersPrefixed = true;
+    } else {
+      errorReporter.reportError(
+          propertyNameToUnprefixedSetters.values().iterator().next().getSetter(),
+          "If any setter methods use the setFoo convention then all must");
+      return false;
+    }
+    getterToPropertyName.forEach(
+        (getter, property) -> {
+          TypeMirror propertyType = getterToPropertyType.get(getter);
+          boolean hasSetter = propertyNameToSetter.containsKey(property);
+          PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property);
+          boolean hasBuilder = propertyBuilder != null;
+          if (hasBuilder) {
+            // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must
+            // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a
+            // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is
+            // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll
+            // method that accepts a Bar argument.
+            boolean canMakeBarBuilder =
+                (propertyBuilder.getBuiltToBuilder() != null
+                    || propertyBuilder.getCopyAll() != null);
+            boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter);
+            if (needToMakeBarBuilder && !canMakeBarBuilder) {
+              errorReporter.reportError(
+                  propertyBuilder.getPropertyBuilderMethod(),
+                  "Property builder method returns %1$s but there is no way to make that type"
+                      + " from %2$s: %2$s does not have a non-static toBuilder() method that"
+                      + " returns %1$s, and %1$s does not have a method addAll or"
+                      + " putAll that accepts an argument of type %2$s",
+                  propertyBuilder.getBuilderTypeMirror(),
+                  propertyType);
+            }
+          } else if (!hasSetter) {
+            // We have neither barBuilder() nor setBar(Bar), so we should complain.
+            String setterName = settersPrefixed ? prefixWithSet(property) : property;
+            errorReporter.reportError(
+                builderType,
+                "Expected a method with this signature: %s%s %s(%s), or a %sBuilder() method",
+                builderType, typeParamsString(), setterName, propertyType, property);
+          }
+        });
+    return errorReporter.errorCount() == startErrorCount;
+  }
+
+  /** Classifies a method and update the state of this object based on what is found. */
+  private void classifyMethod(ExecutableElement method) {
+    switch (method.getParameters().size()) {
+      case 0:
+        classifyMethodNoArgs(method);
+        break;
+      case 1:
+        classifyMethodOneArg(method);
+        break;
+      default:
+        errorReporter.reportError(method, "Builder methods must have 0 or 1 parameters");
+    }
+  }
+
+  /**
+   * Classifies a method given that it has no arguments. Currently a method with no arguments can be
+   * a {@code build()} method, meaning that its return type must be the {@code @AutoValue} class; it
+   * can be a getter, with the same signature as one of the property getters in the
+   * {@code @AutoValue} class; or it can be a property builder, like {@code
+   * ImmutableList.Builder<String> foosBuilder()} for the property defined by {@code
+   * ImmutableList<String> foos()} or {@code getFoos()}.
+   */
+  private void classifyMethodNoArgs(ExecutableElement method) {
+    String methodName = method.getSimpleName().toString();
+    TypeMirror returnType = builderMethodReturnType(method);
+
+    ExecutableElement getter = getterNameToGetter.get(methodName);
+    if (getter != null) {
+      classifyGetter(method, getter);
+      return;
+    }
+
+    if (methodName.endsWith("Builder")) {
+      String property = methodName.substring(0, methodName.length() - "Builder".length());
+      if (getterToPropertyName.containsValue(property)) {
+        PropertyBuilderClassifier propertyBuilderClassifier =
+            new PropertyBuilderClassifier(
+                errorReporter,
+                typeUtils,
+                elementUtils,
+                this,
+                getterToPropertyName,
+                getterToPropertyType,
+                eclipseHack);
+        Optional<PropertyBuilder> propertyBuilder =
+            propertyBuilderClassifier.makePropertyBuilder(method, property);
+        if (propertyBuilder.isPresent()) {
+          propertyNameToPropertyBuilder.put(property, propertyBuilder.get());
+        }
+        return;
+      }
+    }
+
+    if (TYPE_EQUIVALENCE.equivalent(returnType, autoValueClass.asType())) {
+      buildMethods.add(method);
+    } else {
+      errorReporter.reportError(
+          method,
+          "Method without arguments should be a build method returning %1$s%2$s,"
+              + " or a getter method with the same name and type as a getter method of %1$s,"
+              + " or fooBuilder() where foo() or getFoo() is a getter method of %1$s",
+          autoValueClass, typeParamsString());
+    }
+  }
+
+  private void classifyGetter(ExecutableElement builderGetter, ExecutableElement originalGetter) {
+    String propertyName = getterToPropertyName.get(originalGetter);
+    TypeMirror originalGetterType = getterToPropertyType.get(originalGetter);
+    TypeMirror builderGetterType = builderMethodReturnType(builderGetter);
+    String builderGetterTypeString = TypeEncoder.encodeWithAnnotations(builderGetterType);
+    if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) {
+      builderGetters.put(
+          propertyName,
+          new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, null));
+      return;
+    }
+    Optionalish optional = Optionalish.createIfOptional(builderGetterType);
+    if (optional != null) {
+      TypeMirror containedType = optional.getContainedType(typeUtils);
+      // If the original method is int getFoo() then we allow Optional<Integer> here.
+      // boxedOriginalType is Integer, and containedType is also Integer.
+      // We don't need any special code for OptionalInt because containedType will be int then.
+      TypeMirror boxedOriginalType =
+          originalGetterType.getKind().isPrimitive()
+              ? typeUtils.boxedClass(MoreTypes.asPrimitiveType(originalGetterType)).asType()
+              : null;
+      if (TYPE_EQUIVALENCE.equivalent(containedType, originalGetterType)
+          || TYPE_EQUIVALENCE.equivalent(containedType, boxedOriginalType)) {
+        builderGetters.put(
+            propertyName,
+            new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, optional));
+        return;
+      }
+    }
+    errorReporter.reportError(
+        builderGetter,
+        "Method matches a property of %1$s but has return type %2$s instead of %3$s "
+            + "or an Optional wrapping of %3$s",
+        autoValueClass, builderGetterType, originalGetterType);
+  }
+
+  /**
+   * Classifies a method given that it has one argument. Currently, a method with one argument can
+   * only be a setter, meaning that it must look like {@code foo(T)} or {@code setFoo(T)}, where the
+   * {@code AutoValue} class has a property called {@code foo} of type {@code T}.
+   */
+  private void classifyMethodOneArg(ExecutableElement method) {
+    String methodName = method.getSimpleName().toString();
+    Map<String, ExecutableElement> propertyNameToGetter = getterToPropertyName.inverse();
+    String propertyName = null;
+    ExecutableElement valueGetter = propertyNameToGetter.get(methodName);
+    Multimap<String, PropertySetter> propertyNameToSetters = null;
+    if (valueGetter != null) {
+      propertyNameToSetters = propertyNameToUnprefixedSetters;
+      propertyName = methodName;
+    } else if (valueGetter == null && methodName.startsWith("set") && methodName.length() > 3) {
+      propertyNameToSetters = propertyNameToPrefixedSetters;
+      propertyName = PropertyNames.decapitalizeLikeJavaBeans(methodName.substring(3));
+      valueGetter = propertyNameToGetter.get(propertyName);
+      if (valueGetter == null) {
+        // If our property is defined by a getter called getOAuth() then it is called "OAuth"
+        // because of Introspector.decapitalize. Therefore we want Introspector.decapitalize to
+        // be used for the setter too, so that you can write setOAuth(x). Meanwhile if the property
+        // is defined by a getter called oAuth() then it is called "oAuth", but you would still
+        // expect to be able to set it using setOAuth(x). Hence the second try using a decapitalize
+        // method without the quirky two-leading-capitals rule.
+        propertyName = PropertyNames.decapitalizeNormally(methodName.substring(3));
+        valueGetter = propertyNameToGetter.get(propertyName);
+      }
+    }
+    if (valueGetter == null || propertyNameToSetters == null) {
+      // The second disjunct isn't needed but convinces control-flow checkers that
+      // propertyNameToSetters can't be null when we call put on it below.
+      errorReporter.reportError(
+          method, "Method does not correspond to a property of %s", autoValueClass);
+      checkForFailedJavaBean(method);
+      return;
+    }
+    Optional<Function<String, String>> function = getSetterFunction(valueGetter, method);
+    if (function.isPresent()) {
+      DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
+      ExecutableType methodMirror =
+          MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, method));
+      if (TYPE_EQUIVALENCE.equivalent(methodMirror.getReturnType(), builderType.asType())) {
+        TypeMirror parameterType = Iterables.getOnlyElement(methodMirror.getParameterTypes());
+        propertyNameToSetters.put(
+            propertyName, new PropertySetter(method, parameterType, function.get()));
+      } else {
+        errorReporter.reportError(
+            method, "Setter methods must return %s%s", builderType, typeParamsString());
+      }
+    }
+  }
+
+  // A frequent source of problems is where the JavaBeans conventions have been followed for
+  // most but not all getters. Then AutoValue considers that they haven't been followed at all,
+  // so you might have a property called getFoo where you thought it was called just foo, and
+  // you might not understand why your setter called setFoo is rejected (it would have to be called
+  // setGetFoo).
+  private void checkForFailedJavaBean(ExecutableElement rejectedSetter) {
+    ImmutableSet<ExecutableElement> allGetters = getterToPropertyName.keySet();
+    ImmutableSet<ExecutableElement> prefixedGetters =
+        AutoValueProcessor.prefixedGettersIn(allGetters);
+    if (prefixedGetters.size() < allGetters.size()
+        && prefixedGetters.size() >= allGetters.size() / 2) {
+      errorReporter.reportNote(
+          rejectedSetter,
+          "This might be because you are using the getFoo() convention"
+              + " for some but not all methods. These methods don't follow the convention: %s",
+          difference(allGetters, prefixedGetters));
+    }
+  }
+
+  /**
+   * Returns an {@code Optional} describing how to convert a value from the setter's parameter type
+   * to the getter's return type, or {@code Optional.empty()} if the conversion isn't possible. An
+   * error will have been reported in the latter case. We can convert if they are already the same
+   * type, when the returned function will be the identity; or if the setter type can be copied
+   * using a method like {@code ImmutableList.copyOf} or {@code Optional.of}, when the returned
+   * function will be something like {@code s -> "Optional.of(" + s + ")"}.
+   */
+  private Optional<Function<String, String>> getSetterFunction(
+      ExecutableElement valueGetter, ExecutableElement setter) {
+    VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
+    boolean nullableParameter =
+        nullableAnnotationFor(parameterElement, parameterElement.asType()).isPresent();
+    TypeMirror targetType = getterToPropertyType.get(valueGetter);
+    ExecutableType finalSetter =
+        MoreTypes.asExecutable(
+            typeUtils.asMemberOf(MoreTypes.asDeclared(builderType.asType()), setter));
+    TypeMirror parameterType = finalSetter.getParameterTypes().get(0);
+    // Two types are assignable to each other if they are the same type, or if one is primitive and
+    // the other is the corresponding boxed type. There might be other cases where this is true, but
+    // we're likely to want to accept those too.
+    if (typeUtils.isAssignable(parameterType, targetType)
+        && typeUtils.isAssignable(targetType, parameterType)) {
+      if (nullableParameter) {
+        boolean nullableProperty =
+            nullableAnnotationFor(valueGetter, valueGetter.getReturnType()).isPresent();
+        if (!nullableProperty) {
+          errorReporter.reportError(
+              setter,
+              "Parameter of setter method is @Nullable but property method %s.%s() is not",
+              autoValueClass, valueGetter.getSimpleName());
+          return Optional.empty();
+        }
+      }
+      return Optional.of(s -> s);
+    }
+
+    // Parameter type is not equal to property type, but might be convertible with copyOf.
+    ImmutableList<ExecutableElement> copyOfMethods = copyOfMethods(targetType, nullableParameter);
+    if (!copyOfMethods.isEmpty()) {
+      return getConvertingSetterFunction(copyOfMethods, valueGetter, setter, parameterType);
+    }
+    errorReporter.reportError(
+        setter,
+        "Parameter type %s of setter method should be %s to match getter %s.%s",
+        parameterType, targetType, autoValueClass, valueGetter.getSimpleName());
+    return Optional.empty();
+  }
+
+  /**
+   * Returns an {@code Optional} describing how to convert a value from the setter's parameter type
+   * to the getter's return type using one of the given methods, or {@code Optional.empty()} if the
+   * conversion isn't possible. An error will have been reported in the latter case.
+   */
+  private Optional<Function<String, String>> getConvertingSetterFunction(
+      ImmutableList<ExecutableElement> copyOfMethods,
+      ExecutableElement valueGetter,
+      ExecutableElement setter,
+      TypeMirror parameterType) {
+    DeclaredType targetType = MoreTypes.asDeclared(getterToPropertyType.get(valueGetter));
+    for (ExecutableElement copyOfMethod : copyOfMethods) {
+      Optional<Function<String, String>> function =
+          getConvertingSetterFunction(copyOfMethod, targetType, parameterType);
+      if (function.isPresent()) {
+        return function;
+      }
+    }
+    String targetTypeSimpleName = targetType.asElement().getSimpleName().toString();
+    errorReporter.reportError(
+        setter,
+        "Parameter type %s of setter method should be %s to match getter %s.%s,"
+            + " or it should be a type that can be passed to %s.%s to produce %s",
+        parameterType,
+        targetType,
+        autoValueClass,
+        valueGetter.getSimpleName(),
+        targetTypeSimpleName,
+        copyOfMethods.get(0).getSimpleName(),
+        targetType);
+    return Optional.empty();
+  }
+
+  /**
+   * Returns an {@code Optional} containing a function to use {@code copyOfMethod} to copy the
+   * {@code parameterType} to the {@code targetType}, or {@code Optional.empty()} if the method
+   * can't be used. For example, we might have a property of type {@code ImmutableSet<T>} and our
+   * setter has a parameter of type {@code Set<? extends T>}. Can we use {@code ImmutableSet<E>
+   * ImmutableSet.copyOf(Collection<? extends E>)} to set the property? What about {@code
+   * ImmutableSet<E> ImmutableSet.copyOf(E[])}?
+   *
+   * <p>The example here is deliberately complicated, in that it has a type parameter of its own,
+   * presumably because the {@code @AutoValue} class is {@code Foo<T>}. One subtle point is that the
+   * builder will then be {@code Builder<T>} where this {@code T} is a <i>different</i> type
+   * variable. However, we've used {@link TypeVariables} to ensure that the {@code T} in {@code
+   * ImmutableSet<T>} is actually the one from {@code Builder<T>} instead of the original one from
+   * {@code Foo<T>}.}
+   *
+   * @param copyOfMethod the candidate method to do the copy, {@code
+   *     ImmutableSet.copyOf(Collection<? extends E>)} or {@code ImmutableSet.copyOf(E[])} in the
+   *     examples.
+   * @param targetType the type of the property to be set, {@code ImmutableSet<T>} in the example.
+   * @param parameterType the type of the setter parameter, {@code Set<? extends T>} in the example.
+   * @return a function that maps a string parameter to a method call using that parameter. For
+   *     example it might map {@code foo} to {@code ImmutableList.copyOf(foo)}.
+   */
+  private Optional<Function<String, String>> getConvertingSetterFunction(
+      ExecutableElement copyOfMethod, DeclaredType targetType, TypeMirror parameterType) {
+    // We have a parameter type, for example Set<? extends T>, and we want to know if it can be
+    // passed to the given copyOf method, which might for example be one of these methods from
+    // ImmutableSet:
+    //    public static <E> ImmutableSet<E> copyOf(Collection<? extends E> elements)
+    //    public static <E> ImmutableSet<E> copyOf(E[] elements)
+    // Additionally, if it can indeed be passed to the method, we want to know whether the result
+    // (here ImmutableSet<? extends T>) is compatible with the property to be set.
+    // We can't use Types.asMemberOf to do the substitution for us, because the methods in question
+    // are static. So even if our target type is ImmutableSet<String>, if we ask what the type of
+    // copyOf is in ImmutableSet<String> it will still tell us <T> Optional<T> (T).
+    // Instead, we do the variable substitutions ourselves.
+    if (TypeVariables.canAssignStaticMethodResult(
+        copyOfMethod, parameterType, targetType, typeUtils)) {
+      String method = TypeEncoder.encodeRaw(targetType) + "." + copyOfMethod.getSimpleName();
+      return Optional.of(s -> method + "(" + s + ")");
+    }
+    return Optional.empty();
+  }
+
+  /**
+   * Returns {@code copyOf} methods from the given type. These are static methods with a single
+   * parameter, called {@code copyOf} or {@code copyOfSorted} for Guava collection types, and called
+   * {@code of} or {@code ofNullable} for {@code Optional}. All of Guava's concrete immutable
+   * collection types have at least one such method, but we will also accept other classes with an
+   * appropriate {@code copyOf} method, such as {@link java.util.EnumSet}.
+   */
+  private ImmutableList<ExecutableElement> copyOfMethods(
+      TypeMirror targetType, boolean nullableParameter) {
+    if (!targetType.getKind().equals(TypeKind.DECLARED)) {
+      return ImmutableList.of();
+    }
+    ImmutableSet<String> copyOfNames;
+    Optionalish optionalish = Optionalish.createIfOptional(targetType);
+    if (optionalish == null) {
+      copyOfNames = ImmutableSet.of("copyOfSorted", "copyOf");
+    } else {
+      copyOfNames = ImmutableSet.of(nullableParameter ? optionalish.ofNullable() : "of");
+    }
+    TypeElement targetTypeElement = MoreElements.asType(typeUtils.asElement(targetType));
+    ImmutableList.Builder<ExecutableElement> copyOfMethods = ImmutableList.builder();
+    for (String copyOfName : copyOfNames) {
+      for (ExecutableElement method :
+          ElementFilter.methodsIn(targetTypeElement.getEnclosedElements())) {
+        if (method.getSimpleName().contentEquals(copyOfName)
+            && method.getParameters().size() == 1
+            && method.getModifiers().contains(Modifier.STATIC)) {
+          copyOfMethods.add(method);
+        }
+      }
+    }
+    return copyOfMethods.build();
+  }
+
+  /**
+   * Returns the return type of the given method from the builder. This should be the final type of
+   * the method when any bound type variables are substituted. Consider this example:
+   *
+   * <pre>{@code
+   * abstract static class ParentBuilder<B extends ParentBuilder> {
+   *   B setFoo(String s);
+   * }
+   * abstract static class ChildBuilder extends ParentBuilder<ChildBuilder> {
+   *   ...
+   * }
+   * }</pre>
+   *
+   * If the builder is {@code ChildBuilder} then the return type of {@code setFoo} is also {@code
+   * ChildBuilder}, and not {@code B} as its {@code getReturnType()} method would claim.
+   *
+   * <p>If the caller is in a version of Eclipse with <a
+   * href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=382590">this bug</a> then the {@code
+   * asMemberOf} call will fail if the method is inherited from an interface. We work around that
+   * for methods in the {@code @AutoValue} class using {@link EclipseHack#methodReturnTypes} but we
+   * don't try to do so here because it should be much less likely. You might need to change {@code
+   * ParentBuilder} from an interface to an abstract class to make it work, but you'll often need to
+   * do that anyway.
+   */
+  TypeMirror builderMethodReturnType(ExecutableElement builderMethod) {
+    DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
+    TypeMirror methodMirror;
+    try {
+      methodMirror = typeUtils.asMemberOf(builderTypeMirror, builderMethod);
+    } catch (IllegalArgumentException e) {
+      // Presumably we've hit the Eclipse bug cited.
+      return builderMethod.getReturnType();
+    }
+    return MoreTypes.asExecutable(methodMirror).getReturnType();
+  }
+
+  private static String prefixWithSet(String propertyName) {
+    // This is not internationalizationally correct, but it corresponds to what
+    // Introspector.decapitalize does.
+    return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
+  }
+
+  private String typeParamsString() {
+    return TypeSimplifier.actualTypeParametersString(autoValueClass);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
new file mode 100644
index 0000000..613d545
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
+import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.hasAnnotationMirror;
+import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.nullableAnnotationFor;
+import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME;
+import static com.google.common.collect.Sets.immutableEnumSet;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+import static javax.lang.model.util.ElementFilter.typesIn;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.auto.value.processor.AutoValueOrOneOfProcessor.Property;
+import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+
+/**
+ * Support for AutoValue builders.
+ *
+ * @author Éamonn McManus
+ */
+class BuilderSpec {
+  private final TypeElement autoValueClass;
+  private final ProcessingEnvironment processingEnv;
+  private final ErrorReporter errorReporter;
+
+  BuilderSpec(
+      TypeElement autoValueClass,
+      ProcessingEnvironment processingEnv,
+      ErrorReporter errorReporter) {
+    this.autoValueClass = autoValueClass;
+    this.processingEnv = processingEnv;
+    this.errorReporter = errorReporter;
+  }
+
+  private static final ImmutableSet<ElementKind> CLASS_OR_INTERFACE =
+      immutableEnumSet(ElementKind.CLASS, ElementKind.INTERFACE);
+
+  /**
+   * Determines if the {@code @AutoValue} class for this instance has a correct nested
+   * {@code @AutoValue.Builder} class or interface and return a representation of it in an {@code
+   * Optional} if so.
+   */
+  Optional<Builder> getBuilder() {
+    Optional<TypeElement> builderTypeElement = Optional.empty();
+    for (TypeElement containedClass : typesIn(autoValueClass.getEnclosedElements())) {
+      if (hasAnnotationMirror(containedClass, AUTO_VALUE_BUILDER_NAME)) {
+        if (!CLASS_OR_INTERFACE.contains(containedClass.getKind())) {
+          errorReporter.reportError(
+              containedClass, "@AutoValue.Builder can only apply to a class or an interface");
+        } else if (!containedClass.getModifiers().contains(Modifier.STATIC)) {
+          errorReporter.reportError(
+              containedClass, "@AutoValue.Builder cannot be applied to a non-static class");
+        } else if (builderTypeElement.isPresent()) {
+          errorReporter.reportError(
+              containedClass,
+              "%s already has a Builder: %s",
+              autoValueClass,
+              builderTypeElement.get());
+        } else {
+          builderTypeElement = Optional.of(containedClass);
+        }
+      }
+    }
+
+    if (builderTypeElement.isPresent()) {
+      return builderFrom(builderTypeElement.get());
+    } else {
+      return Optional.empty();
+    }
+  }
+
+  /** Representation of an {@code AutoValue.Builder} class or interface. */
+  class Builder implements AutoValueExtension.BuilderContext {
+    private final TypeElement builderTypeElement;
+    private ImmutableSet<ExecutableElement> toBuilderMethods;
+    private ExecutableElement buildMethod;
+    private BuilderMethodClassifier classifier;
+
+    Builder(TypeElement builderTypeElement) {
+      this.builderTypeElement = builderTypeElement;
+    }
+
+    @Override
+    public TypeElement builderType() {
+      return builderTypeElement;
+    }
+
+    @Override
+    public Set<ExecutableElement> builderMethods() {
+      return methodsIn(autoValueClass.getEnclosedElements()).stream()
+          .filter(
+              m ->
+                  m.getParameters().isEmpty()
+                      && m.getModifiers().contains(Modifier.STATIC)
+                      && !m.getModifiers().contains(Modifier.PRIVATE)
+                      && erasedTypeIs(m.getReturnType(), builderTypeElement))
+          .collect(toSet());
+    }
+
+    @Override
+    public Optional<ExecutableElement> buildMethod() {
+      return methodsIn(builderTypeElement.getEnclosedElements()).stream()
+          .filter(
+              m ->
+                  m.getSimpleName().contentEquals("build")
+                      && !m.getModifiers().contains(Modifier.PRIVATE)
+                      && !m.getModifiers().contains(Modifier.STATIC)
+                      && m.getParameters().isEmpty()
+                      && erasedTypeIs(m.getReturnType(), autoValueClass))
+          .findFirst();
+    }
+
+    @Override
+    public ExecutableElement autoBuildMethod() {
+      return buildMethod;
+    }
+
+    @Override
+    public Map<String, Set<ExecutableElement>> setters() {
+      return Maps.transformValues(
+          classifier.propertyNameToSetters().asMap(),
+          propertySetters ->
+              propertySetters.stream().map(PropertySetter::getSetter).collect(toSet()));
+    }
+
+    @Override
+    public Map<String, ExecutableElement> propertyBuilders() {
+      return Maps.transformValues(
+          classifier.propertyNameToPropertyBuilder(), PropertyBuilder::getPropertyBuilderMethod);
+    }
+
+    private boolean erasedTypeIs(TypeMirror type, TypeElement baseType) {
+      return type.getKind().equals(TypeKind.DECLARED)
+          && MoreTypes.asDeclared(type).asElement().equals(baseType);
+    }
+
+    @Override
+    public Set<ExecutableElement> toBuilderMethods() {
+      return toBuilderMethods;
+    }
+
+    /**
+     * Finds any methods in the set that return the builder type. If the builder has type parameters
+     * {@code <A, B>}, then the return type of the method must be {@code Builder<A, B>} with the
+     * same parameter names. We enforce elsewhere that the names and bounds of the builder
+     * parameters must be the same as those of the @AutoValue class. Here's a correct example:
+     *
+     * <pre>
+     * {@code @AutoValue abstract class Foo<A extends Number, B> {
+     *   abstract int someProperty();
+     *
+     *   abstract Builder<A, B> toBuilder();
+     *
+     *   interface Builder<A extends Number, B> {...}
+     * }}
+     * </pre>
+     *
+     * <p>We currently impose that there cannot be more than one such method.
+     */
+    ImmutableSet<ExecutableElement> toBuilderMethods(
+        Types typeUtils, Set<ExecutableElement> abstractMethods) {
+
+      List<String> builderTypeParamNames =
+          builderTypeElement.getTypeParameters().stream()
+              .map(e -> e.getSimpleName().toString())
+              .collect(toList());
+
+      ImmutableSet.Builder<ExecutableElement> methods = ImmutableSet.builder();
+      for (ExecutableElement method : abstractMethods) {
+        if (builderTypeElement.equals(typeUtils.asElement(method.getReturnType()))) {
+          methods.add(method);
+          DeclaredType returnType = MoreTypes.asDeclared(method.getReturnType());
+          List<String> typeArguments =
+              returnType.getTypeArguments().stream()
+                  .filter(t -> t.getKind().equals(TypeKind.TYPEVAR))
+                  .map(t -> typeUtils.asElement(t).getSimpleName().toString())
+                  .collect(toList());
+          if (!builderTypeParamNames.equals(typeArguments)) {
+            errorReporter.reportError(
+                method,
+                "Builder converter method should return %s%s",
+                builderTypeElement,
+                TypeSimplifier.actualTypeParametersString(builderTypeElement));
+          }
+        }
+      }
+      ImmutableSet<ExecutableElement> builderMethods = methods.build();
+      if (builderMethods.size() > 1) {
+        errorReporter.reportError(
+            builderMethods.iterator().next(), "There can be at most one builder converter method");
+      }
+      this.toBuilderMethods = builderMethods;
+      return builderMethods;
+    }
+
+    void defineVars(
+        AutoValueTemplateVars vars,
+        ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
+      Iterable<ExecutableElement> builderMethods = abstractMethods(builderTypeElement);
+      boolean autoValueHasToBuilder = !toBuilderMethods.isEmpty();
+      ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType =
+          TypeVariables.rewriteReturnTypes(
+              processingEnv.getElementUtils(),
+              processingEnv.getTypeUtils(),
+              getterToPropertyName.keySet(),
+              autoValueClass,
+              builderTypeElement);
+      Optional<BuilderMethodClassifier> optionalClassifier =
+          BuilderMethodClassifier.classify(
+              builderMethods,
+              errorReporter,
+              processingEnv,
+              autoValueClass,
+              builderTypeElement,
+              getterToPropertyName,
+              getterToPropertyType,
+              autoValueHasToBuilder);
+      if (!optionalClassifier.isPresent()) {
+        return;
+      }
+      for (ExecutableElement method : methodsIn(builderTypeElement.getEnclosedElements())) {
+        if (method.getSimpleName().contentEquals("builder")
+                && method.getModifiers().contains(Modifier.STATIC)
+                && method.getAnnotationMirrors().isEmpty()) {
+          // For now we ignore methods with annotations, because for example we do want to allow
+          // Jackson's @JsonCreator.
+          errorReporter.reportWarning(
+              method, "Static builder() method should be in the containing class");
+        }
+      }
+      this.classifier = optionalClassifier.get();
+      Set<ExecutableElement> buildMethods = classifier.buildMethods();
+      if (buildMethods.size() != 1) {
+        Set<? extends Element> errorElements =
+            buildMethods.isEmpty() ? ImmutableSet.of(builderTypeElement) : buildMethods;
+        for (Element buildMethod : errorElements) {
+          errorReporter.reportError(
+              buildMethod,
+              "Builder must have a single no-argument method returning %s%s",
+              autoValueClass,
+              typeParamsString());
+        }
+        return;
+      }
+      this.buildMethod = Iterables.getOnlyElement(buildMethods);
+      vars.builderIsInterface = builderTypeElement.getKind() == ElementKind.INTERFACE;
+      vars.builderTypeName = TypeSimplifier.classNameOf(builderTypeElement);
+      vars.builderFormalTypes = TypeEncoder.formalTypeParametersString(builderTypeElement);
+      vars.builderActualTypes = TypeSimplifier.actualTypeParametersString(builderTypeElement);
+      vars.buildMethod = Optional.of(new SimpleMethod(buildMethod));
+      vars.builderGetters = classifier.builderGetters();
+      vars.builderSetters = classifier.propertyNameToSetters();
+
+      vars.builderPropertyBuilders =
+          ImmutableMap.copyOf(classifier.propertyNameToPropertyBuilder());
+
+      Set<Property> required = new LinkedHashSet<>(vars.props);
+      for (Property property : vars.props) {
+        if (property.isNullable()
+            || property.getOptional() != null
+            || vars.builderPropertyBuilders.containsKey(property.getName())) {
+          required.remove(property);
+        }
+      }
+      vars.builderRequiredProperties = ImmutableSet.copyOf(required);
+    }
+  }
+
+  /**
+   * Information about a builder property getter, referenced from the autovalue.vm template. A
+   * property called foo (defined by a method {@code T foo()} or {@code T getFoo()}) can have a
+   * getter method in the builder with the same name ({@code foo()} or {@code getFoo()}) and a
+   * return type of either {@code T} or {@code Optional<T>}. The {@code Optional<T>} form can be
+   * used to tell whether the property has been set. Here, {@code Optional<T>} can be either {@code
+   * java.util.Optional} or {@code com.google.common.base.Optional}. If {@code T} is {@code int},
+   * {@code long}, or {@code double}, then instead of {@code Optional<T>} we can have {@code
+   * OptionalInt} etc. If {@code T} is a primitive type (including these ones but also the other
+   * five) then {@code Optional<T>} can be the corresponding boxed type.
+   */
+  public static class PropertyGetter {
+    private final String access;
+    private final String type;
+    private final Optionalish optional;
+
+    /**
+     * Makes a new {@code PropertyGetter} instance.
+     *
+     * @param method the source method which this getter is implementing.
+     * @param type the type that the getter returns. This is written to take imports into account,
+     *     so it might be {@code List<String>} for example. It is either identical to the type of
+     *     the corresponding getter in the {@code @AutoValue} class, or it is an optional wrapper,
+     *     like {@code Optional<List<String>>}.
+     * @param optional a representation of the {@code Optional} type that the getter returns, if
+     *     this is an optional getter, or null otherwise. An optional getter is one that returns
+     *     {@code Optional<T>} rather than {@code T}, as explained above.
+     */
+    PropertyGetter(ExecutableElement method, String type, Optionalish optional) {
+      this.access = SimpleMethod.access(method);
+      this.type = type;
+      this.optional = optional;
+    }
+
+    public String getAccess() {
+      return access;
+    }
+
+    public String getType() {
+      return type;
+    }
+
+    public Optionalish getOptional() {
+      return optional;
+    }
+  }
+
+  /**
+   * Information about a property setter, referenced from the autovalue.vm template. A property
+   * called foo (defined by a method {@code T foo()} or {@code T getFoo()}) can have a setter method
+   * {@code foo(T)} or {@code setFoo(T)} that returns the builder type. Additionally, it can have a
+   * setter with a type that can be copied to {@code T} through a {@code copyOf} method; for example
+   * a property {@code foo} of type {@code ImmutableSet<String>} can be set with a method {@code
+   * setFoo(Collection<String> foos)}. And, if {@code T} is {@code Optional}, it can have a setter
+   * with a type that can be copied to {@code T} through {@code Optional.of}.
+   */
+  public static class PropertySetter {
+    private final ExecutableElement setter;
+    private final String access;
+    private final String name;
+    private final String parameterTypeString;
+    private final boolean primitiveParameter;
+    private final String nullableAnnotation;
+    private final Function<String, String> copyFunction;
+
+    PropertySetter(
+        ExecutableElement setter, TypeMirror parameterType, Function<String, String> copyFunction) {
+      this.setter = setter;
+      this.copyFunction = copyFunction;
+      this.access = SimpleMethod.access(setter);
+      this.name = setter.getSimpleName().toString();
+      primitiveParameter = parameterType.getKind().isPrimitive();
+      this.parameterTypeString = parameterTypeString(setter, parameterType);
+      VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
+      Optional<String> maybeNullable = nullableAnnotationFor(parameterElement, parameterType);
+      this.nullableAnnotation = maybeNullable.orElse("");
+    }
+
+    ExecutableElement getSetter() {
+      return setter;
+    }
+
+    private static String parameterTypeString(ExecutableElement setter, TypeMirror parameterType) {
+      if (setter.isVarArgs()) {
+        TypeMirror componentType = MoreTypes.asArray(parameterType).getComponentType();
+        // This is a bit ugly. It's OK to annotate just the component type, because if it is
+        // say `@Nullable String` then we will end up with `@Nullable String...`. Unlike the
+        // normal array case, we can't have the situation where the array itself is annotated;
+        // you can write `String @Nullable []` to mean that, but you can't write
+        // `String @Nullable ...`.
+        return TypeEncoder.encodeWithAnnotations(componentType) + "...";
+      } else {
+        return TypeEncoder.encodeWithAnnotations(parameterType);
+      }
+    }
+
+    public String getAccess() {
+      return access;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public String getParameterType() {
+      return parameterTypeString;
+    }
+
+    public boolean getPrimitiveParameter() {
+      return primitiveParameter;
+    }
+
+    public String getNullableAnnotation() {
+      return nullableAnnotation;
+    }
+
+    public String copy(AutoValueProcessor.Property property) {
+      String copy = copyFunction.apply(property.toString());
+
+      // Add a null guard only in cases where we are using copyOf and the property is @Nullable.
+      if (property.isNullable() && !copy.equals(property.toString())) {
+        copy = String.format("(%s == null ? null : %s)", property, copy);
+      }
+
+      return copy;
+    }
+  }
+
+  /**
+   * Returns a representation of the given {@code @AutoValue.Builder} class or interface. If the
+   * class or interface has abstract methods that could not be part of any builder, emits error
+   * messages and returns Optional.empty().
+   */
+  private Optional<Builder> builderFrom(TypeElement builderTypeElement) {
+
+    // We require the builder to have the same type parameters as the @AutoValue class, meaning the
+    // same names and bounds. In principle the type parameters could have different names, but that
+    // would be confusing, and our code would reject it anyway because it wouldn't consider that
+    // the return type of Foo<U> build() was really the same as the declaration of Foo<T>. This
+    // check produces a better error message in that case and similar ones.
+
+    if (!sameTypeParameters(autoValueClass, builderTypeElement)) {
+      errorReporter.reportError(
+          builderTypeElement,
+          "Type parameters of %s must have same names and bounds as type parameters of %s",
+          builderTypeElement,
+          autoValueClass);
+      return Optional.empty();
+    }
+    return Optional.of(new Builder(builderTypeElement));
+  }
+
+  private static boolean sameTypeParameters(TypeElement a, TypeElement b) {
+    int nTypeParameters = a.getTypeParameters().size();
+    if (nTypeParameters != b.getTypeParameters().size()) {
+      return false;
+    }
+    for (int i = 0; i < nTypeParameters; i++) {
+      TypeParameterElement aParam = a.getTypeParameters().get(i);
+      TypeParameterElement bParam = b.getTypeParameters().get(i);
+      if (!aParam.getSimpleName().equals(bParam.getSimpleName())) {
+        return false;
+      }
+      Set<TypeMirror> autoValueBounds = new TypeMirrorSet(aParam.getBounds());
+      Set<TypeMirror> builderBounds = new TypeMirrorSet(bParam.getBounds());
+      if (!autoValueBounds.equals(builderBounds)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns a set of all abstract methods in the given TypeElement or inherited from ancestors. If
+   * any of the abstract methods has a return type or parameter type that is not currently defined
+   * then this method will throw an exception that will cause us to defer processing of the current
+   * class until a later annotation-processing round.
+   */
+  private ImmutableSet<ExecutableElement> abstractMethods(TypeElement typeElement) {
+    Set<ExecutableElement> methods =
+        getLocalAndInheritedMethods(
+            typeElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
+    ImmutableSet.Builder<ExecutableElement> abstractMethods = ImmutableSet.builder();
+    for (ExecutableElement method : methods) {
+      if (method.getModifiers().contains(Modifier.ABSTRACT)) {
+        MissingTypes.deferIfMissingTypesIn(method);
+        abstractMethods.add(method);
+      }
+    }
+    return abstractMethods.build();
+  }
+
+  private String typeParamsString() {
+    return TypeSimplifier.actualTypeParametersString(autoValueClass);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/ClassNames.java b/value/src/main/java/com/google/auto/value/processor/ClassNames.java
new file mode 100644
index 0000000..d23cfd2
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/ClassNames.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 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.processor;
+
+/**
+ * Names of classes that are referenced in the processors.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+final class ClassNames {
+  private ClassNames() {}
+
+  static final String AUTO_VALUE_PACKAGE_NAME = "com.google.auto.value.";
+  static final String AUTO_ANNOTATION_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoAnnotation";
+  static final String AUTO_ONE_OF_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoOneOf";
+  static final String AUTO_VALUE_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoValue";
+  static final String AUTO_VALUE_BUILDER_NAME = AUTO_VALUE_NAME + ".Builder";
+  static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations";
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/EclipseHack.java b/value/src/main/java/com/google/auto/value/processor/EclipseHack.java
new file mode 100644
index 0000000..727e328
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/EclipseHack.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2013 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.processor;
+
+import static java.util.stream.Collectors.toList;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+/**
+ * Hacks needed to work around various bugs and incompatibilities in Eclipse's implementation of
+ * annotation processing.
+ *
+ * @author Éamonn McManus
+ */
+class EclipseHack {
+  private final Elements elementUtils;
+  private final Types typeUtils;
+
+  EclipseHack(ProcessingEnvironment processingEnv) {
+    this(processingEnv.getElementUtils(), processingEnv.getTypeUtils());
+  }
+
+  EclipseHack(Elements elementUtils, Types typeUtils) {
+    this.elementUtils = elementUtils;
+    this.typeUtils = typeUtils;
+  }
+
+  /**
+   * Returns the enclosing type of {@code type}, if {@code type} is an inner class. Otherwise
+   * returns a {@code NoType}. This is what {@link DeclaredType#getEnclosingType()} is supposed to
+   * do. However, some versions of Eclipse have a bug where, for example, asking for the enclosing
+   * type of {@code PrimitiveIterator.OfInt} will return {@code PrimitiveIterator<T, T_CONS>} rather
+   * than plain {@code PrimitiveIterator}, as if {@code OfInt} were an inner class rather than a
+   * static one. This would lead us to write a reference to {@code OfInt} as {@code
+   * PrimitiveIterator<T, T_CONS>.OfInt}, which would obviously break. We attempt to avert this by
+   * detecting that:
+   *
+   * <ul>
+   *   <li>there is an enclosing type that is a {@code DeclaredType}, which should mean that {@code
+   *       type} is an inner class;
+   *   <li>we are in the Eclipse compiler;
+   *   <li>the type arguments of the purported enclosing type are all type variables with the same
+   *       names as the corresponding type parameters.
+   * </ul>
+   *
+   * <p>If all these conditions are met, we assume we're hitting the Eclipse bug, and we return no
+   * enclosing type instead. That does mean that in the unlikely event where we really do have an
+   * inner class of an instantiation of the outer class with type arguments that happen to be type
+   * variables with the same names as the corresponding parameters, we will do the wrong thing on
+   * Eclipse. But doing the wrong thing in that case is better than doing the wrong thing in the
+   * usual case.
+   */
+  static TypeMirror getEnclosingType(DeclaredType type) {
+    TypeMirror enclosing = type.getEnclosingType();
+    if (!enclosing.getKind().equals(TypeKind.DECLARED)
+        || !enclosing.getClass().getName().contains("eclipse")) {
+      // If the class representing the enclosing type comes from the Eclipse compiler, it will be
+      // something like org.eclipse.jdt.internal.compiler.apt.model.DeclaredTypeImpl. If we're not
+      // in the Eclipse compiler then we don't expect to see "eclipse" in the name of this
+      // implementation class.
+      return enclosing;
+    }
+    DeclaredType declared = MoreTypes.asDeclared(enclosing);
+    List<? extends TypeMirror> arguments = declared.getTypeArguments();
+    if (!arguments.isEmpty()) {
+      boolean allVariables = arguments.stream().allMatch(t -> t.getKind().equals(TypeKind.TYPEVAR));
+      if (allVariables) {
+        List<Name> argumentNames =
+            arguments.stream()
+                .map(t -> MoreTypes.asTypeVariable(t).asElement().getSimpleName())
+                .collect(toList());
+        TypeElement enclosingElement = MoreTypes.asTypeElement(declared);
+        List<Name> parameterNames =
+            enclosingElement.getTypeParameters().stream()
+                .map(Element::getSimpleName)
+                .collect(toList());
+        if (argumentNames.equals(parameterNames)) {
+          // We're going to return a NoType. We don't have a Types to hand so we can't call
+          // Types.getNoType(). Instead, just keep going through outer types until we get to
+          // the outside, which will be a NoType.
+          while (enclosing.getKind().equals(TypeKind.DECLARED)) {
+            enclosing = MoreTypes.asDeclared(enclosing).getEnclosingType();
+          }
+          return enclosing;
+        }
+      }
+    }
+    return declared;
+  }
+
+  TypeMirror methodReturnType(ExecutableElement method, DeclaredType in) {
+    try {
+      TypeMirror methodMirror = typeUtils.asMemberOf(in, method);
+      return MoreTypes.asExecutable(methodMirror).getReturnType();
+    } catch (IllegalArgumentException e) {
+      return methodReturnTypes(ImmutableSet.of(method), in).get(method);
+    }
+  }
+
+  /**
+   * Returns a map containing the real return types of the given methods, knowing that they appear
+   * in the given type. This means that if the given type is say {@code StringIterator implements
+   * Iterator<String>} then we want the {@code next()} method to map to String, rather than the
+   * {@code T} that it returns as inherited from {@code Iterator<T>}. This method is in EclipseHack
+   * because if it weren't for <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=382590">this
+   * Eclipse bug</a> it would be trivial. Unfortunately, versions of Eclipse up to at least 4.5 have
+   * a bug where the {@link Types#asMemberOf} method throws IllegalArgumentException if given a
+   * method that is inherited from an interface. Fortunately, Eclipse's implementation of {@link
+   * Elements#getAllMembers} does the type substitution that {@code asMemberOf} would have done. But
+   * javac's implementation doesn't. So we try the way that would work if Eclipse weren't buggy, and
+   * only if we get IllegalArgumentException do we use {@code getAllMembers}.
+   */
+  ImmutableMap<ExecutableElement, TypeMirror> methodReturnTypes(
+      Set<ExecutableElement> methods, DeclaredType in) {
+    ImmutableMap.Builder<ExecutableElement, TypeMirror> map = ImmutableMap.builder();
+    Map<Name, ExecutableElement> noArgMethods = null;
+    for (ExecutableElement method : methods) {
+      TypeMirror returnType = null;
+      try {
+        TypeMirror methodMirror = typeUtils.asMemberOf(in, method);
+        returnType = MoreTypes.asExecutable(methodMirror).getReturnType();
+      } catch (IllegalArgumentException e) {
+        if (method.getParameters().isEmpty()) {
+          if (noArgMethods == null) {
+            noArgMethods = noArgMethodsIn(in);
+          }
+          returnType = noArgMethods.get(method.getSimpleName()).getReturnType();
+        }
+      }
+      if (returnType == null) {
+        returnType = method.getReturnType();
+      }
+      map.put(method, returnType);
+    }
+    return map.build();
+  }
+
+  /**
+   * Constructs a map from name to method of the no-argument methods in the given type. We need this
+   * because an ExecutableElement returned by {@link Elements#getAllMembers} will not compare equal
+   * to the original ExecutableElement if {@code getAllMembers} substituted type parameters, as it
+   * does in Eclipse.
+   */
+  private Map<Name, ExecutableElement> noArgMethodsIn(DeclaredType in) {
+    TypeElement autoValueType = MoreElements.asType(typeUtils.asElement(in));
+    List<ExecutableElement> allMethods =
+        ElementFilter.methodsIn(elementUtils.getAllMembers(autoValueType));
+    Map<Name, ExecutableElement> map = new LinkedHashMap<Name, ExecutableElement>();
+    for (ExecutableElement method : allMethods) {
+      if (method.getParameters().isEmpty()) {
+        map.put(method.getSimpleName(), method);
+      }
+    }
+    return map;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java
new file mode 100644
index 0000000..e2a3d83
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014 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.processor;
+
+import com.google.errorprone.annotations.FormatMethod;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.tools.Diagnostic;
+
+/**
+ * Handle error reporting for an annotation processor.
+ *
+ * @author Éamonn McManus
+ */
+class ErrorReporter {
+  private final Messager messager;
+  private int errorCount;
+
+  ErrorReporter(ProcessingEnvironment processingEnv) {
+    this.messager = processingEnv.getMessager();
+  }
+
+  /**
+   * Issue a compilation note.
+   *
+   * @param e the element to which it pertains
+   * @param format the format string for the text of the note
+   * @param args arguments for the format string
+   */
+  @FormatMethod
+  void reportNote(Element e, String format, Object... args) {
+    messager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args), e);
+  }
+
+  /**
+   * Issue a compilation warning.
+   *
+   * @param e the element to which it pertains
+   * @param format the format string for the text of the warning
+   * @param args arguments for the format string
+   */
+  @FormatMethod
+  void reportWarning(Element e, String format, Object... args) {
+    messager.printMessage(Diagnostic.Kind.WARNING, String.format(format, args), e);
+  }
+
+  /**
+   * Issue a compilation error. This method does not throw an exception, since we want to continue
+   * processing and perhaps report other errors. It is a good idea to introduce a test case in
+   * CompilationTest for any new call to reportError(...) to ensure that we continue correctly after
+   * an error.
+   *
+   * @param e the element to which it pertains
+   * @param format the format string for the text of the warning
+   * @param args arguments for the format string
+   */
+  @FormatMethod
+  void reportError(Element e, String format, Object... args) {
+    messager.printMessage(Diagnostic.Kind.ERROR, String.format(format, args), e);
+    errorCount++;
+  }
+
+  /**
+   * Issue a compilation error and abandon the processing of this class. This does not prevent the
+   * processing of other classes.
+   *
+   * @param e the element to which it pertains
+   * @param format the format string for the text of the error
+   * @param args arguments for the format string
+   */
+  @FormatMethod
+  void abortWithError(Element e, String format, Object... args) {
+    reportError(e, format, args);
+    throw new AbortProcessingException();
+  }
+
+  /** The number of errors that have been output by calls to {@link #reportError}. */
+  int errorCount() {
+    return errorCount;
+  }
+
+  /** Abandon the processing of this class if any errors have been output. */
+  void abortIfAnyError() {
+    if (errorCount > 0) {
+      throw new AbortProcessingException();
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java b/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java
new file mode 100644
index 0000000..da844b0
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015 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.processor;
+
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.auto.value.extension.AutoValueExtension.BuilderContext;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+
+class ExtensionContext implements AutoValueExtension.Context {
+
+  private final ProcessingEnvironment processingEnvironment;
+  private final TypeElement autoValueClass;
+  private final ImmutableMap<String, ExecutableElement> properties;
+  private final ImmutableMap<String, TypeMirror> propertyTypes;
+  private final ImmutableSet<ExecutableElement> abstractMethods;
+  private Optional<BuilderContext> builderContext = Optional.empty();
+
+  ExtensionContext(
+      ProcessingEnvironment processingEnvironment,
+      TypeElement autoValueClass,
+      ImmutableMap<String, ExecutableElement> properties,
+      ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
+      ImmutableSet<ExecutableElement> abstractMethods) {
+    this.processingEnvironment = processingEnvironment;
+    this.autoValueClass = autoValueClass;
+    this.properties = properties;
+    this.propertyTypes =
+        ImmutableMap.copyOf(Maps.transformValues(properties, propertyMethodsAndTypes::get));
+    this.abstractMethods = abstractMethods;
+  }
+
+  void setBuilderContext(BuilderContext builderContext) {
+    this.builderContext = Optional.of(builderContext);
+  }
+
+  @Override
+  public ProcessingEnvironment processingEnvironment() {
+    return processingEnvironment;
+  }
+
+  @Override
+  public String packageName() {
+    return TypeSimplifier.packageNameOf(autoValueClass);
+  }
+
+  @Override
+  public TypeElement autoValueClass() {
+    return autoValueClass;
+  }
+
+  @Override
+  public String finalAutoValueClassName() {
+    return AutoValueProcessor.generatedSubclassName(autoValueClass, 0);
+  }
+
+  @Override
+  public Map<String, ExecutableElement> properties() {
+    return properties;
+  }
+
+  @Override
+  public Map<String, TypeMirror> propertyTypes() {
+    return propertyTypes;
+  }
+
+  @Override
+  public Set<ExecutableElement> abstractMethods() {
+    return abstractMethods;
+  }
+
+  @Override
+  public Optional<BuilderContext> builder() {
+    return builderContext;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java
new file mode 100644
index 0000000..7220754
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static java.util.stream.Collectors.joining;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+
+class GwtCompatibility {
+  private final Optional<AnnotationMirror> gwtCompatibleAnnotation;
+
+  GwtCompatibility(TypeElement type) {
+    Optional<AnnotationMirror> gwtCompatibleAnnotation = Optional.empty();
+    List<? extends AnnotationMirror> annotations = type.getAnnotationMirrors();
+    for (AnnotationMirror annotation : annotations) {
+      Name name = annotation.getAnnotationType().asElement().getSimpleName();
+      if (name.contentEquals("GwtCompatible")) {
+        gwtCompatibleAnnotation = Optional.of(annotation);
+      }
+    }
+    this.gwtCompatibleAnnotation = gwtCompatibleAnnotation;
+  }
+
+  Optional<AnnotationMirror> gwtCompatibleAnnotation() {
+    return gwtCompatibleAnnotation;
+  }
+
+  // Get rid of the misconceived <? extends ExecutableElement, ? extends AnnotationValue>
+  // in the return type of getElementValues().
+  static Map<ExecutableElement, AnnotationValue> getElementValues(AnnotationMirror annotation) {
+    return Collections.<ExecutableElement, AnnotationValue>unmodifiableMap(
+        annotation.getElementValues());
+  }
+
+  String gwtCompatibleAnnotationString() {
+    if (gwtCompatibleAnnotation.isPresent()) {
+      AnnotationMirror annotation = gwtCompatibleAnnotation.get();
+      TypeElement annotationElement = (TypeElement) annotation.getAnnotationType().asElement();
+      String annotationArguments;
+      if (annotation.getElementValues().isEmpty()) {
+        annotationArguments = "";
+      } else {
+        annotationArguments =
+            getElementValues(annotation)
+                .entrySet()
+                .stream()
+                .map(e -> e.getKey().getSimpleName() + " = " + e.getValue())
+                .collect(joining(", ", "(", ")"));
+      }
+      return "@" + annotationElement.getQualifiedName() + annotationArguments;
+    } else {
+      return "";
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
new file mode 100644
index 0000000..cf928d5
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
+
+import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import com.google.escapevelocity.Template;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.zip.CRC32;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+/**
+ * Generates GWT serialization code for {@code @AutoValue} classes also marked
+ * {@code @GwtCompatible(serializable = true)}.
+ *
+ * @author Éamonn McManus
+ */
+class GwtSerialization {
+  private final GwtCompatibility gwtCompatibility;
+  private final ProcessingEnvironment processingEnv;
+  private final TypeElement type;
+
+  GwtSerialization(
+      GwtCompatibility gwtCompatibility, ProcessingEnvironment processingEnv, TypeElement type) {
+    this.gwtCompatibility = gwtCompatibility;
+    this.processingEnv = processingEnv;
+    this.type = type;
+  }
+
+  private boolean shouldWriteGwtSerializer() {
+    Optional<AnnotationMirror> optionalGwtCompatible = gwtCompatibility.gwtCompatibleAnnotation();
+    if (optionalGwtCompatible.isPresent()) {
+      AnnotationMirror gwtCompatible = optionalGwtCompatible.get();
+      for (Map.Entry<ExecutableElement, AnnotationValue> entry :
+          GwtCompatibility.getElementValues(gwtCompatible).entrySet()) {
+        if (entry.getKey().getSimpleName().contentEquals("serializable")
+            && entry.getValue().getValue().equals(true)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Writes the GWT serializer for the given type, if appropriate. An {@code @AutoValue} class gets
+   * a GWT serializer if it is annotated with {@code @GwtCompatible(serializable = true)}, where the
+   * {@code @GwtCompatible} annotation can come from any package.
+   *
+   * <p>If the type is com.example.Foo then the generated AutoValue subclass is
+   * com.example.AutoValue_Foo and the GWT serializer is
+   * com.example.AutoValue_Foo_CustomFieldSerializer.
+   *
+   * @param autoVars the template variables defined for this type.
+   */
+  void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars) {
+    if (shouldWriteGwtSerializer()) {
+      GwtTemplateVars vars = new GwtTemplateVars();
+      vars.pkg = autoVars.pkg;
+      vars.subclass = autoVars.finalSubclass;
+      vars.formalTypes = autoVars.formalTypes;
+      vars.actualTypes = autoVars.actualTypes;
+      vars.useBuilder = !autoVars.builderTypeName.isEmpty();
+      vars.builderSetters = autoVars.builderSetters;
+      vars.builderPropertyBuilders = autoVars.builderPropertyBuilders;
+      vars.generated = autoVars.generated;
+      String className =
+          (vars.pkg.isEmpty() ? "" : vars.pkg + ".") + vars.subclass + "_CustomFieldSerializer";
+      vars.serializerClass = TypeSimplifier.simpleNameOf(className);
+      vars.props = autoVars.props.stream().map(Property::new).collect(toList());
+      vars.classHashString = computeClassHash(autoVars.props, vars.pkg);
+      String text = vars.toText();
+      text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType());
+      writeSourceFile(className, text, type);
+    }
+  }
+
+  public static class Property {
+    private final AutoValueProcessor.Property property;
+    private final boolean isCastingUnchecked;
+
+    Property(AutoValueProcessor.Property property) {
+      this.property = property;
+      this.isCastingUnchecked = TypeSimplifier.isCastingUnchecked(property.getTypeMirror());
+    }
+
+    @Override
+    public String toString() {
+      return property.toString();
+    }
+
+    public String getGetter() {
+      return property.getGetter();
+    }
+
+    public String getType() {
+      return property.getType();
+    }
+
+    public String getName() {
+      return property.getName();
+    }
+
+    /**
+     * Returns the suffix in serializer method names for values of the given type. For example, if
+     * the type is "int" then the returned value will be "Int" because the serializer methods are
+     * called readInt and writeInt. There are methods for all primitive types and String; every
+     * other type uses readObject and writeObject.
+     */
+    public String getGwtType() {
+      TypeMirror typeMirror = property.getTypeMirror();
+      String type = typeMirror.toString();
+      if (property.getKind().isPrimitive()) {
+        return Character.toUpperCase(type.charAt(0)) + type.substring(1);
+      } else if (type.equals("java.lang.String")) {
+        return "String";
+      } else {
+        return "Object";
+      }
+    }
+
+    /**
+     * Returns a string to be inserted before the call to the readFoo() call so that the expression
+     * can be assigned to the given type. For primitive types and String, the readInt() etc methods
+     * already return the right type so the string is empty. For other types, the string is a cast
+     * like "(Foo) ".
+     */
+    public String getGwtCast() {
+      if (property.getKind().isPrimitive() || getType().equals("String")) {
+        return "";
+      } else {
+        return "(" + getType() + ") ";
+      }
+    }
+
+    public boolean isCastingUnchecked() {
+      return isCastingUnchecked;
+    }
+  }
+
+  @SuppressWarnings("unused") // some fields are only read through reflection
+  static class GwtTemplateVars extends TemplateVars {
+    /** The properties defined by the parent class's abstract methods. */
+    List<Property> props;
+
+    /**
+     * The package of the class with the {@code @AutoValue} annotation and its generated subclass.
+     */
+    String pkg;
+
+    /** The simple name of the generated subclass. */
+    String subclass;
+
+    /**
+     * The formal generic signature of the class with the {@code @AutoValue} annotation and its
+     * generated subclass. This is empty, or contains type variables with optional bounds, for
+     * example {@code <K, V extends K>}.
+     */
+    String formalTypes;
+    /**
+     * The generic signature used by the generated subclass for its superclass reference. This is
+     * empty, or contains only type variables with no bounds, for example {@code <K, V>}.
+     */
+    String actualTypes;
+
+    /** True if the {@code @AutoValue} class is constructed using a generated builder. */
+    Boolean useBuilder;
+
+    /**
+     * A multimap from property names (like foo) to the corresponding setter methods (foo or
+     * setFoo).
+     */
+    Multimap<String, BuilderSpec.PropertySetter> builderSetters;
+
+    /**
+     * A map from property names to information about the associated property builder. A property
+     * called foo (defined by a method foo() or getFoo()) can have a property builder called
+     * fooBuilder(). The type of foo must be a type that has an associated builder following certain
+     * conventions. Guava immutable types such as ImmutableList follow those conventions, as do many
+     * {@code @AutoValue} types.
+     */
+    ImmutableMap<String, PropertyBuilder> builderPropertyBuilders = ImmutableMap.of();
+
+    /** The simple name of the generated GWT serializer class. */
+    String serializerClass;
+
+    /**
+     * The encoding of the {@code Generated} class. Empty if no {@code Generated} class is
+     * available.
+     */
+    String generated;
+
+    /** A string that should change if any salient details of the serialized class change. */
+    String classHashString;
+
+    private static final Template TEMPLATE = parsedTemplateForResource("gwtserializer.vm");
+
+    @Override
+    Template parsedTemplate() {
+      return TEMPLATE;
+    }
+  }
+
+  private void writeSourceFile(String className, String text, TypeElement originatingType) {
+    try {
+      JavaFileObject sourceFile =
+          processingEnv.getFiler().createSourceFile(className, originatingType);
+      try (Writer writer = sourceFile.openWriter()) {
+        writer.write(text);
+      }
+    } catch (IOException e) {
+      processingEnv
+          .getMessager()
+          .printMessage(
+              Diagnostic.Kind.WARNING, "Could not write generated class " + className + ": " + e);
+      // A warning rather than an error for the reason explained in
+      // AutoValueOrOneOfProcessor.writeSourceFile.
+    }
+  }
+
+  // Compute a hash that is guaranteed to change if the names, types, or order of the fields
+  // change. We use TypeEncoder so that we can get a defined string for types, since
+  // TypeMirror.toString() isn't guaranteed to remain the same.
+  private String computeClassHash(Iterable<AutoValueProcessor.Property> props, String pkg) {
+    CRC32 crc = new CRC32();
+    String encodedType = TypeEncoder.encode(type.asType()) + ":";
+    String decodedType = TypeEncoder.decode(encodedType, processingEnv, "", null);
+    if (!decodedType.startsWith(pkg)) {
+      // This is for compatibility with the way an earlier version did things. Preserving hash
+      // codes probably isn't vital, since client and server should be in sync.
+      decodedType = pkg + "." + decodedType;
+    }
+    crc.update(decodedType.getBytes(UTF_8));
+    for (AutoValueProcessor.Property prop : props) {
+      String encodedProp = prop + ":" + TypeEncoder.encode(prop.getTypeMirror()) + ";";
+      String decodedProp = TypeEncoder.decode(encodedProp, processingEnv, pkg, null);
+      crc.update(decodedProp.getBytes(UTF_8));
+    }
+    return String.format("%08x", crc.getValue());
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/JavaScanner.java b/value/src/main/java/com/google/auto/value/processor/JavaScanner.java
new file mode 100644
index 0000000..1a32180
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/JavaScanner.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2015 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.processor;
+
+/**
+ * A simplistic Java scanner. This scanner returns a sequence of tokens that can be used to
+ * reconstruct the source code. Since the source code is coming from a string, the scanner in fact
+ * just returns token boundaries rather than the tokens themselves.
+ *
+ * <p>We are not dealing with arbitrary user code so we can assume there are no exotic things like
+ * tabs or Unicode escapes that resolve into quotes. The purpose of the scanner here is to return a
+ * sequence of offsets that split the string up in a way that allows us to work with spaces without
+ * having to worry whether they are inside strings or comments. The particular properties we use are
+ * that every string and character literal and every comment is a single token; every newline plus
+ * all following indentation is a single token; and every other string of consecutive spaces outside
+ * a comment or literal is a single token. That means that we can safely compress a token that
+ * starts with a space into a single space, without falsely removing indentation or changing the
+ * contents of strings.
+ *
+ * <p>In addition to real Java syntax, this scanner recognizes tokens of the form {@code `text`},
+ * which are used in the templates to wrap fully-qualified type names, so that they can be extracted
+ * and replaced by imported names if possible.
+ *
+ * @author Éamonn McManus
+ */
+class JavaScanner {
+  private final String s;
+
+  JavaScanner(String s) {
+    this.s = s.endsWith("\n") ? s : (s + '\n');
+    // This allows us to avoid checking for the end of the string in most cases.
+  }
+
+  /**
+   * Returns the string being scanned, which is either the original input string or that string plus
+   * a newline.
+   */
+  String string() {
+    return s;
+  }
+
+  /** Returns the position at which this token ends and the next token begins. */
+  int tokenEnd(int start) {
+    if (start >= s.length()) {
+      return s.length();
+    }
+    switch (s.charAt(start)) {
+      case ' ':
+      case '\n':
+        return spaceEnd(start);
+      case '/':
+        if (s.charAt(start + 1) == '*') {
+          return blockCommentEnd(start);
+        } else if (s.charAt(start + 1) == '/') {
+          return lineCommentEnd(start);
+        } else {
+          return start + 1;
+        }
+      case '\'':
+      case '"':
+      case '`':
+        return quoteEnd(start);
+      default:
+        // Every other character is considered to be its own token.
+        return start + 1;
+    }
+  }
+
+  private int spaceEnd(int start) {
+    assert s.charAt(start) == ' ' || s.charAt(start) == '\n';
+    int i;
+    for (i = start + 1; i < s.length() && s.charAt(i) == ' '; i++) {}
+    return i;
+  }
+
+  private int blockCommentEnd(int start) {
+    assert s.charAt(start) == '/' && s.charAt(start + 1) == '*';
+    int i;
+    for (i = start + 2; s.charAt(i) != '*' || s.charAt(i + 1) != '/'; i++) {}
+    return i + 2;
+  }
+
+  private int lineCommentEnd(int start) {
+    assert s.charAt(start) == '/' && s.charAt(start + 1) == '/';
+    int end = s.indexOf('\n', start + 2);
+    assert end > 0;
+    return end;
+  }
+
+  private int quoteEnd(int start) {
+    char quote = s.charAt(start);
+    assert quote == '\'' || quote == '"' || quote == '`';
+    int i;
+    for (i = start + 1; s.charAt(i) != quote; i++) {
+      if (s.charAt(i) == '\\') {
+        i++;
+      }
+    }
+    return i + 1;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/MissingTypes.java b/value/src/main/java/com/google/auto/value/processor/MissingTypes.java
new file mode 100644
index 0000000..ea8126b
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/MissingTypes.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2014 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.processor;
+
+import java.util.List;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.SimpleTypeVisitor8;
+
+/**
+ * Handling of undefined types. When we see an undefined type, it might genuinely be undefined, or
+ * it might be a type whose source code will be generated later on as part of the same compilation.
+ * If we encounter an undefined type in a place where we need to know the type, we throw {@link
+ * MissingTypeException}. We then catch that and defer processing for the current class until the
+ * next annotation-processing "round". If the missing class has been generated in the meanwhile, we
+ * may now be able to complete processing. After a round has completed without generating any new
+ * source code, if there are still missing types then we report an error.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+final class MissingTypes {
+  private MissingTypes() {}
+
+  /**
+   * Exception thrown in the specific case where processing of a class was abandoned because it
+   * required types that the class references to be present and they were not. This case is handled
+   * specially because it is possible that those types might be generated later during annotation
+   * processing, so we should reattempt the processing of the class in a later annotation processing
+   * round.
+   */
+  @SuppressWarnings("serial")
+  static class MissingTypeException extends RuntimeException {
+    MissingTypeException(ErrorType missingType) {
+      // Although it is not specified as such, in practice ErrorType.toString() is the type name
+      // that appeared in the source code. Showing it here can help in debugging issues with
+      // deferral.
+      super(missingType == null ? null : missingType.toString());
+    }
+  }
+
+  /**
+   * Check that the return type and parameter types of the given method are all defined, and arrange
+   * to defer processing until the next round if not.
+   *
+   * @throws MissingTypeException if the return type or a parameter type of the given method is
+   *     undefined
+   */
+  static void deferIfMissingTypesIn(ExecutableElement method) {
+    MISSING_TYPE_VISITOR.check(method.getReturnType());
+    for (VariableElement param : method.getParameters()) {
+      MISSING_TYPE_VISITOR.check(param.asType());
+    }
+  }
+
+  private static final MissingTypeVisitor MISSING_TYPE_VISITOR = new MissingTypeVisitor();
+
+  private static class MissingTypeVisitor extends SimpleTypeVisitor8<Void, TypeMirrorSet> {
+    // Avoid infinite recursion for a type like `Enum<E extends Enum<E>>` by remembering types that
+    // we have already seen on this visit. Recursion has to go through a declared type, such as Enum
+    // in this example, so in principle it should be enough to check only in visitDeclared. However
+    // Eclipse has a quirk where the second E in `Enum<E extends Enum<E>>` is not the same as the
+    // first, and if you ask for its bounds you will get another `Enum<E>` with a third E. So we
+    // also check in visitTypeVariable. TypeMirrorSet does consider that all these E variables are
+    // the same so infinite recursion is avoided.
+    void check(TypeMirror type) {
+      type.accept(this, new TypeMirrorSet());
+    }
+
+    @Override
+    public Void visitError(ErrorType t, TypeMirrorSet visiting) {
+      throw new MissingTypeException(t);
+    }
+
+    @Override
+    public Void visitArray(ArrayType t, TypeMirrorSet visiting) {
+      return t.getComponentType().accept(this, visiting);
+    }
+
+    @Override
+    public Void visitDeclared(DeclaredType t, TypeMirrorSet visiting) {
+      if (visiting.add(t)) {
+        visitAll(t.getTypeArguments(), visiting);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitTypeVariable(TypeVariable t, TypeMirrorSet visiting) {
+      if (visiting.add(t)) {
+        t.getLowerBound().accept(this, visiting);
+        t.getUpperBound().accept(this, visiting);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitWildcard(WildcardType t, TypeMirrorSet visiting) {
+      if (t.getSuperBound() != null) {
+        t.getSuperBound().accept(this, visiting);
+      }
+      if (t.getExtendsBound() != null) {
+        t.getExtendsBound().accept(this, visiting);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitIntersection(IntersectionType t, TypeMirrorSet visiting) {
+      return visitAll(t.getBounds(), visiting);
+    }
+
+    private Void visitAll(List<? extends TypeMirror> types, TypeMirrorSet visiting) {
+      for (TypeMirror type : types) {
+        type.accept(this, visiting);
+      }
+      return null;
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/Optionalish.java b/value/src/main/java/com/google/auto/value/processor/Optionalish.java
new file mode 100644
index 0000000..e4a24e8
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/Optionalish.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016 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.processor;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+
+/**
+ * A wrapper for properties of Optional-like classes. This can be com.google.common.base.Optional,
+ * or any of Optional, OptionalDouble, OptionalInt, OptionalLong in java.util.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+public class Optionalish {
+  private static final ImmutableSet<String> OPTIONAL_CLASS_NAMES =
+      ImmutableSet.of(
+          "com.".concat("google.common.base.Optional"), // subterfuge to foil shading
+          "java.util.Optional",
+          "java.util.OptionalDouble",
+          "java.util.OptionalInt",
+          "java.util.OptionalLong");
+
+  private final DeclaredType optionalType;
+  private final String className;
+
+  private Optionalish(DeclaredType optionalType) {
+    this.optionalType = optionalType;
+    this.className = MoreElements.asType(optionalType.asElement()).getQualifiedName().toString();
+  }
+
+  /**
+   * Returns an instance wrapping the given TypeMirror, or null if it is not any kind of Optional.
+   *
+   * @param type the TypeMirror for the original optional type, for example {@code
+   *     Optional<String>}.
+   */
+  static Optionalish createIfOptional(TypeMirror type) {
+    if (isOptional(type)) {
+      return new Optionalish(MoreTypes.asDeclared(type));
+    } else {
+      return null;
+    }
+  }
+
+  static boolean isOptional(TypeMirror type) {
+    if (type.getKind() != TypeKind.DECLARED) {
+      return false;
+    }
+    DeclaredType declaredType = MoreTypes.asDeclared(type);
+    TypeElement typeElement = MoreElements.asType(declaredType.asElement());
+    return OPTIONAL_CLASS_NAMES.contains(typeElement.getQualifiedName().toString())
+        && typeElement.getTypeParameters().size() == declaredType.getTypeArguments().size();
+  }
+
+  /**
+   * Returns a string representing the raw type of this Optional. This will typically be just {@code
+   * "Optional"}, but it might be {@code "OptionalInt"} or {@code "java.util.Optional"} for example.
+   */
+  public String getRawType() {
+    return TypeEncoder.encodeRaw(optionalType);
+  }
+
+  /**
+   * Returns a string representing the method call to obtain the empty version of this Optional.
+   * This will be something like {@code "Optional.empty()"} or possibly {@code
+   * "java.util.Optional.empty()"}. It does not have a final semicolon.
+   *
+   * <p>This method is public so that it can be referenced as {@code p.optional.empty} from
+   * templates.
+   */
+  public String getEmpty() {
+    String empty = className.startsWith("java.util.") ? ".empty()" : ".absent()";
+    return TypeEncoder.encodeRaw(optionalType) + empty;
+  }
+
+  TypeMirror getContainedType(Types typeUtils) {
+    List<? extends TypeMirror> typeArguments = optionalType.getTypeArguments();
+    switch (typeArguments.size()) {
+      case 1:
+        return typeArguments.get(0);
+      case 0:
+        return getContainedPrimitiveType(typeUtils);
+      default:
+        throw new AssertionError("Wrong number of type arguments: " + optionalType);
+    }
+  }
+
+  String ofNullable() {
+    return className.equals("java.util.Optional") ? "ofNullable" : "fromNullable";
+  }
+
+  private static final ImmutableMap<String, TypeKind> PRIMITIVE_TYPE_KINDS =
+      ImmutableMap.of(
+          "OptionalDouble", TypeKind.DOUBLE,
+          "OptionalInt", TypeKind.INT,
+          "OptionalLong", TypeKind.LONG);
+
+  private TypeMirror getContainedPrimitiveType(Types typeUtils) {
+    String simpleName = optionalType.asElement().getSimpleName().toString();
+    TypeKind typeKind = PRIMITIVE_TYPE_KINDS.get(simpleName);
+    Verify.verifyNotNull(typeKind, "Could not get contained type of %s", optionalType);
+    return typeUtils.getPrimitiveType(typeKind);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java
new file mode 100644
index 0000000..dafb582
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2016 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.processor;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+/**
+ * Classifies methods inside builder types that return builders for properties. For example, if
+ * {@code @AutoValue} class Foo has a method {@code ImmutableList<String> bar()} then Foo.Builder
+ * can have a method {@code ImmutableList.Builder<String> barBuilder()}. This class checks that a
+ * method like {@code barBuilder()} follows the rules, and if so constructs a {@link
+ * PropertyBuilder} instance with information about {@code barBuilder}.
+ *
+ * @author Éamonn McManus
+ */
+class PropertyBuilderClassifier {
+  private final ErrorReporter errorReporter;
+  private final Types typeUtils;
+  private final Elements elementUtils;
+  private final BuilderMethodClassifier builderMethodClassifier;
+  private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName;
+  private final ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType;
+  private final EclipseHack eclipseHack;
+
+  PropertyBuilderClassifier(
+      ErrorReporter errorReporter,
+      Types typeUtils,
+      Elements elementUtils,
+      BuilderMethodClassifier builderMethodClassifier,
+      ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
+      ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType,
+      EclipseHack eclipseHack) {
+    this.errorReporter = errorReporter;
+    this.typeUtils = typeUtils;
+    this.elementUtils = elementUtils;
+    this.builderMethodClassifier = builderMethodClassifier;
+    this.getterToPropertyName = getterToPropertyName;
+    this.getterToPropertyType = getterToPropertyType;
+    this.eclipseHack = eclipseHack;
+  }
+
+  /**
+   * Information about a property builder, referenced from the autovalue.vm template. A property
+   * called bar (defined by a method bar() or getBar()) can have a property builder called
+   * barBuilder(). For example, if {@code bar()} returns {@code ImmutableSet<String>} then {@code
+   * barBuilder()} might return {@code ImmutableSet.Builder<String>}.
+   */
+  public static class PropertyBuilder {
+    private final ExecutableElement propertyBuilderMethod;
+    private final String name;
+    private final String builderType;
+    private final TypeMirror builderTypeMirror;
+    private final String initializer;
+    private final String beforeInitDefault;
+    private final String initDefault;
+    private final String builtToBuilder;
+    private final String copyAll;
+
+    PropertyBuilder(
+        ExecutableElement propertyBuilderMethod,
+        String builderType,
+        TypeMirror builderTypeMirror,
+        String initializer,
+        String beforeInitDefault,
+        String initDefault,
+        String builtToBuilder,
+        String copyAll) {
+      this.propertyBuilderMethod = propertyBuilderMethod;
+      this.name = propertyBuilderMethod.getSimpleName() + "$";
+      this.builderType = builderType;
+      this.builderTypeMirror = builderTypeMirror;
+      this.initializer = initializer;
+      this.beforeInitDefault = beforeInitDefault;
+      this.initDefault = initDefault;
+      this.builtToBuilder = builtToBuilder;
+      this.copyAll = copyAll;
+    }
+
+    /** The property builder method, for example {@code barBuilder()}. */
+    public ExecutableElement getPropertyBuilderMethod() {
+      return propertyBuilderMethod;
+    }
+
+    public String getAccess() {
+      return SimpleMethod.access(propertyBuilderMethod);
+    }
+
+    /** The name of the field to hold this builder. */
+    public String getName() {
+      return name;
+    }
+
+    /** The type of the builder, for example {@code ImmutableSet.Builder<String>}. */
+    public String getBuilderType() {
+      return builderType;
+    }
+
+    TypeMirror getBuilderTypeMirror() {
+      return builderTypeMirror;
+    }
+
+    /** An initializer for the builder field, for example {@code ImmutableSet.builder()}. */
+    public String getInitializer() {
+      return initializer;
+    }
+
+    /**
+     * An empty string, or a complete statement to be included before the expression returned by
+     * {@link #getInitDefault()}.
+     */
+    public String getBeforeInitDefault() {
+      return beforeInitDefault;
+    }
+
+    /**
+     * An expression to return a default instance of the type that this builder builds. For example,
+     * if this is an {@code ImmutableList<String>} then the method {@code ImmutableList.of()} will
+     * correctly return an empty {@code ImmutableList<String>}, assuming the appropriate context for
+     * type inference. The expression here can assume that the statement from {@link
+     * #getBeforeInitDefault} has preceded it.
+     */
+    public String getInitDefault() {
+      return initDefault;
+    }
+
+    /**
+     * A method to convert the built type back into a builder. Unfortunately Guava collections don't
+     * have this (you can't say {@code myImmutableMap.toBuilder()}), but for other types such as
+     * {@code @AutoValue} types this is {@code toBuilder()}.
+     */
+    public String getBuiltToBuilder() {
+      return builtToBuilder;
+    }
+
+    /**
+     * The method to copy another collection into this builder. It is {@code addAll} for
+     * one-dimensional collections like {@code ImmutableList} and {@code ImmutableSet}, and it is
+     * {@code putAll} for two-dimensional collections like {@code ImmutableMap} and {@code
+     * ImmutableTable}.
+     */
+    public String getCopyAll() {
+      return copyAll;
+    }
+  }
+
+  // Our @AutoValue class `Foo` has a property `Bar bar()` or `Bar getBar()` and we've encountered
+  // a builder method like `BarBuilder barBuilder()`. Here `BarBuilder` can have any name (its name
+  // doesn't have to be the name of `Bar` with `Builder` stuck on the end), but `barBuilder()` does
+  // have to be the name of the property with `Builder` stuck on the end. The requirements for the
+  // `BarBuilder` type are:
+  // (1) It must have an instance method called `build()` that returns `Bar`. If the type of
+  //     `bar()` is `Bar<String>` then the type of `build()` must be `Bar<String>`.
+  // (2) `BarBuilder` must have a public no-arg constructor, or `Bar` must have a static method
+  //     `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`. The
+  //     `naturalOrder()` case is specifically for ImmutableSortedSet and ImmutableSortedMap.
+  // (3) If `Foo` has a `toBuilder()` method, or if we have both `barBuilder()` and `setBar(Bar)`
+  //     methods, then `Bar` must have an instance method `BarBuilder toBuilder()`, or `BarBuilder`
+  //     must have an `addAll` or `putAll` method that accepts an argument of type `Bar`.
+  //
+  // This method outputs an error and returns Optional.empty() if the barBuilder() method has a
+  // problem.
+  Optional<PropertyBuilder> makePropertyBuilder(ExecutableElement method, String property) {
+    TypeMirror barBuilderTypeMirror = builderMethodClassifier.builderMethodReturnType(method);
+    if (barBuilderTypeMirror.getKind() != TypeKind.DECLARED) {
+      errorReporter.reportError(
+          method,
+          "Method looks like a property builder, but its return type is not a class or interface");
+      return Optional.empty();
+    }
+    DeclaredType barBuilderDeclaredType = MoreTypes.asDeclared(barBuilderTypeMirror);
+    TypeElement barBuilderTypeElement = MoreTypes.asTypeElement(barBuilderTypeMirror);
+    Map<String, ExecutableElement> barBuilderNoArgMethods = noArgMethodsOf(barBuilderTypeElement);
+
+    ExecutableElement barGetter = getterToPropertyName.inverse().get(property);
+    TypeMirror barTypeMirror = getterToPropertyType.get(barGetter);
+    if (barTypeMirror.getKind() != TypeKind.DECLARED) {
+      errorReporter.reportError(
+          method,
+          "Method looks like a property builder, but the type of property %s is not a class or"
+              + " interface",
+          property);
+      return Optional.empty();
+    }
+    if (isNullable(barGetter)) {
+      errorReporter.reportError(
+          barGetter,
+          "Property %s has a property builder so it cannot be @Nullable",
+          property);
+    }
+    TypeElement barTypeElement = MoreTypes.asTypeElement(barTypeMirror);
+    Map<String, ExecutableElement> barNoArgMethods = noArgMethodsOf(barTypeElement);
+
+    // Condition (1), must have build() method returning Bar.
+    ExecutableElement build = barBuilderNoArgMethods.get("build");
+    if (build == null || build.getModifiers().contains(Modifier.STATIC)) {
+      errorReporter.reportError(
+          method,
+          "Method looks like a property builder, but it returns %s which does not have a"
+              + " non-static build() method",
+          barBuilderTypeElement);
+      return Optional.empty();
+    }
+
+    // We've determined that `BarBuilder` has a method `build()`. But it must return `Bar`.
+    // And if the type of `bar()` is Bar<String> then `BarBuilder.build()` must return Bar<String>.
+    TypeMirror buildType = eclipseHack.methodReturnType(build, barBuilderDeclaredType);
+    if (!MoreTypes.equivalence().equivalent(barTypeMirror, buildType)) {
+      errorReporter.reportError(
+          method,
+          "Property builder for %s has type %s whose build() method returns %s instead of %s",
+          property,
+          barBuilderTypeElement,
+          buildType,
+          barTypeMirror);
+      return Optional.empty();
+    }
+
+    Optional<ExecutableElement> maybeBuilderMaker =
+        builderMaker(barNoArgMethods, barBuilderTypeElement);
+    if (!maybeBuilderMaker.isPresent()) {
+      errorReporter.reportError(
+          method,
+          "Method looks like a property builder, but its type %s does not have a public"
+              + " constructor and %s does not have a static builder() or newBuilder() method that"
+              + " returns %s",
+          barBuilderTypeElement,
+          barTypeElement,
+          barBuilderTypeElement);
+      return Optional.empty();
+    }
+    ExecutableElement builderMaker = maybeBuilderMaker.get();
+
+    String barBuilderType = TypeEncoder.encodeWithAnnotations(barBuilderTypeMirror);
+    String rawBarType = TypeEncoder.encodeRaw(barTypeMirror);
+    String initializer =
+        (builderMaker.getKind() == ElementKind.CONSTRUCTOR)
+            ? "new " + barBuilderType + "()"
+            : rawBarType + "." + builderMaker.getSimpleName() + "()";
+    String builtToBuilder = null;
+    String copyAll = null;
+    ExecutableElement toBuilder = barNoArgMethods.get("toBuilder");
+    if (toBuilder != null
+        && !toBuilder.getModifiers().contains(Modifier.STATIC)
+        && typeUtils.isAssignable(
+            typeUtils.erasure(toBuilder.getReturnType()),
+            typeUtils.erasure(barBuilderTypeMirror))) {
+      builtToBuilder = toBuilder.getSimpleName().toString();
+    } else {
+      Optional<ExecutableElement> maybeCopyAll =
+          addAllPutAll(barBuilderTypeElement, barBuilderDeclaredType, barTypeMirror);
+      if (maybeCopyAll.isPresent()) {
+        copyAll = maybeCopyAll.get().getSimpleName().toString();
+      }
+    }
+    ExecutableElement barOf = barNoArgMethods.get("of");
+    boolean hasOf = (barOf != null && barOf.getModifiers().contains(Modifier.STATIC));
+    // An expression (initDefault) to make a default one of these, plus optionally a statement
+    // (beforeInitDefault) that prepares the expression. For a collection, beforeInitDefault is
+    // empty and initDefault is (e.g.) `ImmutableList.of()`. For a nested value type,
+    // beforeInitDefault is (e.g.)
+    //   `NestedAutoValueType.Builder foo$builder = NestedAutoValueType.builder();`
+    // and initDefault is `foo$builder.build();`. The reason for the separate statement is to
+    // exploit type inference rather than having to write `NestedAutoValueType.<Bar>build();`.
+    String beforeInitDefault;
+    String initDefault;
+    if (hasOf) {
+      beforeInitDefault = "";
+      initDefault = rawBarType + ".of()";
+    } else {
+      String localBuilder = property + "$builder";
+      beforeInitDefault = barBuilderType + " " + localBuilder + " = " + initializer + ";";
+      initDefault = localBuilder + ".build()";
+    }
+
+    PropertyBuilder propertyBuilder =
+        new PropertyBuilder(
+            method,
+            barBuilderType,
+            barBuilderTypeMirror,
+            initializer,
+            beforeInitDefault,
+            initDefault,
+            builtToBuilder,
+            copyAll);
+    return Optional.of(propertyBuilder);
+  }
+
+  private static final ImmutableSet<String> BUILDER_METHOD_NAMES =
+      ImmutableSet.of("naturalOrder", "builder", "newBuilder");
+
+  // (2) `BarBuilder must have a public no-arg constructor, or `Bar` must have a visible static
+  //      method `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`.
+  private Optional<ExecutableElement> builderMaker(
+      Map<String, ExecutableElement> barNoArgMethods, TypeElement barBuilderTypeElement) {
+    for (String builderMethodName : BUILDER_METHOD_NAMES) {
+      ExecutableElement method = barNoArgMethods.get(builderMethodName);
+      if (method != null
+          && method.getModifiers().contains(Modifier.STATIC)
+          && typeUtils.isSameType(
+              typeUtils.erasure(method.getReturnType()),
+              typeUtils.erasure(barBuilderTypeElement.asType()))) {
+        // TODO(emcmanus): check visibility. We don't want to require public for @AutoValue
+        // builders. By not checking visibility we risk accepting something as a builder maker
+        // and then failing when the generated code tries to call Bar.builder(). But the risk
+        // seems small.
+        return Optional.of(method);
+      }
+    }
+    return ElementFilter.constructorsIn(barBuilderTypeElement.getEnclosedElements())
+        .stream()
+        .filter(c -> c.getParameters().isEmpty())
+        .filter(c -> c.getModifiers().contains(Modifier.PUBLIC))
+        .findFirst();
+  }
+
+  private Map<String, ExecutableElement> noArgMethodsOf(TypeElement type) {
+    // Can't easily use ImmutableMap here because getAllMembers could return more than one method
+    // with the same name.
+    Map<String, ExecutableElement> methods = new LinkedHashMap<>();
+    for (ExecutableElement method : ElementFilter.methodsIn(elementUtils.getAllMembers(type))) {
+      if (method.getParameters().isEmpty() && !isStaticInterfaceMethodNotIn(method, type)) {
+        methods.put(method.getSimpleName().toString(), method);
+      }
+    }
+    return methods;
+  }
+
+  // Work around an Eclipse compiler bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=547185
+  // The result of Elements.getAllMembers includes static methods declared in superinterfaces.
+  // That's wrong because those aren't inherited. So this method checks whether the given method is
+  // a static interface method not in the given type.
+  private static boolean isStaticInterfaceMethodNotIn(ExecutableElement method, TypeElement type) {
+    return method.getModifiers().contains(Modifier.STATIC)
+        && !method.getEnclosingElement().equals(type)
+        && method.getEnclosingElement().getKind().equals(ElementKind.INTERFACE);
+  }
+
+  private static final ImmutableSet<String> ADD_ALL_PUT_ALL = ImmutableSet.of("addAll", "putAll");
+
+  // We have `Bar bar()` and `Foo.Builder toBuilder()` in the @AutoValue type Foo, and we have
+  // `BarBuilder barBuilder()` in Foo.Builder. That means that we need to be able to make a
+  // `BarBuilder` from a `Bar` as part of the implementation of `Foo.toBuilder()`. We can do that
+  // if `Bar` has a method `BarBuilder toBuilder()`, but what if it doesn't? For example, Guava's
+  // `ImmutableList` doesn't have a method `ImmutableList.Builder toBuilder()`. So we also allow it
+  // to work if `BarBuilder` has a method `addAll(T)` or `putAll(T)`, where `Bar` is assignable to
+  // `T`. `ImmutableList.Builder<E>` does have a method `addAll(Iterable<? extends E>)` and
+  // `ImmutableList<E>` is assignable to `Iterable<? extends E>`, so that works.
+  private Optional<ExecutableElement> addAllPutAll(
+      TypeElement barBuilderTypeElement,
+      DeclaredType barBuilderDeclaredType,
+      TypeMirror barTypeMirror) {
+    return MoreElements.getLocalAndInheritedMethods(barBuilderTypeElement, typeUtils, elementUtils)
+        .stream()
+        .filter(
+            method ->
+                ADD_ALL_PUT_ALL.contains(method.getSimpleName().toString())
+                    && method.getParameters().size() == 1)
+        .filter(
+            method -> {
+              ExecutableType methodMirror =
+                  MoreTypes.asExecutable(typeUtils.asMemberOf(barBuilderDeclaredType, method));
+              return typeUtils.isAssignable(barTypeMirror, methodMirror.getParameterTypes().get(0));
+            })
+        .findFirst();
+  }
+
+  private static boolean isNullable(ExecutableElement getter) {
+    List<List<? extends AnnotationMirror>> annotationLists =
+        ImmutableList.of(
+            getter.getAnnotationMirrors(), getter.getReturnType().getAnnotationMirrors());
+    for (List<? extends AnnotationMirror> annotations : annotationLists) {
+      for (AnnotationMirror annotation : annotations) {
+        if (annotation.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/PropertyNames.java b/value/src/main/java/com/google/auto/value/processor/PropertyNames.java
new file mode 100644
index 0000000..62892d2
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/PropertyNames.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 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.processor;
+
+import com.google.common.base.Strings;
+
+/** Helper methods to create property names. */
+class PropertyNames {
+  /**
+   * Returns the {@code propertyName} with its first character in lower case, but leaves it intact
+   * if it starts with two capitals.
+   *
+   * <p>For consistency with JavaBeans, a getter called {@code getHTMLPage()} defines a property
+   * called {@code HTMLPage}. The <a
+   * href="https://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize-java.lang.String-">
+   * rule</a> is: the name of the property is the part after {@code get} or {@code
+   * is}, with the first letter lowercased <i>unless</i> the first two letters are uppercase. This
+   * works well for the {@code HTMLPage} example, but in these more enlightened times we use {@code
+   * HtmlPage} anyway, so the special behaviour is not useful, and of course it behaves poorly with
+   * examples like {@code OAuth}. Nevertheless, we preserve it for compatibility.
+   */
+  static String decapitalizeLikeJavaBeans(String propertyName) {
+    if (propertyName != null
+        && propertyName.length() >= 2
+        && Character.isUpperCase(propertyName.charAt(0))
+        && Character.isUpperCase(propertyName.charAt(1))) {
+      return propertyName;
+    }
+    return decapitalizeNormally(propertyName);
+  }
+
+  /**
+   * Returns the {@code propertyName} with its first character in lower case.
+   */
+  static String decapitalizeNormally(String propertyName) {
+    if (Strings.isNullOrEmpty(propertyName)) {
+      return propertyName;
+    }
+    return Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/Reformatter.java b/value/src/main/java/com/google/auto/value/processor/Reformatter.java
new file mode 100644
index 0000000..6926517
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/Reformatter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 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.processor;
+
+/**
+ * Postprocessor that runs over the output of the template engine in order to make it look nicer.
+ * Mostly, this involves removing surplus horizontal and vertical space.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+class Reformatter {
+  static String fixup(String s) {
+    StringBuilder out = new StringBuilder();
+    JavaScanner scanner = new JavaScanner(s);
+    s = scanner.string();
+    int len = s.length();
+    for (int start = 0, previous = 0, braces = 0, parens = 0, end = 0;
+        start < len;
+        previous = start, start = end) {
+      end = scanner.tokenEnd(start);
+      // The tokenized string always ends with \n so we can usually look at s.charAt(end) without
+      // worrying about going past the end of the string.
+      switch (s.charAt(start)) {
+        case '(':
+          parens++;
+          break;
+        case ')':
+          parens--;
+          break;
+        case '{':
+          braces++;
+          break;
+        case '}':
+          braces--;
+          break;
+        case ' ':
+          // This token is a string of consecutive spaces that is not at the start of a line.
+          // Consecutive spaces at the start of a line are attached to the previous newline, and
+          // we don't expect the first line to start with spaces. So we are going to compress this
+          // into just one space, and we are going to delete it entirely if it follows '(' or
+          // precedes a newline or one of the punctuation characters here.
+          if (s.charAt(previous) != '(' && "\n.,;)".indexOf(s.charAt(end)) < 0) {
+            out.append(' ');
+          }
+          continue;
+        case '\n':
+          // This token is a newline plus any following spaces (the indentation of the next line).
+          // If it is followed by something other than a newline then we will output it. Otherwise,
+          // it is part of a sequence of newlines but it is not the last one. If this is a context
+          // where we delete blank lines, or if this is not the first new line in the sequence, or
+          // if we are at the start of the file, we will delete this one. Otherwise we will output a
+          // single newline with no following indentation. Contexts where we delete blank lines are
+          // inside parentheses or inside more than one set of braces.
+          if (end < len && s.charAt(end) != '\n') {
+            if (out.length() == 0) {
+              // Omit newlines at the very start of the file.
+              start++;
+            }
+            break; // Output the newline and its following indentation.
+          }
+          if (parens == 0 && braces < 2 && s.charAt(previous) != '\n' && out.length() > 0) {
+            out.append('\n');
+          }
+          continue;
+        default:
+          break;
+      }
+      out.append(s, start, end);
+    }
+    return out.toString();
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java b/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java
new file mode 100644
index 0000000..0351023
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 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.processor;
+
+import java.util.Set;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+
+/**
+ * A method on an {@code @AutoValue} or {@code AutoOneOf} class that has no specific attached
+ * information, such as a {@code toBuilder()} method, or a {@code build()} method, where only the
+ * name and access type is needed in context.
+ *
+ * <p>It implements JavaBean-style getters which means it can be referenced from templates, for
+ * example {@code $method.access}. This template access means that the class and its getters must be
+ * public.
+ */
+public final class SimpleMethod {
+  private final String access;
+  private final String name;
+
+  SimpleMethod(ExecutableElement method) {
+    this.access = access(method);
+    this.name = method.getSimpleName().toString();
+  }
+
+  public String getAccess() {
+    return access;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Returns an appropriate string to be used in code for the access specification of the given
+   * method. This will be {@code public} or {@code protected} followed by a space, or the empty
+   * string for default access.
+   */
+  static String access(ExecutableElement method) {
+    Set<Modifier> mods = method.getModifiers();
+    if (mods.contains(Modifier.PUBLIC)) {
+      return "public ";
+    } else if (mods.contains(Modifier.PROTECTED)) {
+      return "protected ";
+    } else {
+      return "";
+    }
+  }
+}
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
new file mode 100644
index 0000000..55f9ae4
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 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.processor;
+
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.collect.ImmutableList;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.ServiceConfigurationError;
+
+/**
+ * A replacement for {@link java.util.ServiceLoader} that avoids certain long-standing bugs. This
+ * simpler implementation does not bother with lazy loading but returns all the service
+ * implementations in one list. It makes sure that {@link URLConnection#setUseCaches} is called to
+ * turn off jar caching, since that tends to lead to problems in versions before JDK 9.
+ *
+ * @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>
+ */
+public final class SimpleServiceLoader {
+  private SimpleServiceLoader() {}
+
+  public static <T> ImmutableList<T> load(Class<? extends T> service, ClassLoader loader) {
+    String resourceName = "META-INF/services/" + service.getName();
+    List<URL> resourceUrls;
+    try {
+      resourceUrls = Collections.list(loader.getResources(resourceName));
+    } catch (IOException e) {
+      throw new ServiceConfigurationError("Could not look up " + resourceName, e);
+    }
+    ImmutableList.Builder<T> providers = ImmutableList.builder();
+    for (URL resourceUrl : resourceUrls) {
+      try {
+        providers.addAll(providersFromUrl(resourceUrl, service, loader));
+      } catch (IOException e) {
+        throw new ServiceConfigurationError("Could not read " + resourceUrl, e);
+      }
+    }
+    return providers.build();
+  }
+
+  private static <T> ImmutableList<T> providersFromUrl(
+      URL resourceUrl, Class<T> service, ClassLoader loader) throws IOException {
+    ImmutableList.Builder<T> providers = ImmutableList.builder();
+    URLConnection urlConnection = resourceUrl.openConnection();
+    urlConnection.setUseCaches(false);
+    try (InputStream in = urlConnection.getInputStream();
+        BufferedReader reader =
+            new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
+      for (String line : reader.lines().collect(toList())) {
+        Optional<String> maybeClassName = parseClassName(line);
+        if (maybeClassName.isPresent()) {
+          String className = maybeClassName.get();
+          Class<?> c;
+          try {
+            c = Class.forName(className, false, loader);
+          } catch (ClassNotFoundException e) {
+            throw new ServiceConfigurationError("Could not load " + className, e);
+          }
+          if (!service.isAssignableFrom(c)) {
+            throw new ServiceConfigurationError(
+                "Class " + className + " is not assignable to " + service.getName());
+          }
+          try {
+            Object provider = c.getConstructor().newInstance();
+            providers.add(service.cast(provider));
+          } catch (ReflectiveOperationException e) {
+            throw new ServiceConfigurationError("Could not construct " + className, e);
+          }
+        }
+      }
+      return providers.build();
+    }
+  }
+
+  private static Optional<String> parseClassName(String line) {
+    int hash = line.indexOf('#');
+    if (hash >= 0) {
+      line = line.substring(0, hash);
+    }
+    line = line.trim();
+    return line.isEmpty() ? Optional.empty() : Optional.of(line);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/TemplateVars.java b/value/src/main/java/com/google/auto/value/processor/TemplateVars.java
new file mode 100644
index 0000000..235f0ad
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/TemplateVars.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2014 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.processor;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.escapevelocity.Template;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * A template and a set of variables to be substituted into that template. A concrete subclass of
+ * this class defines a set of fields that are template variables, and an implementation of the
+ * {@link #parsedTemplate()} method which is the template to substitute them into. Once the values
+ * of the fields have been assigned, the {@link #toText()} method returns the result of substituting
+ * them into the template.
+ *
+ * <p>The subclass may be a direct subclass of this class or a more distant descendant. Every field
+ * in the starting class and its ancestors up to this class will be included. Fields cannot be
+ * static unless they are also final. They cannot be private, though they can be package-private if
+ * the class is in the same package as this class. They cannot be primitive or null, so that there
+ * is a clear indication when a field has not been set.
+ *
+ * @author Éamonn McManus
+ */
+abstract class TemplateVars {
+  abstract Template parsedTemplate();
+
+  private final ImmutableList<Field> fields;
+
+  TemplateVars() {
+    this.fields = getFields(getClass());
+  }
+
+  private static ImmutableList<Field> getFields(Class<?> c) {
+    ImmutableList.Builder<Field> fieldsBuilder = ImmutableList.builder();
+    while (c != TemplateVars.class) {
+      addFields(fieldsBuilder, c.getDeclaredFields());
+      c = c.getSuperclass();
+    }
+    return fieldsBuilder.build();
+  }
+
+  private static void addFields(
+      ImmutableList.Builder<Field> fieldsBuilder, Field[] declaredFields) {
+    for (Field field : declaredFields) {
+      if (field.isSynthetic() || isStaticFinal(field)) {
+        continue;
+      }
+      if (Modifier.isPrivate(field.getModifiers())) {
+        throw new IllegalArgumentException("Field cannot be private: " + field);
+      }
+      if (Modifier.isStatic(field.getModifiers())) {
+        throw new IllegalArgumentException("Field cannot be static unless also final: " + field);
+      }
+      if (field.getType().isPrimitive()) {
+        throw new IllegalArgumentException("Field cannot be primitive: " + field);
+      }
+      fieldsBuilder.add(field);
+    }
+  }
+
+  /**
+   * Returns the result of substituting the variables defined by the fields of this class (a
+   * concrete subclass of TemplateVars) into the template returned by {@link #parsedTemplate()}.
+   */
+  String toText() {
+    Map<String, Object> vars = toVars();
+    return parsedTemplate().evaluate(vars);
+  }
+
+  private Map<String, Object> toVars() {
+    Map<String, Object> vars = new TreeMap<>();
+    for (Field field : fields) {
+      Object value = fieldValue(field, this);
+      if (value == null) {
+        throw new IllegalArgumentException("Field cannot be null (was it set?): " + field);
+      }
+      Object old = vars.put(field.getName(), value);
+      if (old != null) {
+        throw new IllegalArgumentException("Two fields called " + field.getName() + "?!");
+      }
+    }
+    return ImmutableMap.copyOf(vars);
+  }
+
+  static Template parsedTemplateForResource(String resourceName) {
+    try {
+      return Template.parseFrom(resourceName, TemplateVars::readerFromResource);
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError(e);
+    } catch (IOException | NullPointerException | IllegalStateException e) {
+      // https://github.com/google/auto/pull/439 says that we can get NullPointerException.
+      // https://github.com/google/auto/issues/715 says that we can get IllegalStateException
+      return retryParseAfterException(resourceName, e);
+    }
+  }
+
+  private static Template retryParseAfterException(String resourceName, Exception exception) {
+    try {
+      return Template.parseFrom(resourceName, TemplateVars::readerFromUrl);
+    } catch (IOException t) {
+      // Chain the original exception so we can see both problems.
+      Throwables.getRootCause(exception).initCause(t);
+      throw new AssertionError(exception);
+    }
+  }
+
+  private static Reader readerFromResource(String resourceName) {
+    InputStream in = TemplateVars.class.getResourceAsStream(resourceName);
+    if (in == null) {
+      throw new IllegalArgumentException("Could not find resource: " + resourceName);
+    }
+    return new InputStreamReader(in, StandardCharsets.UTF_8);
+  }
+
+  // This is an ugly workaround for https://bugs.openjdk.java.net/browse/JDK-6947916, as
+  // reported in https://github.com/google/auto/issues/365.
+  // The issue is that sometimes the InputStream returned by JarURLCollection.getInputStream()
+  // can be closed prematurely, which leads to an IOException saying "Stream closed".
+  // We catch all IOExceptions, and fall back on logic that opens the jar file directly and
+  // loads the resource from it. Since that doesn't use JarURLConnection, it shouldn't be
+  // susceptible to the same bug. We only use this as fallback logic rather than doing it always,
+  // because jars are memory-mapped by URLClassLoader, so loading a resource in the usual way
+  // through the getResourceAsStream should be a lot more efficient than reopening the jar.
+  private static Reader readerFromUrl(String resourceName) throws IOException {
+    URL resourceUrl = TemplateVars.class.getResource(resourceName);
+    if (resourceUrl == null) {
+      // This is unlikely, since getResourceAsStream has already succeeded for the same resource.
+      throw new IllegalArgumentException("Could not find resource: " + resourceName);
+    }
+    InputStream in;
+    try {
+      if (resourceUrl.getProtocol().equalsIgnoreCase("file")) {
+        in = inputStreamFromFile(resourceUrl);
+      } else if (resourceUrl.getProtocol().equalsIgnoreCase("jar")) {
+        in = inputStreamFromJar(resourceUrl);
+      } else {
+        throw new AssertionError("Template fallback logic fails for: " + resourceUrl);
+      }
+    } catch (URISyntaxException e) {
+      throw new IOException(e);
+    }
+    return new InputStreamReader(in, StandardCharsets.UTF_8);
+  }
+
+  private static InputStream inputStreamFromJar(URL resourceUrl)
+      throws URISyntaxException, IOException {
+    // Jar URLs look like this: jar:file:/path/to/file.jar!/entry/within/jar
+    // So take apart the URL to open the jar /path/to/file.jar and read the entry
+    // entry/within/jar from it.
+    String resourceUrlString = resourceUrl.toString().substring("jar:".length());
+    int bang = resourceUrlString.lastIndexOf('!');
+    String entryName = resourceUrlString.substring(bang + 1);
+    if (entryName.startsWith("/")) {
+      entryName = entryName.substring(1);
+    }
+    URI jarUri = new URI(resourceUrlString.substring(0, bang));
+    JarFile jar = new JarFile(new File(jarUri));
+    JarEntry entry = jar.getJarEntry(entryName);
+    InputStream in = jar.getInputStream(entry);
+    // We have to be careful not to close the JarFile before the stream has been read, because
+    // that would also close the stream. So we defer closing the JarFile until the stream is closed.
+    return new FilterInputStream(in) {
+      @Override
+      public void close() throws IOException {
+        super.close();
+        jar.close();
+      }
+    };
+  }
+
+  // We don't really expect this case to arise, since the bug we're working around concerns jars
+  // not individual files. However, when running the test for this workaround from Maven, we do
+  // have files. That does mean the test is basically useless there, but Google's internal build
+  // system does run it using a jar, so we do have coverage.
+  private static InputStream inputStreamFromFile(URL resourceUrl)
+      throws IOException, URISyntaxException {
+    File resourceFile = new File(resourceUrl.toURI());
+    return new FileInputStream(resourceFile);
+  }
+
+  private static Object fieldValue(Field field, Object container) {
+    try {
+      return field.get(container);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static boolean isStaticFinal(Field field) {
+    int modifiers = field.getModifiers();
+    return Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
new file mode 100644
index 0000000..d1bbbab
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright 2017 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.processor;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.stream.Collectors.toList;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleTypeVisitor8;
+import javax.lang.model.util.Types;
+
+/**
+ * Encodes types so they can later be decoded to incorporate imports.
+ *
+ * <p>The idea is that types that appear in generated source code use {@link #encode}, which will
+ * spell out a type like {@code java.util.List<? extends java.lang.Number>}, except that wherever a
+ * class name appears it is replaced by a special token. So the spelling might actually be {@code
+ * `java.util.List`<? extends `java.lang.Number`>}. Then once the entire class has been generated,
+ * {@code #decode} scans for these tokens to determine what classes need to be imported, and
+ * replaces the tokens with the correct spelling given the imports. So here, {@code java.util.List}
+ * would be imported, and the final spelling would be {@code List<? extends Number>} (knowing that
+ * {@code Number} is implicitly imported). The special token {@code `import`} marks where the
+ * imports should be, and {@link #decode} replaces it with the correct list of imports.
+ *
+ * <p>The funky syntax for type annotations on qualified type names requires an adjustment to this
+ * scheme. {@code `«java.util.Map`} stands for {@code java.util.} and {@code `»java.util.Map`}
+ * stands for {@code Map}. If {@code java.util.Map} is imported, then {@code `«java.util.Map`} will
+ * eventually be empty, but if {@code java.util.Map} is not imported (perhaps because there is
+ * another {@code Map} in scope) then {@code `«java.util.Map`} will be {@code java.util.}. The end
+ * result is that the code can contain {@code `«java.util.Map`@`javax.annotation.Nullable`
+ * `»java.util.Map`}. That might decode to {@code @Nullable Map} or to {@code java.util.@Nullable
+ * Map} or even to {@code java.util.@javax.annotation.Nullable Map}.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+final class TypeEncoder {
+  private TypeEncoder() {} // There are no instances of this class.
+
+  private static final EncodingTypeVisitor ENCODING_TYPE_VISITOR = new EncodingTypeVisitor();
+  private static final RawEncodingTypeVisitor RAW_ENCODING_TYPE_VISITOR =
+      new RawEncodingTypeVisitor();
+
+  /**
+   * Returns the encoding for the given type, where class names are marked by special tokens. The
+   * encoding for {@code int} will be {@code int}, but the encoding for {@code
+   * java.util.List<java.lang.Integer>} will be {@code `java.util.List`<`java.lang.Integer`>}.
+   */
+  static String encode(TypeMirror type) {
+    StringBuilder sb = new StringBuilder();
+    return type.accept(ENCODING_TYPE_VISITOR, sb).toString();
+  }
+
+  /**
+   * Like {@link #encode}, except that only the raw type is encoded. So if the given type is {@code
+   * java.util.List<java.lang.Integer>} the result will be {@code `java.util.List`}.
+   */
+  static String encodeRaw(TypeMirror type) {
+    StringBuilder sb = new StringBuilder();
+    return type.accept(RAW_ENCODING_TYPE_VISITOR, sb).toString();
+  }
+
+  /**
+   * Encodes the given type and its type annotations. The class comment for {@link TypeEncoder}
+   * covers the details of annotation encoding.
+   */
+  static String encodeWithAnnotations(TypeMirror type) {
+    return encodeWithAnnotations(type, ImmutableSet.of());
+  }
+
+  /**
+   * Encodes the given type and its type annotations. The class comment for {@link TypeEncoder}
+   * covers the details of annotation encoding.
+   *
+   * @param excludedAnnotationTypes annotations not to include in the encoding. For example, if
+   *     {@code com.example.Nullable} is in this set then the encoding will not include this
+   *     {@code @Nullable} annotation.
+   */
+  static String encodeWithAnnotations(TypeMirror type, Set<TypeMirror> excludedAnnotationTypes) {
+    StringBuilder sb = new StringBuilder();
+    return new AnnotatedEncodingTypeVisitor(excludedAnnotationTypes).visit2(type, sb).toString();
+  }
+
+  /**
+   * Decodes the given string, respelling class names appropriately. The text is scanned for tokens
+   * like {@code `java.util.Locale`} or {@code `«java.util.Locale`} to determine which classes are
+   * referenced. An appropriate set of imports is computed based on the set of those types. If the
+   * special token {@code `import`} appears in {@code text} then it will be replaced by this set of
+   * import statements. Then all of the tokens are replaced by the class names they represent,
+   * spelled appropriately given the import statements.
+   *
+   * @param text the text to be decoded.
+   * @param packageName the package of the generated class. Other classes in the same package do not
+   *     need to be imported.
+   * @param baseType a class or interface that the generated class inherits from. Nested classes in
+   *     that type do not need to be imported, and if another class has the same name as one of
+   *     those nested classes then it will need to be qualified.
+   */
+  static String decode(
+      String text, ProcessingEnvironment processingEnv, String packageName, TypeMirror baseType) {
+    return decode(
+        text, processingEnv.getElementUtils(), processingEnv.getTypeUtils(), packageName, baseType);
+  }
+
+  static String decode(
+      String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType) {
+    TypeRewriter typeRewriter = new TypeRewriter(text, elementUtils, typeUtils, pkg, baseType);
+    return typeRewriter.rewrite();
+  }
+
+  private static String className(DeclaredType declaredType) {
+    return MoreElements.asType(declaredType.asElement()).getQualifiedName().toString();
+  }
+
+  /**
+   * Returns the formal type parameters of the given type. If we have {@code @AutoValue abstract
+   * class Foo<T extends SomeClass>} then this method will return an encoding of {@code <T extends
+   * SomeClass>} for {@code Foo}. Likewise it will return an encoding of the angle-bracket part of:
+   * <br>
+   * {@code Foo<SomeClass>}<br>
+   * {@code Foo<T extends Number>}<br>
+   * {@code Foo<E extends Enum<E>>}<br>
+   * {@code Foo<K, V extends Comparable<? extends K>>}.
+   *
+   * <p>The encoding is simply that classes in the "extends" part are marked, so the examples will
+   * actually look something like this:<br>
+   * {@code <`bar.baz.SomeClass`>}<br>
+   * {@code <T extends `java.lang.Number`>}<br>
+   * {@code <E extends `java.lang.Enum`<E>>}<br>
+   * {@code <K, V extends `java.lang.Comparable`<? extends K>>}.
+   */
+  static String formalTypeParametersString(TypeElement type) {
+    List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
+    if (typeParameters.isEmpty()) {
+      return "";
+    } else {
+      StringBuilder sb = new StringBuilder("<");
+      String sep = "";
+      for (TypeParameterElement typeParameter : typeParameters) {
+        sb.append(sep);
+        sep = ", ";
+        appendTypeParameterWithBounds(typeParameter, sb);
+      }
+      return sb.append(">").toString();
+    }
+  }
+
+  private static void appendTypeParameterWithBounds(
+      TypeParameterElement typeParameter, StringBuilder sb) {
+    appendAnnotations(typeParameter.getAnnotationMirrors(), sb);
+    sb.append(typeParameter.getSimpleName());
+    String sep = " extends ";
+    for (TypeMirror bound : typeParameter.getBounds()) {
+      if (!isUnannotatedJavaLangObject(bound)) {
+        sb.append(sep);
+        sep = " & ";
+        sb.append(encodeWithAnnotations(bound));
+      }
+    }
+  }
+
+  // We can omit "extends Object" from a type bound, but not "extends @NullableType Object".
+  private static boolean isUnannotatedJavaLangObject(TypeMirror type) {
+    return type.getKind().equals(TypeKind.DECLARED)
+        && type.getAnnotationMirrors().isEmpty()
+        && MoreTypes.asTypeElement(type).getQualifiedName().contentEquals("java.lang.Object");
+  }
+
+  private static void appendAnnotations(
+      List<? extends AnnotationMirror> annotationMirrors, StringBuilder sb) {
+    for (AnnotationMirror annotationMirror : annotationMirrors) {
+      sb.append(AnnotationOutput.sourceFormForAnnotation(annotationMirror)).append(" ");
+    }
+  }
+
+  /**
+   * Converts a type into a string, using standard Java syntax, except that every class name is
+   * wrapped in backquotes, like {@code `java.util.List`}.
+   */
+  private static class EncodingTypeVisitor
+      extends SimpleTypeVisitor8<StringBuilder, StringBuilder> {
+    /**
+     * Equivalent to {@code visit(type, sb)} or {@code type.accept(sb)}, except that it fixes a bug
+     * with javac versions up to JDK 8, whereby if the type is a {@code DeclaredType} then the
+     * visitor is called with a version of the type where any annotations have been lost. We can't
+     * override {@code visit} because it is final.
+     */
+    StringBuilder visit2(TypeMirror type, StringBuilder sb) {
+      if (type.getKind().equals(TypeKind.DECLARED)) {
+        // There's no point in using MoreTypes.asDeclared here, and in fact we can't, because it
+        // uses a visitor, so it would trigger the bug we're working around.
+        return visitDeclared((DeclaredType) type, sb);
+      } else {
+        return visit(type, sb);
+      }
+    }
+
+    @Override
+    protected StringBuilder defaultAction(TypeMirror type, StringBuilder sb) {
+      return sb.append(type);
+    }
+
+    @Override
+    public StringBuilder visitArray(ArrayType type, StringBuilder sb) {
+      return visit2(type.getComponentType(), sb).append("[]");
+    }
+
+    @Override
+    public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) {
+      appendTypeName(type, sb);
+      appendTypeArguments(type, sb);
+      return sb;
+    }
+
+    void appendTypeName(DeclaredType type, StringBuilder sb) {
+      TypeMirror enclosing = EclipseHack.getEnclosingType(type);
+      if (enclosing.getKind().equals(TypeKind.DECLARED)) {
+        // We might have something like com.example.Outer<Double>.Inner. We need to encode
+        // com.example.Outer<Double> first, producing `com.example.Outer`<`java.lang.Double`>.
+        // Then we can simply add .Inner after that. If Inner has its own type arguments, we'll
+        // add them with appendTypeArguments below. Of course, it's more usual for the outer class
+        // not to have type arguments, but we'll still follow this path if the nested class is an
+        // inner (not static) class.
+        visit2(enclosing, sb);
+        sb.append(".").append(type.asElement().getSimpleName());
+      } else {
+        sb.append('`').append(className(type)).append('`');
+      }
+    }
+
+    void appendTypeArguments(DeclaredType type, StringBuilder sb) {
+      List<? extends TypeMirror> arguments = type.getTypeArguments();
+      if (!arguments.isEmpty()) {
+        sb.append("<");
+        String sep = "";
+        for (TypeMirror argument : arguments) {
+          sb.append(sep);
+          sep = ", ";
+          visit2(argument, sb);
+        }
+        sb.append(">");
+      }
+    }
+
+    @Override
+    public StringBuilder visitWildcard(WildcardType type, StringBuilder sb) {
+      sb.append("?");
+      TypeMirror extendsBound = type.getExtendsBound();
+      TypeMirror superBound = type.getSuperBound();
+      if (superBound != null) {
+        sb.append(" super ");
+        visit2(superBound, sb);
+      } else if (extendsBound != null) {
+        sb.append(" extends ");
+        visit2(extendsBound, sb);
+      }
+      return sb;
+    }
+
+    @Override
+    public StringBuilder visitError(ErrorType t, StringBuilder p) {
+      throw new MissingTypeException(t);
+    }
+  }
+
+  /** Like {@link EncodingTypeVisitor} except that type parameters are omitted from the result. */
+  private static class RawEncodingTypeVisitor extends EncodingTypeVisitor {
+    @Override
+    void appendTypeArguments(DeclaredType type, StringBuilder sb) {}
+  }
+
+  /**
+   * Like {@link EncodingTypeVisitor} except that annotations on the visited type are also included
+   * in the resultant string. Class names in those annotations are also encoded using the {@code
+   * `java.util.List`} form.
+   */
+  private static class AnnotatedEncodingTypeVisitor extends EncodingTypeVisitor {
+    private final Set<TypeMirror> excludedAnnotationTypes;
+
+    AnnotatedEncodingTypeVisitor(Set<TypeMirror> excludedAnnotationTypes) {
+      this.excludedAnnotationTypes = excludedAnnotationTypes;
+    }
+
+    private void appendAnnotationsWithExclusions(
+        List<? extends AnnotationMirror> annotations, StringBuilder sb) {
+      // Optimization for the very common cases where there are no annotations or there are no
+      // exclusions.
+      if (annotations.isEmpty() || excludedAnnotationTypes.isEmpty()) {
+        appendAnnotations(annotations, sb);
+        return;
+      }
+      List<AnnotationMirror> includedAnnotations =
+          annotations.stream()
+              .filter(a -> !excludedAnnotationTypes.contains(a.getAnnotationType()))
+              .collect(toList());
+      appendAnnotations(includedAnnotations, sb);
+    }
+
+    @Override
+    public StringBuilder visitPrimitive(PrimitiveType type, StringBuilder sb) {
+      appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb);
+      // We can't just append type.toString(), because that will also have the annotation, but
+      // without encoding.
+      return sb.append(type.getKind().toString().toLowerCase());
+    }
+
+    @Override
+    public StringBuilder visitTypeVariable(TypeVariable type, StringBuilder sb) {
+      appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb);
+      return sb.append(type.asElement().getSimpleName());
+    }
+
+    /**
+     * {@inheritDoc} The result respects the Java syntax, whereby {@code Foo @Bar []} is an
+     * annotation on the array type itself, while {@code @Bar Foo[]} would be an annotation on the
+     * component type.
+     */
+    @Override
+    public StringBuilder visitArray(ArrayType type, StringBuilder sb) {
+      visit2(type.getComponentType(), sb);
+      List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors();
+      if (!annotationMirrors.isEmpty()) {
+        sb.append(" ");
+        appendAnnotationsWithExclusions(annotationMirrors, sb);
+      }
+      return sb.append("[]");
+    }
+
+    @Override
+    public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) {
+      List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors();
+      if (annotationMirrors.isEmpty()) {
+        super.visitDeclared(type, sb);
+      } else {
+        TypeMirror enclosing = EclipseHack.getEnclosingType(type);
+        if (enclosing.getKind().equals(TypeKind.DECLARED)) {
+          // We have something like com.example.Outer<Double>.@Annot Inner.
+          // We'll recursively encode com.example.Outer<Double> first,
+          // which if it is also annotated might result in a mouthful like
+          // `«com.example.Outer`@`org.annots.Nullable``»com.example.Outer`<`java.lang.Double`> .
+          // That annotation will have been added by a recursive call to this method.
+          // Then we'll add the annotation on the .Inner class, which we know is there because
+          // annotationMirrors is not empty. That means we'll append .@`org.annots.Annot` Inner .
+          visit2(enclosing, sb);
+          sb.append(".");
+          appendAnnotationsWithExclusions(annotationMirrors, sb);
+          sb.append(type.asElement().getSimpleName());
+        } else {
+          // This isn't an inner class, so we have the simpler (but still complicated) case of
+          // needing to place the annotation correctly in cases like java.util.@Nullable Map .
+          // See the class doc comment for an explanation of « and » here.
+          String className = className(type);
+          sb.append("`«").append(className).append("`");
+          appendAnnotationsWithExclusions(annotationMirrors, sb);
+          sb.append("`»").append(className).append("`");
+        }
+        appendTypeArguments(type, sb);
+      }
+      return sb;
+    }
+  }
+
+  private static class TypeRewriter {
+    private final String text;
+    private final int textLength;
+    private final JavaScanner scanner;
+    private final Elements elementUtils;
+    private final Types typeUtils;
+    private final String packageName;
+    private final TypeMirror baseType;
+
+    TypeRewriter(
+        String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType) {
+      this.text = text;
+      this.textLength = text.length();
+      this.scanner = new JavaScanner(text);
+      this.elementUtils = elementUtils;
+      this.typeUtils = typeUtils;
+      this.packageName = pkg;
+      this.baseType = baseType;
+    }
+
+    String rewrite() {
+      // Scan the text to determine what classes are referenced.
+      Set<TypeMirror> referencedClasses = findReferencedClasses();
+      // Make a type simplifier based on these referenced types.
+      TypeSimplifier typeSimplifier =
+          new TypeSimplifier(elementUtils, typeUtils, packageName, referencedClasses, baseType);
+
+      StringBuilder output = new StringBuilder();
+      int copyStart;
+
+      // Replace the `import` token with the import statements, if it is present.
+      OptionalInt importMarker = findImportMarker();
+      if (importMarker.isPresent()) {
+        output.append(text, 0, importMarker.getAsInt());
+        for (String toImport : typeSimplifier.typesToImport()) {
+          output.append("import ").append(toImport).append(";\n");
+        }
+        copyStart = scanner.tokenEnd(importMarker.getAsInt());
+      } else {
+        copyStart = 0;
+      }
+
+      // Replace each of the classname tokens with the appropriate spelling of the classname.
+      int token;
+      for (token = copyStart; token < textLength; token = scanner.tokenEnd(token)) {
+        if (text.charAt(token) == '`') {
+          output.append(text, copyStart, token);
+          decode(output, typeSimplifier, token);
+          copyStart = scanner.tokenEnd(token);
+        }
+      }
+      output.append(text, copyStart, textLength);
+      return output.toString();
+    }
+
+    private Set<TypeMirror> findReferencedClasses() {
+      Set<TypeMirror> classes = new TypeMirrorSet();
+      for (int token = 0; token < textLength; token = scanner.tokenEnd(token)) {
+        if (text.charAt(token) == '`' && !text.startsWith("`import`", token)) {
+          String className = classNameAt(token);
+          classes.add(classForName(className));
+        }
+      }
+      return classes;
+    }
+
+    private DeclaredType classForName(String className) {
+      TypeElement typeElement = elementUtils.getTypeElement(className);
+      checkState(typeElement != null, "Could not find referenced class %s", className);
+      return MoreTypes.asDeclared(typeElement.asType());
+    }
+
+    private void decode(StringBuilder output, TypeSimplifier typeSimplifier, int token) {
+      String className = classNameAt(token);
+      DeclaredType type = classForName(className);
+      String simplified = typeSimplifier.simplifiedClassName(type);
+      int dot;
+      switch (text.charAt(token + 1)) {
+        case '«':
+          // If this is `«java.util.Map` then we want "java.util." here.
+          // That's because this is the first part of something like "java.util.@Nullable Map"
+          // or "java.util.Map.@Nullable Entry".
+          // If there's no dot, then we want nothing here, for "@Nullable Map".
+          dot = simplified.lastIndexOf('.');
+          output.append(simplified.substring(0, dot + 1)); // correct even if dot == -1
+          break;
+        case '»':
+          dot = simplified.lastIndexOf('.');
+          output.append(simplified.substring(dot + 1)); // correct even if dot == -1
+          break;
+        default:
+          output.append(simplified);
+          break;
+      }
+    }
+
+    private OptionalInt findImportMarker() {
+      for (int token = 0; token < textLength; token = scanner.tokenEnd(token)) {
+        if (text.startsWith("`import`", token)) {
+          return OptionalInt.of(token);
+        }
+      }
+      return OptionalInt.empty();
+    }
+
+    private String classNameAt(int token) {
+      checkArgument(text.charAt(token) == '`');
+      int end = scanner.tokenEnd(token) - 1; // points to the closing `
+      int t = token + 1;
+      char c = text.charAt(t);
+      if (c == '«' || c == '»') {
+        t++;
+      }
+      return text.substring(t, end);
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeMirrorSet.java b/value/src/main/java/com/google/auto/value/processor/TypeMirrorSet.java
new file mode 100644
index 0000000..28b8e05
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/TypeMirrorSet.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014 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.processor;
+
+import com.google.auto.common.MoreTypes;
+import com.google.common.base.Equivalence;
+import com.google.common.collect.ImmutableList;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A set of TypeMirror objects.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+class TypeMirrorSet extends AbstractSet<TypeMirror> {
+  private final Set<Equivalence.Wrapper<TypeMirror>> wrappers = new LinkedHashSet<>();
+
+  TypeMirrorSet() {}
+
+  TypeMirrorSet(Collection<? extends TypeMirror> types) {
+    addAll(types);
+  }
+
+  static TypeMirrorSet of(TypeMirror... types) {
+    return new TypeMirrorSet(ImmutableList.copyOf(types));
+  }
+
+  private Equivalence.Wrapper<TypeMirror> wrap(TypeMirror typeMirror) {
+    return MoreTypes.equivalence().wrap(typeMirror);
+  }
+
+  @Override
+  public boolean add(TypeMirror typeMirror) {
+    return wrappers.add(wrap(typeMirror));
+  }
+
+  @Override
+  public Iterator<TypeMirror> iterator() {
+    final Iterator<Equivalence.Wrapper<TypeMirror>> iterator = wrappers.iterator();
+    return new Iterator<TypeMirror>() {
+      @Override
+      public boolean hasNext() {
+        return iterator.hasNext();
+      }
+
+      @Override
+      public TypeMirror next() {
+        return iterator.next().get();
+      }
+
+      @Override
+      public void remove() {
+        iterator.remove();
+      }
+    };
+  }
+
+  @Override
+  public int size() {
+    return wrappers.size();
+  }
+
+  @Override
+  public boolean contains(Object o) {
+    if (o instanceof TypeMirror) {
+      return wrappers.contains(wrap((TypeMirror) o));
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public boolean remove(Object o) {
+    if (o instanceof TypeMirror) {
+      return wrappers.remove(wrap((TypeMirror) o));
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o instanceof TypeMirrorSet) {
+      TypeMirrorSet that = (TypeMirrorSet) o;
+      return wrappers.equals(that.wrappers);
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    return wrappers.hashCode();
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
new file mode 100644
index 0000000..290e59e
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2012 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.processor;
+
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toCollection;
+import static javax.lang.model.element.Modifier.PRIVATE;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
+import com.google.common.collect.ImmutableSortedSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.NestingKind;
+import javax.lang.model.element.QualifiedNameable;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleTypeVisitor8;
+import javax.lang.model.util.Types;
+
+/**
+ * Takes a set of types and a package and determines which of those types can be imported, and how
+ * to spell any of the types in the set given those imports.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+final class TypeSimplifier {
+  /**
+   * The spelling that should be used to refer to a given class, and an indication of whether it
+   * should be imported.
+   */
+  private static class Spelling {
+    final String spelling;
+    final boolean importIt;
+
+    Spelling(String spelling, boolean importIt) {
+      this.spelling = spelling;
+      this.importIt = importIt;
+    }
+  }
+
+  private final Map<String, Spelling> imports;
+
+  /**
+   * Makes a new simplifier for the given package and set of types.
+   *
+   * @param elementUtils the result of {@code ProcessingEnvironment.getElementUtils()} for the
+   *     current annotation processing environment.
+   * @param typeUtils the result of {@code ProcessingEnvironment.getTypeUtils()} for the current
+   *     annotation processing environment.
+   * @param packageName the name of the package from which classes will be referenced. Classes that
+   *     are in the same package do not need to be imported.
+   * @param types the types that will be referenced.
+   * @param base a base class that the class containing the references will extend. This is needed
+   *     because nested classes in that class or one of its ancestors are in scope in the generated
+   *     subclass, so a reference to another class with the same name as one of them is ambiguous.
+   * @throws MissingTypeException if one of the input types contains an error (typically, is
+   *     undefined).
+   */
+  TypeSimplifier(
+      Elements elementUtils,
+      Types typeUtils,
+      String packageName,
+      Set<TypeMirror> types,
+      TypeMirror base) {
+    Set<TypeMirror> typesPlusBase = new TypeMirrorSet(types);
+    if (base != null) {
+      typesPlusBase.add(base);
+    }
+    Set<TypeMirror> topLevelTypes = topLevelTypes(typeUtils, typesPlusBase);
+    Set<TypeMirror> defined = nonPrivateDeclaredTypes(typeUtils, base);
+    this.imports = findImports(elementUtils, typeUtils, packageName, topLevelTypes, defined);
+  }
+
+  /**
+   * Returns the set of types to import. We import every type that is neither in java.lang nor in
+   * the package containing the AutoValue class, provided that the result refers to the type
+   * unambiguously. For example, if there is a property of type java.util.Map.Entry then we will
+   * import java.util.Map.Entry and refer to the property as Entry. We could also import just
+   * java.util.Map in this case and refer to Map.Entry, but currently we never do that.
+   */
+  ImmutableSortedSet<String> typesToImport() {
+    ImmutableSortedSet.Builder<String> typesToImport = ImmutableSortedSet.naturalOrder();
+    for (Map.Entry<String, Spelling> entry : imports.entrySet()) {
+      if (entry.getValue().importIt) {
+        typesToImport.add(entry.getKey());
+      }
+    }
+    return typesToImport.build();
+  }
+
+  String simplifiedClassName(DeclaredType type) {
+    TypeElement typeElement = MoreElements.asType(type.asElement());
+    TypeElement top = topLevelType(typeElement);
+    // We always want to write a class name starting from the outermost class. For example,
+    // if the type is java.util.Map.Entry then we will import java.util.Map and write Map.Entry.
+    String topString = top.getQualifiedName().toString();
+    if (imports.containsKey(topString)) {
+      String suffix = typeElement.getQualifiedName().toString().substring(topString.length());
+      return imports.get(topString).spelling + suffix;
+    } else {
+      return typeElement.getQualifiedName().toString();
+    }
+  }
+
+  // The actual type parameters of the given type.
+  // If we have @AutoValue abstract class Foo<T extends Something> then the subclass will be
+  // final class AutoValue_Foo<T extends Something> extends Foo<T>.
+  // <T extends Something> is the formal type parameter list and
+  // <T> is the actual type parameter list, which is what this method returns.
+  static String actualTypeParametersString(TypeElement type) {
+    List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
+    if (typeParameters.isEmpty()) {
+      return "";
+    } else {
+      return typeParameters
+          .stream()
+          .map(e -> e.getSimpleName().toString())
+          .collect(joining(", ", "<", ">"));
+    }
+  }
+
+  /** Returns the name of the given type, including any enclosing types but not the package. */
+  static String classNameOf(TypeElement type) {
+    String name = type.getQualifiedName().toString();
+    String pkgName = packageNameOf(type);
+    return pkgName.isEmpty() ? name : name.substring(pkgName.length() + 1);
+  }
+
+  private static TypeElement topLevelType(TypeElement type) {
+    while (type.getNestingKind() != NestingKind.TOP_LEVEL) {
+      type = MoreElements.asType(type.getEnclosingElement());
+    }
+    return type;
+  }
+
+  /**
+   * Returns the name of the package that the given type is in. If the type is in the default
+   * (unnamed) package then the name is the empty string.
+   */
+  static String packageNameOf(TypeElement type) {
+    return MoreElements.getPackage(type).getQualifiedName().toString();
+  }
+
+  static String simpleNameOf(String s) {
+    if (s.contains(".")) {
+      return s.substring(s.lastIndexOf('.') + 1);
+    } else {
+      return s;
+    }
+  }
+
+  /**
+   * Given a set of referenced types, works out which of them should be imported and what the
+   * resulting spelling of each one is.
+   *
+   * <p>This method operates on a {@code Set<TypeMirror>} rather than just a {@code Set<String>}
+   * because it is not strictly possible to determine what part of a fully-qualified type name is
+   * the package and what part is the top-level class. For example, {@code java.util.Map.Entry} is a
+   * class called {@code Map.Entry} in a package called {@code java.util} assuming Java conventions
+   * are being followed, but it could theoretically also be a class called {@code Entry} in a
+   * package called {@code java.util.Map}. Since we are operating as part of the compiler, our goal
+   * should be complete correctness, and the only way to achieve that is to operate on the real
+   * representations of types.
+   *
+   * @param codePackageName The name of the package where the class containing these references is
+   *     defined. Other classes within the same package do not need to be imported.
+   * @param referenced The complete set of declared types (classes and interfaces) that will be
+   *     referenced in the generated code.
+   * @param defined The complete set of declared types (classes and interfaces) that are defined
+   *     within the scope of the generated class (i.e. nested somewhere in its superclass chain, or
+   *     in its interface set)
+   * @return a map where the keys are fully-qualified types and the corresponding values indicate
+   *     whether the type should be imported, and how the type should be spelled in the source code.
+   */
+  private static Map<String, Spelling> findImports(
+      Elements elementUtils,
+      Types typeUtils,
+      String codePackageName,
+      Set<TypeMirror> referenced,
+      Set<TypeMirror> defined) {
+    Map<String, Spelling> imports = new HashMap<>();
+    Set<TypeMirror> typesInScope = new TypeMirrorSet();
+    typesInScope.addAll(referenced);
+    typesInScope.addAll(defined);
+    Set<String> ambiguous = ambiguousNames(typeUtils, typesInScope);
+    for (TypeMirror type : referenced) {
+      TypeElement typeElement = (TypeElement) typeUtils.asElement(type);
+      String fullName = typeElement.getQualifiedName().toString();
+      String simpleName = typeElement.getSimpleName().toString();
+      String pkg = packageNameOf(typeElement);
+      boolean importIt;
+      String spelling;
+      if (ambiguous.contains(simpleName)) {
+        importIt = false;
+        spelling = fullName;
+      } else if (pkg.equals("java.lang")) {
+        importIt = false;
+        spelling = javaLangSpelling(elementUtils, codePackageName, typeElement);
+      } else if (pkg.equals(codePackageName)) {
+        importIt = false;
+        spelling = fullName.substring(pkg.isEmpty() ? 0 : pkg.length() + 1);
+      } else {
+        importIt = true;
+        spelling = simpleName;
+      }
+      imports.put(fullName, new Spelling(spelling, importIt));
+    }
+    return imports;
+  }
+
+  /**
+   * Handles the tricky case where the class being referred to is in {@code java.lang}, but the
+   * package of the referring code contains another class of the same name. For example, if the
+   * current package is {@code foo.bar} and there is a {@code foo.bar.Compiler}, then we will refer
+   * to {@code java.lang.Compiler} by its full name. The plain name {@code Compiler} would reference
+   * {@code foo.bar.Compiler} in this case. We need to write {@code java.lang.Compiler} even if the
+   * other {@code Compiler} class is not being considered here, so the {@link #ambiguousNames} logic
+   * is not enough. We have to look to see if the class exists.
+   */
+  private static String javaLangSpelling(
+      Elements elementUtils, String codePackageName, TypeElement typeElement) {
+    // If this is java.lang.Thread.State or the like, we have to look for a clash with Thread.
+    TypeElement topLevelType = topLevelType(typeElement);
+    TypeElement clash =
+        elementUtils.getTypeElement(codePackageName + "." + topLevelType.getSimpleName());
+    String fullName = typeElement.getQualifiedName().toString();
+    return (clash == null) ? fullName.substring("java.lang.".length()) : fullName;
+  }
+
+  /**
+   * Finds the top-level types for all the declared types (classes and interfaces) in the given
+   * {@code Set<TypeMirror>}.
+   *
+   * <p>The returned set contains only top-level types. If we reference {@code java.util.Map.Entry}
+   * then the returned set will contain {@code java.util.Map}. This is because we want to write
+   * {@code Map.Entry} everywhere rather than {@code Entry}.
+   */
+  private static Set<TypeMirror> topLevelTypes(Types typeUtil, Set<TypeMirror> types) {
+    return types
+        .stream()
+        .map(typeMirror -> MoreElements.asType(typeUtil.asElement(typeMirror)))
+        .map(typeElement -> topLevelType(typeElement).asType())
+        .collect(toCollection(TypeMirrorSet::new));
+  }
+
+  /**
+   * Finds all types that are declared with non private visibility by the given {@code TypeMirror},
+   * any class in its superclass chain, or any interface it implements.
+   */
+  private static Set<TypeMirror> nonPrivateDeclaredTypes(Types typeUtils, TypeMirror type) {
+    if (type == null) {
+      return new TypeMirrorSet();
+    } else {
+      Set<TypeMirror> declared = new TypeMirrorSet();
+      declared.add(type);
+      List<TypeElement> nestedTypes =
+          ElementFilter.typesIn(typeUtils.asElement(type).getEnclosedElements());
+      for (TypeElement nestedType : nestedTypes) {
+        if (!nestedType.getModifiers().contains(PRIVATE)) {
+          declared.add(nestedType.asType());
+        }
+      }
+      for (TypeMirror supertype : typeUtils.directSupertypes(type)) {
+        declared.addAll(nonPrivateDeclaredTypes(typeUtils, supertype));
+      }
+      return declared;
+    }
+  }
+
+  private static Set<String> ambiguousNames(Types typeUtils, Set<TypeMirror> types) {
+    Set<String> ambiguous = new HashSet<>();
+    Map<String, Name> simpleNamesToQualifiedNames = new HashMap<>();
+    for (TypeMirror type : types) {
+      if (type.getKind() == TypeKind.ERROR) {
+        throw new MissingTypeException(MoreTypes.asError(type));
+      }
+      String simpleName = typeUtils.asElement(type).getSimpleName().toString();
+      /*
+       * Compare by qualified names, because in Eclipse JDT, if Java 8 type annotations are used,
+       * the same (unannotated) type may appear multiple times in the Set<TypeMirror>.
+       * TODO(emcmanus): investigate further, because this might cause problems elsewhere.
+       */
+      Name qualifiedName = ((QualifiedNameable) typeUtils.asElement(type)).getQualifiedName();
+      Name previous = simpleNamesToQualifiedNames.put(simpleName, qualifiedName);
+      if (previous != null && !previous.equals(qualifiedName)) {
+        ambiguous.add(simpleName);
+      }
+    }
+    return ambiguous;
+  }
+
+  /**
+   * Returns true if casting to the given type will elicit an unchecked warning from the compiler.
+   * Only generic types such as {@code List<String>} produce such warnings. There will be no warning
+   * if the type's only generic parameters are simple wildcards, as in {@code Map<?, ?>}.
+   */
+  static boolean isCastingUnchecked(TypeMirror type) {
+    return CASTING_UNCHECKED_VISITOR.visit(type, null);
+  }
+
+  private static final TypeVisitor<Boolean, Void> CASTING_UNCHECKED_VISITOR =
+      new SimpleTypeVisitor8<Boolean, Void>(false) {
+        @Override
+        public Boolean visitUnknown(TypeMirror t, Void p) {
+          // We don't know whether casting is unchecked for this mysterious type but assume it is,
+          // so we will insert a possibly unnecessary @SuppressWarnings("unchecked").
+          return true;
+        }
+
+        @Override
+        public Boolean visitArray(ArrayType t, Void p) {
+          return visit(t.getComponentType(), p);
+        }
+
+        @Override
+        public Boolean visitDeclared(DeclaredType t, Void p) {
+          return t.getTypeArguments().stream().anyMatch(TypeSimplifier::uncheckedTypeArgument);
+        }
+
+        @Override
+        public Boolean visitTypeVariable(TypeVariable t, Void p) {
+          return true;
+        }
+      };
+
+  // If a type has a type argument, then casting to the type is unchecked, except if the argument
+  // is <?> or <? extends Object>. The same applies to all type arguments, so casting to Map<?, ?>
+  // does not produce an unchecked warning for example.
+  private static boolean uncheckedTypeArgument(TypeMirror arg) {
+    if (arg.getKind() == TypeKind.WILDCARD) {
+      WildcardType wildcard = (WildcardType) arg;
+      if (wildcard.getExtendsBound() == null || isJavaLangObject(wildcard.getExtendsBound())) {
+        // This is <?>, unless there's a super bound, in which case it is <? super Foo> and
+        // is erased.
+        return (wildcard.getSuperBound() != null);
+      }
+    }
+    return true;
+  }
+
+  private static boolean isJavaLangObject(TypeMirror type) {
+    if (type.getKind() != TypeKind.DECLARED) {
+      return false;
+    }
+    DeclaredType declaredType = (DeclaredType) type;
+    TypeElement typeElement = (TypeElement) declaredType.asElement();
+    return typeElement.getQualifiedName().contentEquals("java.lang.Object");
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeVariables.java b/value/src/main/java/com/google/auto/value/processor/TypeVariables.java
new file mode 100644
index 0000000..d664ff4
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/TypeVariables.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2019 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.processor;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleTypeVisitor8;
+import javax.lang.model.util.Types;
+
+/**
+ * Methods for handling type variables.
+ */
+final class TypeVariables {
+  private TypeVariables() {}
+
+  /**
+   * Returns a map from methods to return types, where the return types are not necessarily the
+   * original return types of the methods. Consider this example:
+   *
+   * <pre>
+   * &#64;AutoValue class {@code Foo<T>} {
+   *   abstract T getFoo();
+   *
+   *   &#64;AutoValue.Builder
+   *   abstract class {@code Builder<T>} {
+   *     abstract Builder setFoo(T t);
+   *     abstract {@code Foo<T>} build();
+   *   }
+   * }
+   * </pre>
+   *
+   * We want to be able to check that the parameter type of {@code setFoo} is the same as the
+   * return type of {@code getFoo}. But in fact it isn't, because the {@code T} of {@code Foo<T>}
+   * is not the same as the {@code T} of {@code Foo.Builder<T>}. So we create a parallel
+   * {@code Foo<T>} where the {@code T} <i>is</i> the one from {@code Foo.Builder<T>}. That way the
+   * types do correspond. This method then returns the return types of the given methods as they
+   * appear in that parallel class, meaning the type given for {@code getFoo()} is the {@code T} of
+   * {@code Foo.Builder<T>}.
+   *
+   * <p>We do the rewrite this way around (applying the type parameter from {@code Foo.Builder} to
+   * {@code Foo}) because if we hit one of the historical Eclipse bugs with {@link Types#asMemberOf}
+   * then {@link EclipseHack#methodReturnType} can use fallback logic, which only works for methods
+   * with no arguments.
+   *
+   * @param methods the methods whose return types are to be rewritten.
+   * @param sourceType the class containing those methods ({@code Foo} in the example).
+   * @param targetType the class to translate the methods into ({@code Foo.Builder<T>}) in the
+   *     example.
+   */
+  static ImmutableMap<ExecutableElement, TypeMirror> rewriteReturnTypes(
+      Elements elementUtils,
+      Types typeUtils,
+      Collection<ExecutableElement> methods,
+      TypeElement sourceType,
+      TypeElement targetType) {
+    List<? extends TypeParameterElement> sourceTypeParameters = sourceType.getTypeParameters();
+    List<? extends TypeParameterElement> targetTypeParameters = targetType.getTypeParameters();
+    Preconditions.checkArgument(
+        sourceTypeParameters.toString().equals(targetTypeParameters.toString()),
+        "%s != %s",
+        sourceTypeParameters,
+        targetTypeParameters);
+    // What we're doing is only valid if the type parameters are "the same". The check here even
+    // requires the names to be the same. The logic would still work without that, but we impose
+    // that requirement elsewhere and it means we can check in this simple way.
+    EclipseHack eclipseHack = new EclipseHack(elementUtils, typeUtils);
+    TypeMirror[] targetTypeParameterMirrors = new TypeMirror[targetTypeParameters.size()];
+    for (int i = 0; i < targetTypeParameters.size(); i++) {
+      targetTypeParameterMirrors[i] = targetTypeParameters.get(i).asType();
+    }
+    DeclaredType parallelSource = typeUtils.getDeclaredType(sourceType, targetTypeParameterMirrors);
+    return methods.stream()
+        .collect(
+            ImmutableMap.toImmutableMap(
+                m -> m, m -> eclipseHack.methodReturnType(m, parallelSource)));
+  }
+
+  /**
+   * Tests whether a given parameter can be given to a static method like
+   * {@code ImmutableMap.copyOf} to produce a value that can be assigned to the given target type.
+   *
+   * <p>For example, suppose we have this method in {@code ImmutableMap}:<br>
+   * {@code static <K, V> ImmutableMap<K, V> copyOf(Map<? extends K, ? extends V>)}<br>
+   * and we want to know if we can do this:
+   *
+   * <pre>
+   * {@code ImmutableMap<String, Integer> actualParameter = ...;}
+   * {@code ImmutableMap<String, Number> target = ImmutableMap.copyOf(actualParameter);}
+   * </pre>
+   *
+   * We will infer {@code K=String}, {@code V=Number} based on the target type, and then rewrite the
+   * formal parameter type from<br>
+   * {@code Map<? extends K, ? extends V>} to<br>
+   * {@code Map<? extends String, ? extends Number>}. Then we can check whether
+   * {@code actualParameter} is assignable to that.
+   *
+   * <p>The logic makes some simplifying assumptions, which are met for the {@code copyOf} and
+   * {@code of} methods that we use this for. The method must be static, it must have exactly one
+   * parameter, and it must have type parameters without bounds that are the same as the type
+   * parameters of its return type. We can see that these assumptions are met for the
+   * {@code ImmutableMap.copyOf} example above.
+   */
+  static boolean canAssignStaticMethodResult(
+      ExecutableElement method,
+      TypeMirror actualParameterType,
+      TypeMirror targetType,
+      Types typeUtils) {
+    if (!targetType.getKind().equals(TypeKind.DECLARED)
+        || !method.getModifiers().contains(Modifier.STATIC)
+        || method.getParameters().size() != 1) {
+      return false;
+    }
+    List<? extends TypeParameterElement> typeParameters = method.getTypeParameters();
+    List<? extends TypeMirror> targetTypeArguments =
+        MoreTypes.asDeclared(targetType).getTypeArguments();
+    if (typeParameters.size() != targetTypeArguments.size()) {
+      return false;
+    }
+    Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables = new LinkedHashMap<>();
+    for (int i = 0; i < typeParameters.size(); i++) {
+      TypeVariable v = MoreTypes.asTypeVariable(typeParameters.get(i).asType());
+      typeVariables.put(MoreTypes.equivalence().wrap(v), targetTypeArguments.get(i));
+    }
+    TypeMirror formalParameterType = method.getParameters().get(0).asType();
+    SubstitutionVisitor substitutionVisitor = new SubstitutionVisitor(typeVariables, typeUtils);
+    TypeMirror substitutedParameterType = substitutionVisitor.visit(formalParameterType, null);
+    if (substitutedParameterType.getKind().equals(TypeKind.WILDCARD)) {
+      // If the target type is Optional<? extends Foo> then <T> T Optional.of(T) will give us
+      // ? extends Foo here, and typeUtils.isAssignable will return false. But we can in fact
+      // give a Foo as an argument, so we just replace ? extends Foo with Foo.
+      WildcardType wildcard = MoreTypes.asWildcard(substitutedParameterType);
+      if (wildcard.getExtendsBound() != null) {
+        substitutedParameterType = wildcard.getExtendsBound();
+      }
+    }
+    return typeUtils.isAssignable(actualParameterType, substitutedParameterType);
+  }
+
+  /**
+   * Rewrites types such that references to type variables in the given map are replaced by the
+   * values of those variables.
+   */
+  private static class SubstitutionVisitor extends SimpleTypeVisitor8<TypeMirror, Void> {
+    private final Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables;
+    private final Types typeUtils;
+
+    SubstitutionVisitor(
+        Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables,
+        Types typeUtils) {
+      this.typeVariables = ImmutableMap.copyOf(typeVariables);
+      this.typeUtils = typeUtils;
+    }
+
+    @Override protected TypeMirror defaultAction(TypeMirror t, Void p) {
+      return t;
+    }
+
+    @Override public TypeMirror visitTypeVariable(TypeVariable t, Void p) {
+      TypeMirror substituted = typeVariables.get(MoreTypes.equivalence().wrap(t));
+      return (substituted == null) ? t : substituted;
+    }
+
+    @Override public TypeMirror visitDeclared(DeclaredType t, Void p) {
+      List<? extends TypeMirror> typeArguments = t.getTypeArguments();
+      TypeMirror[] substitutedTypeArguments = new TypeMirror[typeArguments.size()];
+      for (int i = 0; i < typeArguments.size(); i++) {
+        substitutedTypeArguments[i] = visit(typeArguments.get(i));
+      }
+      return typeUtils.getDeclaredType(
+          MoreElements.asType(t.asElement()), substitutedTypeArguments);
+    }
+
+    @Override public TypeMirror visitWildcard(WildcardType t, Void p) {
+      TypeMirror ext = visitOrNull(t.getExtendsBound());
+      if (ext != null && ext.getKind().equals(TypeKind.WILDCARD)) {
+        // An example of where this happens is if we have this method in ImmutableSet:
+        //    static <E> ImmutableSet<E> copyOf(Collection<? extends E>)
+        // and we want to know if we can do this (where T is parameter of the enclosing class):
+        //    ImmutableSet<T> actualParameter = ...
+        //    ImmutableSet<? extends T> target = ImmutableSet.copyOf(actualParameter);
+        // We will infer E=<? extends T> and rewrite the formal parameter type to
+        // Collection<? extends ? extends T>, which we must simplify to Collection<? extends T>.
+        return ext;
+      }
+      return typeUtils.getWildcardType(ext, visitOrNull(t.getSuperBound()));
+    }
+
+    @Override public TypeMirror visitArray(ArrayType t, Void p) {
+      TypeMirror comp = visit(t.getComponentType());
+      if (comp.getKind().equals(TypeKind.WILDCARD)) {
+        // An example of where this happens is if we have this method in ImmutableSet:
+        //    static <E> ImmutableSet<E> copyOf(E[])
+        // and we want to know if we can do this:
+        //    String[] actualParameter = ...
+        //    ImmutableSet<? extends T> target = ImmutableSet.copyOf(actualParameter);
+        // We will infer E=<? extends T> and rewrite the formal parameter type to
+        // <? extends T>[], which we must simplify to T[].
+        // TODO: what if getExtendsBound() returns null?
+        comp = MoreTypes.asWildcard(comp).getExtendsBound();
+      }
+      return typeUtils.getArrayType(comp);
+    }
+
+    // We'd like to override visitIntersectionType here for completeness, but Types has no method
+    // to fabricate a new IntersectionType. Anyway, currently IntersectionType can only occur as
+    // the result of TypeVariable.getUpperBound(), but the TypeMirror we're starting from is not
+    // that, and if we encounter a TypeVariable during our visit we don't visit its upper bound.
+
+    private TypeMirror visitOrNull(TypeMirror t) {
+      if (t == null) {
+        return null;
+      } else {
+        return visit(t);
+      }
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm
new file mode 100644
index 0000000..1d14d3f
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm
@@ -0,0 +1,403 @@
+## Copyright 2014 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.
+
+## Template for each generated AutoAnnotation_Foo_bar class.
+## This template uses the Apache Velocity Template Language (VTL).
+## The variables ($pkg, $props, and so on) are defined by the fields of AutoAnnotationTemplateVars.
+##
+## Comments, like this one, begin with ##. The comment text extends up to and including the newline
+## character at the end of the line. So comments also serve to join a line to the next one.
+## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there.
+## That does mean that we sometimes need an extra blank line after such a directive.
+##
+## Post-processing will remove unwanted spaces and blank lines, but will not join two lines.
+## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to
+## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not.
+
+#macro (cloneArray $a)
+  #if ($gwtCompatible)
+    `java.util.Arrays`.copyOf($a, ${a}.length)
+  #else
+    ${a}.clone()
+  #end
+#end
+
+#if (!$pkg.empty)
+package $pkg;
+#end
+
+## The following line will be replaced by the required imports during post-processing.
+`import`
+
+#if (!$generated.empty)
+@${generated}("com.google.auto.value.processor.AutoAnnotationProcessor")
+#else
+// Generated by com.google.auto.value.processor.AutoAnnotationProcessor
+#end
+final class $className implements $annotationName {
+
+## Fields
+
+#foreach ($m in $members)
+  #if ($params.containsKey($m.toString()))
+
+  private final $m.type $m;
+
+  #else
+
+  private static final $m.type $m = $m.defaultValue;
+
+  #end
+#end
+
+## Constructor
+
+  $className(
+#foreach ($p in $params.keySet())
+
+      $params[$p].type $members[$p] #if ($foreach.hasNext) , #end
+#end ) {
+#foreach ($p in $params.keySet())
+  #if (!$members[$p].kind.primitive)
+
+    if ($p == null) {
+      throw new NullPointerException("Null $p");
+    }
+
+  #end
+
+  #if ($members[$p].kind == "ARRAY")
+    #if ($params[$p].kind == "ARRAY")
+
+    this.$p = #cloneArray(${p});
+
+    #elseif ($members[$p].typeMirror.componentType.kind.primitive)
+
+    this.$p = ${members[$p].typeMirror.componentType}ArrayFromCollection($p);
+
+    #elseif ($members[$p].arrayOfClassWithBounds)
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    ${members[$p].componentType}[] ${p}$ = ${p}.toArray(new Class[0]);
+    this.$p = ${p}$;
+
+    #else
+
+    this.$p = ${p}.toArray(new ${members[$p].componentType}[0]);
+
+    #end
+  #else
+
+    this.$p = $p;
+
+  #end
+#end
+
+  }
+
+## annotationType method (defined by the Annotation interface)
+
+  @Override
+  public Class<? extends $annotationName> annotationType() {
+    return ${annotationName}.class;
+  }
+
+## Member getters
+
+#foreach ($m in $members)
+
+  @Override
+  public ${m.type} ${m}() {
+
+  #if ($m.kind == "ARRAY")
+
+    return #cloneArray(${m});
+
+  #else
+
+    return ${m};
+
+  #end
+
+  }
+
+#end
+
+## toString
+
+#macro (appendMemberString $m)
+  #if ($m.typeMirror.toString() == "java.lang.String")
+    #set ($appendQuotedStringMethod = "true")
+
+    appendQuoted(sb, $m) ##
+  #elseif ($m.typeMirror.toString() == "char")
+    #set ($appendQuotedCharMethod = "true")
+
+    appendQuoted(sb, $m) ##
+  #elseif ($m.typeMirror.toString() == "java.lang.String[]")
+    #set ($appendQuotedStringArrayMethod = "true")
+
+    appendQuoted(sb, $m) ##
+  #elseif ($m.typeMirror.toString() == "char[]")
+    #set ($appendQuotedCharArrayMethod = "true")
+
+    appendQuoted(sb, $m) ##
+  #elseif ($m.kind == "ARRAY")
+
+    sb.append(`java.util.Arrays`.toString($m)) ##
+  #else
+
+    sb.append($m) ##
+  #end
+#end
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("@$annotationFullName(");
+
+  #foreach ($p in $params.keySet())
+
+    #if ($params.size() > 1 || $params.keySet().iterator().next() != "value")
+
+    sb.append("$p=");
+    #end
+
+    #appendMemberString($members[$p]);
+
+    #if ($foreach.hasNext)
+
+    sb.append(", ");
+    #end
+
+  #end
+
+    return sb.append(')').toString();
+  }
+
+## equals
+
+## An expression that compares `this.something` against `that.something()`.
+## It can appear in a chain of && conditions, so if that would cause precedence
+## problems the expression needs to be parenthesized.
+#macro (memberEqualsThatExpression $m)
+  #if ($m.kind == "FLOAT")
+    Float.floatToIntBits($m) == Float.floatToIntBits(that.${m}()) ##
+  #elseif ($m.kind == "DOUBLE")
+    Double.doubleToLongBits($m) == Double.doubleToLongBits(that.${m}()) ##
+  #elseif ($m.kind.primitive)
+    ($m == that.${m}()) ## parens not strictly needed but avoid confusion when comparing booleans
+  #elseif ($m.kind == "ARRAY")
+    #if ($params.containsKey($m.toString()))
+    `java.util.Arrays`.equals($m,
+        (that instanceof $className)
+            ? (($className) that).$m
+            : that.${m}()) ##
+    #else ## default value, so if |that| is also a $className then it has the same constant value
+    (that instanceof $className || `java.util.Arrays`.equals($m, that.${m}()))
+    #end
+  #else
+    ${m}.equals(that.${m}()) ##
+  #end
+#end
+
+  @Override
+  public boolean equals(Object o) {
+    if (o == this) {
+      return true;
+    }
+    if (o instanceof $annotationName) {
+
+  #if ($members.isEmpty())
+
+      return true;
+
+  #else
+
+      $annotationName that = ($annotationName) o;
+      return ##
+           #foreach ($m in $members)
+           #memberEqualsThatExpression ($m)##
+             #if ($foreach.hasNext)
+
+           && ##
+             #end
+           #end
+           ;
+  #end
+
+    }
+    return false;
+  }
+
+## hashCode
+
+## An expression that returns the hashCode of `this.something`.
+## It appears on the right-hand side of an ^ operator, so if that would cause precedence
+## problems the expression needs to be parenthesized.
+#macro (memberHashCodeExpression $m)
+  #if ($m.kind == "LONG")
+    (int) (($m >>> 32) ^ $m) ##
+  #elseif ($m.kind == "FLOAT")
+    Float.floatToIntBits($m) ##
+  #elseif ($m.kind == "DOUBLE")
+    (int) ((Double.doubleToLongBits($m) >>> 32) ^ Double.doubleToLongBits($m)) ##
+  #elseif ($m.kind == "BOOLEAN")
+    ($m ? 1231 : 1237) ##
+  #elseif ($m.kind.primitive)
+    $m ##
+  #elseif ($m.kind == "ARRAY")
+    `java.util.Arrays`.hashCode($m) ##
+  #else
+    ${m}.hashCode() ##
+  #end
+#end
+
+## The hashCode is the sum of two parts, an invariable part and a variable part. The invariable part
+## comes from defaulted members (ones that don't appear in the @AutoAnnotation method parameters)
+## whose values have hash codes that never change. (That doesn't include Class constants, for
+## example.) We precompute the invariable part, as an optimization but also in order to avoid
+## falling afoul of constant-overflow checks in the compiler.
+
+  @Override
+  public int hashCode() {
+    return
+    ## If the invariable part is 0, we avoid outputting `return 0 + ...` just because it generates
+    ## unnecessary byte code. But if there are no members then we must say `return 0;` here.
+    ## We must write $members.isEmpty() because $members is a Map and Velocity interprets
+    ## $members.empty as meaning $members["empty"] in that case.
+    #if ($invariableHashSum != 0 || $members.isEmpty())
+
+        $invariableHashSum
+        // $invariableHashSum is the contribution from default members $invariableHashes
+    #end
+    #foreach ($m in $members)
+      #if (!$invariableHashes.contains($m.toString()))
+
+        + ($m.nameHash ^ #memberHashCodeExpression($m))
+            // $m.nameHash is 127 * "${m}".hashCode()
+      #end
+    #end
+
+        ;
+
+  }
+
+## support functions
+
+#foreach ($w in $wrapperTypesUsedInCollections)
+  #set ($prim = $w.getField("TYPE").get(""))
+
+  private static ${prim}[] ${prim}ArrayFromCollection(`java.util.Collection`<${w.simpleName}> c) {
+    ${prim}[] a = new ${prim}[c.size()];
+    int i = 0;
+    for (${prim} x : c) {
+      a[i++] = x;
+    }
+    return a;
+  }
+#end
+
+#if ($appendQuotedStringArrayMethod)
+  #set ($appendQuotedStringMethod = "true")
+
+  private static void appendQuoted(StringBuilder sb, String[] strings) {
+    sb.append('[');
+    String sep = "";
+    for (String s : strings) {
+      sb.append(sep);
+      sep = ", ";
+      appendQuoted(sb, s);
+    }
+    sb.append(']');
+  }
+#end
+
+#if ($appendQuotedCharArrayMethod)
+  #set ($appendQuotedCharMethod = "true")
+
+  private static void appendQuoted(StringBuilder sb, char[] chars) {
+    sb.append('[');
+    String sep = "";
+    for (char c : chars) {
+      sb.append(sep);
+      sep = ", ";
+      appendQuoted(sb, c);
+    }
+    sb.append(']');
+  }
+#end
+
+#if ($appendQuotedStringMethod)
+  #set ($appendEscapedMethod = "true")
+
+  private static void appendQuoted(StringBuilder sb, String s) {
+    sb.append('"');
+    for (int i = 0; i < s.length(); i++) {
+      appendEscaped(sb, s.charAt(i));
+    }
+    sb.append('"');
+  }
+#end
+
+#if ($appendQuotedCharMethod)
+  #set ($appendEscapedMethod = "true")
+
+  private static void appendQuoted(StringBuilder sb, char c) {
+    sb.append('\'');
+    appendEscaped(sb, c);
+    sb.append('\'');
+  }
+#end
+
+#if ($appendEscapedMethod)
+  private static void appendEscaped(StringBuilder sb, char c) {
+    switch (c) {
+    case '\\':
+    case '"':
+    case '\'':
+      sb.append('\\').append(c);
+      break;
+    case '\n':
+      sb.append("\\n");
+      break;
+    case '\r':
+      sb.append("\\r");
+      break;
+    case '\t':
+      sb.append("\\t");
+      break;
+    default:
+      if (c < 0x20) {
+        sb.append('\\');
+        appendWithZeroPadding(sb, Integer.toOctalString(c), 3);
+      } else if (c < 0x7f || Character.isLetter(c)) {
+        sb.append(c);
+      } else {
+        sb.append("\\u");
+        appendWithZeroPadding(sb, Integer.toHexString(c), 4);
+      }
+      break;
+    }
+  }
+
+  ## We use this rather than String.format because that doesn't exist on GWT.
+
+  private static void appendWithZeroPadding(StringBuilder sb, String s, int width) {
+    for (int i = width - s.length(); i > 0; i--) {
+      sb.append('0');
+    }
+    sb.append(s);
+  }
+#end
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/autooneof.vm b/value/src/main/java/com/google/auto/value/processor/autooneof.vm
new file mode 100644
index 0000000..04cc194
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/autooneof.vm
@@ -0,0 +1,232 @@
+## Copyright 2018 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.
+
+## Template for each generated AutoOneOf_Foo class.
+## This template uses the Apache Velocity Template Language (VTL).
+## The variables ($pkg, $props, and so on) are defined by the fields of AutoOneOfTemplateVars.
+##
+## Comments, like this one, begin with ##. The comment text extends up to and including the newline
+## character at the end of the line. So comments also serve to join a line to the next one.
+## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there.
+## That does mean that we sometimes need an extra blank line after such a directive.
+##
+## Post-processing will remove unwanted spaces and blank lines, but will not join two lines.
+## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to
+## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not.
+
+## Get #equalsThatExpression($p) and #hashCodeExpression($p).
+#parse("equalshashcode.vm")
+
+#if (!$pkg.empty)
+package $pkg;
+#end
+
+## The following line will be replaced by the required imports during post-processing.
+`import`
+
+#if ($generated.empty)
+// Generated by com.google.auto.value.processor.AutoOneOfProcessor
+#else
+@${generated}("com.google.auto.value.processor.AutoOneOfProcessor")
+#end
+final class $generatedClass {
+  private ${generatedClass}() {} // There are no instances of this type.
+
+## Factory methods.
+#foreach ($p in $props)
+
+  #if ($p.type == "void")
+    #if ($wildcardTypes == "")
+
+  static $origClass $p() {
+    return Impl_${p}.INSTANCE;
+  }
+
+    #else
+
+  @SuppressWarnings("unchecked") // type parameters are unused in void instances
+  static $formalTypes $origClass$actualTypes $p() {
+    return ($origClass$actualTypes) Impl_${p}.INSTANCE;
+  }
+
+    #end
+
+  #else
+
+  ## If the @AutoOneOf type is TaskResult<V extends Serializable>, then we might have here:
+  ## static <V extends Serializable> TaskResult<V> value(V value) {
+  ##   return new Impl_value<V>(value);
+  ## }
+  ## The parameter type might be something else (Throwable for example), but we will still
+  ## want <V extends Serializable> TaskResult<V>.
+
+  static $formalTypes $origClass$actualTypes $p($p.type $p) {
+
+    #if (!$p.kind.primitive)
+
+    if ($p == null) {
+      throw new NullPointerException();
+    }
+
+    #end
+
+    return new Impl_$p$actualTypes($p);
+  }
+
+  #end
+
+#end
+
+  #foreach ($a in $annotations)
+
+  $a
+
+  #end
+
+  // Parent class that each implementation will inherit from.
+  private abstract static class Parent_$formalTypes extends $origClass$actualTypes {
+
+#foreach ($p in $props)
+
+    @Override
+    $p.access $p.type ${p.getter}() {
+      throw new UnsupportedOperationException(${kindGetter}().toString());
+    }
+
+#end
+
+  }
+
+#foreach ($p in $props)
+
+
+  #foreach ($a in $annotations)
+
+  $a
+
+  #end
+
+  // Implementation when the contained property is "${p}".
+  private static final class Impl_$p$formalTypes extends Parent_$actualTypes {
+
+  #if ($p.type == "void")
+
+    // There is only one instance of this class.
+    static final Impl_$p$wildcardTypes INSTANCE = new ##
+      #if ($wildcardTypes == "") Impl_$p() #else Impl_$p<>() #end;
+
+    private Impl_$p() {}
+
+    @Override
+    public void ${p.getter}() {}
+
+    #if ($serializable)
+
+    private Object readResolve() {
+      return INSTANCE;
+    }
+
+    #end
+
+    #if ($toString)
+
+    @Override
+    public String toString() {
+      return "${simpleClassName}{$p.name}";
+    }
+
+    #end
+
+    ## The implementations of equals and hashCode are equivalent to the ones
+    ## we inherit from Object. We only need to define them if they're redeclared
+    ## as abstract in an ancestor class. But currently we define them always.
+
+    #if ($equals)
+
+    @Override
+    public boolean equals($equalsParameterType x) {
+      return x == this;
+    }
+
+    #end
+
+    #if ($hashCode)
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+
+    #end
+
+  #else
+
+    private final $p.type $p;
+
+    Impl_$p($p.type $p) {
+      this.$p = $p;
+    }
+
+    @Override
+    public $p.type ${p.getter}() {
+      return $p;
+    }
+
+    #if ($toString)
+
+    @Override
+    public String toString() {
+      return "${simpleClassName}{$p.name=" ##
+          + #if ($p.kind == "ARRAY") `java.util.Arrays`.toString(this.$p) #else this.$p #end
+          + "}";
+    }
+
+    #end
+
+    #if ($equals)
+
+    @Override
+    public boolean equals($equalsParameterType x) {
+      if (x instanceof $origClass) {
+        $origClass$wildcardTypes that = ($origClass$wildcardTypes) x;
+        return this.${kindGetter}() == that.${kindGetter}()
+            && #equalsThatExpression($p "Impl_$p");
+      } else {
+        return false;
+      }
+    }
+
+    #end
+
+    #if ($hashCode)
+
+    @Override
+    public int hashCode() {
+      return #hashCodeExpression($p);
+    }
+
+    #end
+
+  #end
+
+    @Override
+    public $kindType ${kindGetter}() {
+      return ${kindType}.$propertyToKind[$p.name];
+    }
+
+  }
+
+#end
+
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/autovalue.vm b/value/src/main/java/com/google/auto/value/processor/autovalue.vm
new file mode 100644
index 0000000..80ef79a
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/autovalue.vm
@@ -0,0 +1,434 @@
+## Copyright 2014 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.
+
+## Template for each generated AutoValue_Foo class.
+## This template uses the Apache Velocity Template Language (VTL).
+## The variables ($pkg, $props, and so on) are defined by the fields of AutoValueTemplateVars.
+##
+## Comments, like this one, begin with ##. The comment text extends up to and including the newline
+## character at the end of the line. So comments also serve to join a line to the next one.
+## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there.
+## That does mean that we sometimes need an extra blank line after such a directive.
+##
+## Post-processing will remove unwanted spaces and blank lines, but will not join two lines.
+## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to
+## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not.
+
+## Get #equalsThatExpression($p) and #hashCodeExpression($p).
+#parse("equalshashcode.vm")
+
+#if (!$pkg.empty)
+package $pkg;
+#end
+
+## The following line will be replaced by the required imports during post-processing.
+`import`
+
+${gwtCompatibleAnnotation}
+#foreach ($a in $annotations)
+$a
+#end
+#if (!$generated.empty)
+@${generated}("com.google.auto.value.processor.AutoValueProcessor")
+#else
+// Generated by com.google.auto.value.processor.AutoValueProcessor
+#end
+${modifiers}class $subclass$formalTypes extends $origClass$actualTypes {
+
+## Fields
+
+#foreach ($p in $props)
+  #foreach ($a in ${p.fieldAnnotations})
+
+  ${a}##
+  #end
+
+  private final $p.type $p;
+#end
+
+## Constructor
+
+#if ($isFinal && $builderTypeName != "")
+  private ##
+#end
+  $subclass(
+#foreach ($p in $props)
+
+      ${p.nullableAnnotation}$p.type $p #if ($foreach.hasNext) , #end
+#end ) {
+#foreach ($p in $props)
+  #if (!$p.kind.primitive && !$p.nullable && ($builderTypeName == "" || !$isFinal))
+    ## We don't need a null check if the type is primitive or @Nullable. We also don't need it
+    ## if there is a builder, since the build() method will check for us. However, if there is a
+    ## builder but there are also extensions (!$isFinal) then we can't omit the null check because
+    ## the constructor is called from the extension code.
+
+    #if ($identifiers)
+
+    if ($p == null) {
+      throw new NullPointerException("Null $p.name");
+    }
+    #else
+      ## Just throw NullPointerException with no message if it's null.
+      ## The Object cast has no effect on the code but silences an ErrorProne warning.
+
+    ((`java.lang.Object`) ${p}).getClass();
+    #end
+
+  #end
+
+    this.$p = $p;
+#end
+  }
+
+## Property getters
+
+#foreach ($p in $props)
+
+  #foreach ($a in ${p.methodAnnotations})
+
+  ${a}##
+  #end
+
+  @Override
+  ${p.access}${p.type} ${p.getter}() {
+    return $p;
+  }
+
+#end
+
+#if ($toString)
+
+  @Override
+  public `java.lang.String` toString() {
+    return "#if ($identifiers)$simpleClassName#end{"
+
+  #foreach ($p in $props)
+
+        #if ($identifiers)+ "$p.name=" ##
+        #end+ #if ($p.kind == "ARRAY") `java.util.Arrays`.toString($p) #else $p #end
+        #if ($foreach.hasNext) + ", " #end
+
+  #end
+
+        + "}";
+  }
+
+#end
+
+#if ($equals)
+
+  @Override
+  public boolean equals($equalsParameterType o) {
+    if (o == this) {
+      return true;
+    }
+    if (o instanceof $origClass) {
+
+  #if ($props.empty)
+
+      return true;
+
+  #else
+
+      $origClass$wildcardTypes that = ($origClass$wildcardTypes) o;
+      return ##
+          #foreach ($p in $props)
+          #equalsThatExpression ($p $subclass)##
+            #if ($foreach.hasNext)
+
+          && ##
+            #end
+          #end
+          ;
+  #end
+
+    }
+    return false;
+  }
+
+#end
+
+#if ($hashCode)
+
+  @Override
+  public int hashCode() {
+    int h$ = 1;
+
+  #foreach ($p in $props)
+
+    h$ *= 1000003;
+    h$ ^= #hashCodeExpression($p);
+
+  #end
+
+    return h$;
+  }
+#end
+
+#if (!$serialVersionUID.empty)
+  private static final long serialVersionUID = $serialVersionUID;
+#end
+
+#if ($builderTypeName != "")
+
+  #foreach ($m in $toBuilderMethods)
+
+  @Override
+  ${m.access}${builderTypeName}${builderActualTypes} ${m.name}() {
+    return new Builder${builderActualTypes}(this);
+  }
+
+  #end
+
+  ## BUILDER CLASS
+
+  #foreach ($a in $builderAnnotations)
+
+  $a##
+  #end
+
+  static #if ($isFinal) final #end class Builder${builderFormalTypes} ##
+  #if ($builderIsInterface) implements #else extends #end
+      ${builderTypeName}${builderActualTypes} {
+
+  #foreach ($p in $props)
+
+    #if ($p.kind.primitive)
+
+    private $types.boxedClass($p.typeMirror).simpleName $p;
+
+    #else
+
+      #if ($builderPropertyBuilders[$p.name])
+      ## If you have ImmutableList.Builder<String> stringsBuilder() then we define two fields:
+      ## private ImmutableList.Builder<String> stringsBuilder$;
+      ## private ImmutableList<String> strings;
+
+    private ${builderPropertyBuilders[$p.name].builderType} ##
+        ${builderPropertyBuilders[$p.name].name};
+
+      #end
+
+    private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ;
+
+    #end
+  #end
+
+    Builder() {
+    }
+
+  #if (!$toBuilderMethods.empty)
+
+    private Builder(${origClass}${actualTypes} source) {
+
+    #foreach ($p in $props)
+
+      this.$p = source.${p.getter}();
+
+    #end
+
+    }
+
+  #end
+
+  #foreach ($p in $props)
+
+    ## The following is either null or an instance of PropertyBuilderClassifier.PropertyBuilder
+    #set ($propertyBuilder = $builderPropertyBuilders[$p.name])
+
+    ## Setter and/or property builder
+
+    #foreach ($setter in $builderSetters[$p.name])
+
+    @Override
+    ${setter.access}${builderTypeName}${builderActualTypes} ##
+        ${setter.name}(${setter.nullableAnnotation}$setter.parameterType $p) {
+
+      ## Omit null check for primitive, or @Nullable, or if we are going to be calling a copy method
+      ## such as Optional.of, which will have its own null check if appropriate.
+      #if (!$setter.primitiveParameter && !$p.nullable && ${setter.copy($p)} == $p)
+
+        #if ($identifiers)
+
+      if ($p == null) {
+        throw new NullPointerException("Null $p.name");
+      }
+        #else
+          ## Just throw NullPointerException with no message if it's null.
+          ## The Object cast has no effect on the code but silences an ErrorProne warning.
+
+      ((`java.lang.Object`) ${p}).getClass();
+        #end
+
+      #end
+
+      #if ($propertyBuilder)
+
+      if (${propertyBuilder.name} != null) {
+        throw new IllegalStateException(#if ($identifiers)"Cannot set $p after calling ${p.name}Builder()"#end);
+      }
+
+      #end
+
+      this.$p = ${setter.copy($p)};
+      return this;
+    }
+
+    #end
+
+    #if ($propertyBuilder)
+
+    @Override
+    ${propertyBuilder.access}$propertyBuilder.builderType ${p.name}Builder() {
+      if (${propertyBuilder.name} == null) {
+
+        ## This is the first time someone has asked for the builder. If the property it sets already
+        ## has a value (because it came from a toBuilder() call on the AutoValue class, or because
+        ## there is also a setter for this property) then we copy that value into the builder.
+        ## Otherwise the builder starts out empty.
+        ## If we have neither a setter nor a toBuilder() method, then the builder always starts
+        ## off empty.
+
+        #if ($builderSetters[$p.name].empty && $toBuilderMethods.empty)
+
+        ${propertyBuilder.name} = ${propertyBuilder.initializer};
+
+        #else
+
+        if ($p == null) {
+          ${propertyBuilder.name} = ${propertyBuilder.initializer};
+        } else {
+
+          #if (${propertyBuilder.builtToBuilder})
+
+          ${propertyBuilder.name} = ${p}.${propertyBuilder.builtToBuilder}();
+
+          #else
+
+          ${propertyBuilder.name} = ${propertyBuilder.initializer};
+          ${propertyBuilder.name}.${propertyBuilder.copyAll}($p);
+
+          #end
+
+          $p = null;
+        }
+
+        #end
+
+      }
+      return $propertyBuilder.name;
+    }
+
+    #end
+
+    ## Getter
+
+    #if ($builderGetters[$p.name])
+
+    @Override
+    ${p.nullableAnnotation}${builderGetters[$p.name].access}$builderGetters[$p.name].type ${p.getter}() {
+      #if ($builderGetters[$p.name].optional)
+
+      if ($p == null) {
+        return $builderGetters[$p.name].optional.empty;
+      } else {
+        return ${builderGetters[$p.name].optional.rawType}.of($p);
+      }
+
+      #else
+        #if ($builderRequiredProperties.contains($p))
+
+      if ($p == null) {
+        throw new IllegalStateException(#if ($identifiers)"Property \"$p.name\" has not been set"#end);
+      }
+
+        #end
+
+        #if ($propertyBuilder)
+
+      if (${propertyBuilder.name} != null) {
+        return ${propertyBuilder.name}.build();
+      }
+      if ($p == null) {
+        ${propertyBuilder.beforeInitDefault}
+        $p = ${propertyBuilder.initDefault};
+      }
+
+        #end
+
+      return $p;
+
+      #end
+
+    }
+
+    #end
+  #end
+
+    @Override
+    ${buildMethod.get().access}${origClass}${actualTypes} ${buildMethod.get().name}() {
+
+  #foreach ($p in $props)
+    #set ($propertyBuilder = $builderPropertyBuilders[$p.name])
+    #if ($propertyBuilder)
+
+      if (${propertyBuilder.name} != null) {
+        this.$p = ${propertyBuilder.name}.build();
+      } else if (this.$p == null) {
+        ${propertyBuilder.beforeInitDefault}
+        this.$p = ${propertyBuilder.initDefault};
+      }
+
+    #end
+  #end
+
+  #if (!$builderRequiredProperties.empty)
+    #if ($identifiers)  ## build a friendly message showing all missing properties
+
+      `java.lang.String` missing = "";
+
+      #foreach ($p in $builderRequiredProperties)
+
+      if (this.$p == null) {
+        missing += " $p.name";
+      }
+
+      #end
+
+      if (!missing.isEmpty()) {
+        throw new IllegalStateException("Missing required properties:" + missing);
+      }
+
+    #else  ## just throw an exception if anything is missing
+
+      if (#foreach ($p in $builderRequiredProperties)##
+          this.$p == null##
+          #if ($foreach.hasNext) || #end
+          #end) {
+        throw new IllegalStateException();
+      }
+    #end
+  #end
+
+      return new ${finalSubclass}${actualTypes}(
+  #foreach ($p in $props)
+
+          this.$p #if ($foreach.hasNext) , #end
+  #end );
+    }
+  }
+#end
+
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm b/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm
new file mode 100644
index 0000000..094f6c3
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm
@@ -0,0 +1,82 @@
+## Copyright 2018 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.
+
+## Shared definitions for @AutoFoo templates. This is included in those templates using
+## the #parse directive.
+##
+## This template uses the Apache Velocity Template Language (VTL).
+## The variables ($pkg, $props, and so on) are defined by the fields of AutoValueTemplateVars.
+##
+## Comments, like this one, begin with ##. The comment text extends up to and including the newline
+## character at the end of the line. So comments also serve to join a line to the next one.
+## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there.
+## That does mean that we sometimes need an extra blank line after such a directive.
+##
+## Post-processing will remove unwanted spaces and blank lines, but will not join two lines.
+## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to
+## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not.
+
+## In the following two macros, $p is an object of type AutoValueProcessor.Property
+## or AutoOneOfProcessor.Property. $p.kind means the getKind() method of those classes,
+## and likewise for $p.getter and $p.nullable (isNullable()).
+
+## Expands to an expression appropriate for comparing the $p property in `this` against
+## the $p property in `that`. If we're generating code for `AutoValue_Baz` then
+## `that` is of type `Baz`.
+## As an example, if $p is the `foo` property and $p.kind is FLOAT,
+## this becomes `Float.floatToIntBits(this.foo) == Float.floatToIntBits(that.foo())`
+## or `...that.getFoo()...`.
+## The expression should be surrounded by parentheses if otherwise there might be precedence
+## problems when it is followed by &&.
+## A reminder that trailing ## here serves to delete the newline, which we don't want in the output.
+#macro (equalsThatExpression $p $subclass)
+  #if ($p.kind == "FLOAT")
+    Float.floatToIntBits(this.$p) == Float.floatToIntBits(that.${p.getter}()) ##
+  #elseif ($p.kind == "DOUBLE")
+    Double.doubleToLongBits(this.$p) == Double.doubleToLongBits(that.${p.getter}()) ##
+  #elseif ($p.kind.primitive)
+    this.$p == that.${p.getter}() ##
+  #elseif ($p.kind == "ARRAY")
+    `java.util.Arrays`.equals(this.$p, ##
+        (that instanceof $subclass) ? (($subclass) that).$p : that.${p.getter}()) ##
+  #elseif ($p.nullable)
+    (this.$p == null ? that.${p.getter}() == null : this.${p}.equals(that.${p.getter}())) ##
+  #else
+    this.${p}.equals(that.${p.getter}()) ##
+  #end
+#end
+
+## Expands to an expression to compute the hashCode of the $p property.
+## For example, if $p is the `foo` property and $p.kind is FLOAT,
+## this becomes `Float.floatToIntBits(this.foo)`.
+## A reminder that trailing ## here serves to delete the newline, which we don't want in the output.
+#macro (hashCodeExpression $p)
+  #if ($p.kind == "LONG")
+    (int) (($p >>> 32) ^ $p) ##
+  #elseif ($p.kind == "FLOAT")
+    Float.floatToIntBits($p) ##
+  #elseif ($p.kind == "DOUBLE")
+    (int) ((Double.doubleToLongBits($p) >>> 32) ^ Double.doubleToLongBits($p)) ##
+  #elseif ($p.kind == "BOOLEAN")
+    $p ? 1231 : 1237 ##
+  #elseif ($p.kind.primitive)
+    $p ##
+  #elseif ($p.kind == "ARRAY")
+    `java.util.Arrays`.hashCode($p) ##
+  #elseif ($p.nullable)
+    ($p == null) ? 0 : ${p}.hashCode() ##
+  #else
+    ${p}.hashCode() ##
+  #end
+#end
diff --git a/value/src/main/java/com/google/auto/value/processor/gwtserializer.vm b/value/src/main/java/com/google/auto/value/processor/gwtserializer.vm
new file mode 100644
index 0000000..0450036
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/gwtserializer.vm
@@ -0,0 +1,122 @@
+## Copyright 2014 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.
+
+## Template for each generated AutoValue_Foo_CustomFieldSerializer class.
+## This template uses the Apache Velocity Template Language (VTL).
+## The variables ($pkg, $props, and so on) are defined by the fields of
+## GwtSerialization.GwtTemplateVars.
+##
+## Comments, like this one, begin with ##. The comment text extends up to and including the newline
+## character at the end of the line. So comments also serve to join a line to the next one.
+## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there.
+## That does mean that we sometimes need an extra blank line after such a directive.
+##
+## A post-processing step will remove unwanted spaces and blank lines, but will not join two lines.
+## TODO(emcmanus): perform the post-processing.
+#if (!$pkg.empty)
+package $pkg;
+#end
+
+## The following line will be replaced by the required imports during post-processing.
+`import`
+
+#if (!$generated.empty)
+@${generated}("com.google.auto.value.processor.AutoValueProcessor")
+#else
+// Generated by com.google.auto.value.processor.AutoValueProcessor
+#end
+public final class $serializerClass$formalTypes
+    extends `com.google.gwt.user.client.rpc.CustomFieldSerializer`<$subclass$actualTypes> {
+
+  public static $formalTypes $subclass$actualTypes instantiate(
+      `com.google.gwt.user.client.rpc.SerializationStreamReader` streamReader)
+      throws `com.google.gwt.user.client.rpc.SerializationException` {
+#foreach ($p in $props)
+    #if ($p.castingUnchecked)
+    @SuppressWarnings("unchecked")
+    #end
+    $p.type $p = ${p.gwtCast}streamReader.read${p.gwtType}();
+#end
+#if ($useBuilder)
+
+    ${subclass}.Builder${actualTypes} builder$ = new ${subclass}.Builder${actualTypes}();
+  #foreach ($p in $props)
+
+    #if ($builderSetters[$p.name].empty)
+      #set ($propertyBuilder = $builderPropertyBuilders[$p.name])
+
+    builder$.${propertyBuilder.propertyBuilderMethod}.${propertyBuilder.copyAll}($p);
+
+    #else
+
+    builder$.${builderSetters[$p.name].iterator().next().name}($p);
+    #end
+  #end
+
+    return (${subclass}${actualTypes}) builder$.build();
+#else
+
+    return new ${subclass}$actualTypes(
+        #foreach ($p in $props) $p #if ($foreach.hasNext) , #end #end);
+
+#end
+  }
+
+  public static $formalTypes void serialize(
+      `com.google.gwt.user.client.rpc.SerializationStreamWriter` streamWriter,
+      $subclass$actualTypes instance) throws `com.google.gwt.user.client.rpc.SerializationException` {
+#foreach ($p in $props)
+    streamWriter.write${p.gwtType}(instance.${p.getter}());
+#end
+  }
+
+  public static $formalTypes void deserialize(
+      @SuppressWarnings("unused") `com.google.gwt.user.client.rpc.SerializationStreamReader` streamReader,
+      @SuppressWarnings("unused") $subclass$actualTypes instance) {
+    // instantiate already did all the work.
+  }
+
+  // This dummy field is a hash of the fields in ${subclass}. It will change if they do, including
+  // if their order changes. This is because GWT identity for a class that has a serializer is
+  // based on the fields of the serializer rather than the class itself.
+  @SuppressWarnings("unused")
+  private int dummy_$classHashString;
+
+  @Override
+  public void deserializeInstance(
+      `com.google.gwt.user.client.rpc.SerializationStreamReader` streamReader,
+      $subclass$actualTypes instance) {
+    deserialize(streamReader, instance);
+  }
+
+  @Override
+  public boolean hasCustomInstantiateInstance() {
+    return true;
+  }
+
+  @Override
+  public $subclass$actualTypes instantiateInstance(
+      `com.google.gwt.user.client.rpc.SerializationStreamReader` streamReader)
+      throws `com.google.gwt.user.client.rpc.SerializationException` {
+    return instantiate(streamReader);
+  }
+
+  @Override
+  public void serializeInstance(
+      `com.google.gwt.user.client.rpc.SerializationStreamWriter` streamWriter,
+      $subclass$actualTypes instance)
+      throws `com.google.gwt.user.client.rpc.SerializationException` {
+    serialize(streamWriter, instance);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/package-info.java b/value/src/main/java/com/google/auto/value/processor/package-info.java
new file mode 100644
index 0000000..74ff41f
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013 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.
+ */
+/**
+ * This package contains the annotation processor that implements the {@link
+ * com.google.auto.value.AutoValue} API.
+ */
+package com.google.auto.value.processor;
diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java
new file mode 100644
index 0000000..18c7736
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 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.memoized;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.auto.value.extension.memoized.processor.MemoizeExtension;
+import com.google.auto.value.processor.AutoValueProcessor;
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+
+final class MemoizedMethodSubject extends Subject {
+  private final String actual;
+
+  MemoizedMethodSubject(FailureMetadata failureMetadata, String actual) {
+    super(failureMetadata, actual);
+    this.actual = actual;
+  }
+
+  void hasError(String error) {
+    JavaFileObject file =
+        JavaFileObjects.forSourceLines(
+            "Value",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.auto.value.extension.memoized.Memoized;",
+            "",
+            "@AutoValue abstract class Value {",
+            "  abstract String string();",
+            actual,
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new MemoizeExtension())))
+            .compile(file);
+    assertThat(compilation)
+        .hadErrorContaining(error)
+        .inFile(file)
+        .onLineContaining(actual);
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubjectFactory.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubjectFactory.java
new file mode 100644
index 0000000..336a798
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubjectFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 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.memoized;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+
+final class MemoizedMethodSubjectFactory implements Subject.Factory<MemoizedMethodSubject, String> {
+
+  static MemoizedMethodSubjectFactory memoizeMethod() {
+    return new MemoizedMethodSubjectFactory();
+  }
+
+  static MemoizedMethodSubject assertThatMemoizeMethod(String method) {
+    return assertAbout(memoizeMethod()).that(method);
+  }
+
+  @Override
+  public MemoizedMethodSubject createSubject(FailureMetadata failureMetadata, String that) {
+    return new MemoizedMethodSubject(failureMetadata, that);
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java
new file mode 100644
index 0000000..7bd61ac
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright 2016 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.memoized;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.AutoValue.CopyAnnotations;
+import com.google.auto.value.extension.memoized.MemoizedTest.HashCodeEqualsOptimization.EqualsCounter;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MemoizedTest {
+
+  private Value value;
+  private ListValue<Integer, String> listValue;
+
+  @AutoValue
+  abstract static class ValueWithKeywordName {
+    abstract boolean getNative();
+
+    abstract boolean getNative0();
+
+    @Memoized
+    boolean getMemoizedNative() {
+      return getNative();
+    }
+
+    @Memoized
+    boolean getMemoizedNative0() {
+      return getNative0();
+    }
+  }
+
+  @AutoValue
+  @CopyAnnotations
+  @javax.annotation.Nullable
+  abstract static class ValueWithCopyAnnotations {
+    abstract boolean getNative();
+
+    @Memoized
+    boolean getMemoizedNative() {
+      return getNative();
+    }
+  }
+
+  @AutoValue
+  @javax.annotation.Nullable
+  abstract static class ValueWithoutCopyAnnotations {
+    abstract boolean getNative();
+
+    @Memoized
+    boolean getMemoizedNative() {
+      return getNative();
+    }
+  }
+
+  @AutoValue
+  abstract static class Value {
+    private int primitiveCount;
+    private int notNullableCount;
+    private int nullableCount;
+    private int returnsNullCount;
+    private int nullableWithTypeAnnotationCount;
+    private int returnsNullWithTypeAnnotationCount;
+    private int notNullableButReturnsNullCount;
+    private int throwsExceptionCount;
+
+    @javax.annotation.Nullable
+    abstract String string();
+
+    abstract @org.checkerframework.checker.nullness.qual.Nullable String stringWithTypeAnnotation();
+
+    abstract HashCodeAndToStringCounter counter();
+
+    @Memoized
+    int primitive() {
+      return ++primitiveCount;
+    }
+
+    @Memoized
+    String notNullable() {
+      notNullableCount++;
+      return "derived " + string() + " " + notNullableCount;
+    }
+
+    @Memoized
+    @javax.annotation.Nullable
+    String nullable() {
+      nullableCount++;
+      return "nullable derived " + string() + " " + nullableCount;
+    }
+
+    @Memoized
+    @javax.annotation.Nullable
+    String returnsNull() {
+      returnsNullCount++;
+      return null;
+    }
+
+    @Memoized
+    @org.checkerframework.checker.nullness.qual.Nullable
+    String nullableWithTypeAnnotation() {
+      nullableWithTypeAnnotationCount++;
+      return "nullable derived " + stringWithTypeAnnotation() + " "
+          + nullableWithTypeAnnotationCount;
+    }
+
+    @Memoized
+    @org.checkerframework.checker.nullness.qual.Nullable
+    String returnsNullWithTypeAnnotation() {
+      returnsNullWithTypeAnnotationCount++;
+      return null;
+    }
+
+    @Memoized
+    String notNullableButReturnsNull() {
+      notNullableButReturnsNullCount++;
+      return null;
+    }
+
+    @Memoized
+    String throwsException() throws SomeCheckedException {
+      throwsExceptionCount++;
+      throw new SomeCheckedException();
+    }
+
+    @Override
+    @Memoized
+    public abstract int hashCode();
+
+    @Override
+    @Memoized
+    public abstract String toString();
+  }
+
+  static final class SomeCheckedException extends Exception {}
+
+  @AutoValue
+  abstract static class ListValue<T extends Number, K> {
+
+    abstract T value();
+
+    abstract K otherValue();
+
+    @Memoized
+    ImmutableList<T> myTypedList() {
+      return ImmutableList.of(value());
+    }
+  }
+
+  static class HashCodeAndToStringCounter {
+    int hashCodeCount;
+    int toStringCount;
+
+    @Override
+    public int hashCode() {
+      return ++hashCodeCount;
+    }
+
+    @Override
+    public String toString() {
+      return "a string" + ++toStringCount;
+    }
+  }
+
+  @AutoValue
+  abstract static class HashCodeEqualsOptimization {
+    int overrideHashCode;
+    int hashCodeCount;
+
+    abstract EqualsCounter equalsCounter();
+
+    @Memoized
+    @Override
+    public int hashCode() {
+      hashCodeCount++;
+      return overrideHashCode;
+    }
+
+    static class EqualsCounter {
+      int equalsCount;
+
+      @Override
+      public int hashCode() {
+        return 0;
+      }
+
+      @Override
+      public boolean equals(Object obj) {
+        equalsCount++;
+        return true;
+      }
+    }
+  }
+
+  @AutoValue
+  abstract static class HashCodeEqualsOptimizationOffWhenEqualsIsFinal {
+    int hashCodeCount;
+
+    @Override
+    @Memoized
+    public int hashCode() {
+      hashCodeCount++;
+      return 1;
+    }
+
+    @Override
+    public final boolean equals(Object that) {
+      return that instanceof HashCodeEqualsOptimizationOffWhenEqualsIsFinal;
+    }
+  }
+
+  @Before
+  public void setUp() {
+    value = new AutoValue_MemoizedTest_Value(
+        "string", "stringWithTypeAnnotation", new HashCodeAndToStringCounter());
+    listValue = new AutoValue_MemoizedTest_ListValue<Integer, String>(0, "hello");
+  }
+
+  @Test
+  public void listValueList() {
+    assertThat(listValue.myTypedList()).containsExactly(listValue.value());
+  }
+
+  @Test
+  public void listValueString() {
+    assertThat(listValue.otherValue()).isEqualTo("hello");
+  }
+
+  @Test
+  public void primitive() {
+    assertThat(value.primitive()).isEqualTo(1);
+    assertThat(value.primitive()).isEqualTo(1);
+    assertThat(value.primitiveCount).isEqualTo(1);
+  }
+
+  @Test
+  public void notNullable() {
+    assertThat(value.notNullable()).isEqualTo("derived string 1");
+    assertThat(value.notNullable()).isSameInstanceAs(value.notNullable());
+    assertThat(value.notNullableCount).isEqualTo(1);
+  }
+
+  @Test
+  public void nullable() {
+    assertThat(value.nullable()).isEqualTo("nullable derived string 1");
+    assertThat(value.nullable()).isSameInstanceAs(value.nullable());
+    assertThat(value.nullableCount).isEqualTo(1);
+  }
+
+  @Test
+  public void nullableWithTypeAnnotation() {
+    assertThat(value.nullableWithTypeAnnotation())
+        .isEqualTo("nullable derived stringWithTypeAnnotation 1");
+    assertThat(value.nullableWithTypeAnnotation())
+        .isSameInstanceAs(value.nullableWithTypeAnnotation());
+    assertThat(value.nullableWithTypeAnnotationCount).isEqualTo(1);
+  }
+
+  @Test
+  public void returnsNull() {
+    assertThat(value.returnsNull()).isNull();
+    assertThat(value.returnsNull()).isNull();
+    assertThat(value.returnsNullCount).isEqualTo(1);
+  }
+
+  @Test
+  public void returnsNullWithTypeAnnotation() {
+    assertThat(value.returnsNullWithTypeAnnotation()).isNull();
+    assertThat(value.returnsNullWithTypeAnnotation()).isNull();
+    assertThat(value.returnsNullWithTypeAnnotationCount).isEqualTo(1);
+  }
+
+  @Test
+  public void notNullableButReturnsNull() {
+    try {
+      value.notNullableButReturnsNull();
+      fail();
+    } catch (NullPointerException expected) {
+      assertThat(expected)
+          .hasMessageThat()
+          .isEqualTo("notNullableButReturnsNull() cannot return null");
+    }
+    assertThat(value.notNullableButReturnsNullCount).isEqualTo(1);
+  }
+
+  @Test
+  public void methodThrows() {
+    // The exception is thrown.
+    try {
+      value.throwsException();
+      fail();
+    } catch (SomeCheckedException expected1) {
+      // The exception is not memoized.
+      try {
+        value.throwsException();
+        fail();
+      } catch (SomeCheckedException expected2) {
+        assertThat(expected2).isNotSameInstanceAs(expected1);
+      }
+    }
+    assertThat(value.throwsExceptionCount).isEqualTo(2);
+  }
+
+  @Test
+  public void testHashCode() {
+    assertThat(value.hashCode()).isEqualTo(value.hashCode());
+    assertThat(value.counter().hashCodeCount).isEqualTo(1);
+  }
+
+  @Test
+  public void testToString() {
+    assertThat(value.toString()).isEqualTo(value.toString());
+    assertThat(value.counter().toStringCount).isEqualTo(1);
+  }
+
+  @Test
+  public void keywords() {
+    ValueWithKeywordName value = new AutoValue_MemoizedTest_ValueWithKeywordName(true, false);
+    assertThat(value.getNative()).isTrue();
+    assertThat(value.getMemoizedNative()).isTrue();
+    assertThat(value.getNative0()).isFalse();
+    assertThat(value.getMemoizedNative0()).isFalse();
+  }
+
+  @Test
+  public void copyAnnotations() {
+    ValueWithCopyAnnotations valueWithCopyAnnotations =
+        new AutoValue_MemoizedTest_ValueWithCopyAnnotations(true);
+    ValueWithoutCopyAnnotations valueWithoutCopyAnnotations =
+        new AutoValue_MemoizedTest_ValueWithoutCopyAnnotations(true);
+
+    assertThat(
+            valueWithCopyAnnotations
+                .getClass()
+                .isAnnotationPresent(javax.annotation.Nullable.class))
+        .isTrue();
+    assertThat(
+            valueWithoutCopyAnnotations
+                .getClass()
+                .isAnnotationPresent(javax.annotation.Nullable.class))
+        .isFalse();
+  }
+
+  @Test
+  public void nullableHasAnnotation() throws ReflectiveOperationException {
+    Method nullable = AutoValue_MemoizedTest_Value.class.getDeclaredMethod("nullable");
+    assertThat(nullable.isAnnotationPresent(javax.annotation.Nullable.class)).isTrue();
+  }
+
+  @Test
+  public void nullableWithTypeAnnotationHasAnnotation() throws ReflectiveOperationException {
+    Method nullable =
+        AutoValue_MemoizedTest_Value.class.getDeclaredMethod("nullableWithTypeAnnotation");
+    AnnotatedType returnType = nullable.getAnnotatedReturnType();
+    assertThat(returnType.isAnnotationPresent(
+                   org.checkerframework.checker.nullness.qual.Nullable.class))
+        .isTrue();
+  }
+
+  @Test
+  public void nullableConstructorParameter() throws ReflectiveOperationException {
+    // Constructor parameters are potentially:
+    // [0] @javax.annotation.Nullable String string,
+    // [1] @org.checkerframework.checker.nullness.qual.Nullable String stringWithTypeAnnotation,
+    // [2] HashCodeAndToStringCounter counter
+    // We don't currently copy @javax.annotation.Nullable because it is not a TYPE_USE annotation.
+    Constructor<?> constructor = AutoValue_MemoizedTest_Value.class.getDeclaredConstructor(
+        String.class, String.class, HashCodeAndToStringCounter.class);
+    AnnotatedType paramType = constructor.getAnnotatedParameterTypes()[1];
+    assertThat(paramType.isAnnotationPresent(
+                   org.checkerframework.checker.nullness.qual.Nullable.class))
+        .isTrue();
+  }
+
+  @Test
+  public void hashCodeEqualsOptimization() {
+    HashCodeEqualsOptimization first =
+        new AutoValue_MemoizedTest_HashCodeEqualsOptimization(new EqualsCounter());
+    HashCodeEqualsOptimization second =
+        new AutoValue_MemoizedTest_HashCodeEqualsOptimization(new EqualsCounter());
+
+    first.overrideHashCode = 2;
+    second.overrideHashCode = 2;
+    assertThat(first.equals(second)).isTrue();
+    assertThat(first.equalsCounter().equalsCount).isEqualTo(1);
+
+    HashCodeEqualsOptimization otherwiseEqualsButDifferentHashCode =
+        new AutoValue_MemoizedTest_HashCodeEqualsOptimization(new EqualsCounter());
+    otherwiseEqualsButDifferentHashCode.overrideHashCode = 4;
+
+    assertThat(otherwiseEqualsButDifferentHashCode.equals(first)).isFalse();
+    assertThat(otherwiseEqualsButDifferentHashCode.equalsCounter().equalsCount).isEqualTo(0);
+  }
+
+  @Test
+  public void hashCodeEqualsOptimization_otherTypes() {
+    HashCodeEqualsOptimization optimizedEquals =
+        new AutoValue_MemoizedTest_HashCodeEqualsOptimization(new EqualsCounter());
+
+    assertThat(optimizedEquals.equals(new Object())).isFalse();
+    assertThat(optimizedEquals.equals(null)).isFalse();
+
+    assertThat(optimizedEquals.equalsCounter().equalsCount).isEqualTo(0);
+    assertThat(optimizedEquals.hashCodeCount).isEqualTo(0);
+  }
+
+  @Test
+  public void hashCodeEqualsOptimization_hashCodeIgnoredForSameInstance() {
+    HashCodeEqualsOptimization optimizedEquals =
+        new AutoValue_MemoizedTest_HashCodeEqualsOptimization(new EqualsCounter());
+
+    assertThat(optimizedEquals.equals(optimizedEquals)).isTrue();
+
+    assertThat(optimizedEquals.equalsCounter().equalsCount).isEqualTo(0);
+    assertThat(optimizedEquals.hashCodeCount).isEqualTo(0);
+  }
+
+  @Test
+  public void hashCodeEqualsOptimization_offWhenEqualsIsFinal() {
+    HashCodeEqualsOptimizationOffWhenEqualsIsFinal memoizedHashCodeAndFinalEqualsMethod =
+        new AutoValue_MemoizedTest_HashCodeEqualsOptimizationOffWhenEqualsIsFinal();
+    HashCodeEqualsOptimizationOffWhenEqualsIsFinal second =
+        new AutoValue_MemoizedTest_HashCodeEqualsOptimizationOffWhenEqualsIsFinal();
+
+    assertThat(memoizedHashCodeAndFinalEqualsMethod.equals(second)).isTrue();
+    assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(0);
+
+    memoizedHashCodeAndFinalEqualsMethod.hashCode();
+    memoizedHashCodeAndFinalEqualsMethod.hashCode();
+    assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(1);
+  }
+
+  interface TypeEdgeIterable<InputT, ResultT> {}
+
+  interface ResourceUri {}
+
+  interface TypePath<InputT, ResultT> {}
+
+  abstract static class AbstractTypePath<InputT, ResultT> {
+    abstract TypeEdgeIterable<InputT, ResultT> edges();
+  }
+
+  @AutoValue
+  abstract static class ResourceUriPath<InputT> extends AbstractTypePath<InputT, ResourceUri> {
+    static <InputT> ResourceUriPath<InputT> create(
+        TypeEdgeIterable<InputT, ResourceUri> edges) {
+      return new AutoValue_MemoizedTest_ResourceUriPath<>(edges);
+    }
+
+    @Memoized
+    TypePath<InputT, String> toServiceName() {
+      return new TypePath<InputT, String>() {};
+    }
+  }
+
+  @Test
+  public void methodTypeFromTypeVariableSubsitution() {
+    ResourceUriPath<String> path =
+        ResourceUriPath.create(new TypeEdgeIterable<String, ResourceUri>() {});
+    assertThat(path.edges()).isNotNull();
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedValidationTest.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedValidationTest.java
new file mode 100644
index 0000000..72213b3
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedValidationTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 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.memoized;
+
+import static com.google.auto.value.extension.memoized.MemoizedMethodSubjectFactory.assertThatMemoizeMethod;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.auto.value.extension.memoized.processor.MemoizedValidator;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MemoizedValidationTest {
+
+  @Test
+  public void privateMethod() {
+    assertThatMemoizeMethod("@Memoized private String method() { return \"\"; }")
+        .hasError("@Memoized methods cannot be private");
+  }
+
+  @Test
+  public void staticMethod() {
+    assertThatMemoizeMethod("@Memoized static String method() { return \"\"; }")
+        .hasError("@Memoized methods cannot be static");
+  }
+
+  @Test
+  public void finalMethod() {
+    assertThatMemoizeMethod("@Memoized final String method() { return \"\"; }")
+        .hasError("@Memoized methods cannot be final");
+  }
+
+  @Test
+  public void abstractMethod() {
+    assertThatMemoizeMethod("@Memoized abstract String method();")
+        .hasError("@Memoized methods cannot be abstract");
+  }
+
+  @Test
+  public void voidMethod() {
+    assertThatMemoizeMethod("@Memoized void method() {}")
+        .hasError("@Memoized methods cannot be void");
+  }
+
+  @Test
+  public void parameters() {
+    assertThatMemoizeMethod("@Memoized String method(Object param) { return \"\"; }")
+        .hasError("@Memoized methods cannot have parameters");
+  }
+
+  @Test
+  public void notInAutoValueClass() {
+    JavaFileObject source =
+        JavaFileObjects.forSourceLines(
+            "test.EnclosingClass",
+            "package test;",
+            "",
+            "import com.google.auto.value.extension.memoized.Memoized;",
+            "",
+            "abstract class EnclosingClass {",
+            "  @Memoized",
+            "  String string() {",
+            "    return \"\";",
+            "  }",
+            "}");
+    Compilation compilation = javac().withProcessors(new MemoizedValidator()).compile(source);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining("@Memoized methods must be declared only in @AutoValue classes")
+        .inFile(source)
+        .onLine(6);
+  }
+}
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..1879d1e
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java
@@ -0,0 +1,418 @@
+/*
+ * 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);
+  }
+
+  @SerializableAutoValue
+  @AutoValue
+  abstract static class MultiplePropertiesSameType implements Serializable {
+    abstract String a();
+
+    abstract String b();
+
+    static MultiplePropertiesSameType.Builder builder() {
+      return new AutoValue_SerializableAutoValueExtensionTest_MultiplePropertiesSameType.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract MultiplePropertiesSameType.Builder setA(String value);
+
+      abstract MultiplePropertiesSameType.Builder setB(String value);
+
+      abstract MultiplePropertiesSameType build();
+    }
+  }
+
+  @Test
+  public void multiplePropertiesSameType_allFieldsSerialized() {
+    MultiplePropertiesSameType autoValue =
+        MultiplePropertiesSameType.builder().setA("A").setB("B").build();
+
+    MultiplePropertiesSameType 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");
+    }
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java
new file mode 100644
index 0000000..b8a36ea
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class AutoAnnotationCompilationTest {
+
+  @Test
+  public void testSimple() {
+    JavaFileObject myAnnotationJavaFile =
+        JavaFileObjects.forSourceLines(
+            "com.example.annotations.MyAnnotation",
+            "package com.example.annotations;",
+            "",
+            "import com.example.enums.MyEnum;",
+            "",
+            "public @interface MyAnnotation {",
+            "  MyEnum value();",
+            "  int defaultedValue() default 23;",
+            "}");
+    int invariableHash = ("defaultedValue".hashCode() * 127) ^ 23;
+    JavaFileObject myEnumJavaFile =
+        JavaFileObjects.forSourceLines(
+            "com.example.enums.MyEnum",
+            "package com.example.enums;",
+            "",
+            "public enum MyEnum {",
+            "  ONE",
+            "}");
+    JavaFileObject annotationFactoryJavaFile =
+        JavaFileObjects.forSourceLines(
+            "com.example.factories.AnnotationFactory",
+            "package com.example.factories;",
+            "",
+            "import com.google.auto.value.AutoAnnotation;",
+            "import com.example.annotations.MyAnnotation;",
+            "import com.example.enums.MyEnum;",
+            "",
+            "public class AnnotationFactory {",
+            "  @AutoAnnotation",
+            "  public static MyAnnotation newMyAnnotation(MyEnum value) {",
+            "    return new AutoAnnotation_AnnotationFactory_newMyAnnotation(value);",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "com.example.factories.AutoAnnotation_AnnotationFactory_newMyAnnotation",
+            "package com.example.factories;",
+            "",
+            "import com.example.annotations.MyAnnotation;",
+            "import com.example.enums.MyEnum;",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
+            "final class AutoAnnotation_AnnotationFactory_newMyAnnotation",
+            "     implements MyAnnotation {",
+            "  private final MyEnum value;",
+            "  private static final int defaultedValue = 23;",
+            "",
+            "  AutoAnnotation_AnnotationFactory_newMyAnnotation(MyEnum value) {",
+            "    if (value == null) {",
+            "      throw new NullPointerException(\"Null value\");",
+            "    }",
+            "    this.value = value;",
+            "  }",
+            "",
+            "  @Override public Class<? extends MyAnnotation> annotationType() {",
+            "    return MyAnnotation.class;",
+            "  }",
+            "",
+            "  @Override public MyEnum value() {",
+            "    return value;",
+            "  }",
+            "",
+            "  @Override public int defaultedValue() {",
+            "    return defaultedValue;",
+            "  }",
+            "",
+            "  @Override public String toString() {",
+            "    StringBuilder sb = new StringBuilder(\"@com.example.annotations.MyAnnotation(\");",
+            "    sb.append(value);",
+            "    return sb.append(')').toString();",
+            "  }",
+            "",
+            "  @Override public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof MyAnnotation) {",
+            "      MyAnnotation that = (MyAnnotation) o;",
+            "      return value.equals(that.value())",
+            "          && (defaultedValue == that.defaultedValue());",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+            "  @Override public int hashCode() {",
+            "    return ",
+            "        " + invariableHash,
+            "        + (" + 127 * "value".hashCode() + " ^ value.hashCode())",
+            "    ;",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(annotationFactoryJavaFile, myAnnotationJavaFile, myEnumJavaFile);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile(
+            "com.example.factories.AutoAnnotation_AnnotationFactory_newMyAnnotation")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  @Test
+  public void testEmptyPackage() {
+    JavaFileObject myAnnotationJavaFile =
+        JavaFileObjects.forSourceLines(
+            "MyAnnotation", //
+            "public @interface MyAnnotation {}");
+    JavaFileObject annotationFactoryJavaFile =
+        JavaFileObjects.forSourceLines(
+            "AnnotationFactory",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "public class AnnotationFactory {",
+            "  @AutoAnnotation",
+            "  public static MyAnnotation newMyAnnotation() {",
+            "    return new AutoAnnotation_AnnotationFactory_newMyAnnotation();",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "AutoAnnotation_AnnotationFactory_newMyAnnotation",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
+            "final class AutoAnnotation_AnnotationFactory_newMyAnnotation",
+            "    implements MyAnnotation {",
+            "  AutoAnnotation_AnnotationFactory_newMyAnnotation() {",
+            "  }",
+            "",
+            "  @Override public Class<? extends MyAnnotation> annotationType() {",
+            "    return MyAnnotation.class;",
+            "  }",
+            "",
+            "  @Override public String toString() {",
+            "    StringBuilder sb = new StringBuilder(\"@MyAnnotation(\");",
+            "    return sb.append(')').toString();",
+            "  }",
+            "",
+            "  @Override public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof MyAnnotation) {",
+            "      return true;",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+            "  @Override public int hashCode() {",
+            "    return 0;",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(annotationFactoryJavaFile, myAnnotationJavaFile);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("AutoAnnotation_AnnotationFactory_newMyAnnotation")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  @Test
+  public void testGwtSimple() {
+    JavaFileObject myAnnotationJavaFile =
+        JavaFileObjects.forSourceLines(
+            "com.example.annotations.MyAnnotation",
+            "package com.example.annotations;",
+            "",
+            "import com.google.common.annotations.GwtCompatible;",
+            "",
+            "@GwtCompatible",
+            "public @interface MyAnnotation {",
+            "  int[] value();",
+            "}");
+    JavaFileObject gwtCompatibleJavaFile =
+        JavaFileObjects.forSourceLines(
+            "com.google.common.annotations.GwtCompatible",
+            "package com.google.common.annotations;",
+            "",
+            "public @interface GwtCompatible {}");
+    JavaFileObject annotationFactoryJavaFile =
+        JavaFileObjects.forSourceLines(
+            "com.example.factories.AnnotationFactory",
+            "package com.example.factories;",
+            "",
+            "import com.google.auto.value.AutoAnnotation;",
+            "import com.example.annotations.MyAnnotation;",
+            "",
+            "public class AnnotationFactory {",
+            "  @AutoAnnotation",
+            "  public static MyAnnotation newMyAnnotation(int[] value) {",
+            "    return new AutoAnnotation_AnnotationFactory_newMyAnnotation(value);",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "com.example.factories.AutoAnnotation_AnnotationFactory_newMyAnnotation",
+            "package com.example.factories;",
+            "",
+            "import com.example.annotations.MyAnnotation;",
+            "import java.util.Arrays;",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
+            "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation"
+                + " {",
+            "  private final int[] value;",
+            "",
+            "  AutoAnnotation_AnnotationFactory_newMyAnnotation(int[] value) {",
+            "    if (value == null) {",
+            "      throw new NullPointerException(\"Null value\");",
+            "    }",
+            "    this.value = Arrays.copyOf(value, value.length);",
+            "  }",
+            "",
+            "  @Override public Class<? extends MyAnnotation> annotationType() {",
+            "    return MyAnnotation.class;",
+            "  }",
+            "",
+            "  @Override public int[] value() {",
+            "    return Arrays.copyOf(value, value.length);",
+            "  }",
+            "",
+            "  @Override public String toString() {",
+            "    StringBuilder sb = new StringBuilder(\"@com.example.annotations.MyAnnotation(\");",
+            "    sb.append(Arrays.toString(value));",
+            "    return sb.append(')').toString();",
+            "  }",
+            "",
+            "  @Override public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof MyAnnotation) {",
+            "      MyAnnotation that = (MyAnnotation) o;",
+            "      return Arrays.equals(value,",
+            "          (that instanceof AutoAnnotation_AnnotationFactory_newMyAnnotation)",
+            "              ? ((AutoAnnotation_AnnotationFactory_newMyAnnotation) that).value",
+            "              : that.value());",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+            "  @Override public int hashCode() {",
+            "    return ",
+            "        + (" + 127 * "value".hashCode() + " ^ Arrays.hashCode(value));",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(annotationFactoryJavaFile, myAnnotationJavaFile, gwtCompatibleJavaFile);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile(
+            "com.example.factories.AutoAnnotation_AnnotationFactory_newMyAnnotation")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  @Test
+  public void testCollectionsForArrays() {
+    JavaFileObject myAnnotationJavaFile =
+        JavaFileObjects.forSourceLines(
+            "com.example.annotations.MyAnnotation",
+            "package com.example.annotations;",
+            "",
+            "import com.example.enums.MyEnum;",
+            "",
+            "public @interface MyAnnotation {",
+            "  int[] value();",
+            "  MyEnum[] enums() default {};",
+            "}");
+    JavaFileObject myEnumJavaFile =
+        JavaFileObjects.forSourceLines(
+            "com.example.enums.MyEnum",
+            "package com.example.enums;",
+            "",
+            "public enum MyEnum {",
+            "  ONE",
+            "}");
+    JavaFileObject annotationFactoryJavaFile =
+        JavaFileObjects.forSourceLines(
+            "com.example.factories.AnnotationFactory",
+            "package com.example.factories;",
+            "",
+            "import com.google.auto.value.AutoAnnotation;",
+            "import com.example.annotations.MyAnnotation;",
+            "import com.example.enums.MyEnum;",
+            "",
+            "import java.util.List;",
+            "import java.util.Set;",
+            "",
+            "public class AnnotationFactory {",
+            "  @AutoAnnotation",
+            "  public static MyAnnotation newMyAnnotation(",
+            "      List<Integer> value, Set<MyEnum> enums) {",
+            "    return new AutoAnnotation_AnnotationFactory_newMyAnnotation(value, enums);",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "com.example.factories.AutoAnnotation_AnnotationFactory_newMyAnnotation",
+            "package com.example.factories;",
+            "",
+            "import com.example.annotations.MyAnnotation;",
+            "import com.example.enums.MyEnum;",
+            "import java.util.Arrays;",
+            "import java.util.Collection;",
+            "import java.util.List;",
+            "import java.util.Set;",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
+            "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation"
+                + " {",
+            "  private final int[] value;",
+            "  private final MyEnum[] enums;",
+            "",
+            "  AutoAnnotation_AnnotationFactory_newMyAnnotation(",
+            "      List<Integer> value,",
+            "      Set<MyEnum> enums) {",
+            "    if (value == null) {",
+            "      throw new NullPointerException(\"Null value\");",
+            "    }",
+            "    this.value = intArrayFromCollection(value);",
+            "    if (enums == null) {",
+            "      throw new NullPointerException(\"Null enums\");",
+            "    }",
+            "    this.enums = enums.toArray(new MyEnum[0];",
+            "  }",
+            "",
+            "  @Override public Class<? extends MyAnnotation> annotationType() {",
+            "    return MyAnnotation.class;",
+            "  }",
+            "",
+            "  @Override public int[] value() {",
+            "    return value.clone();",
+            "  }",
+            "",
+            "  @Override public MyEnum[] enums() {",
+            "    return enums.clone();",
+            "  }",
+            "",
+            "  @Override public String toString() {",
+            "    StringBuilder sb = new StringBuilder(\"@com.example.annotations.MyAnnotation(\");",
+            "    sb.append(\"value=\");",
+            "    sb.append(Arrays.toString(value));",
+            "    sb.append(\", \");",
+            "    sb.append(\"enums=\");",
+            "    sb.append(Arrays.toString(enums));",
+            "    return sb.append(')').toString();",
+            "  }",
+            "",
+            "  @Override public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof MyAnnotation) {",
+            "      MyAnnotation that = (MyAnnotation) o;",
+            "      return Arrays.equals(value,",
+            "          (that instanceof AutoAnnotation_AnnotationFactory_newMyAnnotation)",
+            "              ? ((AutoAnnotation_AnnotationFactory_newMyAnnotation) that).value",
+            "              : that.value())",
+            "          && Arrays.equals(enums,",
+            "          (that instanceof AutoAnnotation_AnnotationFactory_newMyAnnotation)",
+            "              ? ((AutoAnnotation_AnnotationFactory_newMyAnnotation) that).enums",
+            "              : that.enums())",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+            "  @Override public int hashCode() {",
+            "    return ",
+            "        + (" + 127 * "value".hashCode() + " ^ Arrays.hashCode(value))",
+            "        + (" + 127 * "enums".hashCode() + " ^ Arrays.hashCode(enums));",
+            "  }",
+            "",
+            "  private static int[] intArrayFromCollection(Collection<Integer> c) {",
+            "    int[] a = new int[c.size()];",
+            "    int i = 0;",
+            "    for (int x : c) {",
+            "      a[i++] = x;",
+            "    }",
+            "    return a;",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(annotationFactoryJavaFile, myEnumJavaFile, myAnnotationJavaFile);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile(
+            "com.example.factories.AutoAnnotation_AnnotationFactory_newMyAnnotation")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  @Test
+  public void testMissingClass() {
+    // Test that referring to an undefined annotation does not trigger @AutoAnnotation processing.
+    // The class Erroneous references an undefined annotation @NotAutoAnnotation. If we didn't have
+    // any special treatment of undefined types then we could run into a compiler bug where
+    // AutoAnnotationProcessor would think that a method annotated with @NotAutoAnnotation was in
+    // fact annotated with @AutoAnnotation. As it is, we do get an error about @NotAutoAnnotation
+    // being undefined, and we do not get an error complaining that this supposed @AutoAnnotation
+    // method is not static. We do need to have @AutoAnnotation appear somewhere so that the
+    // processor will run.
+    JavaFileObject erroneousJavaFileObject =
+        JavaFileObjects.forSourceLines(
+            "com.example.annotations.Erroneous",
+            "package com.example.annotations;",
+            "",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "public class Erroneous {",
+            "  @interface Empty {}",
+            "  @AutoAnnotation static Empty newEmpty() {}",
+            "  @NotAutoAnnotation Empty notNewEmpty() {}",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(erroneousJavaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("NotAutoAnnotation")
+        .inFile(erroneousJavaFileObject)
+        .onLineContaining("@NotAutoAnnotation");
+    assertThat(
+            compilation.errors().stream()
+                .map(diag -> diag.getMessage(null))
+                .filter(m -> m.contains("static")))
+        .isEmpty();
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
new file mode 100644
index 0000000..55a4c5d
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for compilation errors with the AutoAnnotation processor.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class AutoAnnotationErrorsTest {
+  private static final JavaFileObject TEST_ANNOTATION =
+      JavaFileObjects.forSourceLines(
+          "com.example.TestAnnotation",
+          "package com.example;",
+          "",
+          "public @interface TestAnnotation {",
+          "  int value();",
+          "}");
+
+  @Test
+  public void testCorrect() {
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(int value) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(TEST_ANNOTATION, testSource);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void testNotStatic() {
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation TestAnnotation newTestAnnotation(int value) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(TEST_ANNOTATION, testSource);
+    assertThat(compilation)
+        .hadErrorContaining("must be static")
+        .inFile(testSource)
+        .onLineContaining("TestAnnotation newTestAnnotation(int value)");
+  }
+
+  @Test
+  public void testDoesNotReturnAnnotation() {
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static String newString(int value) {",
+            "    return new AutoAnnotation_Test_newString(value);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(TEST_ANNOTATION, testSource);
+    assertThat(compilation)
+        .hadErrorContaining("must be an annotation type, not java.lang.String")
+        .inFile(testSource)
+        .onLineContaining("static String newString(int value)");
+  }
+
+  @Test
+  public void testOverload() {
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(int value) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+            "  }",
+            "",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(Integer value) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(TEST_ANNOTATION, testSource);
+    assertThat(compilation)
+        .hadErrorContaining("@AutoAnnotation methods cannot be overloaded")
+        .inFile(testSource)
+        .onLineContaining("newTestAnnotation(Integer value)");
+  }
+
+  // Overload detection used to detect all @AutoAnnotation methods that resulted in
+  // annotation class of the same SimpleName as being an overload.
+  // This verifies that implementations in different packages work correctly.
+  @Test
+  public void testSameNameDifferentPackagesDoesNotTriggerOverload() {
+
+    JavaFileObject fooTestSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(int value) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+            "  }",
+            "}");
+    JavaFileObject barTestSource =
+        JavaFileObjects.forSourceLines(
+            "com.bar.Test",
+            "package com.bar;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(int value) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(fooTestSource, barTestSource, TEST_ANNOTATION);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void testWrongName() {
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(int fred) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(fred);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(TEST_ANNOTATION, testSource);
+    assertThat(compilation)
+        .hadErrorContaining("method parameter 'fred' must have the same name")
+        .inFile(testSource)
+        .onLineContaining("newTestAnnotation(int fred)");
+  }
+
+  @Test
+  public void testWrongType() {
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(String value) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(TEST_ANNOTATION, testSource);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "method parameter 'value' has type java.lang.String "
+                + "but com.example.TestAnnotation.value has type int")
+        .inFile(testSource)
+        .onLineContaining("newTestAnnotation(String value)");
+  }
+
+  @Test
+  public void testWrongTypeCollection() {
+    JavaFileObject testAnnotation =
+        JavaFileObjects.forSourceLines(
+            "com.example.TestAnnotation",
+            "package com.example;",
+            "",
+            "public @interface TestAnnotation {",
+            "  int[] value();",
+            "}");
+    String[] wrongTypes = {
+      "java.util.List<java.lang.Long>",
+      "java.util.List<java.lang.String>",
+      "java.util.Set<java.lang.Long>",
+      "java.util.Map<java.lang.Integer,java.lang.Integer>",
+      "java.util.concurrent.Callable<java.lang.Integer>",
+      "java.util.List<int[]>",
+    };
+    for (String wrongType : wrongTypes) {
+      JavaFileObject testSource =
+          JavaFileObjects.forSourceLines(
+              "com.foo.Test",
+              "package com.foo;",
+              "",
+              "import com.example.TestAnnotation;",
+              "import com.google.auto.value.AutoAnnotation;",
+              "",
+              "class Test {",
+              "  @AutoAnnotation static TestAnnotation newTestAnnotation("
+                  + wrongType
+                  + " value) {",
+              "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+              "  }",
+              "}");
+      Compilation compilation =
+          javac()
+              .withProcessors(new AutoAnnotationProcessor())
+              .compile(testSource, testAnnotation);
+      assertThat(compilation)
+          .hadErrorContaining(
+              "method parameter 'value' has type "
+                  + wrongType
+                  + " but com.example.TestAnnotation.value has type int[]")
+          .inFile(testSource)
+          .onLineContaining("TestAnnotation newTestAnnotation(");
+    }
+  }
+
+  @Test
+  public void testExtraParameters() {
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(int value, int other) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(TEST_ANNOTATION, testSource);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "method parameter 'other' must have the same name as a member of "
+                + "com.example.TestAnnotation")
+        .inFile(testSource)
+        .onLineContaining("newTestAnnotation(int value, int other)");
+  }
+
+  @Test
+  public void testMissingParameters() {
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation() {",
+            "    return new AutoAnnotation_Test_newTestAnnotation();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(TEST_ANNOTATION, testSource);
+    assertThat(compilation)
+        .hadErrorContaining("method needs a parameter with name 'value' and type int")
+        .inFile(testSource)
+        .onLineContaining("TestAnnotation newTestAnnotation()");
+  }
+
+  @Test
+  public void testAnnotationValuedDefaultsNotSupportedYet() {
+    JavaFileObject annotationSource =
+        JavaFileObjects.forSourceLines(
+            "com.example.TestAnnotation",
+            "package com.example;",
+            "",
+            "public @interface TestAnnotation {",
+            "  String value();",
+            "  Override optionalAnnotation() default @Override;",
+            "}");
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(String value) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(annotationSource, testSource);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@AutoAnnotation cannot yet supply a default value for annotation-valued member "
+                + "'optionalAnnotation'")
+        .inFile(testSource)
+        .onLineContaining("TestAnnotation newTestAnnotation(String value)");
+  }
+
+  @Test
+  public void testAnnotationMemberNameConflictWithGeneratedLocal() {
+    JavaFileObject annotationSource =
+        JavaFileObjects.forSourceLines(
+            "com.example.TestAnnotation",
+            "package com.example;",
+            "",
+            "import java.lang.annotation.Annotation;",
+            "",
+            "public @interface TestAnnotation {",
+            "  Class<? extends Annotation>[] value();",
+            "  int value$();",
+            "}");
+    JavaFileObject testSource =
+        JavaFileObjects.forSourceLines(
+            "com.foo.Test",
+            "package com.foo;",
+            "",
+            "import java.lang.annotation.Annotation;",
+            "import java.util.Collection;",
+            "",
+            "import com.example.TestAnnotation;",
+            "import com.google.auto.value.AutoAnnotation;",
+            "",
+            "class Test {",
+            "  @AutoAnnotation static TestAnnotation newTestAnnotation(",
+            "     Collection<Class<? extends Annotation>> value, int value$) {",
+            "    return new AutoAnnotation_Test_newTestAnnotation(value, value$);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoAnnotationProcessor())
+            .compile(annotationSource, testSource);
+    assertThat(compilation)
+        .hadErrorContaining("variable value$ is already defined in constructor");
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java
new file mode 100644
index 0000000..63e8419
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2018 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.processor;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.common.truth.Expect;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class AutoOneOfCompilationTest {
+  @Rule public final Expect expect = Expect.create();
+
+  @Test
+  public void success() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.TaskResult",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoOneOf;",
+            "import java.io.Serializable;",
+            "",
+            "@AutoOneOf(TaskResult.Kind.class)",
+            "public abstract class TaskResult<V, T extends Throwable> {",
+            "  public enum Kind {VALUE, EXCEPTION, EMPTY}",
+            "  public abstract Kind getKind();",
+            "",
+            "  public abstract V value();",
+            "  public abstract Throwable exception();",
+            "  public abstract void empty();",
+            "",
+            "  public static <V> TaskResult<V, ?> value(V value) {",
+            "    return AutoOneOf_TaskResult.value(value);",
+            "  }",
+            "",
+            "  public static <T extends Throwable> TaskResult<?, T> exception(T exception) {",
+            "    return AutoOneOf_TaskResult.exception(exception);",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoOneOf_TaskResult",
+            "package foo.bar;",
+            "",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"com.google.auto.value.processor.AutoOneOfProcessor\")",
+            "final class AutoOneOf_TaskResult {",
+            "  private AutoOneOf_TaskResult() {} // There are no instances of this type.",
+            "",
+            "  static <V, T extends Throwable> TaskResult<V, T> value(V value) {",
+            "    if (value == null) {",
+            "      throw new NullPointerException();",
+            "    }",
+            "    return new Impl_value<V, T>(value);",
+            "  }",
+            "",
+            "  static <V, T extends Throwable> TaskResult<V, T> exception(Throwable exception) {",
+            "    if (exception == null) {",
+            "      throw new NullPointerException();",
+            "    }",
+            "    return new Impl_exception<V, T>(exception);",
+            "  }",
+            "",
+            "  @SuppressWarnings(\"unchecked\") // type parameters are unused in void instances",
+            "  static <V, T extends Throwable> TaskResult<V, T> empty() {",
+            "    return (TaskResult<V, T>) Impl_empty.INSTANCE;",
+            "  }",
+            "",
+            "  // Parent class that each implementation will inherit from.",
+            "  private abstract static class Parent_<V, T extends Throwable> "
+                + "extends TaskResult<V, T> {",
+            "    @Override",
+            "    public V value() {",
+            "      throw new UnsupportedOperationException(getKind().toString());",
+            "    }",
+            "",
+            "    @Override",
+            "    public Throwable exception() {",
+            "      throw new UnsupportedOperationException(getKind().toString());",
+            "    }",
+            "",
+            "    @Override",
+            "    public void empty() {",
+            "      throw new UnsupportedOperationException(getKind().toString());",
+            "    }",
+            "  }",
+            "",
+            "  // Implementation when the contained property is \"value\".",
+            "  private static final class Impl_value<V, T extends Throwable> "
+                + "extends Parent_<V, T> {",
+            "    private final V value;",
+            "",
+            "    Impl_value(V value) {",
+            "      this.value = value;",
+            "    }",
+            "",
+            "    @Override",
+            "    public V value() {",
+            "      return value;",
+            "    }",
+            "",
+            "    @Override",
+            "    public String toString() {",
+            "      return \"TaskResult{value=\" + this.value + \"}\";",
+            "    }",
+            "",
+            "    @Override",
+            "    public boolean equals(Object x) {",
+            "      if (x instanceof TaskResult) {",
+            "        TaskResult<?, ?> that = (TaskResult<?, ?>) x;",
+            "        return this.getKind() == that.getKind()",
+            "            && this.value.equals(that.value());",
+            "      } else {",
+            "        return false;",
+            "      }",
+            "    }",
+            "",
+            "    @Override",
+            "    public int hashCode() {",
+            "      return value.hashCode();",
+            "    }",
+            "",
+            "    @Override",
+            "    public TaskResult.Kind getKind() {",
+            "      return TaskResult.Kind.VALUE;",
+            "    }",
+            "  }",
+            "",
+            "  // Implementation when the contained property is \"exception\".",
+            "  private static final class Impl_exception<V, T extends Throwable> "
+                + "extends Parent_<V, T> {",
+            "    private final Throwable exception;",
+            "",
+            "    Impl_exception(Throwable exception) {",
+            "      this.exception = exception;",
+            "    }",
+            "",
+            "    @Override",
+            "    public Throwable exception() {",
+            "      return exception;",
+            "    }",
+            "",
+            "    @Override",
+            "    public String toString() {",
+            "      return \"TaskResult{exception=\" + this.exception + \"}\";",
+            "    }",
+            "",
+            "    @Override",
+            "    public boolean equals(Object x) {",
+            "      if (x instanceof TaskResult) {",
+            "        TaskResult<?, ?> that = (TaskResult<?, ?>) x;",
+            "        return this.getKind() == that.getKind()",
+            "            && this.exception.equals(that.exception());",
+            "      } else {",
+            "        return false;",
+            "      }",
+            "    }",
+            "",
+            "    @Override",
+            "    public int hashCode() {",
+            "      return exception.hashCode();",
+            "    }",
+            "",
+            "    @Override",
+            "    public TaskResult.Kind getKind() {",
+            "      return TaskResult.Kind.EXCEPTION;",
+            "    }",
+            "  }",
+            "",
+            "  // Implementation when the contained property is \"empty\".",
+            "  private static final class Impl_empty<V, T extends Throwable> "
+                + "extends Parent_<V, T> {",
+            "    static final Impl_empty<?, ?> INSTANCE = new Impl_empty<>();",
+            "",
+            "    private Impl_empty() {}",
+            "",
+            "    @Override",
+            "    public void empty() {}",
+            "",
+            "    @Override",
+            "    public String toString() {",
+            "      return \"TaskResult{empty}\";",
+            "    }",
+            "",
+            "    @Override",
+            "    public boolean equals(Object x) {",
+            "      return x == this;",
+            "    }",
+            "",
+            "    @Override",
+            "    public int hashCode() {",
+            "      return System.identityHashCode(this);",
+            "    }",
+            "",
+            "    @Override",
+            "    public TaskResult.Kind getKind() {",
+            "      return TaskResult.Kind.EMPTY;",
+            "    }",
+            "  }");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoOneOfProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoOneOf_TaskResult")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  @Test
+  public void voidInstanceWithoutGenericTypeParameters() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Nothing",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoOneOf;",
+            "import java.io.Serializable;",
+            "",
+            "@AutoOneOf(Nothing.Kind.class)",
+            "abstract class Nothing {",
+            "",
+            "  enum Kind {NOTHING}",
+            "",
+            "  abstract Kind kind();",
+            "",
+            "  abstract void nothing();",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Nothing",
+            "package foo.bar;",
+            "",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"com.google.auto.value.processor.AutoOneOfProcessor\")",
+            "final class AutoOneOf_Nothing {",
+            "  private AutoOneOf_Nothing() {} // There are no instances of this type.",
+            "",
+            "  static Nothing nothing() {",
+            "    return Impl_nothing.INSTANCE;",
+            "  }",
+            "",
+            "  // Parent class that each implementation will inherit from.",
+            "  private abstract static class Parent_ extends Nothing {",
+            "    @Override",
+            "    void nothing() {",
+            "      throw new UnsupportedOperationException(kind().toString());",
+            "    }",
+            "  }",
+            "",
+            "  // Implementation when the contained property is \"nothing\".",
+            "  private static final class Impl_nothing extends Parent_ {",
+            "    // There is only one instance of this class.",
+            "    static final Impl_nothing INSTANCE = new Impl_nothing();",
+            "",
+            "    private Impl_nothing() {}",
+            "",
+            "    @Override",
+            "    public void nothing() {}",
+            "",
+            "    @Override",
+            "    public String toString() {",
+            "      return \"Nothing{nothing}\";",
+            "    }",
+            "",
+            "    @Override",
+            "    public boolean equals(Object x) {",
+            "      return x == this;",
+            "    }",
+            "",
+            "    @Override",
+            "    public int hashCode() {",
+            "      return System.identityHashCode(this);",
+            "    }",
+            "",
+            "    @Override",
+            "    public Nothing.Kind kind() {",
+            "      return Nothing.Kind.NOTHING;",
+            "    }",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoOneOfProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoOneOf_Nothing")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  @Test
+  public void noKindGetter() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Pet",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoOneOf;",
+            "",
+            "@AutoOneOf(Pet.Kind.class)",
+            "public abstract class Pet {",
+            "  public enum Kind {DOG, CAT}",
+            "  public abstract String dog();",
+            "  public abstract String cat();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "foo.bar.Pet must have a no-arg abstract method returning foo.bar.Pet.Kind")
+        .inFile(javaFileObject)
+        .onLineContaining("class Pet");
+  }
+
+  @Test
+  public void kindGetterHasParam() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Pet",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoOneOf;",
+            "",
+            "@AutoOneOf(Pet.Kind.class)",
+            "public abstract class Pet {",
+            "  public enum Kind {DOG, CAT}",
+            "  public abstract Kind getKind(String wut);",
+            "  public abstract String dog();",
+            "  public abstract String cat();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "foo.bar.Pet must have a no-arg abstract method returning foo.bar.Pet.Kind")
+        .inFile(javaFileObject)
+        .onLineContaining("class Pet");
+  }
+
+  @Test
+  public void twoKindGetters() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Pet",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoOneOf;",
+            "",
+            "@AutoOneOf(Pet.Kind.class)",
+            "public abstract class Pet {",
+            "  public enum Kind {DOG, CAT}",
+            "  public abstract Kind getKind();",
+            "  public abstract Kind alsoGetKind();",
+            "  public abstract String dog();",
+            "  public abstract String cat();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("More than one abstract method returns foo.bar.Pet.Kind")
+        .inFile(javaFileObject)
+        .onLineContaining("getKind");
+    assertThat(compilation)
+        .hadErrorContaining("More than one abstract method returns foo.bar.Pet.Kind")
+        .inFile(javaFileObject)
+        .onLineContaining("alsoGetKind");
+  }
+
+  @Test
+  public void enumMissingCase() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Pet",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoOneOf;",
+            "",
+            "@AutoOneOf(Pet.Kind.class)",
+            "public abstract class Pet {",
+            "  public enum Kind {DOG}",
+            "  public abstract Kind getKind();",
+            "  public abstract String dog();",
+            "  public abstract String cat();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Enum has no constant with name corresponding to property 'cat'")
+        .inFile(javaFileObject)
+        .onLineContaining("enum Kind");
+  }
+
+  @Test
+  public void enumExtraCase() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Pet",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoOneOf;",
+            "",
+            "@AutoOneOf(Pet.Kind.class)",
+            "public abstract class Pet {",
+            "  public enum Kind {",
+            "    DOG,",
+            "    CAT,",
+            "    GERBIL,",
+            "  }",
+            "  public abstract Kind getKind();",
+            "  public abstract String dog();",
+            "  public abstract String cat();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Name of enum constant 'GERBIL' does not correspond to any property name")
+        .inFile(javaFileObject)
+        .onLineContaining("GERBIL");
+  }
+
+  @Test
+  public void cantBeNullable() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Pet",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoOneOf;",
+            "",
+            "@AutoOneOf(Pet.Kind.class)",
+            "public abstract class Pet {",
+            "  @interface Nullable {}",
+            "",
+            "  public enum Kind {",
+            "    DOG,",
+            "    CAT,",
+            "  }",
+            "  public abstract Kind getKind();",
+            "  public abstract @Nullable String dog();",
+            "  public abstract String cat();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("@AutoOneOf properties cannot be @Nullable")
+        .inFile(javaFileObject)
+        .onLineContaining("@Nullable String dog()");
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java
new file mode 100644
index 0000000..2d4aa7c
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java
@@ -0,0 +1,3337 @@
+/*
+ * Copyright 2018 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.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.CompilationSubject.compilations;
+import static com.google.testing.compile.Compiler.javac;
+import static java.util.stream.Collectors.joining;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.truth.Expect;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.JavaFileObject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class AutoValueCompilationTest {
+  @Rule public final Expect expect = Expect.create();
+
+  @Test
+  public void simpleSuccess() {
+    // Positive test case that ensures we generate the expected code for at least one case.
+    // Most AutoValue code-generation tests are functional, meaning that we check that the generated
+    // code does the right thing rather than checking what it looks like, but this test is a sanity
+    // check that we are not generating correct but weird code.
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract long buh();",
+            "",
+            "  public static Baz create(long buh) {",
+            "    return new AutoValue_Baz(buh);",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoValue_Baz",
+            "package foo.bar;",
+            "",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"" + AutoValueProcessor.class.getName() + "\")",
+            "final class AutoValue_Baz extends Baz {",
+            "  private final long buh;",
+            "",
+            "  AutoValue_Baz(long buh) {",
+            "    this.buh = buh;",
+            "  }",
+            "",
+            "  @Override public long buh() {",
+            "    return buh;",
+            "  }",
+            "",
+            "  @Override public String toString() {",
+            "    return \"Baz{\"",
+            "        + \"buh=\" + buh",
+            "        + \"}\";",
+            "  }",
+            "",
+            "  @Override public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof Baz) {",
+            "      Baz that = (Baz) o;",
+            "      return this.buh == that.buh();",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+            "  @Override public int hashCode() {",
+            "    int h$ = 1;",
+            "    h$ *= 1000003;",
+            "    h$ ^= (int) ((buh >>> 32) ^ buh);",
+            "    return h$;",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  @Test
+  public void importTwoWays() {
+    // Test that referring to the same class in two different ways does not confuse the import logic
+    // into thinking it is two different classes and that therefore it can't import. The code here
+    // is nonsensical but successfully reproduces a real problem, which is that a TypeMirror that is
+    // extracted using Elements.getTypeElement(name).asType() does not compare equal to one that is
+    // extracted from ExecutableElement.getReturnType(), even though Types.isSameType considers them
+    // equal. So unless we are careful, the java.util.Arrays that we import explicitly to use its
+    // methods will appear different from the java.util.Arrays that is the return type of the
+    // arrays() method here.
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "import java.util.Arrays;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  @SuppressWarnings(\"mutable\")",
+            "  public abstract int[] ints();",
+            "  public abstract Arrays arrays();",
+            "",
+            "  public static Baz create(int[] ints, Arrays arrays) {",
+            "    return new AutoValue_Baz(ints, arrays);",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoValue_Baz",
+            "package foo.bar;",
+            "",
+            "import java.util.Arrays;",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"" + AutoValueProcessor.class.getName() + "\")",
+            "final class AutoValue_Baz extends Baz {",
+            "  private final int[] ints;",
+            "  private final Arrays arrays;",
+            "",
+            "  AutoValue_Baz(int[] ints, Arrays arrays) {",
+            "    if (ints == null) {",
+            "      throw new NullPointerException(\"Null ints\");",
+            "    }",
+            "    this.ints = ints;",
+            "    if (arrays == null) {",
+            "      throw new NullPointerException(\"Null arrays\");",
+            "    }",
+            "    this.arrays = arrays;",
+            "  }",
+            "",
+            "  @SuppressWarnings(\"mutable\")",
+            "  @Override public int[] ints() {",
+            "    return ints;",
+            "  }",
+            "",
+            "  @Override public Arrays arrays() {",
+            "    return arrays;",
+            "  }",
+            "",
+            "  @Override public String toString() {",
+            "    return \"Baz{\"",
+            "        + \"ints=\" + Arrays.toString(ints) + \", \"",
+            "        + \"arrays=\" + arrays",
+            "        + \"}\";",
+            "  }",
+            "",
+            "  @Override public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof Baz) {",
+            "      Baz that = (Baz) o;",
+            "      return Arrays.equals(this.ints, (that instanceof AutoValue_Baz) "
+                + "? ((AutoValue_Baz) that).ints : that.ints())",
+            "          && this.arrays.equals(that.arrays());",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+            "  @Override public int hashCode() {",
+            "    int h$ = 1;",
+            "    h$ *= 1000003;",
+            "    h$ ^= Arrays.hashCode(ints);",
+            "    h$ *= 1000003;",
+            "    h$ ^= arrays.hashCode();",
+            "    return h$;",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  @Test
+  public void testNoWarningsFromGenerics() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue",
+            "public abstract class Baz<T extends Number, U extends T> {",
+            "  public abstract T t();",
+            "  public abstract U u();",
+            "  public static <T extends Number, U extends T> Baz<T, U> create(T t, U u) {",
+            "    return new AutoValue_Baz<T, U>(t, u);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void testNestedParameterizedTypesWithTypeAnnotations() {
+    JavaFileObject annotFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Annot",
+            "package foo.bar;",
+            "",
+            "import java.lang.annotation.ElementType;",
+            "import java.lang.annotation.Target;",
+            "",
+            "@Target(ElementType.TYPE_USE)",
+            "public @interface Annot {",
+            "  int value();",
+            "}");
+    JavaFileObject outerFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.baz.OuterWithTypeParam",
+            "package foo.baz;",
+            "",
+            "public class OuterWithTypeParam<T extends Number> {",
+            "  public class InnerWithTypeParam<U> {}",
+            "}");
+    JavaFileObject nestyFileObject =
+        JavaFileObjects.forSourceLines(
+            "com.example.Nesty",
+            "package com.example;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import foo.bar.Annot;",
+            "import foo.baz.OuterWithTypeParam;",
+            "",
+            "@AutoValue",
+            "abstract class Nesty {",
+            "  abstract @Annot(1) OuterWithTypeParam<@Annot(2) Double>",
+            "      .@Annot(3) InnerWithTypeParam<@Annot(4) String> inner();",
+            "",
+            "  static Nesty of(",
+            "      @Annot(1) OuterWithTypeParam<@Annot(2) Double>",
+            "          .@Annot(3) InnerWithTypeParam<@Annot(4) String> inner) {",
+            "    return new AutoValue_Nesty(inner);",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "com.example.AutoValue_Nesty",
+            "package com.example;",
+            "",
+            "import foo.bar.Annot;",
+            "import foo.baz.OuterWithTypeParam;",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")",
+            "final class AutoValue_Nesty extends Nesty {",
+            "  private final @Annot(1) OuterWithTypeParam<@Annot(2) Double>"
+                + ".@Annot(3) InnerWithTypeParam<@Annot(4) String> inner;",
+            "",
+            "  AutoValue_Nesty(",
+            "      @Annot(1) OuterWithTypeParam<@Annot(2) Double>"
+                + ".@Annot(3) InnerWithTypeParam<@Annot(4) String> inner) {",
+            "    if (inner == null) {",
+            "      throw new NullPointerException(\"Null inner\");",
+            "    }",
+            "    this.inner = inner;",
+            "  }",
+            "",
+            "  @Override",
+            "  @Annot(1) OuterWithTypeParam<@Annot(2) Double>"
+                + ".@Annot(3) InnerWithTypeParam<@Annot(4) String> inner() {",
+            "    return inner;",
+            "  }",
+            "",
+            "  @Override",
+            "  public String toString() {",
+            "    return \"Nesty{\"",
+            "        + \"inner=\" + inner",
+            "        + \"}\";",
+            "  }",
+            "",
+            "  @Override",
+            "  public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof Nesty) {",
+            "      Nesty that = (Nesty) o;",
+            "      return this.inner.equals(that.inner());",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+
+            "  @Override",
+            "  public int hashCode() {",
+            "    int h$ = 1;",
+            "    h$ *= 1000003;",
+            "    h$ ^= inner.hashCode();",
+            "    return h$;",
+            "  }",
+            "}");
+
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(annotFileObject, outerFileObject, nestyFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("com.example.AutoValue_Nesty")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  // Tests that type annotations are correctly copied from the bounds of type parameters in the
+  // @AutoValue class to the bounds of the corresponding parameters in the generated class. For
+  // example, if we have `@AutoValue abstract class Foo<T extends @NullableType Object>`, then the
+  // generated class should be `class AutoValue_Foo<T extends @NullableType Object> extends Foo<T>`.
+  // Some buggy versions of javac do not report type annotations correctly in this context.
+  // AutoValue can't copy them if it can't see them, so we make a special annotation processor to
+  // detect if we are in the presence of this bug and if so we don't fail.
+  @Test
+  public void testTypeParametersWithAnnotationsOnBounds() {
+    @SupportedAnnotationTypes("*")
+    class CompilerBugProcessor extends AbstractProcessor {
+      boolean checkedAnnotationsOnTypeBounds;
+      boolean reportsAnnotationsOnTypeBounds;
+
+      @Override
+      public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.latestSupported();
+      }
+
+      @Override
+      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        if (roundEnv.processingOver()) {
+          TypeElement test = processingEnv.getElementUtils().getTypeElement("com.example.Test");
+          TypeParameterElement t = test.getTypeParameters().get(0);
+          this.checkedAnnotationsOnTypeBounds = true;
+          this.reportsAnnotationsOnTypeBounds =
+              !t.getBounds().get(0).getAnnotationMirrors().isEmpty();
+        }
+        return false;
+      }
+    }
+    CompilerBugProcessor compilerBugProcessor = new CompilerBugProcessor();
+    JavaFileObject nullableTypeFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.NullableType",
+            "package foo.bar;",
+            "",
+            "import java.lang.annotation.ElementType;",
+            "import java.lang.annotation.Target;",
+            "",
+            "@Target(ElementType.TYPE_USE)",
+            "public @interface NullableType {}");
+    JavaFileObject autoValueFileObject =
+        JavaFileObjects.forSourceLines(
+            "com.example.Test",
+            "package com.example;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import foo.bar.NullableType;",
+            "",
+            "@AutoValue",
+            "abstract class Test<T extends @NullableType Object & @NullableType Cloneable> {}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), compilerBugProcessor)
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(nullableTypeFileObject, autoValueFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilerBugProcessor.checkedAnnotationsOnTypeBounds).isTrue();
+    if (compilerBugProcessor.reportsAnnotationsOnTypeBounds) {
+      assertThat(compilation)
+          .generatedSourceFile("com.example.AutoValue_Test")
+          .contentsAsUtf8String()
+          .contains(
+              "class AutoValue_Test<T extends @NullableType Object & @NullableType Cloneable>"
+                  + " extends Test<T> {");
+    }
+  }
+
+  // In the following few tests, see AutoValueProcessor.validateMethods for why unrecognized
+  // abstract methods provoke only a warning rather than an error. Compilation will fail anyway
+  // because the generated class is not abstract and does not implement the unrecognized methods.
+
+  @Test
+  public void testAbstractVoid() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract void foo();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadWarningContaining(
+            "Abstract method is neither a property getter nor a Builder converter")
+        .inFile(javaFileObject)
+        .onLineContaining("void foo()");
+  }
+
+  @Test
+  public void testAbstractWithParams() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract int foo(int bar);",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadWarningContaining(
+            "Abstract method is neither a property getter nor a Builder converter")
+        .inFile(javaFileObject)
+        .onLineContaining("int foo(int bar)");
+  }
+
+  @Test
+  public void testPrimitiveArrayWarning() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract byte[] bytes();",
+            "  public static Baz create(byte[] bytes) {",
+            "    return new AutoValue_Baz(bytes);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .hadWarningContaining(
+            "An @AutoValue property that is a primitive array returns the original array")
+        .inFile(javaFileObject)
+        .onLineContaining("byte[] bytes()");
+  }
+
+  @Test
+  public void testPrimitiveArrayWarningFromParent() {
+    // If the array-valued property is defined by an ancestor then we shouldn't try to attach
+    // the warning to the method that defined it, but rather to the @AutoValue class itself.
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "public abstract class Baz {",
+            "  public abstract byte[] bytes();",
+            "",
+            "  @AutoValue",
+            "  public abstract static class BazChild extends Baz {",
+            "    public static BazChild create(byte[] bytes) {",
+            "      return new AutoValue_Baz_BazChild(bytes);",
+            "    }",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .hadWarningContainingMatch(
+            "An @AutoValue property that is a primitive array returns the original array"
+                + ".*foo\\.bar\\.Baz\\.bytes")
+        .inFile(javaFileObject)
+        .onLineContaining("BazChild extends Baz");
+  }
+
+  @Test
+  public void testPrimitiveArrayWarningSuppressed() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  @SuppressWarnings(\"mutable\")",
+            "  public abstract byte[] bytes();",
+            "  public static Baz create(byte[] bytes) {",
+            "    return new AutoValue_Baz(bytes);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void autoValueMustBeStatic() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "public class Baz {",
+            "  @AutoValue",
+            "  public abstract class NotStatic {",
+            "    public abstract String buh();",
+            "    public NotStatic create(String buh) {",
+            "      return new AutoValue_Baz_NotStatic(buh);",
+            "    }",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Nested @AutoValue class must be static")
+        .inFile(javaFileObject)
+        .onLineContaining("abstract class NotStatic");
+  }
+
+  @Test
+  public void autoValueMustBeNotBePrivate() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "public class Baz {",
+            "  @AutoValue",
+            "  private abstract static class Private {",
+            "    public abstract String buh();",
+            "    public Private create(String buh) {",
+            "      return new AutoValue_Baz_Private(buh);",
+            "    }",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("@AutoValue class must not be private")
+        .inFile(javaFileObject)
+        .onLineContaining("class Private");
+  }
+
+  @Test
+  public void autoValueMustBeNotBeNestedInPrivate() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "public class Baz {",
+            "  private static class Private {",
+            "    @AutoValue",
+            "    abstract static class Nested {",
+            "      public abstract String buh();",
+            "      public Nested create(String buh) {",
+            "        return new AutoValue_Baz_Private_Nested(buh);",
+            "      }",
+            "    }",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("@AutoValue class must not be nested in a private class")
+        .inFile(javaFileObject)
+        .onLineContaining("class Nested");
+  }
+
+  @Test
+  public void noMultidimensionalPrimitiveArrays() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract int[][] ints();",
+            "",
+            "  public static Baz create(int[][] ints) {",
+            "    return new AutoValue_Baz(ints);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@AutoValue class cannot define an array-valued property "
+                + "unless it is a primitive array")
+        .inFile(javaFileObject)
+        .onLineContaining("int[][] ints()");
+  }
+
+  @Test
+  public void noObjectArrays() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract String[] strings();",
+            "",
+            "  public static Baz create(String[] strings) {",
+            "    return new AutoValue_Baz(strings);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@AutoValue class cannot define an array-valued property "
+                + "unless it is a primitive array")
+        .inFile(javaFileObject)
+        .onLineContaining("String[] strings()");
+  }
+
+  @Test
+  public void annotationOnInterface() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public interface Baz {}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("AutoValue only applies to classes")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Baz");
+  }
+
+  @Test
+  public void annotationOnEnum() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public enum Baz {}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("AutoValue only applies to classes")
+        .inFile(javaFileObject)
+        .onLineContaining("enum Baz");
+  }
+
+  @Test
+  public void extendAutoValue() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Outer",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "public class Outer {",
+            "  @AutoValue",
+            "  static abstract class Parent {",
+            "    static Parent create(int randomProperty) {",
+            "      return new AutoValue_Outer_Parent(randomProperty);",
+            "    }",
+            "",
+            "    abstract int randomProperty();",
+            "  }",
+            "",
+            "  @AutoValue",
+            "  static abstract class Child extends Parent {",
+            "    static Child create(int randomProperty) {",
+            "      return new AutoValue_Outer_Child(randomProperty);",
+            "    }",
+            "",
+            "    abstract int randomProperty();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("may not extend")
+        .inFile(javaFileObject)
+        .onLineContaining("Child extends Parent");
+  }
+
+  @Test
+  public void bogusSerialVersionUID() {
+    String[] mistakes = {
+      "final long serialVersionUID = 1234L", // not static
+      "static long serialVersionUID = 1234L", // not final
+      "static final Long serialVersionUID = 1234L", // not long
+      "static final long serialVersionUID = (Long) 1234L", // not a compile-time constant
+    };
+    for (String mistake : mistakes) {
+      JavaFileObject javaFileObject =
+          JavaFileObjects.forSourceLines(
+              "foo.bar.Baz",
+              "package foo.bar;",
+              "",
+              "import com.google.auto.value.AutoValue;",
+              "",
+              "@AutoValue",
+              "public abstract class Baz implements java.io.Serializable {",
+              "  " + mistake + ";",
+              "",
+              "  public abstract int foo();",
+              "}");
+      Compilation compilation =
+          javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+      expect
+          .about(compilations())
+          .that(compilation)
+          .hadErrorContaining("serialVersionUID must be a static final long compile-time constant")
+          .inFile(javaFileObject)
+          .onLineContaining(mistake);
+    }
+  }
+
+  @Test
+  public void nonExistentSuperclass() {
+    // The main purpose of this test is to check that AutoValueProcessor doesn't crash the
+    // compiler in this case.
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Existent extends NonExistent {",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("NonExistent")
+        .inFile(javaFileObject)
+        .onLineContaining("NonExistent");
+  }
+
+  @Test
+  public void cannotImplementAnnotation() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.RetentionImpl",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import java.lang.annotation.Retention;",
+            "import java.lang.annotation.RetentionPolicy;",
+            "",
+            "@AutoValue",
+            "public abstract class RetentionImpl implements Retention {",
+            "  public static Retention create(RetentionPolicy policy) {",
+            "    return new AutoValue_RetentionImpl(policy);",
+            "  }",
+            "",
+            "  @Override public Class<? extends Retention> annotationType() {",
+            "    return Retention.class;",
+            "  }",
+            "",
+            "  @Override public boolean equals(Object o) {",
+            "    return (o instanceof Retention && value().equals((Retention) o).value());",
+            "  }",
+            "",
+            "  @Override public int hashCode() {",
+            "    return (\"value\".hashCode() * 127) ^ value().hashCode();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("may not be used to implement an annotation interface")
+        .inFile(javaFileObject)
+        .onLineContaining("RetentionImpl implements Retention");
+  }
+
+  @Test
+  public void missingPropertyType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract MissingType missingType();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("MissingType")
+        .inFile(javaFileObject)
+        .onLineContaining("MissingType");
+  }
+
+  @Test
+  public void missingGenericPropertyType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract MissingType<?> missingType();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("MissingType")
+        .inFile(javaFileObject)
+        .onLineContaining("MissingType");
+  }
+
+  @Test
+  public void missingComplexGenericPropertyType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "import java.util.Map;",
+            "import java.util.Set;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract Map<Set<?>, MissingType<?>> missingType();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("MissingType")
+        .inFile(javaFileObject)
+        .onLineContaining("MissingType");
+  }
+
+  @Test
+  public void missingSuperclassGenericParameter() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T extends MissingType<?>> {",
+            "  public abstract int foo();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("MissingType")
+        .inFile(javaFileObject)
+        .onLineContaining("MissingType");
+  }
+
+  @Test
+  public void nullablePrimitive() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  @interface Nullable {}",
+            "  public abstract @Nullable int foo();",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Primitive types cannot be @Nullable")
+        .inFile(javaFileObject)
+        .onLineContaining("@Nullable int");
+  }
+
+  @Test
+  public void correctBuilder() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.base.Optional;",
+            "import com.google.common.collect.ImmutableList;",
+            "",
+            "import java.util.ArrayList;",
+            "import java.util.List;",
+            "import javax.annotation.Nullable;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T extends Number> {",
+            "  public abstract int anInt();",
+            "  @SuppressWarnings(\"mutable\")",
+            "  public abstract byte[] aByteArray();",
+            "  @SuppressWarnings(\"mutable\")",
+            "  @Nullable public abstract int[] aNullableIntArray();",
+            "  public abstract List<T> aList();",
+            "  public abstract ImmutableList<T> anImmutableList();",
+            "  public abstract Optional<String> anOptionalString();",
+            "  public abstract NestedAutoValue<T> aNestedAutoValue();",
+            "",
+            "  public abstract Builder<T> toBuilder();",
+            "",
+            "  @AutoValue.Builder",
+            "  public abstract static class Builder<T extends Number> {",
+            "    public abstract Builder<T> anInt(int x);",
+            "    public abstract Builder<T> aByteArray(byte[] x);",
+            "    public abstract Builder<T> aNullableIntArray(@Nullable int[] x);",
+            "    public abstract Builder<T> aList(List<T> x);",
+            "    public abstract Builder<T> anImmutableList(List<T> x);",
+            "    public abstract ImmutableList.Builder<T> anImmutableListBuilder();",
+            "    public abstract Builder<T> anOptionalString(Optional<String> s);",
+            "    public abstract Builder<T> anOptionalString(String s);",
+            "    public abstract NestedAutoValue.Builder<T> aNestedAutoValueBuilder();",
+            "",
+            "    public Builder<T> aList(ArrayList<T> x) {",
+            // ArrayList should not be imported in the generated class.
+            "      return aList((List<T>) x);",
+            "    }",
+            "",
+            "    public abstract Optional<Integer> anInt();",
+            "    public abstract List<T> aList();",
+            "    public abstract ImmutableList<T> anImmutableList();",
+            "",
+            "    public abstract Baz<T> build();",
+            "  }",
+            "",
+            "  public static <T extends Number> Builder<T> builder() {",
+            "    return AutoValue_Baz.builder();",
+            "  }",
+            "}");
+    JavaFileObject nestedJavaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.NestedAutoValue",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class NestedAutoValue<T extends Number> {",
+            "  public abstract T t();",
+            "",
+            "  public abstract Builder<T> toBuilder();",
+            "",
+            "  @AutoValue.Builder",
+            "  public abstract static class Builder<T extends Number> {",
+            "    public abstract Builder<T> t(T t);",
+            "    public abstract NestedAutoValue<T> build();",
+            "  }",
+            "",
+            "  public static <T extends Number> Builder<T> builder() {",
+            "    return AutoValue_NestedAutoValue.builder();",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoValue_Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.common.base.Optional;",
+            "import com.google.common.collect.ImmutableList;",
+            "import java.util.Arrays;",
+            "import java.util.List;",
+            sorted(
+                GeneratedImport.importGeneratedAnnotationType(),
+                "import javax.annotation.Nullable;"),
+            "",
+            "@Generated(\"" + AutoValueProcessor.class.getName() + "\")",
+            "final class AutoValue_Baz<T extends Number> extends Baz<T> {",
+            "  private final int anInt;",
+            "  private final byte[] aByteArray;",
+            "  private final int[] aNullableIntArray;",
+            "  private final List<T> aList;",
+            "  private final ImmutableList<T> anImmutableList;",
+            "  private final Optional<String> anOptionalString;",
+            "  private final NestedAutoValue<T> aNestedAutoValue;",
+            "",
+            "  private AutoValue_Baz(",
+            "      int anInt,",
+            "      byte[] aByteArray,",
+            "      @Nullable int[] aNullableIntArray,",
+            "      List<T> aList,",
+            "      ImmutableList<T> anImmutableList,",
+            "      Optional<String> anOptionalString,",
+            "      NestedAutoValue<T> aNestedAutoValue) {",
+            "    this.anInt = anInt;",
+            "    this.aByteArray = aByteArray;",
+            "    this.aNullableIntArray = aNullableIntArray;",
+            "    this.aList = aList;",
+            "    this.anImmutableList = anImmutableList;",
+            "    this.anOptionalString = anOptionalString;",
+            "    this.aNestedAutoValue = aNestedAutoValue;",
+            "  }",
+            "",
+            "  @Override public int anInt() {",
+            "    return anInt;",
+            "  }",
+            "",
+            "  @SuppressWarnings(\"mutable\")",
+            "  @Override public byte[] aByteArray() {",
+            "    return aByteArray;",
+            "  }",
+            "",
+            "  @SuppressWarnings(\"mutable\")",
+            "  @Nullable",
+            "  @Override public int[] aNullableIntArray() {",
+            "    return aNullableIntArray;",
+            "  }",
+            "",
+            "  @Override public List<T> aList() {",
+            "    return aList;",
+            "  }",
+            "",
+            "  @Override public ImmutableList<T> anImmutableList() {",
+            "    return anImmutableList;",
+            "  }",
+            "",
+            "  @Override public Optional<String> anOptionalString() {",
+            "    return anOptionalString;",
+            "  }",
+            "",
+            "  @Override public NestedAutoValue<T> aNestedAutoValue() {",
+            "    return aNestedAutoValue;",
+            "  }",
+            "",
+            "  @Override public String toString() {",
+            "    return \"Baz{\"",
+            "        + \"anInt=\" + anInt + \", \"",
+            "        + \"aByteArray=\" + Arrays.toString(aByteArray) + \", \"",
+            "        + \"aNullableIntArray=\" + Arrays.toString(aNullableIntArray) + \", \"",
+            "        + \"aList=\" + aList + \", \"",
+            "        + \"anImmutableList=\" + anImmutableList + \", \"",
+            "        + \"anOptionalString=\" + anOptionalString + \", \"",
+            "        + \"aNestedAutoValue=\" + aNestedAutoValue",
+            "        + \"}\";",
+            "  }",
+            "",
+            "  @Override public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof Baz) {",
+            "      Baz<?> that = (Baz<?>) o;",
+            "      return this.anInt == that.anInt()",
+            "          && Arrays.equals(this.aByteArray, "
+                + "(that instanceof AutoValue_Baz) "
+                + "? ((AutoValue_Baz) that).aByteArray : that.aByteArray())",
+            "          && Arrays.equals(this.aNullableIntArray, "
+                + "(that instanceof AutoValue_Baz) "
+                + "? ((AutoValue_Baz) that).aNullableIntArray : that.aNullableIntArray())",
+            "          && this.aList.equals(that.aList())",
+            "          && this.anImmutableList.equals(that.anImmutableList())",
+            "          && this.anOptionalString.equals(that.anOptionalString())",
+            "          && this.aNestedAutoValue.equals(that.aNestedAutoValue());",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+            "  @Override public int hashCode() {",
+            "    int h$ = 1;",
+            "    h$ *= 1000003;",
+            "    h$ ^= anInt;",
+            "    h$ *= 1000003;",
+            "    h$ ^= Arrays.hashCode(aByteArray);",
+            "    h$ *= 1000003;",
+            "    h$ ^= Arrays.hashCode(aNullableIntArray);",
+            "    h$ *= 1000003;",
+            "    h$ ^= aList.hashCode();",
+            "    h$ *= 1000003;",
+            "    h$ ^= anImmutableList.hashCode();",
+            "    h$ *= 1000003;",
+            "    h$ ^= anOptionalString.hashCode();",
+            "    h$ *= 1000003;",
+            "    h$ ^= aNestedAutoValue.hashCode();",
+            "    return h$;",
+            "  }",
+            "",
+            "  @Override public Baz.Builder<T> toBuilder() {",
+            "    return new Builder<T>(this);",
+            "  }",
+            "",
+            "  static final class Builder<T extends Number> extends Baz.Builder<T> {",
+            "    private Integer anInt;",
+            "    private byte[] aByteArray;",
+            "    private int[] aNullableIntArray;",
+            "    private List<T> aList;",
+            "    private ImmutableList.Builder<T> anImmutableListBuilder$;",
+            "    private ImmutableList<T> anImmutableList;",
+            "    private Optional<String> anOptionalString = Optional.absent();",
+            "    private NestedAutoValue.Builder<T> aNestedAutoValueBuilder$;",
+            "    private NestedAutoValue<T> aNestedAutoValue;",
+            "",
+            "    Builder() {",
+            "    }",
+            "",
+            "    private Builder(Baz<T> source) {",
+            "      this.anInt = source.anInt();",
+            "      this.aByteArray = source.aByteArray();",
+            "      this.aNullableIntArray = source.aNullableIntArray();",
+            "      this.aList = source.aList();",
+            "      this.anImmutableList = source.anImmutableList();",
+            "      this.anOptionalString = source.anOptionalString();",
+            "      this.aNestedAutoValue = source.aNestedAutoValue();",
+            "    }",
+            "",
+            "    @Override",
+            "    public Baz.Builder<T> anInt(int anInt) {",
+            "      this.anInt = anInt;",
+            "      return this;",
+            "    }",
+            "",
+            "    @Override",
+            "    public Optional<Integer> anInt() {",
+            "      if (anInt == null) {",
+            "        return Optional.absent();",
+            "      } else {",
+            "        return Optional.of(anInt);",
+            "      }",
+            "    }",
+            "",
+            "    @Override",
+            "    public Baz.Builder<T> aByteArray(byte[] aByteArray) {",
+            "      if (aByteArray == null) {",
+            "        throw new NullPointerException(\"Null aByteArray\");",
+            "      }",
+            "      this.aByteArray = aByteArray;",
+            "      return this;",
+            "    }",
+            "",
+            "    @Override",
+            "    public Baz.Builder<T> aNullableIntArray(@Nullable int[] aNullableIntArray) {",
+            "      this.aNullableIntArray = aNullableIntArray;",
+            "      return this;",
+            "    }",
+            "",
+            "    @Override",
+            "    public Baz.Builder<T> aList(List<T> aList) {",
+            "      if (aList == null) {",
+            "        throw new NullPointerException(\"Null aList\");",
+            "      }",
+            "      this.aList = aList;",
+            "      return this;",
+            "    }",
+            "",
+            "    @Override",
+            "    public List<T> aList() {",
+            "      if (aList == null) {",
+            "        throw new IllegalStateException(\"Property \\\"aList\\\" has not been set\");",
+            "      }",
+            "      return aList;",
+            "    }",
+            "",
+            "    @Override",
+            "    public Baz.Builder<T> anImmutableList(List<T> anImmutableList) {",
+            "      if (anImmutableListBuilder$ != null) {",
+            "        throw new IllegalStateException("
+                + "\"Cannot set anImmutableList after calling anImmutableListBuilder()\");",
+            "      }",
+            "      this.anImmutableList = ImmutableList.copyOf(anImmutableList);",
+            "      return this;",
+            "    }",
+            "",
+            "    @Override",
+            "    public ImmutableList.Builder<T> anImmutableListBuilder() {",
+            "      if (anImmutableListBuilder$ == null) {",
+            "        if (anImmutableList == null) {",
+            "          anImmutableListBuilder$ = ImmutableList.builder();",
+            "        } else {",
+            "          anImmutableListBuilder$ = ImmutableList.builder();",
+            "          anImmutableListBuilder$.addAll(anImmutableList);",
+            "          anImmutableList = null;",
+            "        }",
+            "      }",
+            "      return anImmutableListBuilder$;",
+            "    }",
+            "",
+            "    @Override",
+            "    public ImmutableList<T> anImmutableList() {",
+            "      if (anImmutableListBuilder$ != null) {",
+            "        return anImmutableListBuilder$.build();",
+            "      }",
+            "      if (anImmutableList == null) {",
+            "        anImmutableList = ImmutableList.of();",
+            "      }",
+            "      return anImmutableList;",
+            "    }",
+            "",
+            "    @Override",
+            "    public Baz.Builder<T> anOptionalString(Optional<String> anOptionalString) {",
+            "      if (anOptionalString == null) {",
+            "        throw new NullPointerException(\"Null anOptionalString\");",
+            "      }",
+            "      this.anOptionalString = anOptionalString;",
+            "      return this;",
+            "    }",
+            "",
+            "    @Override",
+            "    public Baz.Builder<T> anOptionalString(String anOptionalString) {",
+            "      this.anOptionalString = Optional.of(anOptionalString);",
+            "      return this;",
+            "    }",
+            "",
+            "    @Override",
+            "    public NestedAutoValue.Builder<T> aNestedAutoValueBuilder() {",
+            "      if (aNestedAutoValueBuilder$ == null) {",
+            "        if (aNestedAutoValue == null) {",
+            "          aNestedAutoValueBuilder$ = NestedAutoValue.builder();",
+            "        } else {",
+            "          aNestedAutoValueBuilder$ = aNestedAutoValue.toBuilder();",
+            "          aNestedAutoValue = null;",
+            "        }",
+            "      }",
+            "      return aNestedAutoValueBuilder$;",
+            "    }",
+            "",
+            "    @Override",
+            "    public Baz<T> build() {",
+            "      if (anImmutableListBuilder$ != null) {",
+            "        this.anImmutableList = anImmutableListBuilder$.build();",
+            "      } else if (this.anImmutableList == null) {",
+            "        this.anImmutableList = ImmutableList.of();",
+            "      }",
+            "      if (aNestedAutoValueBuilder$ != null) {",
+            "        this.aNestedAutoValue = aNestedAutoValueBuilder$.build();",
+            "      } else if (this.aNestedAutoValue == null) {",
+            "        NestedAutoValue.Builder<T> aNestedAutoValue$builder = "
+                + "NestedAutoValue.builder();",
+            "        this.aNestedAutoValue = aNestedAutoValue$builder.build();",
+            "      }",
+            "      String missing = \"\";",
+            "      if (this.anInt == null) {",
+            "        missing += \" anInt\";",
+            "      }",
+            "      if (this.aByteArray == null) {",
+            "        missing += \" aByteArray\";",
+            "      }",
+            "      if (this.aList == null) {",
+            "        missing += \" aList\";",
+            "      }",
+            "      if (!missing.isEmpty()) {",
+            "        throw new IllegalStateException(\"Missing required properties:\" + missing);",
+            "      }",
+            "      return new AutoValue_Baz<T>(",
+            "          this.anInt,",
+            "          this.aByteArray,",
+            "          this.aNullableIntArray,",
+            "          this.aList,",
+            "          this.anImmutableList,",
+            "          this.anOptionalString,",
+            "          this.aNestedAutoValue);",
+            "    }",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(javaFileObject, nestedJavaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .hasSourceEquivalentTo(expectedOutput);
+  }
+
+  @Test
+  public void autoValueBuilderOnTopLevelClass() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Builder",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue.Builder",
+            "public interface Builder {",
+            "  Builder foo(int x);",
+            "  Object build();",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("can only be applied to a class or interface inside")
+        .inFile(javaFileObject)
+        .onLineContaining("public interface Builder");
+  }
+
+  @Test
+  public void autoValueBuilderNotInsideAutoValue() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "public abstract class Baz {",
+            "  abstract int foo();",
+            "",
+            "  static Builder builder() {",
+            "    return new AutoValue_Baz.Builder();",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder foo(int x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("can only be applied to a class or interface inside")
+        .inFile(javaFileObject)
+        .onLineContaining("public interface Builder");
+  }
+
+  @Test
+  public void autoValueBuilderNotStatic() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Example",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "class Example {",
+            "  @AutoValue",
+            "  abstract static class Baz {",
+            "    abstract int foo();",
+            "",
+            "    static Builder builder() {",
+            "      return new AutoValue_Example_Baz.Builder();",
+            "    }",
+            "",
+            "    @AutoValue.Builder",
+            "    abstract class Builder {",
+            "      abstract Builder foo(int x);",
+            "      abstract Baz build();",
+            "    }",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("@AutoValue.Builder cannot be applied to a non-static class")
+        .inFile(javaFileObject)
+        .onLineContaining("abstract class Builder");
+  }
+
+  @Test
+  public void autoValueBuilderOnEnum() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract int foo();",
+            "",
+            "  static Builder builder() {",
+            "    return null;",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public enum Builder {}",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("can only apply to a class or an interface")
+        .inFile(javaFileObject)
+        .onLineContaining("public enum Builder");
+  }
+
+  @Test
+  public void autoValueBuilderDuplicate() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  @AutoValue.Builder",
+            "  public interface Builder1 {",
+            "    Baz build();",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder2 {",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("already has a Builder: foo.bar.Baz.Builder1")
+        .inFile(javaFileObject)
+        .onLineContaining("public interface Builder2");
+  }
+
+  @Test
+  public void autoValueBuilderMissingSetter() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract int blim();",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blam(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("with this signature: foo.bar.Baz.Builder blim(int)")
+        .inFile(javaFileObject)
+        .onLineContaining("public interface Builder");
+  }
+
+  @Test
+  public void autoValueBuilderMissingSetterUsingSetPrefix() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract int blim();",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder setBlam(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("with this signature: foo.bar.Baz.Builder setBlim(int)")
+        .inFile(javaFileObject)
+        .onLineContaining("public interface Builder");
+  }
+
+  @Test
+  public void autoValueBuilderWrongTypeSetter() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract int blim();",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blim(String x);",
+            "    Builder blam(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Parameter type java.lang.String of setter method should be int "
+                + "to match getter foo.bar.Baz.blim")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder blim(String x)");
+  }
+
+  @Test
+  public void autoValueBuilderWrongTypeSetterWithCopyOf() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableList;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String blim();",
+            "  abstract ImmutableList<String> blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blim(String x);",
+            "    Builder blam(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Parameter type java.lang.String of setter method should be "
+                + "com.google.common.collect.ImmutableList<java.lang.String> to match getter "
+                + "foo.bar.Baz.blam, or it should be a type that can be passed to "
+                + "ImmutableList.copyOf")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder blam(String x)");
+  }
+
+  @Test
+  public void autoValueBuilderWrongTypeSetterWithCopyOfGenericallyWrong() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableList;",
+            "import java.util.Collection;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String blim();",
+            "  abstract ImmutableList<String> blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blim(String x);",
+            "    Builder blam(Collection<Integer> x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Parameter type java.util.Collection<java.lang.Integer> of setter method should be "
+                + "com.google.common.collect.ImmutableList<java.lang.String> to match getter "
+                + "foo.bar.Baz.blam, or it should be a type that can be passed to "
+                + "ImmutableList.copyOf to produce "
+                + "com.google.common.collect.ImmutableList<java.lang.String>")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder blam(Collection<Integer> x)");
+  }
+
+  @Test
+  public void autoValueBuilderWrongTypeSetterWithGetPrefix() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract int getBlim();",
+            "  abstract String getBlam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blim(String x);",
+            "    Builder blam(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Parameter type java.lang.String of setter method should be int "
+                + "to match getter foo.bar.Baz.getBlim")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder blim(String x)");
+  }
+
+  @Test
+  public void autoValueBuilderNullableSetterForNonNullable() {
+    JavaFileObject nullableFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Nullable",
+            "package foo.bar;",
+            "",
+            "import java.lang.annotation.ElementType;",
+            "import java.lang.annotation.Target;",
+            "",
+            "@Target(ElementType.TYPE_USE)",
+            "public @interface Nullable {}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String notNull();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder setNotNull(@Nullable String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject, nullableFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Parameter of setter method is @Nullable but property method"
+                + " foo.bar.Baz.notNull() is not")
+        .inFile(javaFileObject)
+        .onLineContaining("setNotNull");
+  }
+
+  // Check that we get a helpful error message if some of your properties look like getters but
+  // others don't.
+  @Test
+  public void autoValueBuilderBeansConfusion() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Item",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Item {",
+            "  abstract String getTitle();",
+            "  abstract boolean hasThumbnail();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder setTitle(String title);",
+            "    Builder setHasThumbnail(boolean t);",
+            "    Item build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Method does not correspond to a property of foo.bar.Item")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder setTitle(String title)");
+    assertThat(compilation)
+        .hadNoteContaining("hasThumbnail")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder setTitle(String title)");
+  }
+
+  @Test
+  public void autoValueBuilderExtraSetter() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blim(int x);",
+            "    Builder blam(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Method does not correspond to a property of foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder blim(int x)");
+  }
+
+  @Test
+  public void autoValueBuilderSetPrefixAndNoSetPrefix() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract int blim();",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blim(int x);",
+            "    Builder setBlam(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("If any setter methods use the setFoo convention then all must")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder blim(int x)");
+  }
+
+  @Test
+  public void autoValueBuilderWrongTypeGetter() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  abstract T blim();",
+            "  abstract U blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    Builder<T, U> blim(T x);",
+            "    Builder<T, U> blam(U x);",
+            "    T blim();",
+            "    T blam();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Method matches a property of foo.bar.Baz but has return type T instead of U")
+        .inFile(javaFileObject)
+        .onLineContaining("T blam()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderInvalidType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  abstract String blim();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    StringBuilder blimBuilder();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Method looks like a property builder, but it returns java.lang.StringBuilder which "
+                + "does not have a non-static build() method")
+        .inFile(javaFileObject)
+        .onLineContaining("StringBuilder blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderNullable() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableList;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  @interface Nullable {}",
+            "  abstract @Nullable ImmutableList<String> strings();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    ImmutableList.Builder<String> stringsBuilder();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Property strings has a property builder so it cannot be @Nullable")
+        .inFile(javaFileObject)
+        .onLineContaining("@Nullable ImmutableList<String> strings()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderNullableType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableList;",
+            "import java.lang.annotation.ElementType;",
+            "import java.lang.annotation.Target;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  @Target(ElementType.TYPE_USE)",
+            "  @interface Nullable {}",
+            "  abstract @Nullable ImmutableList<String> strings();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    ImmutableList.Builder<String> stringsBuilder();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Property strings has a property builder so it cannot be @Nullable")
+        .inFile(javaFileObject)
+        .onLineContaining("@Nullable ImmutableList<String> strings()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderWrongCollectionType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableList;",
+            "import com.google.common.collect.ImmutableSet;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  abstract ImmutableList<T> blim();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    ImmutableSet.Builder<T> blimBuilder();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Property builder for blim has type com.google.common.collect.ImmutableSet.Builder "
+                + "whose build() method returns com.google.common.collect.ImmutableSet<T> "
+                + "instead of com.google.common.collect.ImmutableList<T>")
+        .inFile(javaFileObject)
+        .onLineContaining("ImmutableSet.Builder<T> blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderWeirdBuilderType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  abstract Integer blim();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    int blimBuilder();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Method looks like a property builder, but its return type is not a class or interface")
+        .inFile(javaFileObject)
+        .onLineContaining("int blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderWeirdBuiltType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  abstract int blim();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    Integer blimBuilder();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Method looks like a property builder, but the type of property blim is not a class "
+                + "or interface")
+        .inFile(javaFileObject)
+        .onLineContaining("Integer blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderHasNoBuild() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  abstract String blim();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    StringBuilder blimBuilder();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Method looks like a property builder, but it returns java.lang.StringBuilder which "
+                + "does not have a non-static build() method")
+        .inFile(javaFileObject)
+        .onLineContaining("StringBuilder blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderHasStaticBuild() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  abstract String blim();",
+            "",
+            "  public static class StringFactory {",
+            "    public static String build() {",
+            "      return null;",
+            "    }",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    StringFactory blimBuilder();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Method looks like a property builder, but it returns foo.bar.Baz.StringFactory which "
+                + "does not have a non-static build() method")
+        .inFile(javaFileObject)
+        .onLineContaining("StringFactory blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderReturnsWrongType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "import java.util.List;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<E> {",
+            "  abstract List<E> blim();",
+            "",
+            "  public static class ListFactory<E> {",
+            "    public List<? extends E> build() {",
+            "      return null;",
+            "    }",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<E> {",
+            "    ListFactory<E> blimBuilder();",
+            "    Baz<E> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Property builder for blim has type foo.bar.Baz.ListFactory whose build() method "
+                + "returns java.util.List<? extends E> instead of java.util.List<E>")
+        .inFile(javaFileObject)
+        .onLineContaining("ListFactory<E> blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderCantConstruct() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<E> {",
+            "  abstract String blim();",
+            "",
+            "  public static class StringFactory {",
+            "    private StringFactory() {}",
+            "",
+            "    public String build() {",
+            "      return null;",
+            "    }",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<E> {",
+            "    StringFactory blimBuilder();",
+            "    Baz<E> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Method looks like a property builder, but its type foo.bar.Baz.StringFactory "
+                + "does not have a public constructor and java.lang.String does not have a static "
+                + "builder() or newBuilder() method that returns foo.bar.Baz.StringFactory")
+        .inFile(javaFileObject)
+        .onLineContaining("StringFactory blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderCantReconstruct() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<E> {",
+            "  abstract String blim();",
+            "  abstract Builder<E> toBuilder();",
+            "",
+            "  public static class StringFactory {",
+            "    public String build() {",
+            "      return null;",
+            "    }",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<E> {",
+            "    StringFactory blimBuilder();",
+            "    Baz<E> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Property builder method returns foo.bar.Baz.StringFactory but there is no way to make"
+                + " that type from java.lang.String: java.lang.String does not have a non-static"
+                + " toBuilder() method that returns foo.bar.Baz.StringFactory, and"
+                + " foo.bar.Baz.StringFactory does not have a method addAll or putAll that accepts"
+                + " an argument of type java.lang.String")
+        .inFile(javaFileObject)
+        .onLineContaining("StringFactory blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderWrongTypeAddAll() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "import java.util.Iterator;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T> {",
+            "  abstract ImmutableSet<String> strings();",
+            "  abstract Builder<T> toBuilder();",
+            "",
+            "  public static class ImmutableSetBuilder<E> {",
+            "    public void addAll(Iterator<? extends E> elements) {}",
+            "",
+            "    public ImmutableSet<E> build() {",
+            "      return null;",
+            "    }",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T> {",
+            "    ImmutableSetBuilder<String> stringsBuilder();",
+            "    Baz<T> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Property builder method returns foo.bar.Baz.ImmutableSetBuilder<java.lang.String> but"
+                + " there is no way to make that type from"
+                + " com.google.common.collect.ImmutableSet<java.lang.String>:"
+                + " com.google.common.collect.ImmutableSet<java.lang.String> does not have a"
+                + " non-static toBuilder() method that returns"
+                + " foo.bar.Baz.ImmutableSetBuilder<java.lang.String>, and"
+                + " foo.bar.Baz.ImmutableSetBuilder<java.lang.String> does not have a method"
+                + " addAll or putAll that accepts an argument of type"
+                + " com.google.common.collect.ImmutableSet<java.lang.String>")
+        .inFile(javaFileObject)
+        .onLineContaining("ImmutableSetBuilder<String> stringsBuilder();");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderCantSet() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<E> {",
+            "  abstract String blim();",
+            "",
+            "  public static class StringFactory {",
+            "    public String build() {",
+            "      return null;",
+            "    }",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<E> {",
+            "    Builder<E> setBlim(String s);",
+            "    StringFactory blimBuilder();",
+            "    Baz<E> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Property builder method returns foo.bar.Baz.StringFactory but there is no way to make "
+                + "that type from java.lang.String: java.lang.String does not have a non-static "
+                + "toBuilder() method that returns foo.bar.Baz.StringFactory")
+        .inFile(javaFileObject)
+        .onLineContaining("StringFactory blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderWrongTypeToBuilder() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<E> {",
+            "  abstract Buh blim();",
+            "  abstract Builder<E> toBuilder();",
+            "",
+            "  public static class Buh {",
+            "    StringBuilder toBuilder() {",
+            "      return null;",
+            "    }",
+            "  }",
+            "",
+            "  public static class BuhBuilder {",
+            "    public Buh build() {",
+            "      return null;",
+            "    }",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<E> {",
+            "    BuhBuilder blimBuilder();",
+            "    Baz<E> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Property builder method returns foo.bar.Baz.BuhBuilder but there is no way to make "
+                + "that type from foo.bar.Baz.Buh: foo.bar.Baz.Buh does not have a non-static "
+                + "toBuilder() method that returns foo.bar.Baz.BuhBuilder")
+        .inFile(javaFileObject)
+        .onLineContaining("BuhBuilder blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderPropertyBuilderWrongElementType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableSet;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T, U> {",
+            "  abstract ImmutableSet<T> blim();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T, U> {",
+            "    ImmutableSet.Builder<U> blimBuilder();",
+            "    Baz<T, U> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Property builder for blim has type com.google.common.collect.ImmutableSet.Builder "
+                + "whose build() method returns com.google.common.collect.ImmutableSet<U> "
+                + "instead of com.google.common.collect.ImmutableSet<T>")
+        .inFile(javaFileObject)
+        .onLineContaining("ImmutableSet.Builder<U> blimBuilder()");
+  }
+
+  @Test
+  public void autoValueBuilderAlienMethod0() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blam(String x);",
+            "    Builder whut();",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Method without arguments should be a build method returning foo.bar.Baz, or a getter"
+                + " method with the same name and type as a getter method of foo.bar.Baz, or"
+                + " fooBuilder() where foo() or getFoo() is a getter method of foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder whut()");
+  }
+
+  @Test
+  public void autoValueBuilderAlienMethod1() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    void whut(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Method does not correspond to a property of foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("void whut(String x)");
+  }
+
+  @Test
+  public void autoValueBuilderAlienMethod2() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blam(String x, String y);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Builder methods must have 0 or 1 parameters")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder blam(String x, String y)");
+  }
+
+  @Test
+  public void autoValueBuilderMissingBuildMethod() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T> {",
+            "  abstract T blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T> {",
+            "    Builder<T> blam(T x);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Builder must have a single no-argument method returning foo.bar.Baz<T>")
+        .inFile(javaFileObject)
+        .onLineContaining("public interface Builder<T>");
+  }
+
+  @Test
+  public void autoValueBuilderDuplicateBuildMethods() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blam(String x);",
+            "    Baz build();",
+            "    Baz create();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("Baz build()");
+    assertThat(compilation)
+        .hadErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("Baz create()");
+  }
+
+  @Test
+  public void autoValueBuilderWrongTypeBuildMethod() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blam(String x);",
+            "    String build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Method without arguments should be a build method returning foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("String build()");
+  }
+
+  @Test
+  public void autoValueBuilderTypeParametersDontMatch1() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T> {",
+            "  abstract String blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder blam(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Type parameters of foo.bar.Baz.Builder must have same names and "
+                + "bounds as type parameters of foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("public interface Builder");
+  }
+
+  @Test
+  public void autoValueBuilderTypeParametersDontMatch2() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T> {",
+            "  abstract T blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<E> {",
+            "    Builder<E> blam(E x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Type parameters of foo.bar.Baz.Builder must have same names and "
+                + "bounds as type parameters of foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("public interface Builder<E>");
+  }
+
+  @Test
+  public void autoValueBuilderTypeParametersDontMatch3() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz<T extends Number & Comparable<T>> {",
+            "  abstract T blam();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder<T extends Number> {",
+            "    Builder<T> blam(T x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Type parameters of foo.bar.Baz.Builder must have same names and "
+                + "bounds as type parameters of foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("public interface Builder<T extends Number>");
+  }
+
+  @Test
+  public void autoValueBuilderToBuilderWrongTypeParameters() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "abstract class Baz<K extends Comparable<K>, V> {",
+            "  abstract K key();",
+            "  abstract V value();",
+            "  abstract Builder<V, K> toBuilder1();",
+            "",
+            "  @AutoValue.Builder",
+            "  interface Builder<K extends Comparable<K>, V> {",
+            "    Builder<K, V> key(K key);",
+            "    Builder<K, V> value(V value);",
+            "    Baz<K, V> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Builder converter method should return foo.bar.Baz.Builder<K, V>")
+        .inFile(javaFileObject)
+        .onLineContaining("abstract Builder<V, K> toBuilder1()");
+  }
+
+  @Test
+  public void autoValueBuilderToBuilderDuplicate() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "abstract class Baz<K extends Comparable<K>, V> {",
+            "  abstract K key();",
+            "  abstract V value();",
+            "  abstract Builder<K, V> toBuilder1();",
+            "  abstract Builder<K, V> toBuilder2();",
+            "",
+            "  @AutoValue.Builder",
+            "  interface Builder<K extends Comparable<K>, V> {",
+            "    Builder<K, V> key(K key);",
+            "    Builder<K, V> value(V value);",
+            "    Baz<K, V> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("There can be at most one builder converter method")
+        .inFile(javaFileObject)
+        .onLineContaining("abstract Builder<K, V> toBuilder1()");
+  }
+
+  @Test
+  public void getFooIsFoo() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract int getFoo();",
+            "  abstract boolean isFoo();",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("More than one @AutoValue property called foo")
+        .inFile(javaFileObject)
+        .onLineContaining("getFoo");
+    assertThat(compilation)
+        .hadErrorContaining("More than one @AutoValue property called foo")
+        .inFile(javaFileObject)
+        .onLineContaining("isFoo");
+  }
+
+  @Retention(RetentionPolicy.SOURCE)
+  public @interface Foo {}
+
+  /* Processor that generates an empty class BarFoo every time it sees a class Bar annotated with
+   * @Foo.
+   */
+  public static class FooProcessor extends AbstractProcessor {
+    @Override
+    public Set<String> getSupportedAnnotationTypes() {
+      return ImmutableSet.of(Foo.class.getCanonicalName());
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Foo.class);
+      for (TypeElement type : ElementFilter.typesIn(elements)) {
+        try {
+          generateFoo(type);
+        } catch (IOException e) {
+          throw new AssertionError(e);
+        }
+      }
+      return false;
+    }
+
+    private void generateFoo(TypeElement type) throws IOException {
+      String pkg = TypeSimplifier.packageNameOf(type);
+      String className = type.getSimpleName().toString();
+      String generatedClassName = className + "Foo";
+      JavaFileObject source =
+          processingEnv.getFiler().createSourceFile(pkg + "." + generatedClassName, type);
+      PrintWriter writer = new PrintWriter(source.openWriter());
+      writer.println("package " + pkg + ";");
+      writer.println("public class " + generatedClassName + " {}");
+      writer.close();
+    }
+  }
+
+  @Test
+  public void referencingGeneratedClass() {
+    // Test that ensures that a type that does not exist can be the type of an @AutoValue property
+    // as long as it later does come into existence. The BarFoo type referenced here does not exist
+    // when the AutoValueProcessor runs on the first round, but the FooProcessor then generates it.
+    // That generation provokes a further round of annotation processing and AutoValueProcessor
+    // should succeed then.
+    JavaFileObject bazFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract BarFoo barFoo();",
+            "",
+            "  public static Baz create(BarFoo barFoo) {",
+            "    return new AutoValue_Baz(barFoo);",
+            "  }",
+            "}");
+    JavaFileObject barFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Bar",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@" + Foo.class.getCanonicalName(),
+            "public abstract class Bar {",
+            "  public abstract BarFoo barFoo();",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new FooProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(bazFileObject, barFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void annotationReferencesUndefined() {
+    // Test that we don't throw an exception if asked to compile @SuppressWarnings(UNDEFINED)
+    // where UNDEFINED is an undefined symbol.
+    JavaFileObject bazFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  @SuppressWarnings(UNDEFINED)",
+            "  public abstract int[] buh();",
+            "}");
+    Compilation compilation1 =
+        javac()
+            .withOptions("-Xlint:-processing")
+            .withProcessors(new AutoValueProcessor())
+            .compile(bazFileObject);
+    assertThat(compilation1).hadErrorCount(1);
+    assertThat(compilation1)
+        .hadErrorContaining("UNDEFINED")
+        .inFile(bazFileObject)
+        .onLineContaining("UNDEFINED");
+    assertThat(compilation1).hadWarningCount(1);
+    assertThat(compilation1)
+        .hadWarningContaining("mutable")
+        .inFile(bazFileObject)
+        .onLineContaining("public abstract int[] buh()");
+
+    // Same test, except we do successfully suppress the warning despite the UNDEFINED.
+    bazFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  @SuppressWarnings({UNDEFINED, \"mutable\"})",
+            "  public abstract int[] buh();",
+            "}");
+    Compilation compilation2 =
+        javac()
+            .withOptions("-Xlint:-processing")
+            .withProcessors(new AutoValueProcessor())
+            .compile(bazFileObject);
+    assertThat(compilation2).hadErrorCount(1);
+    assertThat(compilation2)
+        .hadErrorContaining("UNDEFINED")
+        .inFile(bazFileObject)
+        .onLineContaining("UNDEFINED");
+    assertThat(compilation2).hadWarningCount(0);
+  }
+
+  @Test
+  public void packagePrivateAnnotationFromOtherPackage() {
+    JavaFileObject bazFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz extends otherpackage.Parent {",
+            "}");
+    JavaFileObject parentFileObject =
+        JavaFileObjects.forSourceLines(
+            "otherpackage.Parent",
+            "package otherpackage;",
+            "",
+            "public abstract class Parent {",
+            "  @PackageAnnotation",
+            "  public abstract String foo();",
+            "",
+            "  @interface PackageAnnotation {}",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(bazFileObject, parentFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation).generatedSourceFile("foo.bar.AutoValue_Baz");
+  }
+
+  @Test
+  public void visibleProtectedAnnotationFromOtherPackage() {
+    JavaFileObject bazFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz extends otherpackage.Parent {}");
+    JavaFileObject parentFileObject =
+        JavaFileObjects.forSourceLines(
+            "otherpackage.Parent",
+            "package otherpackage;",
+            "",
+            "public abstract class Parent {",
+            "  @ProtectedAnnotation",
+            "  public abstract String foo();",
+            "",
+            "  protected @interface ProtectedAnnotation {}",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(bazFileObject, parentFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .contentsAsUtf8String()
+        .containsMatch("(?s:@Parent.ProtectedAnnotation\\s*@Override\\s*public String foo\\(\\))");
+  }
+
+  @Test
+  public void nonVisibleProtectedAnnotationFromOtherPackage() {
+    JavaFileObject bazFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz extends otherpackage.Parent {",
+            "}");
+    JavaFileObject parentFileObject =
+        JavaFileObjects.forSourceLines(
+            "otherpackage.Parent",
+            "package otherpackage;",
+            "",
+            "import otherpackage.Annotations.ProtectedAnnotation;",
+            "",
+            "public abstract class Parent {",
+            "  @ProtectedAnnotation",
+            "  public abstract String foo();",
+            "}");
+    JavaFileObject annotationsFileObject =
+        JavaFileObjects.forSourceLines(
+            "otherpackage.Annotations",
+            "package otherpackage;",
+            "",
+            "public class Annotations {",
+            "  protected @interface ProtectedAnnotation {}",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(bazFileObject, parentFileObject, annotationsFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .contentsAsUtf8String()
+        .doesNotContain("ProtectedAnnotation");
+  }
+
+  @Test
+  public void nonVisibleProtectedClassAnnotationFromOtherPackage() {
+    JavaFileObject bazFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Outer",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "class Outer extends otherpackage.Parent {",
+            "  @AutoValue",
+            "  @AutoValue.CopyAnnotations",
+            "  @ProtectedAnnotation",
+            "  abstract static class Inner {",
+            "    abstract String foo();",
+            "  }",
+            "}");
+    JavaFileObject parentFileObject =
+        JavaFileObjects.forSourceLines(
+            "otherpackage.Parent",
+            "package otherpackage;",
+            "",
+            "public abstract class Parent {",
+            "  protected @interface ProtectedAnnotation {}",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(bazFileObject, parentFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Outer_Inner")
+        .contentsAsUtf8String()
+        .doesNotContain("ProtectedAnnotation");
+  }
+
+  @Test
+  public void builderWithVarArgsDoesNotImportJavaUtilArrays() {
+    // Repro from https://github.com/google/auto/issues/373.
+    JavaFileObject testFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Test",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableList;",
+            "",
+            "@AutoValue",
+            "public abstract class Test {",
+            "  abstract ImmutableList<String> foo();",
+            "",
+            "  @AutoValue.Builder",
+            "  abstract static class Builder {",
+            "    abstract Builder foo(String... foos);",
+            "    abstract Test build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(testFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Test")
+        .contentsAsUtf8String()
+        .doesNotContain("java.util.Arrays");
+  }
+
+  @Test
+  public void staticBuilderMethodInBuilderClass() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "com.example.Foo",
+            "package com.example;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Foo {",
+            "  public abstract String bar();",
+            "",
+            "  @AutoValue.Builder",
+            "  public abstract static class Builder {",
+            "    public static Builder builder() {",
+            "      return new AutoValue_Foo.Builder();",
+            "    }",
+            "",
+            "    public abstract Builder setBar(String s);",
+            "    public abstract Foo build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(javaFileObject);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .hadWarningContaining("Static builder() method should be in the containing class")
+        .inFile(javaFileObject)
+        .onLineContaining("builder()");
+  }
+
+  /**
+   * Tests behaviour when the package containing an {@code @AutoValue} class also has classes with
+   * the same name as classes in {@code java.lang}. If you call a class {@code Object} you are
+   * asking for trouble, but you could innocently call a class {@code Compiler} without realizing
+   * there is a {@code java.lang.Compiler}.
+   *
+   * <p>The case where the class in question is mentioned in the {@code @AutoValue} class is the
+   * easy one, because then our logic can easily see that there is a clash and will use
+   * fully-qualified names. This is the case of the {@code Compiler} class below. The case where the
+   * class is <i>not</i> mentioned is harder. We have to realize that we can't elide the package
+   * name in {@code java.lang.Object} because there is also a {@code foo.bar.Object} in scope, and
+   * in fact it takes precedence.
+   */
+  @Test
+  public void javaLangClash() {
+    JavaFileObject object =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Object", //
+            "package foo.bar;",
+            "",
+            "public class Object {}");
+    JavaFileObject string =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.String", //
+            "package foo.bar;",
+            "",
+            "public class String {}");
+    JavaFileObject integer =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Integer", //
+            "package foo.bar;",
+            "",
+            "public class Integer {}");
+    JavaFileObject thread =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Thread", //
+            "package foo.bar;",
+            "",
+            "public class Thread {}");
+    JavaFileObject test =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Test",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Test {",
+            "  public abstract java.lang.Integer integer();",
+            "  public abstract java.lang.Thread.State state();",
+            "  public static Builder builder() {",
+            "    return new AutoValue_Test.Builder();",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public abstract static class Builder {",
+            "    public abstract Builder setInteger(java.lang.Integer x);",
+            "    public abstract Builder setState(java.lang.Thread.State x);",
+            "    public abstract Test build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(object, string, integer, thread, test);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  // This is a regression test for the problem described in
+  // https://github.com/google/auto/issues/847#issuecomment-629857642.
+  @Test
+  public void generatedParentWithGeneratedGetterButSetterInBuilder() {
+    JavaFileObject test =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Test",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import foo.baz.GeneratedParent;",
+            "import foo.baz.GeneratedPropertyType;",
+            "import java.util.Optional;",
+            "",
+            "@AutoValue",
+            "public abstract class Test extends GeneratedParent {",
+            "  public abstract String string();",
+            "",
+            "  public static Builder builder() {",
+            "    return new AutoValue_Test.Builder();",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  public abstract static class Builder extends GeneratedParent.Builder<Builder> {",
+            "    public abstract Builder setString(String x);",
+            "    public abstract Builder setGenerated(GeneratedPropertyType x);",
+            "    public abstract Test build();",
+            "  }",
+            "}");
+    AutoValueProcessor autoValueProcessor = new AutoValueProcessor();
+    GeneratedParentProcessor generatedParentProcessor =
+        new GeneratedParentProcessor(autoValueProcessor, expect);
+    Compilation compilation =
+        javac()
+            .withProcessors(autoValueProcessor, generatedParentProcessor)
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(test);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Test")
+        .contentsAsUtf8String()
+        .contains("  public int integer() {");
+  }
+
+  @SupportedAnnotationTypes("*")
+  private static class GeneratedParentProcessor extends AbstractProcessor {
+    private static final String GENERATED_PARENT =
+        String.join(
+            "\n",
+            "package foo.baz;",
+            "",
+            "public abstract class GeneratedParent {",
+            "  public abstract int integer();",
+            "  public abstract GeneratedPropertyType generated();",
+            "",
+            "  public abstract static class Builder<B extends Builder<B>> {",
+            "    public abstract B setInteger(int x);",
+            "  }",
+            "}");
+    private static final String GENERATED_PROPERTY_TYPE =
+        String.join(
+            "\n",
+            "package foo.baz;",
+            "",
+            "public class GeneratedPropertyType {}");
+    private static final ImmutableMap<String, String> GENERATED_TYPES =
+        ImmutableMap.of(
+            "foo.baz.GeneratedParent", GENERATED_PARENT,
+            "foo.baz.GeneratedPropertyType", GENERATED_PROPERTY_TYPE);
+
+    private final AutoValueProcessor autoValueProcessor;
+    private final Expect expect;
+
+    GeneratedParentProcessor(AutoValueProcessor autoValueProcessor, Expect expect) {
+      this.autoValueProcessor = autoValueProcessor;
+      this.expect = expect;
+    }
+
+    private boolean generated;
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      if (!generated) {
+        generated = true;
+        // Check that AutoValueProcessor has already run and deferred the foo.bar.Test type because
+        // we haven't generated its parent yet.
+        expect.that(autoValueProcessor.deferredTypeNames()).contains("foo.bar.Test");
+        GENERATED_TYPES.forEach(
+            (typeName, source) -> {
+              try {
+                JavaFileObject generated =
+                    processingEnv
+                        .getFiler()
+                        .createSourceFile(typeName);
+                try (Writer writer = generated.openWriter()) {
+                  writer.write(source);
+                }
+              } catch (IOException e) {
+                throw new UncheckedIOException(e);
+              }
+            }
+        );
+      }
+      return false;
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+  }
+
+  private String sorted(String... imports) {
+     return Arrays.stream(imports).sorted().collect(joining("\n"));
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
new file mode 100644
index 0000000..ce9eeed
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
@@ -0,0 +1,1187 @@
+/*
+ * Copyright 2015 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.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.auto.value.extension.AutoValueExtension.BuilderContext;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Truth;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ServiceConfigurationError;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.SupportedOptions;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ExtensionTest {
+
+  @Test
+  public void testExtensionCompilation() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String foo();",
+            "}");
+    JavaFileObject expectedExtensionOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoValue_Baz",
+            "package foo.bar;",
+            "",
+            "final class AutoValue_Baz extends $AutoValue_Baz {",
+            "  public AutoValue_Baz(String foo) {",
+            "    super(foo);",
+            "  }",
+            "  @Override public String foo() {",
+            "    return \"foo\";",
+            "  }",
+            "  public String dizzle() {\n",
+            "    return \"dizzle\";\n",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .hasSourceEquivalentTo(expectedExtensionOutput);
+  }
+
+  @Test
+  public void testExtensionConsumesProperties() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String foo();",
+            "  abstract String dizzle();",
+            "}");
+    JavaFileObject expectedExtensionOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.$AutoValue_Baz",
+            "package foo.bar;",
+            "",
+            GeneratedImport.importGeneratedAnnotationType(),
+            "",
+            "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")",
+            " abstract class $AutoValue_Baz extends Baz {",
+            "",
+            "  private final String foo;",
+            "",
+            "  $AutoValue_Baz(",
+            "      String foo) {",
+            "    if (foo == null) {",
+            "      throw new NullPointerException(\"Null foo\");",
+            "    }",
+            "    this.foo = foo;",
+            "  }",
+            "",
+            "  @Override",
+            "  String foo() {",
+            "    return foo;",
+            "  }",
+            "",
+            "  @Override",
+            "  public String toString() {",
+            "    return \"Baz{\"",
+            "        + \"foo=\" + foo",
+            "        + \"}\";",
+            "  }",
+            "",
+            "  @Override",
+            "  public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof Baz) {",
+            "      Baz that = (Baz) o;",
+            "      return this.foo.equals(that.foo());",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+            "  @Override",
+            "  public int hashCode() {",
+            "    int h$ = 1;",
+            "    h$ *= 1000003;",
+            "    h$ ^= foo.hashCode();",
+            "    return h$;",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.$AutoValue_Baz")
+        .hasSourceEquivalentTo(expectedExtensionOutput);
+  }
+
+  @Test
+  public void testDoesntRaiseWarningForConsumedProperties() {
+    JavaFileObject impl =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue public abstract class Baz {",
+            "  abstract String foo();",
+            "  abstract String dizzle();",
+            "",
+            "  @AutoValue.Builder",
+            "  public abstract static class Builder {",
+            "    public abstract Builder foo(String s);",
+            "    public abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+            .compile(impl);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void testDoesntRaiseWarningForToBuilder() {
+    JavaFileObject impl =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue public abstract class Baz {",
+            "  abstract String foo();",
+            "  abstract String dizzle();",
+            "  abstract Builder toBuilder();",
+            "",
+            "  @AutoValue.Builder",
+            "  public abstract static class Builder {",
+            "    public abstract Builder foo(String s);",
+            "    public abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+            .compile(impl);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void testCantConsumeTwice() {
+    class ConsumeDizzle extends NonFinalExtension {
+      @Override
+      public Set<String> consumeProperties(Context context) {
+        return ImmutableSet.of("dizzle");
+      }
+    }
+    class AlsoConsumeDizzle extends ConsumeDizzle {}
+    AutoValueExtension ext1 = new ConsumeDizzle();
+    AutoValueExtension ext2 = new AlsoConsumeDizzle();
+    Truth.assertThat(ext1).isNotEqualTo(ext2);
+    JavaFileObject impl =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue public abstract class Baz {",
+            "  abstract String foo();",
+            "  abstract String dizzle();",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(ext1, ext2)))
+            .compile(impl);
+    assertThat(compilation)
+        .hadErrorContaining("wants to consume a method that was already consumed")
+        .inFile(impl)
+        .onLineContaining("String dizzle()");
+  }
+
+  @Test
+  public void testCantConsumeNonExistentProperty() {
+    class ConsumeDizzle extends NonFinalExtension {
+      @Override
+      public Set<String> consumeProperties(Context context) {
+        return ImmutableSet.of("dizzle");
+      }
+    }
+    JavaFileObject impl =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue public abstract class Baz {",
+            "  abstract String foo();",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new ConsumeDizzle())))
+            .compile(impl);
+    assertThat(compilation)
+        .hadErrorContaining("wants to consume a property that does not exist: dizzle")
+        .inFile(impl)
+        .onLineContaining("@AutoValue public abstract class Baz");
+  }
+
+  @Test
+  public void testCantConsumeConcreteMethod() {
+    class ConsumeConcreteMethod extends NonFinalExtension {
+      @Override
+      public Set<ExecutableElement> consumeMethods(Context context) {
+        TypeElement autoValueClass = context.autoValueClass();
+        for (ExecutableElement method :
+            ElementFilter.methodsIn(autoValueClass.getEnclosedElements())) {
+          if (method.getSimpleName().contentEquals("frob")) {
+            return ImmutableSet.of(method);
+          }
+        }
+        throw new AssertionError("Could not find frob method");
+      }
+    }
+    JavaFileObject impl =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue public abstract class Baz {",
+            "  abstract String foo();",
+            "  void frob(int x) {}",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new ConsumeConcreteMethod())))
+            .compile(impl);
+    assertThat(compilation)
+        .hadErrorContainingMatch(
+            "wants to consume a method that is not one of the abstract methods in this class"
+                + ".*frob\\(int\\)")
+        .inFile(impl)
+        .onLineContaining("@AutoValue public abstract class Baz");
+  }
+
+  @Test
+  public void testCantConsumeNonExistentMethod() {
+    class ConsumeBogusMethod extends NonFinalExtension {
+      @Override
+      public Set<ExecutableElement> consumeMethods(Context context) {
+        // Find Integer.intValue() and try to consume that.
+        Elements elementUtils = context.processingEnvironment().getElementUtils();
+        TypeElement javaLangInteger = elementUtils.getTypeElement(Integer.class.getName());
+        for (ExecutableElement method :
+            ElementFilter.methodsIn(javaLangInteger.getEnclosedElements())) {
+          if (method.getSimpleName().contentEquals("intValue")) {
+            return ImmutableSet.of(method);
+          }
+        }
+        throw new AssertionError("Could not find Integer.intValue()");
+      }
+    }
+    JavaFileObject impl =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "import com.google.auto.value.AutoValue;",
+            "@AutoValue public abstract class Baz {",
+            "  abstract String foo();",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new ConsumeBogusMethod())))
+            .compile(impl);
+    assertThat(compilation)
+        .hadErrorContainingMatch(
+            "wants to consume a method that is not one of the abstract methods in this class"
+                + ".*intValue\\(\\)")
+        .inFile(impl)
+        .onLineContaining("@AutoValue public abstract class Baz");
+  }
+
+  @Test
+  public void testExtensionWithoutConsumedPropertiesFails() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String foo();",
+            "  abstract String dizzle();",
+            "  abstract Double[] bad();",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "An @AutoValue class cannot define an array-valued property unless "
+                + "it is a primitive array")
+        .inFile(javaFileObject)
+        .onLineContaining("abstract Double[] bad()");
+  }
+
+  @Test
+  public void testConsumeMethodWithArguments() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String foo();",
+            "  abstract void writeToParcel(Object parcel, int flags);",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(
+                new AutoValueProcessor(ImmutableList.of(new FakeWriteToParcelExtension())))
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void testExtensionWithBuilderCompilation() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String foo();",
+            "  abstract String bar();",
+            "",
+            "  @AutoValue.Builder public static abstract class Builder {",
+            "    public abstract Builder foo(String foo);",
+            "    public abstract Builder bar(String bar);",
+            "    public abstract Baz build();",
+            "  }",
+            "}");
+    JavaFileObject expectedExtensionOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoValue_Baz",
+            "package foo.bar;",
+            "",
+            "final class AutoValue_Baz extends $AutoValue_Baz {",
+            "  public AutoValue_Baz(String foo, String bar) {",
+            "    super(foo, bar);",
+            "  }",
+            "  @Override public String foo() {",
+            "    return \"foo\";",
+            "  }",
+            "  public String dizzle() {\n",
+            "    return \"dizzle\";\n",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .hasSourceEquivalentTo(expectedExtensionOutput);
+  }
+
+  @Test
+  public void testLastExtensionGeneratesNoCode() {
+    doTestNoCode(new FooExtension(), new NonFinalExtension(), new SideFileExtension());
+  }
+
+  @Test
+  public void testFirstExtensionGeneratesNoCode() {
+    doTestNoCode(new SideFileExtension(), new FooExtension(), new NonFinalExtension());
+  }
+
+  @Test
+  public void testMiddleExtensionGeneratesNoCode() {
+    doTestNoCode(new FooExtension(), new SideFileExtension(), new NonFinalExtension());
+  }
+
+  @Test
+  public void testLoneExtensionGeneratesNoCode() {
+    doTestNoCode(new SideFileExtension());
+  }
+
+  private void doTestNoCode(AutoValueExtension... extensions) {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public abstract String foo();",
+            "",
+            "  public static Baz create(String foo) {",
+            "    return new AutoValue_Baz(foo);",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.copyOf(extensions)))
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedFile(StandardLocation.SOURCE_OUTPUT, "foo.bar", "Side_Baz.java");
+  }
+
+  @Test
+  public void testTwoExtensionsBothWantToBeFinal() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String foo();",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(
+                new AutoValueProcessor(ImmutableList.of(new FooExtension(), new FinalExtension())))
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "More than one extension wants to generate the final class: "
+                + FooExtension.class.getName()
+                + ", "
+                + FinalExtension.class.getName())
+        .inFile(javaFileObject)
+        .onLineContaining("public abstract class Baz");
+  }
+
+  @Test
+  public void testNonFinalThenFinal() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String foo();",
+            "}");
+    FinalExtension finalExtension = new FinalExtension();
+    NonFinalExtension nonFinalExtension = new NonFinalExtension();
+    assertThat(finalExtension.generated).isFalse();
+    assertThat(nonFinalExtension.generated).isFalse();
+    Compilation compilation =
+        javac()
+            .withProcessors(
+                new AutoValueProcessor(ImmutableList.of(finalExtension, nonFinalExtension)))
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(finalExtension.generated).isTrue();
+    assertThat(nonFinalExtension.generated).isTrue();
+  }
+
+  @Test
+  public void testFinalThenNonFinal() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String foo();",
+            "}");
+    FinalExtension finalExtension = new FinalExtension();
+    NonFinalExtension nonFinalExtension = new NonFinalExtension();
+    assertThat(finalExtension.generated).isFalse();
+    assertThat(nonFinalExtension.generated).isFalse();
+    Compilation compilation =
+        javac()
+            .withProcessors(
+                new AutoValueProcessor(ImmutableList.of(nonFinalExtension, finalExtension)))
+            .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(finalExtension.generated).isTrue();
+    assertThat(nonFinalExtension.generated).isTrue();
+  }
+
+  @Test
+  public void testUnconsumedMethod() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract String foo();",
+            "  abstract void writeToParcel(Object parcel, int flags);",
+            "}");
+    Compilation compilation =
+        javac()
+        .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+        .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("writeToParcel");
+    assertThat(compilation)
+        .hadWarningContaining(
+            "Abstract method is neither a property getter nor a Builder converter, "
+                + "and no extension consumed it")
+        .inFile(javaFileObject)
+        .onLineContaining("abstract void writeToParcel");
+    // The error here comes from the Java compiler rather than AutoValue, so we don't assume
+    // much about what it looks like. On the other hand, the warning does come from AutoValue
+    // so we know what to expect.
+  }
+
+  /**
+   * Tests that the search for extensions doesn't completely blow AutoValue up if there is a corrupt
+   * jar in the {@code processorpath}. If we're not careful, that can lead to a
+   * ServiceConfigurationError.
+   */
+  @Test
+  public void testBadJarDoesntBlowUp() throws IOException {
+    File badJar = File.createTempFile("bogus", ".jar");
+    try {
+      doTestBadJarDoesntBlowUp(badJar);
+    } finally {
+      badJar.delete();
+    }
+  }
+
+  private void doTestBadJarDoesntBlowUp(File badJar) throws IOException {
+    FileOutputStream fileOutputStream = new FileOutputStream(badJar);
+    JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream);
+    byte[] bogusLine = "bogus line\n".getBytes("UTF-8");
+    ZipEntry zipEntry = new ZipEntry("META-INF/services/" + AutoValueExtension.class.getName());
+    zipEntry.setSize(bogusLine.length);
+    jarOutputStream.putNextEntry(zipEntry);
+    jarOutputStream.write(bogusLine);
+    jarOutputStream.close();
+    ClassLoader badJarLoader = new URLClassLoader(new URL[] {badJar.toURI().toURL()});
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(badJarLoader))
+            .compile(javaFileObject);
+    assertThat(compilation).succeeded();
+    assertThat(compilation).hadWarningContaining(
+        "This may be due to a corrupt jar file in the compiler's classpath.\n  "
+            + ServiceConfigurationError.class.getName());
+    assertThat(compilation).generatedSourceFile("foo.bar.AutoValue_Baz");
+  }
+
+  private static final String CUSTOM_OPTION = "customAnnotation.customOption";
+
+  /**
+   * Tests that extensions providing their own (annotated) annotation types or options get picked
+   * up.
+   */
+  @Test
+  public void extensionsWithAnnotatedOptions() {
+    ExtensionWithAnnotatedOptions extension = new ExtensionWithAnnotatedOptions();
+
+    // Ensure default annotation support works
+    assertThat(extension.getSupportedOptions()).contains(CUSTOM_OPTION);
+
+    // Ensure it's carried over to the AutoValue processor
+    assertThat(new AutoValueProcessor(ImmutableList.of(extension)).getSupportedOptions())
+        .contains(CUSTOM_OPTION);
+  }
+
+  /**
+   * Tests that extensions providing their own implemented annotation types or options get picked
+   * up.
+   */
+  @Test
+  public void extensionsWithImplementedOptions() {
+    ExtensionWithImplementedOptions extension = new ExtensionWithImplementedOptions();
+
+    // Ensure it's carried over to the AutoValue processor
+    assertThat(new AutoValueProcessor(ImmutableList.of(extension)).getSupportedOptions())
+        .contains(CUSTOM_OPTION);
+  }
+
+  @SupportedOptions(CUSTOM_OPTION)
+  static class ExtensionWithAnnotatedOptions extends AutoValueExtension {
+    @Override
+    public String generateClass(
+        Context context, String className, String classToExtend, boolean isFinal) {
+      return null;
+    }
+  }
+
+  static class ExtensionWithImplementedOptions extends AutoValueExtension {
+    @Override
+    public Set<String> getSupportedOptions() {
+      return ImmutableSet.of(CUSTOM_OPTION);
+    }
+
+    @Override
+    public String generateClass(
+        Context context, String className, String classToExtend, boolean isFinal) {
+      return null;
+    }
+  }
+
+  private static class FooExtension extends AutoValueExtension {
+
+    @Override
+    public boolean applicable(Context context) {
+      return true;
+    }
+
+    @Override
+    public boolean mustBeFinal(Context context) {
+      return true;
+    }
+
+    @Override
+    public Set<String> consumeProperties(Context context) {
+      if (context.properties().containsKey("dizzle")) {
+        return ImmutableSet.of("dizzle");
+      } else {
+        return Collections.emptySet();
+      }
+    }
+
+    @Override
+    public String generateClass(
+        Context context, String className, String classToExtend, boolean isFinal) {
+      StringBuilder constructor =
+          new StringBuilder().append("  public ").append(className).append("(");
+
+      boolean first = true;
+      for (Map.Entry<String, ExecutableElement> el : context.properties().entrySet()) {
+        if (first) {
+          first = false;
+        } else {
+          constructor.append(", ");
+        }
+        constructor.append("String ").append(el.getKey());
+      }
+
+      constructor.append(") {\n");
+      constructor.append("    super(");
+
+      first = true;
+      for (Map.Entry<String, ExecutableElement> el : context.properties().entrySet()) {
+        if (first) {
+          first = false;
+        } else {
+          constructor.append(", ");
+        }
+        constructor.append(el.getKey());
+      }
+      constructor.append(");\n");
+      constructor.append("  }\n");
+
+      return String.format(
+          "package %s;\n"
+              + "\n"
+              + "%s class %s extends %s {\n"
+              + constructor
+              + "  @Override public String foo() {\n"
+              + "    return \"foo\";\n"
+              + "  }\n"
+              + "  public String dizzle() {\n"
+              + "    return \"dizzle\";\n"
+              + "  }\n"
+              + "}",
+          context.packageName(),
+          isFinal ? "final" : "abstract",
+          className,
+          classToExtend);
+    }
+  }
+
+  // Extension that generates a class that just forwards to the parent constructor.
+  // We will make subclasses that are respectively final and non-final.
+  private abstract static class EmptyExtension extends AutoValueExtension {
+    @Override
+    public boolean applicable(Context context) {
+      return true;
+    }
+
+    @Override
+    public abstract boolean mustBeFinal(Context context);
+
+    String extraText(Context context) {
+      return "";
+    }
+
+    boolean generated = false;
+
+    @Override
+    public String generateClass(
+        Context context, String className, String classToExtend, boolean isFinal) {
+      generated = true;
+
+      ImmutableList.Builder<String> typesAndNamesBuilder = ImmutableList.builder();
+      context.propertyTypes().forEach((name, type) -> typesAndNamesBuilder.add(type + " " + name));
+      String typesAndNames = Joiner.on(", ").join(typesAndNamesBuilder.build());
+      String template =
+          "package {pkg};\n"
+              + "\n"
+              + "{finalOrAbstract} class {className} extends {classToExtend} {\n"
+              + "  {className}({propertyTypesAndNames}) {\n"
+              + "    super({propertyNames});\n"
+              + "  }\n"
+              + "  {extraText}\n"
+              + "}\n";
+      return template
+          .replace("{pkg}", context.packageName())
+          .replace("{finalOrAbstract}", isFinal ? "final" : "abstract")
+          .replace("{className}", className)
+          .replace("{classToExtend}", classToExtend)
+          .replace("{propertyTypesAndNames}", typesAndNames)
+          .replace("{propertyNames}", Joiner.on(", ").join(context.properties().keySet()))
+          .replace("{extraText}", extraText(context));
+    }
+  }
+
+  private static class NonFinalExtension extends EmptyExtension {
+    @Override
+    public boolean mustBeFinal(Context context) {
+      return false;
+    }
+  }
+
+  private static class FinalExtension extends EmptyExtension {
+    @Override
+    public boolean mustBeFinal(Context context) {
+      return true;
+    }
+  }
+
+  private static class SideFileExtension extends AutoValueExtension {
+    @Override
+    public boolean applicable(Context context) {
+      return true;
+    }
+
+    @Override
+    public boolean mustBeFinal(Context context) {
+      return false;
+    }
+
+    @Override
+    public String generateClass(
+        Context context, String className, String classToExtend, boolean isFinal) {
+      String sideClassName = "Side_" + context.autoValueClass().getSimpleName();
+      String sideClass =
+          "" //
+          + "package " + context.packageName() + ";\n"
+          + "class " + sideClassName + " {}\n";
+      Filer filer = context.processingEnvironment().getFiler();
+      try {
+        String sideClassFqName = context.packageName() + "." + sideClassName;
+        JavaFileObject sourceFile =
+            filer.createSourceFile(sideClassFqName, context.autoValueClass());
+        try (Writer sourceWriter = sourceFile.openWriter()) {
+          sourceWriter.write(sideClass);
+        }
+      } catch (IOException e) {
+        context
+            .processingEnvironment()
+            .getMessager()
+            .printMessage(Diagnostic.Kind.ERROR, e.toString());
+      }
+      return null;
+    }
+  }
+
+  private static class FakeWriteToParcelExtension extends NonFinalExtension {
+    private ExecutableElement writeToParcelMethod(Context context) {
+      for (ExecutableElement method : context.abstractMethods()) {
+        if (method.getSimpleName().contentEquals("writeToParcel")) {
+          return method;
+        }
+      }
+      throw new AssertionError("Did not see abstract method writeToParcel");
+    }
+
+    @Override
+    public Set<ExecutableElement> consumeMethods(Context context) {
+      return ImmutableSet.of(writeToParcelMethod(context));
+    }
+
+    @Override
+    String extraText(Context context) {
+      // This is perhaps overgeneral. It is simply going to generate this:
+      // @Override void writeToParcel(Object parcel, int flags) {}
+      ExecutableElement methodToImplement = writeToParcelMethod(context);
+      assertThat(methodToImplement.getReturnType().getKind()).isEqualTo(TypeKind.VOID);
+      ImmutableList.Builder<String> typesAndNamesBuilder = ImmutableList.builder();
+      for (VariableElement p : methodToImplement.getParameters()) {
+        typesAndNamesBuilder.add(p.asType() + " " + p.getSimpleName());
+      }
+      return "@Override void "
+          + methodToImplement.getSimpleName()
+          + "("
+          + Joiner.on(", ").join(typesAndNamesBuilder.build())
+          + ") {}";
+    }
+  }
+
+  @Test
+  public void propertyTypes() {
+    JavaFileObject parent = JavaFileObjects.forSourceLines(
+        "foo.bar.Parent",
+        "package foo.bar;",
+        "",
+        "import java.util.List;",
+        "",
+        "interface Parent<T> {",
+        "  T thing();",
+        "  List<T> list();",
+        "}");
+    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
+        "foo.bar.Baz",
+        "package foo.bar;",
+        "",
+        "import com.google.auto.value.AutoValue;",
+        "",
+        "@AutoValue",
+        "abstract class Baz implements Parent<String> {",
+        "}");
+    ContextChecker checker =
+        context -> {
+          assertThat(context.builder()).isEmpty();
+          Map<String, TypeMirror> propertyTypes = context.propertyTypes();
+          assertThat(propertyTypes.keySet()).containsExactly("thing", "list");
+          TypeMirror thingType = propertyTypes.get("thing");
+          assertThat(thingType).isNotNull();
+          assertThat(thingType.getKind()).isEqualTo(TypeKind.DECLARED);
+          assertThat(MoreTypes.asTypeElement(thingType).getQualifiedName().toString())
+              .isEqualTo("java.lang.String");
+          TypeMirror listType = propertyTypes.get("list");
+          assertThat(listType).isNotNull();
+          assertThat(listType.toString()).isEqualTo("java.util.List<java.lang.String>");
+        };
+    ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(extension)))
+            .compile(autoValueClass, parent);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void finalAutoValueClassName() {
+    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
+        "foo.bar.Baz",
+        "package foo.bar;",
+        "",
+        "import com.google.auto.value.AutoValue;",
+        "",
+        "@AutoValue",
+        "abstract class Baz {",
+        "}");
+    ContextChecker checker =
+        context -> {
+          assertThat(context.finalAutoValueClassName()).isEqualTo("foo.bar.AutoValue_Baz");
+        };
+    ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+    Compilation compilation =
+        javac()
+            .withProcessors(
+                new AutoValueProcessor(ImmutableList.of(extension, new FinalExtension())))
+            .compile(autoValueClass);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation).generatedSourceFile("foo.bar.AutoValue_Baz");
+    // ContextCheckingExtension doesn't generate any code, so that name must be the class generated
+    // by FinalExtension.
+  }
+
+  @Test
+  public void builderContext() {
+    JavaFileObject parent = JavaFileObjects.forSourceLines(
+        "foo.bar.Parent",
+        "package foo.bar;",
+        "",
+        "import com.google.common.collect.ImmutableList;",
+        "",
+        "interface Parent<T> {",
+        "  T thing();",
+        "  ImmutableList<T> list();",
+        "}");
+    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
+        "foo.bar.Baz",
+        "package foo.bar;",
+        "",
+        "import com.google.auto.value.AutoValue;",
+        "import com.google.common.collect.ImmutableList;",
+        "",
+        "@AutoValue",
+        "abstract class Baz implements Parent<String> {",
+        "  static Builder builder() {",
+        "    return new AutoValue_Baz.Builder();",
+        "  }",
+        "",
+        "  abstract Builder toBuilder();",
+        "",
+        "  @AutoValue.Builder",
+        "  abstract static class Builder {",
+        "    abstract Builder setThing(String x);",
+        "    abstract Builder setList(Iterable<String> x);",
+        "    abstract Builder setList(ImmutableList<String> x);",
+        "    abstract ImmutableList.Builder<String> listBuilder();",
+        "    abstract Baz autoBuild();",
+        "    Baz build() {",
+        "      return autoBuild();",
+        "    }",
+        "  }",
+        "}");
+    ContextChecker checker =
+        context -> {
+          assertThat(context.builder()).isPresent();
+          BuilderContext builderContext = context.builder().get();
+
+          assertThat(builderContext.builderType().getQualifiedName().toString())
+              .isEqualTo("foo.bar.Baz.Builder");
+
+          Set<ExecutableElement> builderMethods = builderContext.builderMethods();
+          assertThat(builderMethods).hasSize(1);
+          ExecutableElement builderMethod = Iterables.getOnlyElement(builderMethods);
+          assertThat(builderMethod.getSimpleName().toString()).isEqualTo("builder");
+
+          Set<ExecutableElement> toBuilderMethods = builderContext.toBuilderMethods();
+          assertThat(toBuilderMethods).hasSize(1);
+          ExecutableElement toBuilderMethod = Iterables.getOnlyElement(toBuilderMethods);
+          assertThat(toBuilderMethod.getSimpleName().toString()).isEqualTo("toBuilder");
+
+          Optional<ExecutableElement> buildMethod = builderContext.buildMethod();
+          assertThat(buildMethod).isPresent();
+          assertThat(buildMethod.get().getSimpleName().toString()).isEqualTo("build");
+          assertThat(buildMethod.get().getParameters()).isEmpty();
+          assertThat(buildMethod.get().getReturnType().toString()).isEqualTo("foo.bar.Baz");
+
+          ExecutableElement autoBuildMethod = builderContext.autoBuildMethod();
+          assertThat(autoBuildMethod.getSimpleName().toString()).isEqualTo("autoBuild");
+          assertThat(autoBuildMethod.getModifiers()).contains(Modifier.ABSTRACT);
+          assertThat(autoBuildMethod.getParameters()).isEmpty();
+          assertThat(autoBuildMethod.getReturnType().toString()).isEqualTo("foo.bar.Baz");
+
+          Map<String, Set<ExecutableElement>> setters = builderContext.setters();
+          assertThat(setters.keySet()).containsExactly("thing", "list");
+          Set<ExecutableElement> thingSetters = setters.get("thing");
+          assertThat(thingSetters).hasSize(1);
+          ExecutableElement thingSetter = Iterables.getOnlyElement(thingSetters);
+          assertThat(thingSetter.getSimpleName().toString()).isEqualTo("setThing");
+          Set<ExecutableElement> listSetters = setters.get("list");
+          assertThat(listSetters).hasSize(2);
+          for (ExecutableElement listSetter : listSetters) {
+            assertThat(listSetter.getSimpleName().toString()).isEqualTo("setList");
+          }
+
+          Map<String, ExecutableElement> propertyBuilders = builderContext.propertyBuilders();
+          assertThat(propertyBuilders.keySet()).containsExactly("list");
+          assertThat(propertyBuilders.get("list").getSimpleName().toString())
+              .isEqualTo("listBuilder");
+        };
+    ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(extension)))
+            .compile(autoValueClass, parent);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void oddBuilderContext() {
+    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
+        "foo.bar.Baz",
+        "package foo.bar;",
+        "",
+        "import com.google.auto.value.AutoValue;",
+        "import com.google.common.collect.ImmutableList;",
+        "",
+        "@AutoValue",
+        "abstract class Baz {",
+        "  abstract String string();",
+        "",
+        "  @AutoValue.Builder",
+        "  abstract static class Builder {",
+        "    abstract Builder setString(String x);",
+        "    abstract Baz oddBuild();",
+        "    Baz build(int butNotReallyBecauseOfThisParameter) {",
+        "      return null;",
+        "    }",
+        "  }",
+        "}");
+    ContextChecker checker =
+        context -> {
+          assertThat(context.builder()).isPresent();
+          BuilderContext builderContext = context.builder().get();
+          assertThat(builderContext.builderMethods()).isEmpty();
+          assertThat(builderContext.toBuilderMethods()).isEmpty();
+          assertThat(builderContext.buildMethod()).isEmpty();
+          assertThat(builderContext.autoBuildMethod().getSimpleName().toString())
+              .isEqualTo("oddBuild");
+
+          Map<String, Set<ExecutableElement>> setters = builderContext.setters();
+          assertThat(setters.keySet()).containsExactly("string");
+          Set<ExecutableElement> thingSetters = setters.get("string");
+          assertThat(thingSetters).hasSize(1);
+          ExecutableElement thingSetter = Iterables.getOnlyElement(thingSetters);
+          assertThat(thingSetter.getSimpleName().toString()).isEqualTo("setString");
+
+          assertThat(builderContext.propertyBuilders()).isEmpty();
+        };
+    ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(extension)))
+            .compile(autoValueClass);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  // https://github.com/google/auto/issues/809
+  @Test
+  public void propertyErrorShouldNotCrash() {
+    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
+        "test.Test",
+        "package test;",
+        "import com.google.auto.value.AutoValue;",
+        "import java.util.List;",
+        "",
+        "@AutoValue",
+        "public abstract class Test {",
+        "  abstract Integer property();",
+        "  abstract List<String> listProperty();",
+        "",
+        "  @AutoValue.Builder",
+        "  public interface Builder {",
+        "    Builder property(Integer property);",
+        "    Builder listProperty(List<String> listProperty);",
+        "    Builder listProperty(Integer listPropertyValues);",
+        "    Test build();",
+        "  }",
+        "}");
+    // We don't actually expect the extension to be invoked. Previously it was, and that led to a
+    // NullPointerException when calling .setters() in the checker.
+    ContextChecker checker =
+        context -> {
+          assertThat(context.builder()).isPresent();
+          assertThat(context.builder().get().setters()).isEmpty();
+        };
+    ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(extension)))
+            .compile(autoValueClass);
+    assertThat(compilation)
+        .hadErrorContaining("Parameter type java.lang.Integer of setter method")
+        .inFile(autoValueClass)
+        .onLineContaining("Builder listProperty(Integer listPropertyValues)");
+  }
+
+  private interface ContextChecker extends Consumer<AutoValueExtension.Context> {}
+
+  private static class ContextCheckingExtension extends AutoValueExtension {
+    private final Consumer<Context> checker;
+
+    ContextCheckingExtension(Consumer<Context> checker) {
+      this.checker = checker;
+    }
+
+    @Override
+    public boolean applicable(Context context) {
+      return true;
+    }
+
+    @Override
+    public String generateClass(
+        Context context, String className, String classToExtend, boolean isFinal) {
+      checker.accept(context);
+      return null;
+    }
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java b/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java
new file mode 100644
index 0000000..f3d3d61
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2015 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.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.Reflection;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests that {@link AutoValueProcessor} works even if run in a context where the {@code @Generated}
+ * annotation does not exist.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(Parameterized.class)
+public class GeneratedDoesNotExistTest {
+
+  @Parameters(name = "{0}")
+  public static Collection<Object[]> data() {
+    ImmutableList.Builder<Object[]> params = ImmutableList.builder();
+    if (SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0) {
+      // use default options when running on JDK > 8
+      // TODO(b/72513371): use --release 8 once compile-testing supports that
+      params.add(
+          new Object[] {
+            ImmutableList.of(), "javax.annotation.processing.Generated",
+          });
+    }
+    params.add(
+        new Object[] {
+          ImmutableList.of("-source", "8", "-target", "8"), "javax.annotation.Generated",
+        });
+    return params.build();
+  }
+
+  private final ImmutableList<String> javacOptions;
+  private final String expectedAnnotation;
+
+  public GeneratedDoesNotExistTest(ImmutableList<String> javacOptions, String expectedAnnotation) {
+    this.javacOptions = javacOptions;
+    this.expectedAnnotation = expectedAnnotation;
+  }
+
+  // The classes here are basically just rigmarole to ensure that
+  // Types.getTypeElement("javax.annotation.Generated") returns null, and to check that something
+  // called that. We want a Processor that forwards everything to AutoValueProcessor, except that
+  // the init(ProcessingEnvironment) method should forward a ProcessingEnvironment that filters
+  // out the Generated class. So that ProcessingEnvironment forwards everything to the real
+  // ProcessingEnvironment, except the ProcessingEnvironment.getElementUtils() method. That method
+  // returns an Elements object that forwards everything to the real Elements except
+  // getTypeElement("javax.annotation.Generated") and
+  // getTypeElement("javax.annotation.processing.Generated").
+
+  private static final ImmutableSet<String> GENERATED_ANNOTATIONS =
+      ImmutableSet.of("javax.annotation.Generated", "javax.annotation.processing.Generated");
+
+  /**
+   * InvocationHandler that forwards every method to an original object, except methods where there
+   * is an implementation in this class with the same signature. So for example in the subclass
+   * {@link ElementsHandler} there is a method {@link ElementsHandler#getTypeElement(CharSequence)},
+   * which means that a call of {@link Elements#getTypeElement(CharSequence)} on the proxy with this
+   * invocation handler will end up calling that method, but a call of any of the other methods of
+   * {@code Elements} will end up calling the method on the original object.
+   */
+  private abstract static class OverridableInvocationHandler<T> implements InvocationHandler {
+    final T original;
+
+    OverridableInvocationHandler(T original) {
+      this.original = original;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+      try {
+        Method override = getClass().getMethod(method.getName(), method.getParameterTypes());
+        if (override.getDeclaringClass() == getClass()) {
+          return override.invoke(this, args);
+        }
+      } catch (NoSuchMethodException ignored) {
+        // OK: we don't have an override for this method, so just invoke the original method.
+      }
+      return method.invoke(original, args);
+    }
+  }
+
+  private static <T> T partialProxy(Class<T> type, OverridableInvocationHandler<T> handler) {
+    return Reflection.newProxy(type, handler);
+  }
+
+  private static class ElementsHandler extends OverridableInvocationHandler<Elements> {
+
+    private final Set<String> ignoredGenerated;
+
+    ElementsHandler(Elements original, Set<String> ignoredGenerated) {
+      super(original);
+      this.ignoredGenerated = ignoredGenerated;
+    }
+
+    public TypeElement getTypeElement(CharSequence name) {
+      if (GENERATED_ANNOTATIONS.contains(name.toString())) {
+        ignoredGenerated.add(name.toString());
+        return null;
+      } else {
+        return original.getTypeElement(name);
+      }
+    }
+  }
+
+  private static class ProcessingEnvironmentHandler
+      extends OverridableInvocationHandler<ProcessingEnvironment> {
+    private final Elements noGeneratedElements;
+
+    ProcessingEnvironmentHandler(ProcessingEnvironment original, Set<String> ignoredGenerated) {
+      super(original);
+      ElementsHandler elementsHandler =
+          new ElementsHandler(original.getElementUtils(), ignoredGenerated);
+      this.noGeneratedElements = partialProxy(Elements.class, elementsHandler);
+    }
+
+    public Elements getElementUtils() {
+      return noGeneratedElements;
+    }
+  }
+
+  private static class ProcessorHandler extends OverridableInvocationHandler<Processor> {
+    private final Set<String> ignoredGenerated;
+
+    ProcessorHandler(Processor original, Set<String> ignoredGenerated) {
+      super(original);
+      this.ignoredGenerated = ignoredGenerated;
+    }
+
+    public void init(ProcessingEnvironment processingEnv) {
+      ProcessingEnvironmentHandler processingEnvironmentHandler =
+          new ProcessingEnvironmentHandler(processingEnv, ignoredGenerated);
+      ProcessingEnvironment noGeneratedProcessingEnvironment =
+          partialProxy(ProcessingEnvironment.class, processingEnvironmentHandler);
+      original.init(noGeneratedProcessingEnvironment);
+    }
+  }
+
+  @Test
+  public void test() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  public static Baz create() {",
+            "    return new AutoValue_Baz();",
+            "  }",
+            "}");
+    JavaFileObject expectedOutput =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.AutoValue_Baz",
+            "package foo.bar;",
+            "",
+            "final class AutoValue_Baz extends Baz {",
+            "  AutoValue_Baz() {",
+            "  }",
+            "",
+            "  @Override public String toString() {",
+            "    return \"Baz{\"",
+            "        + \"}\";",
+            "  }",
+            "",
+            "  @Override public boolean equals(Object o) {",
+            "    if (o == this) {",
+            "      return true;",
+            "    }",
+            "    if (o instanceof Baz) {",
+            "      return true;",
+            "    }",
+            "    return false;",
+            "  }",
+            "",
+            "  @Override public int hashCode() {",
+            "    int h$ = 1;",
+            "    return h$;",
+            "  }",
+            "}");
+    Set<String> ignoredGenerated = ConcurrentHashMap.newKeySet();
+    Processor autoValueProcessor = new AutoValueProcessor();
+    ProcessorHandler handler = new ProcessorHandler(autoValueProcessor, ignoredGenerated);
+    Processor noGeneratedProcessor = partialProxy(Processor.class, handler);
+    Compilation compilation =
+        javac()
+        .withOptions(javacOptions)
+        .withProcessors(noGeneratedProcessor)
+        .compile(javaFileObject);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .hasSourceEquivalentTo(expectedOutput);
+    assertThat(ignoredGenerated).containsExactly(expectedAnnotation);
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/GeneratedImport.java b/value/src/test/java/com/google/auto/value/processor/GeneratedImport.java
new file mode 100644
index 0000000..35af5a8
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/GeneratedImport.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 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.processor;
+
+import javax.lang.model.SourceVersion;
+
+/**
+ * Utility methods for compile-testing tests to know which {@code @Generated} annotation is
+ * available.
+ */
+final class GeneratedImport {
+
+  /**
+   * Returns the qualified name of the {@code @Generated} annotation available during a compilation
+   * task.
+   */
+  static String generatedAnnotationType() {
+    return SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0
+        ? "javax.annotation.processing.Generated"
+        : "javax.annotation.Generated";
+  }
+
+  /**
+   * Returns an {@code import} statement that imports the {@code @Generated} annotation {@linkplain
+   * #generatedAnnotationType() available during a compilation task}.
+   */
+  static String importGeneratedAnnotationType() {
+    return "import " + generatedAnnotationType() + ";";
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/GuavaCollectionBuildersTest.java b/value/src/test/java/com/google/auto/value/processor/GuavaCollectionBuildersTest.java
new file mode 100644
index 0000000..4e28224
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/GuavaCollectionBuildersTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2015 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.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.ClassPath;
+import com.google.common.truth.Expect;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Validates the assumptions AutoValue makes about Guava immutable collection builders. We expect
+ * for each public class {@code com.google.common.collect.ImmutableFoo} that:
+ *
+ * <ul>
+ *   <li>it contains a public nested class {@code ImmutableFoo.Builder} with the same type
+ *       parameters;
+ *   <li>there is a public static method {@code ImmutableFoo.builder()} that returns {@code
+ *       ImmutableFoo.Builder};
+ *   <li>there is a method {@code ImmutableFoo.Builder.build()} that returns {@code ImmutableFoo};
+ *   <li>and there is a method in {@code ImmutableFoo.Builder} called either {@code addAll} or
+ *       {@code putAll} with a single parameter to which {@code ImmutableFoo} can be assigned.
+ * </ul>
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class GuavaCollectionBuildersTest {
+  private static final ImmutableSet<String> NON_BUILDABLE_COLLECTIONS =
+      ImmutableSet.of("ImmutableCollection");
+
+  @Rule public final Expect expect = Expect.create();
+
+  @Test
+  public void testImmutableBuilders() throws Exception {
+    ClassPath classPath = ClassPath.from(getClass().getClassLoader());
+    ImmutableSet<ClassPath.ClassInfo> classes = classPath.getAllClasses();
+    int checked = 0;
+    for (ClassPath.ClassInfo classInfo : classes) {
+      if (classInfo.getPackageName().equals("com.google.common.collect")
+          && classInfo.getSimpleName().startsWith("Immutable")
+          && !NON_BUILDABLE_COLLECTIONS.contains(classInfo.getSimpleName())) {
+        Class<?> c = Class.forName(classInfo.getName());
+        if (Modifier.isPublic(c.getModifiers())) {
+          checked++;
+          checkImmutableClass(c);
+        }
+      }
+    }
+    expect.that(checked).isGreaterThan(10);
+  }
+
+  private void checkImmutableClass(Class<?> c)
+      throws ClassNotFoundException, NoSuchMethodException {
+    if (!Modifier.isPublic(c.getModifiers())) {
+      return;
+    }
+
+    // We have a public static ImmutableFoo.builder()
+    Method builderMethod = c.getMethod("builder");
+    assertThat(Modifier.isStatic(builderMethod.getModifiers())).isTrue();
+
+    // Its return type is Builder with the same type parameters.
+    Type builderMethodReturn = builderMethod.getGenericReturnType();
+    expect.that(builderMethodReturn).isInstanceOf(ParameterizedType.class);
+    ParameterizedType builderMethodParameterizedReturn = (ParameterizedType) builderMethodReturn;
+    Class<?> builderClass = Class.forName(c.getName() + "$Builder");
+    expect.that(builderMethod.getReturnType()).isEqualTo(builderClass);
+    expect
+        .withMessage(c.getName())
+        .that(Arrays.toString(builderMethodParameterizedReturn.getActualTypeArguments()))
+        .isEqualTo(Arrays.toString(builderClass.getTypeParameters()));
+
+    // The Builder has a public build() method that returns ImmutableFoo.
+    Method buildMethod = builderClass.getMethod("build");
+    expect.that(buildMethod.getReturnType()).isEqualTo(c);
+
+    // The Builder has either an addAll or a putAll public method with a parameter that
+    // ImmutableFoo can be assigned to.
+    boolean found = false;
+    for (Method m : builderClass.getMethods()) {
+      if ((m.getName().equals("addAll") || m.getName().equals("putAll"))
+          && m.getParameterTypes().length == 1) {
+        Class<?> parameter = m.getParameterTypes()[0];
+        if (parameter.isAssignableFrom(c)) {
+          found = true;
+          break;
+        }
+      }
+    }
+    expect.withMessage(builderClass.getName() + " has addAll or putAll").that(found).isTrue();
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java
new file mode 100644
index 0000000..27cb093
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.processor;
+
+import static com.google.common.truth.Correspondence.transforming;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.auto.value.extension.AutoValueExtension.IncrementalExtensionType;
+import com.google.auto.value.extension.memoized.processor.MemoizeExtension;
+import com.google.auto.value.extension.serializable.processor.SerializableAutoValueExtension;
+import com.google.common.collect.ImmutableList;
+import javax.annotation.processing.ProcessingEnvironment;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests of Gradle incrementality in the presence of extensions. See
+ * <a href="https://docs.gradle.org/5.0/userguide/java_plugin.html#sec:incremental_annotation_processing">
+ * incremental annotation processing</a> in the Gradle user guide.
+ */
+@RunWith(JUnit4.class)
+public class IncrementalExtensionTest {
+  @Test
+  public void builtInExtensionsAreIsolating() {
+    ImmutableList<AutoValueExtension> builtInExtensions =
+        AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader());
+    // These are the current built-in extensions. We will update this test if we add more.
+    // The (Object) cast is because otherwise the inferred type is Class<?>, and we can't match
+    // that <?> with the class literals here. Even if we cast them to Class<?> it will be a
+    // different <?>.
+    assertThat(builtInExtensions)
+        .comparingElementsUsing(transforming(e -> (Object) e.getClass(), "is class"))
+        .containsExactly(MemoizeExtension.class, SerializableAutoValueExtension.class);
+
+    AutoValueProcessor processor = new AutoValueProcessor(builtInExtensions);
+    assertThat(processor.getSupportedOptions())
+        .contains(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption());
+  }
+
+  @Test
+  public void customExtensionsAreNotIsolatingByDefault() {
+    AutoValueExtension nonIsolatingExtension = new NonIsolatingExtension();
+    assertThat(nonIsolatingExtension.incrementalType((ProcessingEnvironment) null))
+        .isEqualTo(IncrementalExtensionType.UNKNOWN);
+    ImmutableList<AutoValueExtension> extensions = ImmutableList.<AutoValueExtension>builder()
+        .addAll(AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader()))
+        .add(nonIsolatingExtension)
+        .build();
+
+    AutoValueProcessor processor = new AutoValueProcessor(extensions);
+    assertThat(processor.getSupportedOptions())
+        .doesNotContain(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption());
+  }
+
+  @Test
+  public void customExtensionsCanBeIsolating() {
+    AutoValueExtension isolatingExtension = new IsolatingExtension();
+    assertThat(isolatingExtension.incrementalType((ProcessingEnvironment) null))
+        .isEqualTo(IncrementalExtensionType.ISOLATING);
+    ImmutableList<AutoValueExtension> extensions = ImmutableList.<AutoValueExtension>builder()
+        .addAll(AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader()))
+        .add(isolatingExtension)
+        .build();
+
+    AutoValueProcessor processor = new AutoValueProcessor(extensions);
+    assertThat(processor.getSupportedOptions())
+        .contains(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption());
+  }
+
+  // Extensions are "UNKNOWN" by default.
+  private static class NonIsolatingExtension extends AutoValueExtension {
+    @Override
+    public String generateClass(
+        Context context, String className, String classToExtend, boolean isFinal) {
+      return null;
+    }
+  }
+
+  // Extensions are "ISOLATING" if they say they are.
+  private static class IsolatingExtension extends AutoValueExtension {
+    @Override
+    public IncrementalExtensionType incrementalType(ProcessingEnvironment processingEnvironment) {
+      return IncrementalExtensionType.ISOLATING;
+    }
+
+    @Override
+    public String generateClass(
+        Context context, String className, String classToExtend, boolean isFinal) {
+      return null;
+    }
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/JavaScannerTest.java b/value/src/test/java/com/google/auto/value/processor/JavaScannerTest.java
new file mode 100644
index 0000000..3632bbb
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/JavaScannerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 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.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** @author emcmanus@google.com (Éamonn McManus) */
+@RunWith(JUnit4.class)
+public class JavaScannerTest {
+  private static final ImmutableList<String> TOKENS =
+      ImmutableList.of(
+          "  ",
+          "\"hello \\\" world\\n\"",
+          "'a'",
+          "  ",
+          "'\\t'",
+          "  ",
+          "`com.google.Foo`",
+          "   ",
+          "\n  ",
+          "/* comment * comment \" whatever\n     comment continued */",
+          "  ",
+          "t",
+          "h",
+          "i",
+          "n",
+          "g",
+          " ",
+          "t",
+          "h",
+          "i",
+          "n",
+          "g",
+          "  ",
+          "// line comment",
+          "\n",
+          "/*/ tricky comment */",
+          "\n");
+
+  /**
+   * Tests basic scanner functionality. The test concatenates the tokens in {@link #TOKENS} and then
+   * retokenizes that string, checking that the same list of tokens is produced.
+   */
+  @Test
+  public void testScanner() {
+    String input = Joiner.on("").join(TOKENS);
+    ImmutableList.Builder<String> tokensBuilder = ImmutableList.builder();
+    JavaScanner tokenizer = new JavaScanner(input);
+    int end;
+    for (int i = 0; i < input.length(); i = end) {
+      end = tokenizer.tokenEnd(i);
+      tokensBuilder.add(input.substring(i, end));
+    }
+    assertThat(tokensBuilder.build()).containsExactlyElementsIn(TOKENS).inOrder();
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java
new file mode 100644
index 0000000..2c3bea0
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.testing.compile.JavaFileObjects;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests to ensure annotations are kept on AutoValue generated classes
+ *
+ * @author jmcampanini
+ */
+@RunWith(JUnit4.class)
+public class PropertyAnnotationsTest {
+  private static final String TEST_ANNOTATION =
+      "@PropertyAnnotationsTest.TestAnnotation";
+  private static final String TEST_ARRAY_ANNOTATION =
+      "@PropertyAnnotationsTest.TestArrayAnnotation";
+
+  public enum TestEnum {
+    A,
+    B;
+
+    @Override
+    public String toString() {
+      // used to prove that the method we determine the value does not use the `toString()` method
+      // of the enum
+      return "not the same value";
+    }
+  }
+
+  public @interface TestAnnotation {
+    byte testByte() default 1;
+
+    short testShort() default 2;
+
+    int testInt() default 3;
+
+    long testLong() default 4L;
+
+    float testFloat() default 5.6f;
+
+    double testDouble() default 7.8d;
+
+    char testChar() default 'a';
+
+    String testString() default "10";
+
+    boolean testBoolean() default false;
+
+    Class<?> testClass() default TestEnum.class;
+
+    TestEnum testEnum() default TestEnum.A;
+
+    OtherAnnotation testAnnotation() default @OtherAnnotation(foo = 23, bar = "baz");
+  }
+
+  public @interface OtherAnnotation {
+    int foo() default 123;
+
+    String bar() default "bar";
+  }
+
+  public @interface TestArrayAnnotation {
+    byte[] testBytes() default {1, 2};
+
+    short[] testShorts() default {3, 4};
+
+    int[] testInts() default {5, 6};
+
+    long[] testLongs() default {7L, 8L};
+
+    float[] testFloats() default {9.1f, 2.3f};
+
+    double[] testDoubles() default {4.5d, 6.7d};
+
+    char[] testChars() default {'a', 'b'};
+
+    String[] testStrings() default {"cde", "fgh"};
+
+    boolean[] testBooleans() default {true, false};
+
+    Class<?>[] testClasses() default {TestEnum.class, TestEnum.class};
+
+    TestEnum[] testEnums() default {TestEnum.A, TestEnum.B};
+
+    OtherAnnotation[] testAnnotations() default {
+      @OtherAnnotation(foo = 999), @OtherAnnotation(bar = "baz")
+    };
+  }
+
+  @Inherited
+  public @interface InheritedAnnotation {}
+
+  private static class InputFileBuilder {
+    Iterable<String> imports = ImmutableList.of();
+    List<String> annotations = new ArrayList<>();
+    List<String> innerTypes = new ArrayList<>();
+
+    InputFileBuilder setImports(Iterable<String> imports) {
+      this.imports = imports;
+      return this;
+    }
+
+    InputFileBuilder addAnnotations(String... annotations) {
+      this.annotations.addAll(ImmutableList.copyOf(annotations));
+      return this;
+    }
+
+    InputFileBuilder addAnnotations(Iterable<String> annotations) {
+      this.annotations.addAll(ImmutableList.copyOf(annotations));
+      return this;
+    }
+
+    InputFileBuilder addInnerTypes(String... innerTypes) {
+      this.innerTypes.addAll(ImmutableList.copyOf(innerTypes));
+      return this;
+    }
+
+    JavaFileObject build() {
+      ImmutableList<String> list =
+          ImmutableList.<String>builder()
+              .add("package foo.bar;", "", "import com.google.auto.value.AutoValue;")
+              .addAll(imports)
+              .add("", "@AutoValue", "public abstract class Baz {")
+              .addAll(annotations)
+              .add(
+                  "  public abstract int buh();",
+                  "",
+                  "  public static Baz create(int buh) {",
+                  "    return new AutoValue_Baz(buh);",
+                  "  }")
+              .addAll(innerTypes)
+              .add("}")
+              .build();
+      String[] lines = list.toArray(new String[list.size()]);
+      return JavaFileObjects.forSourceLines("foo.bar.Baz", lines);
+    }
+  }
+
+  private static class OutputFileBuilder {
+    Iterable<String> imports = ImmutableList.of();
+    List<String> fieldAnnotations = new ArrayList<>();
+    List<String> methodAnnotations = new ArrayList<>();
+
+    OutputFileBuilder setImports(Iterable<String> imports) {
+      this.imports = imports;
+      return this;
+    }
+
+    OutputFileBuilder addFieldAnnotations(String... annotations) {
+      this.fieldAnnotations.addAll(ImmutableList.copyOf(annotations));
+      return this;
+    }
+
+    OutputFileBuilder addMethodAnnotations(String... innerTypes) {
+      this.methodAnnotations.addAll(ImmutableList.copyOf(innerTypes));
+      return this;
+    }
+
+    OutputFileBuilder addMethodAnnotations(Iterable<String> innerTypes) {
+      this.methodAnnotations.addAll(ImmutableList.copyOf(innerTypes));
+      return this;
+    }
+
+    JavaFileObject build() {
+      String nullable = methodAnnotations.contains("@Nullable") ? "@Nullable " : "";
+      ImmutableSortedSet<String> allImports =
+          ImmutableSortedSet.<String>naturalOrder()
+              .add(GeneratedImport.importGeneratedAnnotationType())
+              .addAll(imports)
+              .build();
+      ImmutableList<String> list =
+          ImmutableList.<String>builder()
+              .add("package foo.bar;", "")
+              .addAll(allImports)
+              .add(
+                  "",
+                  "@Generated(\"" + AutoValueProcessor.class.getName() + "\")",
+                  "final class AutoValue_Baz extends Baz {")
+              .addAll(fieldAnnotations)
+              .add(
+                  "  private final int buh;",
+                  "  AutoValue_Baz(" + nullable + "int buh) {",
+                  "    this.buh = buh;",
+                  "  }",
+                  "")
+              .addAll(methodAnnotations)
+              .add(
+                  "  @Override public int buh() {",
+                  "    return buh;",
+                  "  }",
+                  "",
+                  "  @Override public String toString() {",
+                  "    return \"Baz{\"",
+                  "        + \"buh=\" + buh",
+                  "        + \"}\";",
+                  "  }",
+                  "",
+                  "  @Override public boolean equals(Object o) {",
+                  "    if (o == this) {",
+                  "      return true;",
+                  "    }",
+                  "    if (o instanceof Baz) {",
+                  "      Baz that = (Baz) o;",
+                  "      return this.buh == that.buh();",
+                  "    }",
+                  "    return false;",
+                  "  }",
+                  "",
+                  "  @Override public int hashCode() {",
+                  "    int h$ = 1;",
+                  "    h$ *= 1000003;",
+                  "    h$ ^= buh;",
+                  "    return h$;",
+                  "  }",
+                  "}")
+              .build();
+
+      String[] lines = list.toArray(new String[list.size()]);
+      return JavaFileObjects.forSourceLines("foo.bar.AutoValue_Baz", lines);
+    }
+  }
+
+  private ImmutableSet<String> getImports(Class<?>... classes) {
+    ImmutableSet.Builder<String> stringBuilder = ImmutableSortedSet.naturalOrder();
+    for (Class<?> clazz : classes) {
+      stringBuilder.add("import " + clazz.getName() + ";");
+    }
+    return stringBuilder.build();
+  }
+
+  private void assertGeneratedMatches(
+      Iterable<String> imports,
+      Iterable<String> annotations,
+      Iterable<String> expectedMethodAnnotations) {
+
+    JavaFileObject javaFileObject =
+        new InputFileBuilder().setImports(imports).addAnnotations(annotations).build();
+
+    JavaFileObject expectedOutput =
+        new OutputFileBuilder()
+            .setImports(imports)
+            .addMethodAnnotations(expectedMethodAnnotations)
+            .build();
+
+    assertAbout(javaSource())
+        .that(javaFileObject)
+        .processedWith(new AutoValueProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(expectedOutput);
+  }
+
+  @Test
+  public void testSimpleAnnotation() {
+    assertGeneratedMatches(
+        ImmutableList.of(), ImmutableList.of("@Deprecated"), ImmutableList.of("@Deprecated"));
+  }
+
+  @Test
+  public void testSingleStringValueAnnotation() {
+    assertGeneratedMatches(
+        ImmutableList.of(),
+        ImmutableList.of("@SuppressWarnings(\"a\")"),
+        ImmutableList.of("@SuppressWarnings(\"a\")"));
+  }
+
+  @Test
+  public void testMultiStringValueAnnotation() {
+    assertGeneratedMatches(
+        ImmutableList.of(),
+        ImmutableList.of("@SuppressWarnings({\"a\", \"b\"})"),
+        ImmutableList.of("@SuppressWarnings({\"a\", \"b\"})"));
+  }
+
+  @Test
+  public void testNumberValueAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(TEST_ANNOTATION + "(testShort = 1, testInt = 2, testLong = 3L)"),
+        ImmutableList.of(TEST_ANNOTATION + "(testShort = 1, testInt = 2, testLong = 3L)"));
+  }
+
+  @Test
+  public void testByteValueAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(TEST_ANNOTATION + "(testByte = 0)"),
+        ImmutableList.of(TEST_ANNOTATION + "(testByte = 0)"));
+  }
+
+  @Test
+  public void testDecimalValueAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(TEST_ANNOTATION + "(testDouble = 1.2d, testFloat = 3.4f)"),
+        ImmutableList.of(TEST_ANNOTATION + "(testDouble = 1.2d, testFloat = 3.4f)"));
+  }
+
+  @Test
+  public void testOtherValuesAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ANNOTATION + "(testBoolean = true, testString = \"hallo\", testChar = 'a')"),
+        ImmutableList.of(
+            TEST_ANNOTATION + "(testBoolean = true, testString = \"hallo\", testChar = 'a')"));
+  }
+
+  @Test
+  public void testClassAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(TEST_ANNOTATION + "(testClass = String.class)"),
+        ImmutableList.of(TEST_ANNOTATION + "(testClass = String.class)"));
+  }
+
+  @Test
+  public void testEnumAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ANNOTATION
+                + "(testEnum = "
+                + PropertyAnnotationsTest.class.getName()
+                + ".TestEnum.A)"),
+        ImmutableList.of(TEST_ANNOTATION + "(testEnum = PropertyAnnotationsTest.TestEnum.A)"));
+  }
+
+  @Test
+  public void testEmptyAnnotationAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ANNOTATION + "(testAnnotation = @PropertyAnnotationsTest.OtherAnnotation)"),
+        ImmutableList.of(
+            TEST_ANNOTATION + "(testAnnotation = @PropertyAnnotationsTest.OtherAnnotation)"));
+  }
+
+  @Test
+  public void testValuedAnnotationAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ANNOTATION
+                + "(testAnnotation = @PropertyAnnotationsTest.OtherAnnotation(foo=999))"),
+        ImmutableList.of(
+            TEST_ANNOTATION
+                + "(testAnnotation = @PropertyAnnotationsTest.OtherAnnotation(foo=999))"));
+  }
+
+  @Test
+  public void testNumberArrayAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testShorts = {2, 3}, testInts = {4, 5}, testLongs = {6L, 7L})"),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testShorts = {2, 3}, testInts = {4, 5}, testLongs = {6L, 7L})"));
+  }
+
+  @Test
+  public void testByteArrayAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(TEST_ARRAY_ANNOTATION + "(testBytes = {0, 1})"),
+        ImmutableList.of(TEST_ARRAY_ANNOTATION + "(testBytes = {0, 1})"));
+  }
+
+  @Test
+  public void testDecimalArrayAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION + "(testDoubles = {1.2d, 3.4d}, testFloats = {5.6f, 7.8f})"),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION + "(testDoubles = {1.2d, 3.4d}, testFloats = {5.6f, 7.8f})"));
+  }
+
+  @Test
+  public void testOtherArrayAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testBooleans = {false, false},"
+                + " testStrings = {\"aaa\", \"bbb\"}, testChars={'x', 'y'})"),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testBooleans = {false, false},"
+                + " testStrings = {\"aaa\", \"bbb\"}, testChars={'x', 'y'})"));
+  }
+
+  @Test
+  public void testClassArrayAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(TEST_ARRAY_ANNOTATION + "(testClasses = {String.class, Long.class})"),
+        ImmutableList.of(TEST_ARRAY_ANNOTATION + "(testClasses = {String.class, Long.class})"));
+  }
+
+  @Test
+  public void testImportedClassArrayAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class, Nullable.class),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testClasses = {javax.annotation.Nullable.class, Long.class})"),
+        ImmutableList.of(TEST_ARRAY_ANNOTATION + "(testClasses = {Nullable.class, Long.class})"));
+  }
+
+  @Test
+  public void testEnumArrayAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION + "(testEnums = {PropertyAnnotationsTest.TestEnum.A,"
+                + " PropertyAnnotationsTest.TestEnum.B})"),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION + "(testEnums = {PropertyAnnotationsTest.TestEnum.A,"
+                + " PropertyAnnotationsTest.TestEnum.B})"));
+  }
+
+  @Test
+  public void testArrayOfEmptyAnnotationAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testAnnotations = {@PropertyAnnotationsTest.OtherAnnotation})"),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testAnnotations = @PropertyAnnotationsTest.OtherAnnotation)"));
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testAnnotations = {@PropertyAnnotationsTest.OtherAnnotation,"
+                + " @PropertyAnnotationsTest.OtherAnnotation})"),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testAnnotations = {@PropertyAnnotationsTest.OtherAnnotation,"
+                + " @PropertyAnnotationsTest.OtherAnnotation})"));
+  }
+
+  @Test
+  public void testArrayOfValuedAnnotationAnnotation() {
+    assertGeneratedMatches(
+        getImports(PropertyAnnotationsTest.class),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testAnnotations = {@PropertyAnnotationsTest.OtherAnnotation(foo = 999)})"),
+        ImmutableList.of(
+            TEST_ARRAY_ANNOTATION
+                + "(testAnnotations = @PropertyAnnotationsTest.OtherAnnotation(foo = 999))"));
+  }
+
+  /**
+   * Tests that when CopyAnnotations is present on a method, all non-inherited annotations (except
+   * those appearing in CopyAnnotations.exclude) are copied to the method implementation in the
+   * generated class.
+   */
+  @Test
+  public void testCopyingMethodAnnotations() {
+    ImmutableSet<String> imports = getImports(PropertyAnnotationsTest.class);
+    JavaFileObject inputFile =
+        new InputFileBuilder()
+            .setImports(imports)
+            .addAnnotations(
+                "@AutoValue.CopyAnnotations(exclude="
+                    + "{PropertyAnnotationsTest.TestAnnotation.class})",
+                "@Deprecated",
+                "@PropertyAnnotationsTest.TestAnnotation",
+                "@PropertyAnnotationsTest.InheritedAnnotation")
+            .build();
+
+    JavaFileObject outputFile =
+        new OutputFileBuilder()
+            .setImports(imports)
+            .addMethodAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation")
+            .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation")
+            .build();
+
+    assertAbout(javaSource())
+        .that(inputFile)
+        .processedWith(new AutoValueProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(outputFile);
+  }
+
+  /**
+   * Tests that when CopyAnnotationsToGeneratedField is present on a method, all non-inherited
+   * annotations (except those appearing in CopyAnnotationsToGeneratedField.exclude) are copied to
+   * the method implementation in the generated class.
+   */
+  @Test
+  public void testCopyingMethodAnnotationsToGeneratedFields() {
+    JavaFileObject inputFile =
+        new InputFileBuilder()
+            .setImports(getImports(PropertyAnnotationsTest.class, Target.class, ElementType.class))
+            .addAnnotations(
+                "@AutoValue.CopyAnnotations(exclude={PropertyAnnotationsTest."
+                    + "TestAnnotation.class})",
+                "@Deprecated",
+                "@PropertyAnnotationsTest.TestAnnotation",
+                "@PropertyAnnotationsTest.InheritedAnnotation",
+                "@MethodsOnly")
+            .addInnerTypes("@Target(ElementType.METHOD) @interface MethodsOnly {}")
+            .build();
+
+    JavaFileObject outputFile =
+        new OutputFileBuilder()
+            .setImports(getImports(PropertyAnnotationsTest.class))
+            .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation")
+            .addMethodAnnotations(
+                "@Deprecated",
+                "@PropertyAnnotationsTest.InheritedAnnotation",
+                "@Baz.MethodsOnly")
+            .build();
+
+    assertAbout(javaSource())
+        .that(inputFile)
+        .processedWith(new AutoValueProcessor())
+        .compilesWithoutError()
+        .and()
+        .generatesSources(outputFile);
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java
new file mode 100644
index 0000000..7af240c
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2018 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.processor;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.truth.Expect;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PropertyNamesTest {
+  @Rule public Expect expect = Expect.create();
+
+  private static final ImmutableMap<String, String> NORMAL_CASES =
+      ImmutableMap.<String, String>builder()
+          .put("Foo", "foo")
+          .put("foo", "foo")
+          .put("X", "x")
+          .put("x", "x")
+          .put("", "")
+          .build();
+  
+  @Test
+  public void decapitalizeLikeJavaBeans() {
+    NORMAL_CASES
+        .forEach(
+            (input, output) -> {
+              expect.that(PropertyNames.decapitalizeLikeJavaBeans(input)).isEqualTo(output);
+            });
+    expect.that(PropertyNames.decapitalizeLikeJavaBeans(null)).isNull();
+    expect.that(PropertyNames.decapitalizeLikeJavaBeans("HTMLPage")).isEqualTo("HTMLPage");
+    expect.that(PropertyNames.decapitalizeLikeJavaBeans("OAuth")).isEqualTo("OAuth");
+  }
+
+  @Test
+  public void decapitalizeNormally() {
+    NORMAL_CASES
+        .forEach(
+            (input, output) -> {
+              expect.that(PropertyNames.decapitalizeNormally(input)).isEqualTo(output);
+            });
+    expect.that(PropertyNames.decapitalizeNormally(null)).isNull();
+    expect.that(PropertyNames.decapitalizeNormally("HTMLPage")).isEqualTo("hTMLPage");
+    expect.that(PropertyNames.decapitalizeNormally("OAuth")).isEqualTo("oAuth");
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java b/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java
new file mode 100644
index 0000000..98d31b4
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link Reformatter}.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class ReformatterTest {
+  @Test
+  public void testSimple() {
+    String input =
+        "\n"
+            + "package com.latin.declension;  \n"
+            + "\n"
+            + "\n"
+            + "public  class  Idem  {  \n"
+            + "  \n"
+            + "  Eadem   idem  ;  \n"
+            + "\n"
+            + "  Eundem eandem ( Idem  eiusdem  )  {\n"
+            + "\n"
+            + "    eiusdem (   eiusdem  )  ;  \n"
+            + "\n"
+            + "    eidem_eidem_eidem( ) ;\n"
+            + "\n"
+            + "  }\n"
+            + "\n"
+            + "\n"
+            + "  Eodem ( Eadem eodem ) { }\n";
+    String output =
+            "package com.latin.declension;\n"
+            + "\n"
+            + "public class Idem {\n"
+            + "\n"
+            + "  Eadem idem;\n"
+            + "\n"
+            + "  Eundem eandem (Idem eiusdem) {\n"
+            + "    eiusdem (eiusdem);\n"
+            + "    eidem_eidem_eidem();\n"
+            + "  }\n"
+            + "\n"
+            + "  Eodem (Eadem eodem) { }\n";
+    assertEquals(output, Reformatter.fixup(input));
+  }
+
+  @Test
+  public void testSpecialSpaces() {
+    String input =
+        "\n"
+            + "package com.example.whatever;\n"
+            + "\n"
+            + "public class SomeClass {\n"
+            + "  static final String STRING = \"  hello  world  \\n\";  \n"
+            + "  static final String STRING_WITH_QUOTES = \" \\\"quote  me  now  \\\"  \"  ;\n"
+            + "  static final int INT = /* not a string \" */  23  ;\n"
+            + "  static final char QUOTE = '\"'  ;\n"
+            + "  static final char QUOTE2 = '\\\"'  ;\n"
+            + "}\n";
+    String output =
+            "package com.example.whatever;\n"
+            + "\n"
+            + "public class SomeClass {\n"
+            + "  static final String STRING = \"  hello  world  \\n\";\n"
+            + "  static final String STRING_WITH_QUOTES = \" \\\"quote  me  now  \\\"  \";\n"
+            + "  static final int INT = /* not a string \" */ 23;\n"
+            + "  static final char QUOTE = '\"';\n"
+            + "  static final char QUOTE2 = '\\\"';\n"
+            + "}\n";
+    assertEquals(output, Reformatter.fixup(input));
+  }
+
+  @Test
+  public void noTrailingNewline() {
+    String input = "package com.example.whatever;\n\npublic class SomeClass {}";
+    String output = input + "\n";
+    assertEquals(output, Reformatter.fixup(input));
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java b/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java
new file mode 100644
index 0000000..fab1805
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2019 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.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toList;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class SimpleServiceLoaderTest {
+  @Test
+  public void loadOnce() throws Exception {
+    ClassLoader loader =
+        loaderForJarWithEntries(
+            CharSequence.class.getName(), String.class.getName(), StringBuilder.class.getName());
+
+    ImmutableList<CharSequence> providers = SimpleServiceLoader.load(CharSequence.class, loader);
+
+    // The provider entry for java.lang.String should have caused us to call new String(), which
+    // will produce this "" in the providers.
+    assertThat(providers).contains("");
+    List<Class<?>> classes = providers.stream().map(Object::getClass).collect(toList());
+    assertThat(classes).containsExactly(String.class, StringBuilder.class).inOrder();
+  }
+
+  @Test
+  public void blankLinesAndComments() throws Exception {
+    ClassLoader loader =
+        loaderForJarWithEntries(
+            CharSequence.class.getName(),
+            "",
+            "# this is a comment",
+            "   # this is also a comment",
+            "  java.lang.String  # this is a comment after a class name");
+
+    ImmutableList<CharSequence> providers = SimpleServiceLoader.load(CharSequence.class, loader);
+
+    assertThat(providers).containsExactly("");
+  }
+
+  @Test
+  public void loadTwiceFromSameLoader() throws Exception {
+    ClassLoader loader =
+        loaderForJarWithEntries(
+            CharSequence.class.getName(), String.class.getName(), StringBuilder.class.getName());
+
+    ImmutableList<CharSequence> providers1 = SimpleServiceLoader.load(CharSequence.class, loader);
+    ImmutableList<CharSequence> providers2 = SimpleServiceLoader.load(CharSequence.class, loader);
+
+    List<Class<?>> classes1 = providers1.stream().map(Object::getClass).collect(toList());
+    List<Class<?>> classes2 = providers2.stream().map(Object::getClass).collect(toList());
+    assertThat(classes2).containsExactlyElementsIn(classes1).inOrder();
+  }
+
+  @Test
+  public void loadTwiceFromDifferentLoaders() throws Exception {
+    URL jarUrl =
+        urlForJarWithEntries(
+            CharSequence.class.getName(), String.class.getName(), StringBuilder.class.getName());
+    ClassLoader loader1 = new URLClassLoader(new URL[] {jarUrl});
+
+    ImmutableList<CharSequence> providers1 = SimpleServiceLoader.load(CharSequence.class, loader1);
+    // We should have called `new String()`, so the result should contain "".
+    assertThat(providers1).contains("");
+
+    ClassLoader loader2 = new URLClassLoader(new URL[] {jarUrl});
+    ImmutableList<CharSequence> providers2 = SimpleServiceLoader.load(CharSequence.class, loader2);
+
+    List<Class<?>> classes1 = providers1.stream().map(Object::getClass).collect(toList());
+    List<Class<?>> classes2 = providers2.stream().map(Object::getClass).collect(toList());
+    assertThat(classes2).containsExactlyElementsIn(classes1).inOrder();
+  }
+
+  @Test
+  public void noProviders() throws Exception {
+    ClassLoader loader = loaderForJarWithEntries(CharSequence.class.getName());
+
+    ImmutableList<CharSequence> providers = SimpleServiceLoader.load(CharSequence.class, loader);
+
+    assertThat(providers).isEmpty();
+  }
+
+  @Test
+  public void classNotFound() throws Exception {
+    ClassLoader loader =
+        loaderForJarWithEntries(CharSequence.class.getName(), "this.is.not.a.Class");
+
+    try {
+      SimpleServiceLoader.load(CharSequence.class, loader);
+      fail();
+    } catch (ServiceConfigurationError expected) {
+      assertThat(expected).hasMessageThat().startsWith("Could not load ");
+    }
+  }
+
+  @Test
+  public void wrongTypeClass() throws Exception {
+    ClassLoader loader = loaderForJarWithEntries(CharSequence.class.getName(), "java.lang.Thread");
+
+    try {
+      SimpleServiceLoader.load(CharSequence.class, loader);
+      fail();
+    } catch (ServiceConfigurationError expected) {
+      assertThat(expected).hasMessageThat().startsWith("Class java.lang.Thread is not assignable");
+    }
+  }
+
+  @Test
+  public void couldNotConstruct() throws Exception {
+    ClassLoader loader = loaderForJarWithEntries("java.lang.System", "java.lang.System");
+
+    try {
+      SimpleServiceLoader.load(System.class, loader);
+      fail();
+    } catch (ServiceConfigurationError expected) {
+      assertThat(expected).hasMessageThat().startsWith("Could not construct");
+    }
+  }
+
+  @Test
+  public void brokenLoader() {
+    ClassLoader loader =
+        new URLClassLoader(new URL[0]) {
+          @Override
+          public Enumeration<URL> getResources(String name) throws IOException {
+            throw new IOException("bang");
+          }
+        };
+
+    try {
+      SimpleServiceLoader.load(CharSequence.class, loader);
+      fail();
+    } catch (ServiceConfigurationError expected) {
+      assertThat(expected).hasMessageThat().startsWith("Could not look up");
+      assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("bang");
+    }
+  }
+
+  private static ClassLoader loaderForJarWithEntries(String service, String... lines)
+      throws IOException {
+    URL jarUrl = urlForJarWithEntries(service, lines);
+    return new URLClassLoader(new URL[] {jarUrl});
+  }
+
+  private static URL urlForJarWithEntries(String service, String... lines) throws IOException {
+    File jar = File.createTempFile("SimpleServiceLoaderTest", "jar");
+    jar.deleteOnExit();
+    try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) {
+      JarEntry jarEntry = new JarEntry("META-INF/services/" + service);
+      out.putNextEntry(jarEntry);
+      // It would be bad practice to use try-with-resources below, because closing the PrintWriter
+      // would close the JarOutputStream.
+      PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
+      for (String line : lines) {
+        writer.println(line);
+      }
+      writer.flush();
+    }
+    return jar.toURI().toURL();
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java
new file mode 100644
index 0000000..39e2dc0
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017 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.processor;
+
+import static com.google.common.collect.MoreCollectors.onlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+import static javax.lang.model.util.ElementFilter.fieldsIn;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.truth.Expect;
+import com.google.testing.compile.JavaFileObjects;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.JavaFileObject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * This test verifies the method {@link TypeEncoder#encodeWithAnnotations(TypeMirror).
+ * It takes a list of "type spellings", like {@code @Nullable String}, and compiles a class
+ * with one field for each spelling. So there might be a field {@code @Nullable String x2;}.
+ * Then it examines each compiled field to extract its {@code TypeMirror}, and uses the
+ * {@code TypeSimplifier} method to reconvert that into a string. It should get back the same
+ * type spelling in each case.
+ *
+ * <p>I originally tried to write a less convoluted test using compile-testing. In my test,
+ * each type to be tested was an actual type in the test class (the type of a field, or the
+ * return type of a method). However, I found that if I examined these types by looking up a class
+ * with {@link javax.lang.model.util.Elements#getTypeElement} and following through to the type
+ * of interest, it never had any type annotations.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class SimplifyWithAnnotationsTest {
+  @Rule public final Expect expect = Expect.create();
+
+  /**
+   * The types that we will compile and then recreate. They are referenced in a context where {@code
+   * Set} is unambiguous but not {@code List}, which allows us to test the placement of annotations
+   * in unqualified types like {@code Set<T>} and qualified types like {@code java.util.List<T>}.
+   */
+  private static final ImmutableList<String> TYPE_SPELLINGS =
+      ImmutableList.of(
+          "Object",
+          "Set",
+          "String",
+          "Nullable",
+          "@Nullable String",
+          "String[]",
+          "@Nullable String[]",
+          "String @Nullable []",
+          "String @Nullable [] @Nullable []",
+          "java.awt.List",
+          "java.util.List<String>",
+          "Set<@Nullable String>",
+          "@Nullable Set<String>",
+          "int",
+          "@Nullable int", // whatever that might mean
+          "@Nullable int[]",
+          "int @Nullable []",
+          "T",
+          "@Nullable T",
+          "Set<@Nullable T>",
+          "Set<? extends @Nullable T>",
+          "Set<? extends @Nullable String>",
+          "Set<? extends @Nullable String @Nullable []>",
+          "java.util.@Nullable List<@Nullable T>",
+          "java.util.@Nullable List<java.util.@Nullable List<T>>");
+
+  private static final JavaFileObject NULLABLE_FILE_OBJECT =
+      JavaFileObjects.forSourceLines(
+          "pkg.Nullable",
+          "package pkg;",
+          "",
+          "import java.lang.annotation.ElementType;",
+          "import java.lang.annotation.Target;",
+          "",
+          "@Target(ElementType.TYPE_USE)",
+          "public @interface Nullable {}");
+
+  private static final JavaFileObject TEST_CLASS_FILE_OBJECT =
+      JavaFileObjects.forSourceLines("pkg.TestClass", buildTestClass());
+
+  private static ImmutableList<String> buildTestClass() {
+    // Some older versions of javac don't handle type annotations at all well in annotation
+    // processors. The `witness` method in the generated class is there to detect that, and
+    // skip the test if it is the case.
+    ImmutableList.Builder<String> builder = ImmutableList.builder();
+    builder.add(
+        "package pkg;",
+        "",
+        "import java.util.Set;",
+        "",
+        "public abstract class TestClass<T> {",
+        "  abstract @Nullable T witness();");
+    int i = 0;
+    for (String typeSpelling : TYPE_SPELLINGS) {
+      builder.add(String.format("  %s x%d;\n", typeSpelling, i++));
+    }
+    builder.add("}");
+    return builder.build();
+  }
+
+  @Test
+  public void testSimplifyWithAnnotations() {
+    // The real test happens inside the .compile(...), by virtue of TestProcessor.
+    assertThat(
+            javac()
+                .withOptions("-proc:only")
+                .withProcessors(new TestProcessor())
+                .compile(NULLABLE_FILE_OBJECT, TEST_CLASS_FILE_OBJECT))
+        .succeededWithoutWarnings();
+  }
+
+  @SupportedAnnotationTypes("*")
+  private static class TestProcessor extends AbstractProcessor {
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latest();
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      if (roundEnv.processingOver()) {
+        TypeElement testClass = processingEnv.getElementUtils().getTypeElement("pkg.TestClass");
+        testTypeSpellings(testClass);
+      }
+      return false;
+    }
+
+    void testTypeSpellings(TypeElement testClass) {
+      ExecutableElement witness =
+          ElementFilter.methodsIn(testClass.getEnclosedElements())
+              .stream()
+              .filter(m -> m.getSimpleName().contentEquals("witness"))
+              .collect(onlyElement());
+      if (witness.getReturnType().getAnnotationMirrors().isEmpty()) {
+        System.err.println("SKIPPING TEST BECAUSE OF BUGGY COMPILER");
+        return;
+      }
+      ImmutableMap<String, TypeMirror> typeSpellingToType = typesFromTestClass(testClass);
+      assertThat(typeSpellingToType).isNotEmpty();
+      StringBuilder text = new StringBuilder();
+      StringBuilder expected = new StringBuilder();
+      // Build up a fake source text with the encodings for the types in it, and decode it to
+      // ensure the type spellings are what we expect.
+      typeSpellingToType.forEach(
+          (typeSpelling, type) -> {
+            text.append("{").append(TypeEncoder.encodeWithAnnotations(type)).append("}");
+            expected.append("{").append(typeSpelling).append("}");
+          });
+      String decoded = TypeEncoder.decode(text.toString(), processingEnv, "pkg", null);
+      assertThat(decoded).isEqualTo(expected.toString());
+    }
+
+    private static ImmutableMap<String, TypeMirror> typesFromTestClass(TypeElement type) {
+      // Reads the types of the fields from the compiled TestClass and uses them to produce
+      // a map from type spellings to types. This method depends on type.getEnclosedElements()
+      // returning the fields in source order, which it is specified to do.
+      ImmutableMap.Builder<String, TypeMirror> typeSpellingToTypeBuilder = ImmutableMap.builder();
+      int i = 0;
+      for (VariableElement field : fieldsIn(type.getEnclosedElements())) {
+        String spelling = TYPE_SPELLINGS.get(i);
+        typeSpellingToTypeBuilder.put(spelling, field.asType());
+        i++;
+      }
+      return typeSpellingToTypeBuilder.build();
+    }
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/TemplateVarsTest.java b/value/src/test/java/com/google/auto/value/processor/TemplateVarsTest.java
new file mode 100644
index 0000000..bca7bca
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/TemplateVarsTest.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2014 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.processor;
+
+import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
+import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.logging.Level.WARNING;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.Reflection;
+import com.google.escapevelocity.Template;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.logging.Logger;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for FieldReader.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class TemplateVarsTest {
+  static class HappyVars extends TemplateVars {
+    Integer integer;
+    String string;
+    List<Integer> list;
+    private static final String IGNORED_STATIC_FINAL = "hatstand";
+
+    @Override
+    Template parsedTemplate() {
+      return parsedTemplateForString("integer=$integer string=$string list=$list");
+    }
+  }
+
+  static Template parsedTemplateForString(String string) {
+    try {
+      Reader reader = new StringReader(string);
+      return Template.parseFrom(reader);
+    } catch (IOException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  @Test
+  public void testHappy() {
+    HappyVars happy = new HappyVars();
+    happy.integer = 23;
+    happy.string = "wibble";
+    happy.list = ImmutableList.of(5, 17, 23);
+    assertThat(HappyVars.IGNORED_STATIC_FINAL).isEqualTo("hatstand"); // avoids unused warning
+    String expectedText = "integer=23 string=wibble list=[5, 17, 23]";
+    String actualText = happy.toText();
+    assertThat(actualText).isEqualTo(expectedText);
+  }
+
+  @Test
+  public void testUnset() {
+    HappyVars sad = new HappyVars();
+    sad.integer = 23;
+    sad.list = ImmutableList.of(23);
+    try {
+      sad.toText();
+      fail("Did not get expected exception");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  static class SubHappyVars extends HappyVars {
+    Character character;
+
+    @Override
+    Template parsedTemplate() {
+      return parsedTemplateForString(
+          "integer=$integer string=$string list=$list character=$character");
+    }
+  }
+
+  @Test
+  public void testSubSub() {
+    SubHappyVars vars = new SubHappyVars();
+    vars.integer = 23;
+    vars.string = "wibble";
+    vars.list = ImmutableList.of(5, 17, 23);
+    vars.character = 'ß';
+    String expectedText = "integer=23 string=wibble list=[5, 17, 23] character=ß";
+    String actualText = vars.toText();
+    assertThat(actualText).isEqualTo(expectedText);
+  }
+
+  static class Private extends TemplateVars {
+    Integer integer;
+    private String unusedString;
+
+    @Override
+    Template parsedTemplate() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  @Test
+  public void testPrivate() {
+    try {
+      new Private();
+      fail("Did not get expected exception");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  static class Static extends TemplateVars {
+    Integer integer;
+    static String string;
+
+    @Override
+    Template parsedTemplate() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  @Test
+  public void testStatic() {
+    try {
+      new Static();
+      fail("Did not get expected exception");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  static class Primitive extends TemplateVars {
+    int integer;
+    String string;
+
+    @Override
+    Template parsedTemplate() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  @Test
+  public void testPrimitive() {
+    try {
+      new Primitive();
+      fail("Did not get expected exception");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test
+  public void testBrokenInputStream_IOException() throws Exception {
+    doTestBrokenInputStream(new IOException("BrokenInputStream"));
+  }
+
+  @Test
+  public void testBrokenInputStream_NullPointerException() throws Exception {
+    doTestBrokenInputStream(new NullPointerException("BrokenInputStream"));
+  }
+
+  @Test
+  public void testBrokenInputStream_IllegalStateException() throws Exception {
+    doTestBrokenInputStream(new IllegalStateException("BrokenInputStream"));
+  }
+
+  // This is a complicated test that tries to simulates the failures that are worked around in
+  // Template.parsedTemplateForResource. Those failures means that the InputStream returned by
+  // ClassLoader.getResourceAsStream sometimes throws IOException or NullPointerException or
+  // IllegalStateException while it is being read. To simulate that, we make a second ClassLoader
+  // with the same configuration as the one that runs this test, and we override getResourceAsStream
+  // so that it wraps the returned InputStream in a BrokenInputStream, which throws an exception
+  // after a certain number of characters. We check that that exception was indeed seen, and that
+  // we did indeed try to read the resource we're interested in, and that we succeeded in loading a
+  // Template nevertheless.
+  private void doTestBrokenInputStream(Exception exception) throws Exception {
+    URLClassLoader shadowLoader = new ShadowLoader(getClass().getClassLoader(), exception);
+    Runnable brokenInputStreamTest =
+        (Runnable)
+            shadowLoader
+                .loadClass(BrokenInputStreamTest.class.getName())
+                .getConstructor()
+                .newInstance();
+    brokenInputStreamTest.run();
+  }
+
+  private static class ShadowLoader extends URLClassLoader implements Callable<Set<String>> {
+
+    private static final Logger logger = Logger.getLogger(ShadowLoader.class.getName());
+
+    private final Exception exception;
+    private final Set<String> result = new TreeSet<String>();
+
+    ShadowLoader(ClassLoader original, Exception exception) {
+      super(getClassPathUrls(original), original.getParent());
+      this.exception = exception;
+    }
+
+    private static URL[] getClassPathUrls(ClassLoader original) {
+      return original instanceof URLClassLoader
+          ? ((URLClassLoader) original).getURLs()
+          : parseJavaClassPath();
+    }
+
+    /**
+     * Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain
+     * System#getProperty system property}.
+     */
+    // TODO(b/65488446): Use a new public API.
+    private static URL[] parseJavaClassPath() {
+      ImmutableList.Builder<URL> urls = ImmutableList.builder();
+      for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
+        try {
+          try {
+            urls.add(new File(entry).toURI().toURL());
+          } catch (SecurityException e) { // File.toURI checks to see if the file is a directory
+            urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
+          }
+        } catch (MalformedURLException e) {
+          logger.log(WARNING, "malformed classpath entry: " + entry, e);
+        }
+      }
+      return urls.build().toArray(new URL[0]);
+    }
+
+    @Override
+    public Set<String> call() throws Exception {
+      return result;
+    }
+
+    @Override
+    public InputStream getResourceAsStream(String resource) {
+      // Make sure this is actually the resource we are expecting. If we're using JaCoCo or the
+      // like, we might end up reading some other resource, and we don't want to break that.
+      if (resource.startsWith("com/google/auto")) {
+        result.add(resource);
+        return new BrokenInputStream(super.getResourceAsStream(resource));
+      } else {
+        return super.getResourceAsStream(resource);
+      }
+    }
+
+    private class BrokenInputStream extends InputStream {
+      private final InputStream original;
+      private int count = 0;
+
+      BrokenInputStream(InputStream original) {
+        this.original = original;
+      }
+
+      @Override
+      public int read() throws IOException {
+        if (++count > 10) {
+          result.add("threw");
+          if (exception instanceof IOException) {
+            throw (IOException) exception;
+          }
+          throw (RuntimeException) exception;
+        }
+        return original.read();
+      }
+    }
+  }
+
+  public static class BrokenInputStreamTest implements Runnable {
+    @Override
+    public void run() {
+      Template template = TemplateVars.parsedTemplateForResource("autovalue.vm");
+      assertThat(template).isNotNull();
+      String resourceName =
+          Reflection.getPackageName(getClass()).replace('.', '/') + "/autovalue.vm";
+      @SuppressWarnings("unchecked")
+      Callable<Set<String>> myLoader = (Callable<Set<String>>) getClass().getClassLoader();
+      try {
+        Set<String> result = myLoader.call();
+        assertThat(result).contains(resourceName);
+        assertThat(result).contains("threw");
+      } catch (Exception e) {
+        throw new AssertionError(e);
+      }
+    }
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java
new file mode 100644
index 0000000..c31f711
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright 2017 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.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+import static java.util.stream.Collectors.joining;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.CompilationRule;
+import com.google.testing.compile.JavaFileObjects;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link TypeEncoder}.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class TypeEncoderTest {
+  @Rule public final CompilationRule compilationRule = new CompilationRule();
+
+  private Types typeUtils;
+  private Elements elementUtils;
+
+  @Before
+  public void setUp() {
+    typeUtils = compilationRule.getTypes();
+    elementUtils = compilationRule.getElements();
+  }
+
+  /**
+   * Assert that the fake program returned by fakeProgramForTypes has the given list of imports and
+   * the given list of spellings. Here, "spellings" means the way each type is referenced in the
+   * decoded program, for example {@code Timer} if {@code java.util.Timer} can be imported, or
+   * {@code java.util.Timer} if not.
+   *
+   * <p>We construct a fake program that references each of the given types in turn.
+   * TypeEncoder.decode doesn't have any real notion of Java syntax, so our program just consists of
+   * START and END markers around the {@code `import`} tag, followed by each type in braces, as
+   * encoded by TypeEncoder.encode. Once decoded, the program should consist of the appropriate
+   * imports (inside START...END) and each type in braces, spelled appropriately.
+   *
+   * @param fakePackage the package that TypeEncoder should consider the fake program to be in.
+   *     Classes in the same package don't usually need to be imported.
+   */
+  private void assertTypeImportsAndSpellings(
+      Set<TypeMirror> types, String fakePackage, List<String> imports, List<String> spellings) {
+    String fakeProgram =
+        "START\n`import`\nEND\n"
+            + types.stream().map(TypeEncoder::encode).collect(joining("}\n{", "{", "}"));
+    String decoded =
+        TypeEncoder.decode(
+            fakeProgram, elementUtils, typeUtils, fakePackage, baseWithoutContainedTypes());
+    String expected =
+        "START\n"
+            + imports.stream().map(s -> "import " + s + ";\n").collect(joining())
+            + "\nEND\n"
+            + spellings.stream().collect(joining("}\n{", "{", "}"));
+    assertThat(decoded).isEqualTo(expected);
+  }
+
+  private static class MultipleBounds<K extends List<V> & Comparable<K>, V> {}
+
+  @Test
+  public void testImportsForNoTypes() {
+    assertTypeImportsAndSpellings(
+        typeMirrorSet(), "foo.bar", ImmutableList.of(), ImmutableList.of());
+  }
+
+  @Test
+  public void testImportsForImplicitlyImportedTypes() {
+    Set<TypeMirror> types =
+        typeMirrorSet(
+            typeMirrorOf(java.lang.String.class),
+            typeMirrorOf(javax.management.MBeanServer.class), // Same package, so no import.
+            typeUtils.getPrimitiveType(TypeKind.INT),
+            typeUtils.getPrimitiveType(TypeKind.BOOLEAN));
+    assertTypeImportsAndSpellings(
+        types,
+        "javax.management",
+        ImmutableList.of(),
+        ImmutableList.of("String", "MBeanServer", "int", "boolean"));
+  }
+
+  @Test
+  public void testImportsForPlainTypes() {
+    Set<TypeMirror> types =
+        typeMirrorSet(
+            typeUtils.getPrimitiveType(TypeKind.INT),
+            typeMirrorOf(java.lang.String.class),
+            typeMirrorOf(java.net.Proxy.class),
+            typeMirrorOf(java.net.Proxy.Type.class),
+            typeMirrorOf(java.util.regex.Pattern.class),
+            typeMirrorOf(javax.management.MBeanServer.class));
+    assertTypeImportsAndSpellings(
+        types,
+        "foo.bar",
+        ImmutableList.of(
+            "java.net.Proxy", "java.util.regex.Pattern", "javax.management.MBeanServer"),
+        ImmutableList.of("int", "String", "Proxy", "Proxy.Type", "Pattern", "MBeanServer"));
+  }
+
+  @Test
+  public void testImportsForComplicatedTypes() {
+    TypeElement list = typeElementOf(java.util.List.class);
+    TypeElement map = typeElementOf(java.util.Map.class);
+    Set<TypeMirror> types =
+        typeMirrorSet(
+            typeUtils.getPrimitiveType(TypeKind.INT),
+            typeMirrorOf(java.util.regex.Pattern.class),
+            typeUtils.getDeclaredType(
+                list, // List<Timer>
+                typeMirrorOf(java.util.Timer.class)),
+            typeUtils.getDeclaredType(
+                map, // Map<? extends Timer, ? super BigInteger>
+                typeUtils.getWildcardType(typeMirrorOf(java.util.Timer.class), null),
+                typeUtils.getWildcardType(null, typeMirrorOf(java.math.BigInteger.class))));
+    // Timer is referenced twice but should obviously only be imported once.
+    assertTypeImportsAndSpellings(
+        types,
+        "foo.bar",
+        ImmutableList.of(
+            "java.math.BigInteger",
+            "java.util.List",
+            "java.util.Map",
+            "java.util.Timer",
+            "java.util.regex.Pattern"),
+        ImmutableList.of(
+            "int", "Pattern", "List<Timer>", "Map<? extends Timer, ? super BigInteger>"));
+  }
+
+  @Test
+  public void testImportsForArrayTypes() {
+    TypeElement list = typeElementOf(java.util.List.class);
+    TypeElement set = typeElementOf(java.util.Set.class);
+    Set<TypeMirror> types =
+        typeMirrorSet(
+            typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.INT)),
+            typeUtils.getArrayType(typeMirrorOf(java.util.regex.Pattern.class)),
+            typeUtils.getArrayType( // Set<Matcher[]>[]
+                typeUtils.getDeclaredType(
+                    set, typeUtils.getArrayType(typeMirrorOf(java.util.regex.Matcher.class)))),
+            typeUtils.getDeclaredType(
+                list, // List<Timer[]>
+                typeUtils.getArrayType(typeMirrorOf(java.util.Timer.class))));
+    // Timer is referenced twice but should obviously only be imported once.
+    assertTypeImportsAndSpellings(
+        types,
+        "foo.bar",
+        ImmutableList.of(
+            "java.util.List",
+            "java.util.Set",
+            "java.util.Timer",
+            "java.util.regex.Matcher",
+            "java.util.regex.Pattern"),
+        ImmutableList.of("int[]", "Pattern[]", "Set<Matcher[]>[]", "List<Timer[]>"));
+  }
+
+  @Test
+  public void testImportNestedType() {
+    Set<TypeMirror> types = typeMirrorSet(typeMirrorOf(java.net.Proxy.Type.class));
+    assertTypeImportsAndSpellings(
+        types, "foo.bar", ImmutableList.of("java.net.Proxy"), ImmutableList.of("Proxy.Type"));
+  }
+
+  @Test
+  public void testImportsForAmbiguousNames() {
+    TypeMirror wildcard = typeUtils.getWildcardType(null, null);
+    Set<TypeMirror> types =
+        typeMirrorSet(
+            typeUtils.getPrimitiveType(TypeKind.INT),
+            typeMirrorOf(java.awt.List.class),
+            typeMirrorOf(java.lang.String.class),
+            typeUtils.getDeclaredType( // List<?>
+                typeElementOf(java.util.List.class), wildcard),
+            typeUtils.getDeclaredType( // Map<?, ?>
+                typeElementOf(java.util.Map.class), wildcard, wildcard));
+    assertTypeImportsAndSpellings(
+        types,
+        "foo.bar",
+        ImmutableList.of("java.util.Map"),
+        ImmutableList.of("int", "java.awt.List", "String", "java.util.List<?>", "Map<?, ?>"));
+  }
+
+  @Test
+  public void testSimplifyJavaLangString() {
+    Set<TypeMirror> types = typeMirrorSet(typeMirrorOf(java.lang.String.class));
+    assertTypeImportsAndSpellings(types, "foo.bar", ImmutableList.of(), ImmutableList.of("String"));
+  }
+
+  @Test
+  public void testSimplifyJavaLangThreadState() {
+    Set<TypeMirror> types = typeMirrorSet(typeMirrorOf(java.lang.Thread.State.class));
+    assertTypeImportsAndSpellings(
+        types, "foo.bar", ImmutableList.of(), ImmutableList.of("Thread.State"));
+  }
+
+  @Test
+  public void testSimplifyJavaLangNamesake() {
+    TypeMirror javaLangType = typeMirrorOf(java.lang.RuntimePermission.class);
+    TypeMirror notJavaLangType =
+        typeMirrorOf(com.google.auto.value.processor.testclasses.RuntimePermission.class);
+    Set<TypeMirror> types = typeMirrorSet(javaLangType, notJavaLangType);
+    assertTypeImportsAndSpellings(
+        types,
+        "foo.bar",
+        ImmutableList.of(),
+        ImmutableList.of(javaLangType.toString(), notJavaLangType.toString()));
+  }
+
+  @Test
+  public void testSimplifyComplicatedTypes() {
+    // This test constructs a set of types and feeds them to TypeEncoder. Then it verifies that
+    // the resultant rewrites of those types are what we would expect.
+    TypeElement list = typeElementOf(java.util.List.class);
+    TypeElement map = typeElementOf(java.util.Map.class);
+    TypeMirror string = typeMirrorOf(java.lang.String.class);
+    TypeMirror integer = typeMirrorOf(java.lang.Integer.class);
+    TypeMirror pattern = typeMirrorOf(java.util.regex.Pattern.class);
+    TypeMirror timer = typeMirrorOf(java.util.Timer.class);
+    TypeMirror bigInteger = typeMirrorOf(java.math.BigInteger.class);
+    ImmutableMap<TypeMirror, String> typeMap =
+        ImmutableMap.<TypeMirror, String>builder()
+            .put(typeUtils.getPrimitiveType(TypeKind.INT), "int")
+            .put(typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.BYTE)), "byte[]")
+            .put(pattern, "Pattern")
+            .put(typeUtils.getArrayType(pattern), "Pattern[]")
+            .put(typeUtils.getArrayType(typeUtils.getArrayType(pattern)), "Pattern[][]")
+            .put(typeUtils.getDeclaredType(list, typeUtils.getWildcardType(null, null)), "List<?>")
+            .put(typeUtils.getDeclaredType(list, timer), "List<Timer>")
+            .put(typeUtils.getDeclaredType(map, string, integer), "Map<String, Integer>")
+            .put(
+                typeUtils.getDeclaredType(
+                    map,
+                    typeUtils.getWildcardType(timer, null),
+                    typeUtils.getWildcardType(null, bigInteger)),
+                "Map<? extends Timer, ? super BigInteger>")
+            .build();
+    assertTypeImportsAndSpellings(
+        typeMap.keySet(),
+        "foo.bar",
+        ImmutableList.of(
+            "java.math.BigInteger",
+            "java.util.List",
+            "java.util.Map",
+            "java.util.Timer",
+            "java.util.regex.Pattern"),
+        ImmutableList.copyOf(typeMap.values()));
+  }
+
+  @Test
+  public void testSimplifyMultipleBounds() {
+    TypeElement multipleBoundsElement = typeElementOf(MultipleBounds.class);
+    TypeMirror multipleBoundsMirror = multipleBoundsElement.asType();
+    String text = "`import`\n";
+    text += "{" + TypeEncoder.encode(multipleBoundsMirror) + "}";
+    text += "{" + TypeEncoder.formalTypeParametersString(multipleBoundsElement) + "}";
+    String myPackage = getClass().getPackage().getName();
+    String decoded =
+        TypeEncoder.decode(text, elementUtils, typeUtils, myPackage, baseWithoutContainedTypes());
+    String expected =
+        "import java.util.List;\n\n"
+            + "{TypeEncoderTest.MultipleBounds<K, V>}"
+            + "{<K extends List<V> & Comparable<K>, V>}";
+    assertThat(decoded).isEqualTo(expected);
+  }
+
+  @SuppressWarnings("ClassCanBeStatic")
+  static class Outer<T extends Number> {
+    class InnerWithoutTypeParam {}
+    class Middle<U> {
+      class InnerWithTypeParam<V> {}
+    }
+  }
+
+  @Test
+  public void testOuterParameterizedInnerNot() {
+    TypeElement outerElement = typeElementOf(Outer.class);
+    DeclaredType doubleMirror = typeMirrorOf(Double.class);
+    DeclaredType outerOfDoubleMirror = typeUtils.getDeclaredType(outerElement, doubleMirror);
+    TypeElement innerWithoutTypeParamElement = typeElementOf(Outer.InnerWithoutTypeParam.class);
+    DeclaredType parameterizedInnerWithoutTypeParam =
+        typeUtils.getDeclaredType(outerOfDoubleMirror, innerWithoutTypeParamElement);
+    String encoded = TypeEncoder.encode(parameterizedInnerWithoutTypeParam);
+    String myPackage = getClass().getPackage().getName();
+    String decoded =
+        TypeEncoder.decode(
+            encoded, elementUtils, typeUtils, myPackage, baseWithoutContainedTypes());
+    String expected = "TypeEncoderTest.Outer<Double>.InnerWithoutTypeParam";
+    assertThat(decoded).isEqualTo(expected);
+  }
+
+  @Test
+  public void testOuterParameterizedInnerAlso() {
+    TypeElement outerElement = typeElementOf(Outer.class);
+    DeclaredType doubleMirror = typeMirrorOf(Double.class);
+    DeclaredType outerOfDoubleMirror = typeUtils.getDeclaredType(outerElement, doubleMirror);
+    TypeElement middleElement = typeElementOf(Outer.Middle.class);
+    DeclaredType stringMirror = typeMirrorOf(String.class);
+    DeclaredType middleOfStringMirror =
+        typeUtils.getDeclaredType(outerOfDoubleMirror, middleElement, stringMirror);
+    TypeElement innerWithTypeParamElement = typeElementOf(Outer.Middle.InnerWithTypeParam.class);
+    DeclaredType integerMirror = typeMirrorOf(Integer.class);
+    DeclaredType parameterizedInnerWithTypeParam =
+        typeUtils.getDeclaredType(middleOfStringMirror, innerWithTypeParamElement, integerMirror);
+    String encoded = TypeEncoder.encode(parameterizedInnerWithTypeParam);
+    String myPackage = getClass().getPackage().getName();
+    String decoded =
+        TypeEncoder.decode(
+            encoded, elementUtils, typeUtils, myPackage, baseWithoutContainedTypes());
+    String expected = "TypeEncoderTest.Outer<Double>.Middle<String>.InnerWithTypeParam<Integer>";
+    assertThat(decoded).isEqualTo(expected);
+  }
+
+  private static Set<TypeMirror> typeMirrorSet(TypeMirror... typeMirrors) {
+    Set<TypeMirror> set = new TypeMirrorSet();
+    for (TypeMirror typeMirror : typeMirrors) {
+      assertThat(set.add(typeMirror)).isTrue();
+    }
+    return set;
+  }
+
+  private TypeElement typeElementOf(Class<?> c) {
+    return elementUtils.getTypeElement(c.getCanonicalName());
+  }
+
+  private DeclaredType typeMirrorOf(Class<?> c) {
+    return MoreTypes.asDeclared(typeElementOf(c).asType());
+  }
+
+  /**
+   * Returns a "base type" for TypeSimplifier that does not contain any nested types. The point
+   * being that every {@code TypeSimplifier} has a base type that the class being generated is going
+   * to extend, and if that class has nested types they will be in scope, and therefore a possible
+   * source of ambiguity.
+   */
+  private TypeMirror baseWithoutContainedTypes() {
+    return typeMirrorOf(Object.class);
+  }
+
+  // This test checks that we correctly throw MissingTypeException if there is an ErrorType anywhere
+  // inside a type we are asked to simplify. There's no way to get an ErrorType from typeUtils or
+  // elementUtils, so we need to fire up the compiler with an erroneous source file and use an
+  // annotation processor to capture the resulting ErrorType. Then we can run tests within that
+  // annotation processor, and propagate any failures out of this test.
+  @Test
+  public void testErrorTypes() {
+    JavaFileObject source =
+        JavaFileObjects.forSourceString(
+            "ExtendsUndefinedType", "class ExtendsUndefinedType extends UndefinedParent {}");
+    Compilation compilation = javac().withProcessors(new ErrorTestProcessor()).compile(source);
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorContaining("UndefinedParent");
+    assertThat(compilation).hadErrorCount(1);
+  }
+
+  @SupportedAnnotationTypes("*")
+  private static class ErrorTestProcessor extends AbstractProcessor {
+    Types typeUtils;
+    Elements elementUtils;
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      if (roundEnv.processingOver()) {
+        typeUtils = processingEnv.getTypeUtils();
+        elementUtils = processingEnv.getElementUtils();
+        test();
+      }
+      return false;
+    }
+
+    private void test() {
+      TypeElement extendsUndefinedType = elementUtils.getTypeElement("ExtendsUndefinedType");
+      ErrorType errorType = (ErrorType) extendsUndefinedType.getSuperclass();
+      TypeElement list = elementUtils.getTypeElement("java.util.List");
+      TypeMirror listOfError = typeUtils.getDeclaredType(list, errorType);
+      TypeMirror queryExtendsError = typeUtils.getWildcardType(errorType, null);
+      TypeMirror listOfQueryExtendsError = typeUtils.getDeclaredType(list, queryExtendsError);
+      TypeMirror querySuperError = typeUtils.getWildcardType(null, errorType);
+      TypeMirror listOfQuerySuperError = typeUtils.getDeclaredType(list, querySuperError);
+      TypeMirror arrayOfError = typeUtils.getArrayType(errorType);
+      testErrorType(errorType);
+      testErrorType(listOfError);
+      testErrorType(listOfQueryExtendsError);
+      testErrorType(listOfQuerySuperError);
+      testErrorType(arrayOfError);
+    }
+
+    @SuppressWarnings("MissingFail") // error message gets converted into assertion failure
+    private void testErrorType(TypeMirror typeWithError) {
+      try {
+        TypeEncoder.encode(typeWithError);
+        processingEnv
+            .getMessager()
+            .printMessage(Diagnostic.Kind.ERROR, "Expected exception for type: " + typeWithError);
+      } catch (MissingTypeException expected) {
+      }
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/TypeSimplifierTest.java b/value/src/test/java/com/google/auto/value/processor/TypeSimplifierTest.java
new file mode 100644
index 0000000..73c7159
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/TypeSimplifierTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2012 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.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.testing.compile.CompilationRule;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link TypeSimplifier}.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class TypeSimplifierTest {
+  @Rule public final CompilationRule compilationRule = new CompilationRule();
+
+  private Types typeUtils;
+  private Elements elementUtils;
+
+  @Before
+  public void setUp() {
+    typeUtils = compilationRule.getTypes();
+    elementUtils = compilationRule.getElements();
+  }
+
+  private static class Erasure<T> {
+    int intNo;
+    boolean booleanNo;
+    int[] intArrayNo;
+    String stringNo;
+    String[] stringArrayNo;
+
+    @SuppressWarnings("rawtypes")
+    List rawListNo;
+
+    List<?> listOfQueryNo;
+    List<? extends Object> listOfQueryExtendsObjectNo;
+    Map<?, ?> mapQueryToQueryNo;
+
+    List<String> listOfStringYes;
+    List<? extends String> listOfQueryExtendsStringYes;
+    List<? super String> listOfQuerySuperStringYes;
+    List<T> listOfTypeVarYes;
+    List<? extends T> listOfQueryExtendsTypeVarYes;
+    List<? super T> listOfQuerySuperTypeVarYes;
+  }
+
+  private abstract static class Wildcards {
+    abstract <T extends V, U extends T, V> Map<? extends T, ? super U> one();
+
+    abstract <T extends V, U extends T, V> Map<? extends T, ? super U> two();
+  }
+
+  /**
+   * This test shows why we need to have TypeMirrorSet. The mirror of java.lang.Object obtained from
+   * {@link Elements#getTypeElement Elements.getTypeElement("java.lang.Object")} does not compare
+   * equal to the mirror of the return type of Object.clone(), even though that is also
+   * java.lang.Object and {@link Types#isSameType} considers them the same.
+   *
+   * <p>There's no requirement that this test must pass and if it starts failing or doesn't work in
+   * another test environment then we can delete it. The specification of {@link TypeMirror#equals}
+   * explicitly says that it cannot be used for type equality, so even if this particular case stops
+   * being a problem (which means this test would fail), we would need TypeMirrorSet for complete
+   * correctness.
+   */
+  @Test
+  public void testQuirkyTypeMirrors() {
+    TypeMirror objectMirror = objectMirror();
+    TypeMirror cloneReturnTypeMirror = cloneReturnTypeMirror();
+    assertThat(objectMirror).isNotEqualTo(cloneReturnTypeMirror);
+    assertThat(typeUtils.isSameType(objectMirror, cloneReturnTypeMirror)).isTrue();
+  }
+
+  @Test
+  @SuppressWarnings("TypeEquals") // We want to test the equals method invocation on TypeMirror.
+  public void testTypeMirrorSet() {
+    // Test the TypeMirrorSet methods. Resist the temptation to rewrite these in terms of
+    // Truth operations! For example, don't change assertThat(set.size()).isEqualTo(0) into
+    // assertThat(set).isEmpty(), because then we wouldn't be testing size().
+    TypeMirror objectMirror = objectMirror();
+    TypeMirror otherObjectMirror = cloneReturnTypeMirror();
+    Set<TypeMirror> set = new TypeMirrorSet();
+    assertThat(set.size()).isEqualTo(0);
+    assertThat(set.isEmpty()).isTrue();
+    boolean added = set.add(objectMirror);
+    assertThat(added).isTrue();
+    assertThat(set.size()).isEqualTo(1);
+
+    Set<TypeMirror> otherSet = typeMirrorSet(otherObjectMirror);
+    assertThat(otherSet).isEqualTo(set);
+    assertThat(set).isEqualTo(otherSet);
+    assertThat(otherSet.hashCode()).isEqualTo(set.hashCode());
+
+    assertThat(set.add(otherObjectMirror)).isFalse();
+    assertThat(set.contains(otherObjectMirror)).isTrue();
+
+    assertThat(set.contains(null)).isFalse();
+    assertThat(set.contains((Object) "foo")).isFalse();
+    assertThat(set.remove(null)).isFalse();
+    assertThat(set.remove((Object) "foo")).isFalse();
+
+    TypeElement list = typeElementOf(java.util.List.class);
+    TypeMirror listOfObjectMirror = typeUtils.getDeclaredType(list, objectMirror);
+    TypeMirror listOfOtherObjectMirror = typeUtils.getDeclaredType(list, otherObjectMirror);
+    assertThat(listOfObjectMirror.equals(listOfOtherObjectMirror)).isFalse();
+    assertThat(typeUtils.isSameType(listOfObjectMirror, listOfOtherObjectMirror)).isTrue();
+    added = set.add(listOfObjectMirror);
+    assertThat(added).isTrue();
+    assertThat(set.size()).isEqualTo(2);
+    assertThat(set.add(listOfOtherObjectMirror)).isFalse();
+    assertThat(set.contains(listOfOtherObjectMirror)).isTrue();
+
+    boolean removed = set.remove(listOfOtherObjectMirror);
+    assertThat(removed).isTrue();
+    assertThat(set.contains(listOfObjectMirror)).isFalse();
+
+    set.removeAll(otherSet);
+    assertThat(set.isEmpty()).isTrue();
+  }
+
+  @Test
+  public void testTypeMirrorSetWildcardCapture() {
+    // TODO(emcmanus): this test should really be in MoreTypesTest.
+    // This test checks the assumption made by MoreTypes that you can find the
+    // upper bounds of a TypeVariable tv like this:
+    //   TypeParameterElement tpe = (TypeParameterElement) tv.asElement();
+    //   List<? extends TypeMirror> bounds = tpe.getBounds();
+    // There was some doubt as to whether this would be true in exotic cases involving
+    // wildcard capture, but apparently it is.
+    // The methods one and two here have identical signatures:
+    //   abstract <T extends V, U extends T, V> Map<? extends T, ? super U> name();
+    // Their return types should be considered equal by TypeMirrorSet. The capture of
+    // each return type is different from the original return type, but the two captures
+    // should compare equal to each other. We also add various other types like ? super U
+    // to the set to ensure that the implied calls to the equals and hashCode visitors
+    // don't cause a ClassCastException for TypeParameterElement.
+    TypeElement wildcardsElement = typeElementOf(Wildcards.class);
+    List<? extends ExecutableElement> methods =
+        ElementFilter.methodsIn(wildcardsElement.getEnclosedElements());
+    assertThat(methods).hasSize(2);
+    ExecutableElement one = methods.get(0);
+    ExecutableElement two = methods.get(1);
+    assertThat(one.getSimpleName().toString()).isEqualTo("one");
+    assertThat(two.getSimpleName().toString()).isEqualTo("two");
+    TypeMirrorSet typeMirrorSet = new TypeMirrorSet();
+    assertThat(typeMirrorSet.add(one.getReturnType())).isTrue();
+    assertThat(typeMirrorSet.add(two.getReturnType())).isFalse();
+    DeclaredType captureOne = (DeclaredType) typeUtils.capture(one.getReturnType());
+    assertThat(typeMirrorSet.add(captureOne)).isTrue();
+    DeclaredType captureTwo = (DeclaredType) typeUtils.capture(two.getReturnType());
+    assertThat(typeMirrorSet.add(captureTwo)).isFalse();
+    // Reminder: captureOne is Map<?#123 extends T, ?#456 super U>
+    TypeVariable extendsT = (TypeVariable) captureOne.getTypeArguments().get(0);
+    assertThat(typeMirrorSet.add(extendsT)).isTrue();
+    assertThat(typeMirrorSet.add(extendsT.getLowerBound())).isTrue(); // NoType
+    for (TypeMirror bound : ((TypeParameterElement) extendsT.asElement()).getBounds()) {
+      assertThat(typeMirrorSet.add(bound)).isTrue();
+    }
+    TypeVariable superU = (TypeVariable) captureOne.getTypeArguments().get(1);
+    assertThat(typeMirrorSet.add(superU)).isTrue();
+    assertThat(typeMirrorSet.add(superU.getLowerBound())).isTrue();
+  }
+
+  @Test
+  public void testPackageNameOfString() {
+    assertThat(TypeSimplifier.packageNameOf(typeElementOf(java.lang.String.class)))
+        .isEqualTo("java.lang");
+  }
+
+  @Test
+  public void testPackageNameOfMapEntry() {
+    assertThat(TypeSimplifier.packageNameOf(typeElementOf(java.util.Map.Entry.class)))
+        .isEqualTo("java.util");
+  }
+
+  // Test TypeSimplifier.isCastingUnchecked. We do this by examining the fields of the Erasure
+  // class. A field whose name ends with Yes has a type where
+  // isCastingUnchecked should return true, and one whose name ends with No has a type where
+  // isCastingUnchecked should return false.
+  @Test
+  public void testIsCastingUnchecked() {
+    TypeElement erasureClass = typeElementOf(Erasure.class);
+    List<VariableElement> fields = ElementFilter.fieldsIn(erasureClass.getEnclosedElements());
+    assertThat(fields).isNotEmpty();
+    for (VariableElement field : fields) {
+      String fieldName = field.getSimpleName().toString();
+      boolean expectUnchecked;
+      if (fieldName.endsWith("Yes")) {
+        expectUnchecked = true;
+      } else if (fieldName.endsWith("No")) {
+        expectUnchecked = false;
+      } else {
+        throw new AssertionError("Fields in Erasure class must end with Yes or No: " + fieldName);
+      }
+      TypeMirror fieldType = field.asType();
+      boolean actualUnchecked = TypeSimplifier.isCastingUnchecked(fieldType);
+      assertWithMessage("Unchecked-cast status for " + fieldType)
+          .that(actualUnchecked)
+          .isEqualTo(expectUnchecked);
+    }
+  }
+
+  private static Set<TypeMirror> typeMirrorSet(TypeMirror... typeMirrors) {
+    Set<TypeMirror> set = new TypeMirrorSet();
+    for (TypeMirror typeMirror : typeMirrors) {
+      assertThat(set.add(typeMirror)).isTrue();
+    }
+    return set;
+  }
+
+  private TypeMirror objectMirror() {
+    return typeMirrorOf(Object.class);
+  }
+
+  private TypeMirror cloneReturnTypeMirror() {
+    TypeElement object = typeElementOf(Object.class);
+    ExecutableElement clone = null;
+    for (Element element : object.getEnclosedElements()) {
+      if (element.getSimpleName().contentEquals("clone")) {
+        clone = (ExecutableElement) element;
+        break;
+      }
+    }
+    return clone.getReturnType();
+  }
+
+  private TypeElement typeElementOf(Class<?> c) {
+    return elementUtils.getTypeElement(c.getCanonicalName());
+  }
+
+  private TypeMirror typeMirrorOf(Class<?> c) {
+    return typeElementOf(c).asType();
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java b/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java
new file mode 100644
index 0000000..078ef51
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2019 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.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.truth.Expect;
+import com.google.testing.compile.CompilationRule;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class TypeVariablesTest {
+  @ClassRule public static final CompilationRule compilationRule = new CompilationRule();
+  @Rule public final Expect expect = Expect.create();
+
+  private static Elements elementUtils;
+  private static Types typeUtils;
+
+  @BeforeClass
+  public static void setUpClass() {
+    elementUtils = compilationRule.getElements();
+    typeUtils = compilationRule.getTypes();
+  }
+
+  abstract static class Source1 {
+    abstract String getFoo();
+  }
+
+  abstract static class Target1 {}
+
+  @Test
+  public void noTypeParameters() {
+    TypeElement source1 = elementUtils.getTypeElement(Source1.class.getCanonicalName());
+    TypeElement target1 = elementUtils.getTypeElement(Target1.class.getCanonicalName());
+    List<ExecutableElement> sourceMethods = ElementFilter.methodsIn(source1.getEnclosedElements());
+    Map<ExecutableElement, TypeMirror> types =
+        TypeVariables.rewriteReturnTypes(elementUtils, typeUtils, sourceMethods, source1, target1);
+    assertThat(types).containsExactly(sourceMethods.get(0), sourceMethods.get(0).getReturnType());
+  }
+
+  abstract static class Source2<T> {
+    abstract List<T> getFoo();
+  }
+
+  abstract static class Target2<T> {
+    abstract void setFoo(List<T> list);
+  }
+
+  @Test
+  public void simpleTypeParameter() {
+    TypeElement source2 = elementUtils.getTypeElement(Source2.class.getCanonicalName());
+    TypeElement target2 = elementUtils.getTypeElement(Target2.class.getCanonicalName());
+    List<ExecutableElement> sourceMethods = ElementFilter.methodsIn(source2.getEnclosedElements());
+    Map<ExecutableElement, TypeMirror> types =
+        TypeVariables.rewriteReturnTypes(elementUtils, typeUtils, sourceMethods, source2, target2);
+    List<ExecutableElement> targetMethods = ElementFilter.methodsIn(target2.getEnclosedElements());
+    TypeMirror setFooParameter = targetMethods.get(0).getParameters().get(0).asType();
+    ExecutableElement getFoo = sourceMethods.get(0);
+    TypeMirror originalGetFooReturn = getFoo.getReturnType();
+    TypeMirror rewrittenGetFooReturn = types.get(getFoo);
+    assertThat(typeUtils.isAssignable(setFooParameter, originalGetFooReturn)).isFalse();
+    assertThat(typeUtils.isAssignable(setFooParameter, rewrittenGetFooReturn)).isTrue();
+  }
+
+  abstract static class Source3<T extends Comparable<T>, U> {
+    abstract Map<T, ? extends U> getFoo();
+  }
+
+  abstract static class Target3<T extends Comparable<T>, U> {
+    abstract void setFoo(Map<T, ? extends U> list);
+  }
+
+  @Test
+  public void hairyTypeParameters() {
+    TypeElement source3 = elementUtils.getTypeElement(Source3.class.getCanonicalName());
+    TypeElement target3 = elementUtils.getTypeElement(Target3.class.getCanonicalName());
+    List<ExecutableElement> sourceMethods = ElementFilter.methodsIn(source3.getEnclosedElements());
+    Map<ExecutableElement, TypeMirror> types =
+        TypeVariables.rewriteReturnTypes(elementUtils, typeUtils, sourceMethods, source3, target3);
+    List<ExecutableElement> targetMethods = ElementFilter.methodsIn(target3.getEnclosedElements());
+    TypeMirror setFooParameter = targetMethods.get(0).getParameters().get(0).asType();
+    ExecutableElement getFoo = sourceMethods.get(0);
+    TypeMirror originalGetFooReturn = getFoo.getReturnType();
+    TypeMirror rewrittenGetFooReturn = types.get(getFoo);
+    assertThat(typeUtils.isAssignable(setFooParameter, originalGetFooReturn)).isFalse();
+    assertThat(typeUtils.isAssignable(setFooParameter, rewrittenGetFooReturn)).isTrue();
+  }
+
+  abstract static class Outer<T, U extends T> {
+    abstract Map<T, U> getFoo();
+    abstract List<? extends T> getBar();
+
+    abstract static class Inner<T, U extends T> {
+      abstract void setFoo(Map<T, U> foo);
+      abstract void setBar(List<? extends T> bar);
+    }
+  }
+
+  @Test
+  public void nestedClasses() {
+    TypeElement outer = elementUtils.getTypeElement(Outer.class.getCanonicalName());
+    TypeElement inner = elementUtils.getTypeElement(Outer.Inner.class.getCanonicalName());
+    List<ExecutableElement> outerMethods = ElementFilter.methodsIn(outer.getEnclosedElements());
+    Map<ExecutableElement, TypeMirror> types =
+        TypeVariables.rewriteReturnTypes(elementUtils, typeUtils, outerMethods, outer, inner);
+    List<ExecutableElement> innerMethods = ElementFilter.methodsIn(inner.getEnclosedElements());
+    ExecutableElement getFoo = methodNamed(outerMethods, "getFoo");
+    ExecutableElement getBar = methodNamed(outerMethods, "getBar");
+    ExecutableElement setFoo = methodNamed(innerMethods, "setFoo");
+    ExecutableElement setBar = methodNamed(innerMethods, "setBar");
+    TypeMirror setFooParameter = setFoo.getParameters().get(0).asType();
+    TypeMirror originalGetFooReturn = getFoo.getReturnType();
+    TypeMirror rewrittenGetFooReturn = types.get(getFoo);
+    assertThat(typeUtils.isAssignable(setFooParameter, originalGetFooReturn)).isFalse();
+    assertThat(typeUtils.isAssignable(setFooParameter, rewrittenGetFooReturn)).isTrue();
+    TypeMirror setBarParameter = setBar.getParameters().get(0).asType();
+    TypeMirror originalGetBarReturn = getBar.getReturnType();
+    TypeMirror rewrittenGetBarReturn = types.get(getBar);
+    assertThat(typeUtils.isAssignable(setBarParameter, originalGetBarReturn)).isFalse();
+    assertThat(typeUtils.isAssignable(setBarParameter, rewrittenGetBarReturn)).isTrue();
+  }
+
+  @Test
+  public void canAssignStaticMethodResult() {
+    TypeElement immutableMap = elementUtils.getTypeElement(ImmutableMap.class.getCanonicalName());
+    TypeElement string = elementUtils.getTypeElement(String.class.getCanonicalName());
+    TypeElement integer = elementUtils.getTypeElement(Integer.class.getCanonicalName());
+    TypeElement number = elementUtils.getTypeElement(Number.class.getCanonicalName());
+    TypeMirror immutableMapStringNumber =
+        typeUtils.getDeclaredType(immutableMap, string.asType(), number.asType());
+    TypeMirror immutableMapStringInteger =
+        typeUtils.getDeclaredType(immutableMap, string.asType(), integer.asType());
+    TypeElement map = elementUtils.getTypeElement(Map.class.getCanonicalName());
+    TypeMirror erasedMap = typeUtils.erasure(map.asType());
+    // If the target type is ImmutableMap<String, Number> then we should be able to use
+    //   static <K, V> ImmutableMap<K, V> ImmutableMap.copyOf(Map<? extends K, ? extends V>)
+    // with a parameter of type ImmutableMap<String, Integer>.
+    List<ExecutableElement> immutableMapMethods =
+        ElementFilter.methodsIn(immutableMap.getEnclosedElements());
+    ExecutableElement copyOf = methodNamed(immutableMapMethods, "copyOf", erasedMap);
+    expect.that(
+        TypeVariables.canAssignStaticMethodResult(
+            copyOf, immutableMapStringInteger, immutableMapStringNumber, typeUtils))
+        .isTrue();
+    expect.that(
+        TypeVariables.canAssignStaticMethodResult(
+            copyOf, immutableMapStringNumber, immutableMapStringInteger, typeUtils))
+        .isFalse();
+  }
+
+  private static ExecutableElement methodNamed(List<ExecutableElement> methods, String name) {
+    return methods.stream().filter(m -> m.getSimpleName().contentEquals(name)).findFirst().get();
+  }
+
+  private static ExecutableElement methodNamed(
+      List<ExecutableElement> methods, String name, TypeMirror erasedParameterType) {
+    return methods.stream()
+        .filter(m -> m.getSimpleName().contentEquals(name))
+        .filter(m -> m.getParameters().size() == 1)
+        .filter(m -> typeUtils.isSameType(
+                    erasedParameterType, typeUtils.erasure(m.getParameters().get(0).asType())))
+        .findFirst()
+        .get();
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/testclasses/RuntimePermission.java b/value/src/test/java/com/google/auto/value/processor/testclasses/RuntimePermission.java
new file mode 100644
index 0000000..f9b4bda
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/testclasses/RuntimePermission.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016 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.processor.testclasses;
+
+/**
+ * This class just exists to test import behaviour when referencing a class which has the same name
+ * as a class in java.lang.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+public class RuntimePermission {}
diff --git a/value/userguide/builders-howto.md b/value/userguide/builders-howto.md
new file mode 100644
index 0000000..00038e7
--- /dev/null
+++ b/value/userguide/builders-howto.md
@@ -0,0 +1,602 @@
+# How do I... (Builder edition)
+
+
+This page answers common how-to questions that may come up when using AutoValue
+**with the builder option**. You should read and understand [AutoValue with
+builders](builders.md) first.
+
+If you are not using a builder, see [Introduction](index.md) and
+[How do I...](howto.md) instead.
+
+## Contents
+
+How do I...
+
+*   ... [use (or not use) `set` **prefixes**?](#beans)
+*   ... [use different **names** besides
+    `builder()`/`Builder`/`build()`?](#build_names)
+*   ... [specify a **default** value for a property?](#default)
+*   ... [initialize a builder to the same property values as an **existing**
+    value instance](#to_builder)
+*   ... [include `with-` methods on my value class for creating slightly
+    **altered** instances?](#withers)
+*   ... [**validate** property values?](#validate)
+*   ... [**normalize** (modify) a property value at `build` time?](#normalize)
+*   ... [expose **both** a builder and a factory method?](#both)
+*   ... [handle `Optional` properties?](#optional)
+*   ... [use a **collection**-valued property?](#collection)
+    *   ... [let my builder **accumulate** values for a collection-valued
+        property (not require them all at once)?](#accumulate)
+    *   ... [accumulate values for a collection-valued property, without
+        **"breaking the chain"**?](#add)
+    *   ... [offer **both** accumulation and set-at-once methods for the same
+        collection-valued property?](#collection_both)
+*   ... [access nested builders while building?](#nested_builders)
+
+## <a name="beans"></a>... use (or not use) `set` prefixes?
+
+Just as you can choose whether to use JavaBeans-style names for property getters
+(`getFoo()` or just `foo()`) in your value class, you have the same choice for
+setters in builders too (`setFoo(value)` or just `foo(value)`). As with getters,
+you must use these prefixes consistently or not at all.
+
+Using `get`/`is` prefixes for getters and using the `set` prefix for setters are
+independent choices. For example, it is fine to use the `set` prefixes on all
+your builder methods, but omit the `get`/`is` prefixes from all your accessors.
+
+Here is the `Animal` example using `get` prefixes but not `set` prefixes:
+
+```java
+@AutoValue
+abstract class Animal {
+  abstract String getName();
+  abstract int getNumberOfLegs();
+
+  static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+    abstract Builder name(String value);
+    abstract Builder numberOfLegs(int value);
+    abstract Animal build();
+  }
+}
+```
+
+## <a name="build_names"></a>... use different names besides `builder()`/`Builder`/`build()`?
+
+Use whichever names you like; AutoValue doesn't actually care.
+
+(We would gently recommend these names as conventional.)
+
+## <a name="default"></a>... specify a default value for a property?
+
+What should happen when a caller does not supply a value for a property before
+calling `build()`? If the property in question is [nullable](howto.md#nullable),
+it will simply default to `null` as you would expect. And if it is
+[Optional](#optional) it will default to an empty `Optional` as you might also
+expect. But if it isn't either of those things (including if it is a
+primitive-valued property, which *can't* be null), then `build()` will throw an
+unchecked exception.
+
+But this presents a problem, since one of the main *advantages* of a builder in
+the first place is that callers can specify only the properties they care about!
+
+The solution is to provide a default value for such properties. Fortunately this
+is easy: just set it on the newly-constructed builder instance before returning
+it from the `builder()` method.
+
+Here is the `Animal` example with the default number of legs being 4:
+
+```java
+@AutoValue
+abstract class Animal {
+  abstract String name();
+  abstract int numberOfLegs();
+
+  static Builder builder() {
+    return new AutoValue_Animal.Builder()
+        .setNumberOfLegs(4);
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+    abstract Builder setName(String value);
+    abstract Builder setNumberOfLegs(int value);
+    abstract Animal build();
+  }
+}
+```
+
+Occasionally you may want to supply a default value, but only if the property is
+not set explicitly. This is covered in the section on
+[normalization](#normalize).
+
+## <a name="to_builder"></a>... initialize a builder to the same property values as an existing value instance
+
+Suppose your caller has an existing instance of your value class, and wants to
+change only one or two of its properties. Of course, it's immutable, but it
+would be convenient if they could easily get a `Builder` instance representing
+the same property values, which they could then modify and use to create a new
+value instance.
+
+To give them this ability, just add an abstract `toBuilder` method, returning
+your abstract builder type, to your value class. AutoValue will implement it.
+
+```java
+  public abstract Builder toBuilder();
+```
+
+## <a name="withers"></a>... include `with-` methods on my value class for creating slightly altered instances?
+
+This is a somewhat common pattern among immutable classes. You can't have
+setters, but you can have methods that act similarly to setters by returning a
+new immutable instance that has one property changed.
+
+If you're already using the builder option, you can add these methods by hand:
+
+```java
+@AutoValue
+public abstract class Animal {
+  public abstract String name();
+  public abstract int numberOfLegs();
+
+  public static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  abstract Builder toBuilder();
+
+  public Animal withName(String name) {
+    return toBuilder().setName(name).build();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String value);
+    public abstract Builder setNumberOfLegs(int value);
+    public abstract Animal build();
+  }
+}
+```
+
+Note that it's your free choice what to make public (`toBuilder`, `withName`,
+neither, or both).
+
+## <a name="validate"></a>... validate property values?
+
+Validating properties is a little less straightforward than it is in the
+[non-builder case](howto.md#validate).
+
+What you need to do is *split* your "build" method into two methods:
+
+*   the non-visible, abstract method that AutoValue implements
+*   and the visible, *concrete* method you provide, which calls the generated
+    method and performs validation.
+
+We recommend naming these methods `autoBuild` and `build`, but any names will
+work. It ends up looking like this:
+
+```java
+@AutoValue
+public abstract class Animal {
+  public abstract String name();
+  public abstract int numberOfLegs();
+
+  public static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String value);
+    public abstract Builder setNumberOfLegs(int value);
+
+    abstract Animal autoBuild();  // not public
+
+    public Animal build() {
+      Animal animal = autoBuild();
+      Preconditions.checkState(animal.numberOfLegs() >= 0, "Negative legs");
+      return animal;
+    }
+  }
+}
+```
+
+## <a name="normalize"></a>... normalize (modify) a property value at `build` time?
+
+Suppose you want to convert the animal's name to lower case.
+
+You'll need to add a *getter* to your builder, as shown:
+
+```java
+@AutoValue
+public abstract class Animal {
+  public abstract String name();
+  public abstract int numberOfLegs();
+
+  public static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String value);
+    public abstract Builder setNumberOfLegs(int value);
+
+    abstract String name(); // must match method name in Animal
+
+    abstract Animal autoBuild(); // not public
+
+    public Animal build() {
+      setName(name().toLowerCase());
+      return autoBuild();
+    }
+  }
+}
+```
+
+The getter in your builder must have the same signature as the abstract property
+accessor method in the value class. It will return the value that has been set
+on the `Builder`. If no value has been set for a
+non-[nullable](howto.md#nullable) property, `IllegalStateException` is thrown.
+
+Getters should generally only be used within the `Builder` as shown, so they are
+not public.
+
+As an alternative to returning the same type as the property accessor method,
+the builder getter can return an Optional wrapping of that type. This can be
+used if you want to supply a default, but only if the property has not been set.
+(The [usual way](#default) of supplying defaults means that the property always
+appears to have been set.) For example, suppose you wanted the default name of
+your Animal to be something like "4-legged creature", where 4 is the
+`numberOfLegs()` property. You might write this:
+
+```java
+@AutoValue
+public abstract class Animal {
+  public abstract String name();
+  public abstract int numberOfLegs();
+
+  public static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String value);
+    public abstract Builder setNumberOfLegs(int value);
+
+    abstract Optional<String> name();
+    abstract int numberOfLegs();
+
+    abstract Animal autoBuild(); // not public
+
+    public Animal build() {
+      if (!name().isPresent()) {
+        setName(numberOfLegs() + "-legged creature");
+      }
+      return autoBuild();
+    }
+  }
+}
+```
+
+Notice that this will throw `IllegalStateException` if the `numberOfLegs`
+property hasn't been set either.
+
+The Optional wrapping can be any of the Optional types mentioned in the
+[section](#optional) on `Optional` properties. If your property has type `int`
+it can be wrapped as either `Optional<Integer>` or `OptionalInt`, and likewise
+for `long` and `double`.
+
+## <a name="both"></a>... expose *both* a builder *and* a factory method?
+
+If you use the builder option, AutoValue will not generate a visible constructor
+for the generated concrete value class. If it's important to offer your caller
+the choice of a factory method as well as the builder, then your factory method
+implementation will have to invoke the builder itself.
+
+## <a name="optional"></a>... handle `Optional` properties?
+
+Properties of type `Optional` benefit from special treatment. If you have a
+property of type `Optional<String>`, say, then it will default to an empty
+`Optional` without needing to [specify](#default) a default explicitly. And,
+instead of or as well as the normal `setFoo(Optional<String>)` method, you can
+have `setFoo(String)`. Then `setFoo(s)` is equivalent to
+`setFoo(Optional.of(s))`.
+
+Here, `Optional` means either [`java.util.Optional`] from Java (Java 8 or
+later), or [`com.google.common.base.Optional`] from Guava. Java 8 also
+introduced related classes in `java.util` called [`OptionalInt`],
+[`OptionalLong`], and [`OptionalDouble`]. You can use those in the same way. For
+example a property of type `OptionalInt` will default to `OptionalInt.empty()`
+and you can set it with either `setFoo(OptionalInt)` or `setFoo(int)`.
+
+```java
+@AutoValue
+public abstract class Animal {
+  public abstract Optional<String> name();
+
+  public static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    // You can have either or both of these two methods:
+    public abstract Builder setName(Optional<String> value);
+    public abstract Builder setName(String value);
+    public abstract Animal build();
+  }
+}
+```
+
+[`java.util.Optional`]: https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
+[`com.google.common.base.Optional`]: https://guava.dev/releases/snapshot/api/docs/com/google/common/base/Optional.html
+[`OptionalDouble`]: https://docs.oracle.com/javase/8/docs/api/java/util/OptionalDouble.html
+[`OptionalInt`]: https://docs.oracle.com/javase/8/docs/api/java/util/OptionalInt.html
+[`OptionalLong`]: https://docs.oracle.com/javase/8/docs/api/java/util/OptionalLong.html
+
+## <a name="collection"></a>... use a collection-valued property?
+
+Value objects should be immutable, so if a property of one is a collection then
+that collection should be immutable too. We recommend using Guava's [immutable
+collections] to make that explicit. AutoValue's builder support includes a few
+special arrangements to make this more convenient.
+
+In the examples here we use `ImmutableSet`, but the same principles apply to all
+of Guava's immutable collection types, like `ImmutableList`,
+`ImmutableMultimap`, and so on.
+
+We recommend using the immutable type (like `ImmutableSet<String>`) as your
+actual property type. However, it can be a pain for callers to always have to
+construct `ImmutableSet` instances to pass into your builder. So AutoValue
+allows your builder method to accept an argument of any type that
+`ImmutableSet.copyOf` accepts.
+
+If our `Animal` acquires an `ImmutableSet<String>` that is the countries it
+lives in, that can be set from a `Set<String>` or a `Collection<String>` or an
+`Iterable<String>` or a `String[]` or any other compatible type. You can even
+offer multiple choices, as in this example:
+
+```java
+@AutoValue
+public abstract class Animal {
+  public abstract String name();
+  public abstract int numberOfLegs();
+  public abstract ImmutableSet<String> countries();
+
+  public static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String value);
+    public abstract Builder setNumberOfLegs(int value);
+    public abstract Builder setCountries(Set<String> value);
+    public abstract Builder setCountries(String... value);
+    public abstract Animal build();
+  }
+}
+```
+
+[immutable collections]: https://github.com/google/guava/wiki/ImmutableCollectionsExplained
+
+### <a name="accumulate"></a>... let my builder *accumulate* values for a collection-valued property (not require them all at once)?
+
+Instead of defining a setter for an immutable collection `foos`, you can define
+a method `foosBuilder()` that returns the associated builder type for that
+collection. In this example, we have an `ImmutableSet<String>` which can be
+built using the `countriesBuilder()` method:
+
+```java
+@AutoValue
+public abstract class Animal {
+  public abstract String name();
+  public abstract int numberOfLegs();
+  public abstract ImmutableSet<String> countries();
+
+  public static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String value);
+    public abstract Builder setNumberOfLegs(int value);
+    public abstract ImmutableSet.Builder<String> countriesBuilder();
+    public abstract Animal build();
+  }
+}
+```
+
+The name of this method must be exactly the property name (`countries` here)
+followed by the string `Builder`. Even if the properties follow the
+`getCountries()` convention, the builder method must be `countriesBuilder()`
+and not `getCountriesBuilder()`.
+
+You may notice a small problem with this example: the caller can no longer
+create their instance in a single chained statement:
+
+```java
+  // This DOES NOT work!
+  Animal dog = Animal.builder()
+      .setName("dog")
+      .setNumberOfLegs(4)
+      .countriesBuilder()
+          .add("Guam")
+          .add("Laos")
+      .build();
+```
+
+Instead they are forced to hold the builder itself in a temporary variable:
+
+```java
+  // This DOES work... but we have to "break the chain"!
+  Animal.Builder builder = Animal.builder()
+      .setName("dog")
+      .setNumberOfLegs(4);
+  builder.countriesBuilder()
+      .add("Guam")
+      .add("Laos");
+  Animal dog = builder.build();
+```
+
+One solution for this problem is just below.
+
+### <a name="add"></a>... accumulate values for a collection-valued property, without "breaking the chain"?
+
+Another option is to keep `countriesBuilder()` itself non-public, and only use
+it to implement a public `addCountry` method:
+
+```java
+@AutoValue
+public abstract class Animal {
+  public abstract String name();
+  public abstract int numberOfLegs();
+  public abstract ImmutableSet<String> countries();
+
+  public static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String value);
+    public abstract Builder setNumberOfLegs(int value);
+
+    abstract ImmutableSet.Builder<String> countriesBuilder();
+    public Builder addCountry(String value) {
+      countriesBuilder().add(value);
+      return this;
+    }
+
+    public abstract Animal build();
+  }
+}
+```
+
+Now the caller can do this:
+
+```java
+  // This DOES work!
+  Animal dog = Animal.builder()
+      .setName("dog")
+      .setNumberOfLegs(4)
+      .addCountry("Guam")
+      .addCountry("Laos") // however many times needed
+      .build();
+```
+
+### <a name="collection_both"></a>... offer both accumulation and set-at-once methods for the same collection-valued property?
+
+Yes, you can provide both methods, letting your caller choose the style they
+prefer.
+
+The same caller can mix the two styles only in limited ways; once `foosBuilder`
+has been called, any subsequent call to `setFoos` will throw an unchecked
+exception. On the other hand, calling `setFoos` first is okay; a later call to
+`foosBuilder` will return a builder already populated with the
+previously-supplied elements.
+
+## <a name="nested_builders"></a>... access nested builders while building?
+
+Often a property of an `@AutoValue` class is itself an immutable class,
+perhaps another `@AutoValue`. In such cases your builder can expose a builder
+for that nested class. This is very similar to exposing a builder for a
+collection property, as described [earlier](#accumulate).
+
+Suppose the `Animal` class has a property of type `Species`:
+
+```java
+@AutoValue
+public abstract class Animal {
+  public abstract String name();
+  public abstract Species species();
+
+  public static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String name);
+    public abstract Species.Builder speciesBuilder();
+    public abstract Animal build();
+  }
+}
+
+@AutoValue
+public abstract class Species {
+  public abstract String genus();
+  public abstract String epithet();
+
+  public static Builder builder() {
+    return new AutoValue_Species.Builder();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setGenus(String genus);
+    public abstract Builder setEpithet(String epithet);
+    public abstract Species build();
+  }
+}
+```
+
+Now you can access the builder of the nested `Species` while you are building
+the `Animal`:
+
+```java
+  Animal.Builder catBuilder = Animal.builder()
+      .setName("cat");
+  catBuilder.speciesBuilder()
+      .setGenus("Felis")
+      .setEpithet("catus");
+  Animal cat = catBuilder.build();
+```
+
+Although the nested class in the example (`Species`) is also an `@AutoValue`
+class, it does not have to be. For example, it could be a [protobuf]. The
+requirements are:
+
+* The nested class must have a way to make a new builder. This can be
+  `new Species.Builder()`, or `Species.builder()`, or `Species.newBuilder()`.
+
+* There must be a way to build an instance from the builder: `Species.Builder`
+  must have a method `Species build()`.
+
+* If there is a need to convert `Species` back into its builder, then `Species`
+  must have a method `Species.Builder toBuilder()`.
+
+  In the example, if `Animal` has an abstract [`toBuilder()`](#to_builder)
+  method then `Species` must also have a `toBuilder()` method. That also applies
+  if there is an abstract `setSpecies` method in addition to the
+  `speciesBuilder` method.
+
+  As an alternative to having a method `Species.Builder toBuilder()` in
+  `Species`, `Species.Builder` can have a method called `addAll` or `putAll`
+  that accepts an argument of type `Species`. This is how AutoValue handles
+  `ImmutableSet` for example. `ImmutableSet` does not have a `toBuilder()`
+  method, but `ImmutableSet.Builder` does have an `addAll` method that accepts
+  an `ImmutableSet`. So given `ImmutableSet<String> strings`, we can achieve the
+  effect of `strings.toBuilder()` by doing:
+
+  ```
+  ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+  builder.addAll(strings);
+  ```
+
+There are no requirements on the name of the builder class. Instead of
+`Species.Builder`, it could be `Species.Factory` or `SpeciesBuilder`.
+
+If `speciesBuilder()` is never called then the final `species()` property will
+be set as if by `speciesBuilder().build()`. In the example, that would result
+in an exception because the required properties of `Species` have not been set.
+
+
+[protobuf]: https://developers.google.com/protocol-buffers/docs/reference/java-generated#builders
diff --git a/value/userguide/builders.md b/value/userguide/builders.md
new file mode 100644
index 0000000..11c68c6
--- /dev/null
+++ b/value/userguide/builders.md
@@ -0,0 +1,101 @@
+# AutoValue with Builders
+
+
+The [introduction](index.md) of this User Guide covers the basic usage of
+AutoValue using a static factory method as your public creation API. But in many
+circumstances (such as those laid out in *Effective Java, 2nd Edition* Item 2),
+you may prefer to let your callers use a *builder* instead.
+
+Fortunately, AutoValue can generate builder classes too! This page explains how.
+Note that we recommend reading and understanding the basic usage shown in the
+[introduction](index.md) first.
+
+
+## How to use AutoValue with Builders<a name="howto"></a>
+
+As explained in the introduction, the AutoValue concept is that **you write an
+abstract value class, and AutoValue implements it**. Builder generation works in
+the exact same way: you also create an abstract builder class, nesting it inside
+your abstract value class, and AutoValue generates implementations for both.
+
+### In `Animal.java`<a name="example_java"></a>
+
+```java
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+abstract class Animal {
+  abstract String name();
+  abstract int numberOfLegs();
+
+  static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+    abstract Builder setName(String value);
+    abstract Builder setNumberOfLegs(int value);
+    abstract Animal build();
+  }
+}
+```
+
+Note that in real life, some classes and methods would presumably be public and
+have **Javadoc**. We're leaving these off in the User Guide only to keep the
+examples clean and short.
+
+### Usage<a name="usage"></a>
+
+```java
+public void testAnimal() {
+  Animal dog = Animal.builder().setName("dog").setNumberOfLegs(4).build();
+  assertEquals("dog", dog.name());
+  assertEquals(4, dog.numberOfLegs());
+
+  // You probably don't need to write assertions like these; just illustrating.
+  assertTrue(
+      Animal.builder().setName("dog").setNumberOfLegs(4).build().equals(dog));
+  assertFalse(
+      Animal.builder().setName("cat").setNumberOfLegs(4).build().equals(dog));
+  assertFalse(
+      Animal.builder().setName("dog").setNumberOfLegs(2).build().equals(dog));
+
+  assertEquals("Animal{name=dog, numberOfLegs=4}", dog.toString());
+}
+```
+
+### What does AutoValue generate?<a name="generated"></a>
+
+For the `Animal` example shown above, here is [typical code AutoValue might
+generate](generated-builder-example.md).
+
+## Warnings<a name="warnings"></a>
+
+Be sure to put the static `builder()` method directly in your value class (e.g.,
+`Animal`) and not the nested abstract `Builder` class. That ensures that the
+`Animal` class is always initialized before `Builder`. Otherwise you may be
+exposing yourself to initialization-order problems.
+
+## <a name="howto"></a>How do I...
+
+*   ... [use (or not use) `set` **prefixes**?](builders-howto.md#beans)
+*   ... [use different **names** besides
+    `builder()`/`Builder`/`build()`?](builders-howto.md#build_names)
+*   ... [specify a **default** value for a property?](builders-howto.md#default)
+*   ... [initialize a builder to the same property values as an **existing**
+    value instance](builders-howto.md#to_builder)
+*   ... [include `with-` methods on my value class for creating slightly
+    **altered** instances?](builders-howto.md#withers)
+*   ... [**validate** property values?](builders-howto.md#validate)
+*   ... [**normalize** (modify) a property value at `build`
+    time?](builders-howto.md#normalize)
+*   ... [expose **both** a builder and a factory
+    method?](builders-howto.md#both)
+*   ... [use a **collection**-valued property?](builders-howto.md#collection)
+    *   ... [let my builder **accumulate** values for a collection-valued
+        property (not require them all at once)?](builders-howto.md#accumulate)
+    *   ... [accumulate values for a collection-valued property, without
+        **breaking the chain**?](builders-howto.md#add)
+    *   ... [offer **both** accumulation and set-at-once methods for the same
+        collection-valued property?](builders-howto.md#collection_both)
diff --git a/value/userguide/design-faq.md b/value/userguide/design-faq.md
new file mode 100644
index 0000000..7c18312
--- /dev/null
+++ b/value/userguide/design-faq.md
@@ -0,0 +1,5 @@
+# Design FAQ
+
+
+TODO. This page will contain various explanations of *why* AutoValue's features
+were designed as they are.
diff --git a/value/userguide/extensions.md b/value/userguide/extensions.md
new file mode 100644
index 0000000..ec6c467
--- /dev/null
+++ b/value/userguide/extensions.md
@@ -0,0 +1,43 @@
+# Extensions
+
+
+AutoValue can be extended to implement new features for classes annotated with
+`@AutoValue`.
+
+## Using extensions
+
+Each extension is a class. If that class is on the `processorpath` when you
+compile your `@AutoValue` class, the extension can run.
+
+
+Some extensions are triggered by their own annotations, which you add to your
+class; others may be triggered in other ways. Consult the extension's
+documentation for usage instructions.
+
+## Writing an extension
+
+To add a feature, write a class that extends [`AutoValueExtension`], and put
+that class on the `processorpath` along with `AutoValueProcessor`.
+
+`AutoValueExtension` uses the [`ServiceLoader`] mechanism, which means:
+
+*   Your class must be public and have a public no-argument constructor.
+*   Its fully-qualified name must appear in a file called
+    `META-INF/services/com.google.auto.value.extension.AutoValueExtension` in a
+    JAR that is on the compiler's `classpath` or `processorpath`.
+
+You can use [AutoService] to make implementing the `ServiceLoader` pattern easy.
+
+Without extensions, AutoValue generates a subclass of the `@AutoValue` class.
+Extensions can work by generating a chain of subclasses, each of which alters
+behavior by overriding or implementing new methods.
+
+## TODO
+
+*   How to distribute extensions.
+*   List of known extensions.
+
+[AutoService]: https://github.com/google/auto/tree/master/service
+[`AutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
+[`ServiceLoader`]: http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html
+
diff --git a/value/userguide/generated-builder-example.md b/value/userguide/generated-builder-example.md
new file mode 100644
index 0000000..70301c8
--- /dev/null
+++ b/value/userguide/generated-builder-example.md
@@ -0,0 +1,103 @@
+# Generated builder example
+
+
+For the code shown in the [builder documentation](builders.md), the following is
+typical code AutoValue might generate:
+
+```java
+import javax.annotation.Generated;
+
+@Generated("com.google.auto.value.processor.AutoValueProcessor")
+final class AutoValue_Animal extends Animal {
+  private final String name;
+  private final int numberOfLegs;
+
+  private AutoValue_Animal(
+      String name,
+      int numberOfLegs) {
+    this.name = name;
+    this.numberOfLegs = numberOfLegs;
+  }
+
+  @Override
+  String name() {
+    return name;
+  }
+
+  @Override
+  int numberOfLegs() {
+    return numberOfLegs;
+  }
+
+  @Override
+  public String toString() {
+    return "Animal{"
+        + "name=" + name + ", "
+        + "numberOfLegs=" + numberOfLegs
+        + "}";
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o == this) {
+      return true;
+    }
+    if (o instanceof Animal) {
+      Animal that = (Animal) o;
+      return (this.name.equals(that.name()))
+           && (this.numberOfLegs == that.numberOfLegs());
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    int h = 1;
+    h *= 1000003;
+    h ^= this.name.hashCode();
+    h *= 1000003;
+    h ^= this.numberOfLegs;
+    return h;
+  }
+
+  static final class Builder extends Animal.Builder {
+    private String name;
+    private Integer numberOfLegs;
+
+    Builder() {
+    }
+
+    @Override
+    Animal.Builder setName(String name) {
+      if (name == null) {
+        throw new NullPointerException("Null name");
+      }
+      this.name = name;
+      return this;
+    }
+
+    @Override
+    Animal.Builder setNumberOfLegs(int numberOfLegs) {
+      this.numberOfLegs = numberOfLegs;
+      return this;
+    }
+
+    @Override
+    Animal build() {
+      String missing = "";
+      if (this.name == null) {
+        missing += " name";
+      }
+      if (this.numberOfLegs == null) {
+        missing += " numberOfLegs";
+      }
+      if (!missing.isEmpty()) {
+        throw new IllegalStateException("Missing required properties:" + missing);
+      }
+      return new AutoValue_Animal(
+          this.name,
+          this.numberOfLegs);
+    }
+  }
+}
+```
diff --git a/value/userguide/generated-example.md b/value/userguide/generated-example.md
new file mode 100644
index 0000000..c514d52
--- /dev/null
+++ b/value/userguide/generated-example.md
@@ -0,0 +1,63 @@
+# Generated example
+
+
+For the code shown in the [introduction](index.md), the following is typical
+code AutoValue might generate:
+
+```java
+import javax.annotation.Generated;
+
+@Generated("com.google.auto.value.processor.AutoValueProcessor")
+final class AutoValue_Animal extends Animal {
+  private final String name;
+  private final int numberOfLegs;
+
+  AutoValue_Animal(String name, int numberOfLegs) {
+    if (name == null) {
+      throw new NullPointerException("Null name");
+    }
+    this.name = name;
+    this.numberOfLegs = numberOfLegs;
+  }
+
+  @Override
+  String name() {
+    return name;
+  }
+
+  @Override
+  int numberOfLegs() {
+    return numberOfLegs;
+  }
+
+  @Override
+  public String toString() {
+    return "Animal{"
+        + "name=" + name + ", "
+        + "numberOfLegs=" + numberOfLegs + "}";
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o == this) {
+      return true;
+    }
+    if (o instanceof Animal) {
+      Animal that = (Animal) o;
+      return this.name.equals(that.name())
+          && this.numberOfLegs == that.numberOfLegs();
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    int h = 1;
+    h *= 1000003;
+    h ^= this.name.hashCode();
+    h *= 1000003;
+    h ^= this.numberOfLegs;
+    return h;
+  }
+}
+```
diff --git a/value/userguide/howto.md b/value/userguide/howto.md
new file mode 100644
index 0000000..1dbc561
--- /dev/null
+++ b/value/userguide/howto.md
@@ -0,0 +1,678 @@
+# How do I...
+
+
+This page answers common how-to questions that may come up when using AutoValue.
+You should read and understand the [Introduction](index.md) first.
+
+Questions specific to usage of the **builder option** are documented separately;
+for this, start by reading [AutoValue with builders](builders.md).
+
+## Contents
+
+How do I...
+
+*   ... [also generate a **builder** for my value class?](#builder)
+*   ... [use AutoValue with a **nested** class?](#nested)
+*   ... [use (or not use) JavaBeans-style name **prefixes**?](#beans)
+*   ... [use **nullable** properties?](#nullable)
+*   ... [perform other **validation**?](#validate)
+*   ... [use a property of a **mutable** type?](#mutable_property)
+*   ... [use a **custom** implementation of `equals`, etc.?](#custom)
+*   ... [have AutoValue implement a concrete or default method?](#concrete)
+*   ... [have multiple **`create`** methods, or name it/them
+    differently?](#create)
+*   ... [**ignore** certain properties in `equals`, etc.?](#ignore)
+*   ... [have AutoValue also implement abstract methods from my
+    **supertypes**?](#supertypes)
+*   ... [use AutoValue with a **generic** class?](#generic)
+*   ... [make my class Java- or GWT\-**serializable**?](#serialize)
+*   ... [use AutoValue to **implement** an **annotation** type?](#annotation)
+*   ... [also include **setter** (mutator) methods?](#setters)
+*   ... [also generate **`compareTo`**?](#compareTo)
+*   ... [use a **primitive array** for a property value?](#primitive_array)
+*   ... [use an **object array** for a property value?](#object_array)
+*   ... [have one `@AutoValue` class **extend** another?](#inherit)
+*   ... [keep my accessor methods **private**?](#private_accessors)
+*   ... [expose a **constructor**, not factory method, as my public creation
+    API?](#public_constructor)
+*   ... [use AutoValue on an **interface**, not abstract class?](#interface)
+*   ... [**memoize** ("cache") derived properties?](#memoize)
+*   ... [memoize the result of `hashCode` or
+    `toString`?](#memoize_hash_tostring)
+*   ... [make a class where only one of its properties is ever set?](#oneof)
+*   ... [copy annotations from a class/method to the implemented
+    class/method/field?](#copy_annotations)
+
+## <a name="builder"></a>... also generate a builder for my value class?
+
+Please see [AutoValue with builders](builders.md).
+
+## <a name="nested"></a>... use AutoValue with a nested class?
+
+AutoValue composes the generated class name in the form
+`AutoValue_`*`Outer_Middle_Inner`*.
+As many of these segments will be used in the generated name as required.
+Only the simple class name will appear in `toString` output.
+
+```java
+class Outer {
+  static class Middle {
+    @AutoValue
+    abstract static class Inner {
+      static Inner create(String foo) {
+        return new AutoValue_Outer_Middle_Inner(foo);
+      }
+      ...
+```
+
+## <a name="beans"></a>... use (or not use) JavaBeans-style name prefixes?
+
+Some developers prefer to name their accessors with a `get-` or `is-` prefix,
+but would prefer that only the "bare" property name be used in `toString` and
+for the generated constructor's parameter names.
+
+AutoValue will do exactly this, but only if you are using these prefixes
+*consistently*. In that case, it infers your intended property name by first
+stripping the `get-` or `is-` prefix, then adjusting the case of what remains as
+specified by
+[Introspector.decapitalize](http://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize).
+
+Note that, in keeping with the JavaBeans specification, the `is-` prefix is only
+allowed on `boolean`-returning methods. `get-` is allowed on any type of
+accessor.
+
+## <a name="nullable"></a>... use nullable properties?
+
+Ordinarily the generated constructor will reject any null values. If you want to
+accept null, simply apply any annotation named `@Nullable` to the appropriate
+accessor methods. This causes AutoValue to remove the null checks and generate
+null-friendly code for `equals`, `hashCode` and `toString`. Example:
+
+```java
+@AutoValue
+public abstract class Foo {
+  public static Foo create(@Nullable Bar bar) {
+    return new AutoValue_Foo(bar);
+  }
+
+  @Nullable abstract Bar bar();
+}
+```
+
+This example also shows annotating the corresponding `create` parameter with
+`@Nullable`. AutoValue does not actually require this annotation, only the one
+on the accessor, but we recommended it as useful documentation to your caller.
+Conversely, if `@Nullable` is only added to the parameter in `create` (or
+similarly the setter method of [AutoValue.Builder](builders)), but not the
+corresponding accessor method, it won't have any effect.
+
+## <a name="validate"></a>... perform other validation?
+
+Null checks are added automatically (as [above](#nullable)). For other types of
+precondition checks or pre-processing, just add them to your factory method:
+
+```java
+static MyType create(String first, String second) {
+  checkArgument(!first.isEmpty());
+  return new AutoValue_MyType(first, second.trim());
+}
+```
+
+## <a name="mutable_property"></a>... use a property of a mutable type?
+
+AutoValue classes are meant and expected to be immutable. But sometimes you
+would want to take a mutable type and use it as a property. In these cases:
+
+First, check if the mutable type has a corresponding immutable cousin. For
+example, the types `List<String>` and `String[]` have the immutable counterpart
+`ImmutableList<String>` in [Guava](http://github.com/google/guava). If so, use
+the immutable type for your property, and only accept the mutable type during
+construction:
+
+```java
+@AutoValue
+public abstract class ListExample {
+  public static ListExample create(String[] mutableNames) {
+    return new AutoValue_ListExample(ImmutableList.copyOf(mutableNames));
+  }
+
+  public abstract ImmutableList<String> names();
+}
+```
+
+Note: this is a perfectly sensible practice, not an ugly workaround!
+
+If there is no suitable immutable type to use, you'll need to proceed with
+caution. Your static factory method should pass a *clone* of the passed object
+to the generated constructor. Your accessor method should document a very loud
+warning never to mutate the object returned.
+
+```java
+@AutoValue
+public abstract class MutableExample {
+  public static MutableExample create(MutablePropertyType ouch) {
+    // Replace `MutablePropertyType.copyOf()` below with the right copying code for this type
+    return new AutoValue_MutableExample(MutablePropertyType.copyOf(ouch));
+  }
+
+  /**
+   * Returns the ouch associated with this object; <b>do not mutate</b> the
+   * returned object.
+   */
+  public abstract MutablePropertyType ouch();
+}
+```
+
+Warning: this is an ugly workaround, not a perfectly sensible practice! Callers
+can trivially break the invariants of the immutable class by mutating the
+accessor's return value. An example where something can go wrong: AutoValue
+objects can be used as keys in Maps.
+
+## <a name="custom"></a>... use a custom implementation of `equals`, etc.?
+
+Simply write your custom implementation; AutoValue will notice this and will
+skip generating its own. Your hand-written logic will thus be inherited on the
+concrete implementation class. We call this *underriding* the method.
+
+Remember when doing this that you are losing AutoValue's protections. Be careful
+to follow the basic rules of hash codes: equal objects must have equal hash
+codes *always*, and equal hash codes should imply equal objects *almost always*.
+You should now test your class more thoroughly, ideally using
+[`EqualsTester`](http://static.javadoc.io/com.google.guava/guava-testlib/19.0/com/google/common/testing/EqualsTester.html)
+from [guava-testlib](http://github.com/google/guava).
+
+Best practice: mark your underriding methods `final` to make it clear to future
+readers that these methods aren't overridden by AutoValue.
+
+## <a name="concrete"></a>... have AutoValue implement a concrete or default method?
+
+If a parent class defines a concrete (non-abstract) method that you would like
+AutoValue to implement, you can *redeclare* it as abstract. This applies to
+`Object` methods like `toString()`, but also to property methods that you would
+like to have AutoValue implement. It also applies to default methods in
+interfaces.
+
+```java
+@AutoValue
+class PleaseOverrideExample extends SuperclassThatDefinesToString {
+  ...
+
+  // cause AutoValue to generate this even though the superclass has it
+  @Override public abstract String toString();
+}
+```
+
+```java
+@AutoValue
+class PleaseReimplementDefaultMethod implements InterfaceWithDefaultMethod {
+  ...
+
+  // cause AutoValue to implement this even though the interface has a default
+  // implementation
+  @Override public abstract int numberOfLegs();
+}
+```
+
+## <a name="create"></a>... have multiple `create` methods, or name it/them differently?
+
+Just do it! AutoValue doesn't actually care. This
+[best practice item](practices.md#one_reference) may be relevant.
+
+## <a name="ignore"></a>... ignore certain properties in `equals`, etc.?
+
+Suppose your value class has an extra field that shouldn't be included in
+`equals` or `hashCode` computations.
+
+If this is because it is a derived value based on other properties, see [How do
+I memoize derived properties?](#memoize).
+
+Otherwise, first make certain that you really want to do this. It is often, but
+not always, a mistake. Remember that libraries will treat two equal instances as
+absolutely *interchangeable* with each other. Whatever information is present in
+this extra field could essentially "disappear" when you aren't expecting it, for
+example when your value is stored and retrieved from certain collections.
+
+If you're sure, here is how to do it:
+
+```java
+@AutoValue
+abstract class IgnoreExample {
+  static IgnoreExample create(String normalProperty, String ignoredProperty) {
+    IgnoreExample ie = new AutoValue_IgnoreExample(normalProperty);
+    ie.ignoredProperty = ignoredProperty;
+    return ie;
+  }
+
+  abstract String normalProperty();
+
+  private String ignoredProperty; // sadly, it can't be `final`
+
+  final String ignoredProperty() {
+    return ignoredProperty;
+  }
+}
+```
+
+Note that this means the field is also ignored by `toString`; to AutoValue
+it simply doesn't exist.
+
+## <a name="supertypes"></a>... have AutoValue also implement abstract methods from my supertypes?
+
+AutoValue will recognize every abstract accessor method whether it is defined
+directly in your own hand-written class or in a supertype.
+
+These abstract methods can come from more than one place, for example from an
+interface and from the superclass. It may not then be obvious what order they
+are in, even though you need to know this order if you want to call the
+generated `AutoValue_Foo` constructor. You might find it clearer to use a
+[builder](builders.md) instead. But the order is deterministic: within a class
+or interface, methods are in the order they appear in the source code; methods
+in ancestors come before methods in descendants; methods in interfaces come
+before methods in classes; and in a class or interface that has more than one
+superinterface, the interfaces are in the order of their appearance in
+`implements` or `extends`.
+
+## <a name="generic"></a>... use AutoValue with a generic class?
+
+There's nothing to it: just add type parameters to your class and to your call
+to the generated constructor.
+
+## <a name="serialize"></a>... make my class Java- or GWT\-serializable?
+
+Just add `implements Serializable` or the `@GwtCompatible(serializable = true)`
+annotation (respectively) to your hand-written class; it (as well as any
+`serialVersionUID`) will be duplicated on the generated class, and you'll be
+good to go.
+
+## <a name="annotation"></a>... use AutoValue to implement an annotation type?
+
+Most users should never have the need to programmatically create "fake"
+annotation instances. But if you do, using `@AutoValue` in the usual way will
+fail because the `Annotation.hashCode` specification is incompatible with
+AutoValue's behavior.
+
+However, we've got you covered anyway! Suppose this annotation definition:
+
+```java
+public @interface Named {
+  String value();
+}
+```
+
+All you need is this:
+
+```java
+public class Names {
+  @AutoAnnotation public static Named named(String value) {
+    return new AutoAnnotation_Names_named(value);
+  }
+}
+```
+
+For more details, see the [`AutoAnnotation`
+javadoc](http://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/AutoAnnotation.java#L24).
+
+## <a name="setters"></a>... also include setter (mutator) methods?
+
+You can't; AutoValue only generates immutable value classes.
+
+Note that giving value semantics to a mutable type is widely considered a
+questionable practice in the first place. Equal instances of a value class are
+treated as *interchangeable*, but they can't truly be interchangeable if one
+might be mutated and the other not.
+
+## <a name="compareTo"></a>... also generate `compareTo`?
+
+AutoValue intentionally does not provide this feature. It is better for you to
+roll your own comparison logic using the new methods added to
+[`Comparator`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html)
+in Java 8, or
+[`ComparisonChain`](https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ComparisonChain.html)
+from [Guava](http://github.com/google/guava).
+
+Since these mechanisms are easy to use, require very little code, and give you
+the flexibility you need, there's really no way for AutoValue to improve on
+them!
+
+## <a name="primitive_array"></a>... use a primitive array for a property value?
+
+Go right ahead! AutoValue will generate code that acts on the *values* stored
+the array, not the object identity of the array itself, which is (with virtual
+certainty) what you want. Heed the warnings given above about [mutable
+properties](#mutable_property).
+
+## <a name="object_array"></a>... use an object array for a property value?
+
+This is not allowed. Object arrays are very badly-behaved and unlike primitive
+arrays, they can be replaced with a proper `List` implementation for very little
+added cost.
+
+
+If it's important to accept an object array at construction time, refer to the
+*first* example shown [here](#mutable_property).
+
+## <a name="inherit"></a>... have one `@AutoValue` class extend another?
+
+This ability is intentionally not supported, because there is no way to do it
+correctly. See *Effective Java, 2nd Edition* Item 8: "Obey the general contract
+when overriding equals".
+
+## <a name="private_accessors"></a>... keep my accessor methods private?
+
+We're sorry. This is one of the rare and unfortunate restrictions AutoValue's
+approach places on your API. Your accessor methods don't have to be *public*,
+but they must be at least package-visible.
+
+## <a name="public_constructor"></a>... expose a constructor, not factory method, as my public creation API?
+
+We're sorry. This is one of the rare restrictions AutoValue's approach places on
+your API. However, note that static factory methods are recommended over public
+constructors by *Effective Java*, Item 1.
+
+## <a name="interface"></a>... use AutoValue on an interface, not abstract class?
+
+AutoValue classes can certainly implement an interface, however an interface may
+not be used in lieu of an abstract class. The only advantage of interfaces we're
+aware of is that you can omit `public abstract` from the methods. That's not
+much. On the other hand, you would lose the immutability guarantee, and you'd
+also invite more of the kind of bad behavior described in
+[this best-practices item](practices.md#simple). On balance, we don't think it's
+worth it.
+
+## <a name="memoize"></a>... memoize ("cache") derived properties?
+
+Sometimes your class has properties that are derived from the ones that
+AutoValue implements. You'd typically implement them with a concrete method that
+uses the other properties:
+
+```java
+@AutoValue
+abstract class Foo {
+  abstract Bar barProperty();
+
+  String derivedProperty() {
+    return someFunctionOf(barProperty());
+  }
+}
+```
+
+But what if `someFunctionOf(Bar)` is expensive? You'd like to calculate it only
+one time, then cache and reuse that value for all future calls. Normally,
+thread-safe lazy initialization involves a lot of tricky boilerplate.
+
+Instead, just write the derived-property accessor method as above, and
+annotate it with [`@Memoized`]. Then AutoValue will override that method to
+return a stored value after the first call:
+
+```java
+@AutoValue
+abstract class Foo {
+  abstract Bar barProperty();
+
+  @Memoized
+  String derivedProperty() {
+    return someFunctionOf(barProperty());
+  }
+}
+```
+
+Then your method will be called at most once, even if multiple threads attempt
+to access the property concurrently.
+
+The annotated method must have the usual form of an accessor method, and may not
+be `abstract`, `final`, or `private`.
+
+The stored value will not be used in the implementation of `equals`, `hashCode`,
+or `toString`.
+
+If a `@Memoized` method is also annotated with `@Nullable`, then `null` values
+will be stored; if not, then the overriding method throws `NullPointerException`
+when the annotated method returns `null`.
+
+[`@Memoized`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
+
+## <a name="memoize_hash_tostring"></a>... memoize the result of `hashCode` or `toString`?
+
+You can also make your class remember and reuse the result of `hashCode`,
+`toString`, or both, like this:
+
+```java
+@AutoValue
+abstract class Foo {
+  abstract Bar barProperty();
+
+  @Memoized
+  @Override
+  public abstract int hashCode();
+
+  @Memoized
+  @Override
+  public abstract String toString();
+}
+```
+
+## <a name="oneof"></a>... make a class where only one of its properties is ever set?
+
+Often, the best way to do this is using inheritance. Although one
+`@AutoValue` class can't inherit from another, two `@AutoValue` classes can
+inherit from a common parent.
+
+```java
+public abstract class StringOrInteger {
+  public abstract String representation();
+
+  public static StringOrInteger ofString(String s) {
+    return new AutoValue_StringOrInteger_StringValue(s);
+  }
+
+  public static StringOrInteger ofInteger(int i) {
+    return new AutoValue_StringOrInteger_IntegerValue(i);
+  }
+
+  @AutoValue
+  abstract class StringValue extends StringOrInteger {
+    abstract String string();
+
+    @Override
+    public String representation() {
+      return '"' + string() + '"';
+    }
+  }
+
+  @AutoValue
+  abstract class IntegerValue extends StringOrInteger {
+    abstract int integer();
+
+    @Override
+    public String representation() {
+      return Integer.toString(integer());
+    }
+  }
+}
+```
+
+So any `StringOrInteger` instance is actually either a `StringValue` or an
+`IntegerValue`. Clients only care about the `representation()` method, so they
+don't need to know which it is.
+
+But if clients of your class may want to take different actions depending on
+which property is set, there is an alternative to `@AutoValue` called
+`@AutoOneOf`. This effectively creates a
+[*tagged union*](https://en.wikipedia.org/wiki/Tagged_union).
+Here is `StringOrInteger` written using `@AutoOneOf`, with the
+`representation()` method moved to a separate client class:
+
+```java
+@AutoOneOf(StringOrInteger.Kind.class)
+public abstract class StringOrInteger {
+  public enum Kind {STRING, INTEGER}
+  public abstract Kind getKind();
+
+  public abstract String string();
+
+  public abstract int integer();
+
+  public static StringOrInteger ofString(String s) {
+    return AutoOneOf_StringOrInteger.string(s);
+  }
+
+  public static StringOrInteger ofInteger(int i) {
+    return AutoOneOf_StringOrInteger.integer(i);
+  }
+}
+
+public class Client {
+  public String representation(StringOrInteger stringOrInteger) {
+    switch (stringOrInteger.getKind()) {
+      case STRING:
+        return '"' + stringOrInteger.string() + '"';
+      case INTEGER:
+        return Integer.toString(stringOrInteger.integer());
+    }
+    throw new AssertionError(stringOrInteger.getKind());
+  }
+}
+```
+
+Switching on an enum like this can lead to more robust code than using
+`instanceof` checks, especially if a tool like [Error
+Prone](https://errorprone.info/bugpattern/MissingCasesInEnumSwitch) can alert you
+if you add a new variant without updating all your switches. (On the other hand,
+if nothing outside your class references `getKind()`, you should consider if a
+solution using inheritance might be better.)
+
+There must be an enum such as `Kind`, though it doesn't have to be called `Kind`
+and it doesn't have to be nested inside the `@AutoOneOf` class. There must be an
+abstract method returning the enum, though it doesn't have to be called
+`getKind()`. For every value of the enum, there must be an abstract method with
+the same name (ignoring case and underscores). An `@AutoOneOf` class called
+`Foo` will then get a generated class called `AutoOneOf_Foo` that has a static
+factory method for each property, with the same name. In the example, the
+`STRING` value in the enum corresponds to the `string()` property and to the
+`AutoOneOf_StringOrInteger.string` factory method.
+
+Properties in an `@AutoOneOf` class can be `void` to indicate that the
+corresponding variant has no data. In that case, the factory method for that
+variant has no parameters:
+
+```java
+@AutoOneOf(Transform.Kind.class)
+public abstract class Transform {
+  public enum Kind {NONE, CIRCLE_CROP, BLUR}
+  public abstract Kind getKind();
+
+  abstract void none();
+
+  abstract void circleCrop();
+
+  public abstract BlurTransformParameters blur();
+
+  public static Transform ofNone() {
+    return AutoOneOf_Transform.none();
+  }
+
+  public static Transform ofCircleCrop() {
+    return AutoOneOf_Transform.circleCrop();
+  }
+
+  public static Transform ofBlur(BlurTransformParmeters params) {
+    return AutoOneOf_Transform.blur(params);
+  }
+}
+```
+
+Here, the `NONE` and `CIRCLE_CROP` variants have no associated data but are
+distinct from each other. The `BLUR` variant does have data. The `none()`
+and `circleCrop()` methods are package-private; they must exist to configure
+`@AutoOneOf`, but calling them is not very useful. (It does nothing if the
+instance is of the correct variant, or throws an exception otherwise.)
+
+The `AutoOneOf_Transform.none()` and `AutoOneOf_Transform.circleCrop()` methods
+return the same instance every time they are called.
+
+If one of the `void` variants means "none", consider using an `Optional<Transform>` or
+a `@Nullable Transform` instead of that variant.
+
+Properties in an `@AutoOneOf` class cannot be null. Instead of a
+`StringOrInteger` with a `@Nullable String`, you probably want a
+`@Nullable StringOrInteger` or an `Optional<StringOrInteger>`, or an empty
+variant as just described.
+
+## <a name="copy_annotations"></a>... copy annotations from a class/method to the implemented class/method/field?
+
+### Copying to the generated class
+
+If you want to copy annotations from your `@AutoValue`-annotated class to the
+generated `AutoValue_...` implemention, annotate your class with
+[`@AutoValue.CopyAnnotations`].
+
+For example, if `Example.java` is:
+
+```java
+@AutoValue
+@AutoValue.CopyAnnotations
+@SuppressWarnings("Immutable") // justification ...
+abstract class Example {
+  // details ...
+}
+```
+
+Then `@AutoValue` will generate `AutoValue_Example.java`:
+
+```java
+@SuppressWarnings("Immutable")
+final class AutoValue_Example extends Example {
+  // implementation ...
+}
+```
+
+Applying `@AutoValue.CopyAnnotations` to an `@AutoValue.Builder` class like
+`Foo.Builder` similarly causes annotations on that class to be copied to the
+generated subclass `AutoValue_Foo.Builder`.
+
+### Copying to the generated method
+
+For historical reasons, annotations on methods of an `@AutoValue`-annotated
+class are copied to the generated implementation class's methods. However, if
+you want to exclude some annotations from being copied, you can use
+[`@AutoValue.CopyAnnotations`]'s `exclude` method to stop this behavior.
+
+### Copying to the generated field
+
+If you want to copy annotations from your `@AutoValue`-annotated class's methods
+to the generated fields in the `AutoValue_...` implementation, annotate your
+method with [`@AutoValue.CopyAnnotations`].
+
+For example, if `Example.java` is:
+
+```java
+@Immutable
+@AutoValue
+abstract class Example {
+  @CopyAnnotations
+  @SuppressWarnings("Immutable") // justification ...
+  abstract Object getObject();
+
+  // other details ...
+}
+```
+
+Then `@AutoValue` will generate `AutoValue_Example.java`:
+
+```java
+final class AutoValue_Example extends Example {
+  @SuppressWarnings("Immutable")
+  private final Object object;
+
+  @SuppressWarnings("Immutable")
+  @Override
+  Object getObject() {
+    return object;
+  }
+
+  // other details ...
+}
+```
+
+[`@AutoValue.CopyAnnotations`]: http://static.javadoc.io/com.google.auto.value/auto-value/1.6/com/google/auto/value/AutoValue.CopyAnnotations.html
+
diff --git a/value/userguide/index.md b/value/userguide/index.md
new file mode 100644
index 0000000..8cbe75f
--- /dev/null
+++ b/value/userguide/index.md
@@ -0,0 +1,271 @@
+# AutoValue
+
+
+*Generated immutable value classes for Java 7+* <br />
+***Éamonn McManus, Kevin Bourrillion*** <br />
+**Google, Inc.**
+
+> "AutoValue is a great tool for eliminating the drudgery of writing mundane
+> value classes in Java. It encapsulates much of the advice in Effective Java
+> Chapter 2, and frees you to concentrate on the more interesting aspects of
+> your program. The resulting program is likely to be shorter, clearer, and
+> freer of bugs. Two thumbs up."
+>
+> -- *Joshua Bloch, author, Effective Java*
+
+
+## <a name="background"></a>Background
+
+**Value classes** are extremely common in Java projects. These are classes for
+which you want to treat any two instances with suitably equal field values as
+interchangeable. That's right: we're talking about those classes where you wind
+up implementing `equals`, `hashCode` and `toString` in a bloated, repetitive,
+formulaic yet error-prone fashion.
+
+Writing these methods the first time is not too bad, with the aid of a few
+helper methods and IDE templates. But once written they continue to burden
+reviewers, editors and future readers. Their wide expanses of boilerplate
+sharply decrease the signal-to-noise ratio of your code... and they love to
+harbor hard-to-spot bugs.
+
+AutoValue provides an easier way to create immutable value classes, with a lot
+less code and less room for error, while **not restricting your freedom** to
+code almost any aspect of your class exactly the way you want it.
+
+This page will walk you through how to use AutoValue. Looking for a little more
+persuasion? Please see [Why AutoValue?](why.md).
+
+## <a name="howto"></a>How to use AutoValue
+
+The AutoValue concept is extremely simple: **You write an abstract class, and
+AutoValue implements it.** That is all there is to it; there is literally *no*
+configuration.
+
+**Note:** Below, we will illustrate an AutoValue class *without* a generated
+builder class. If you're more interested in the builder support, continue
+reading at [AutoValue with Builders](builders.md) instead.
+
+### <a name="example_java"></a>In your value class
+
+Create your value class as an *abstract* class, with an abstract accessor method
+for each desired property, and bearing the `@AutoValue` annotation.
+
+```java
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+abstract class Animal {
+  static Animal create(String name, int numberOfLegs) {
+    return new AutoValue_Animal(name, numberOfLegs);
+  }
+
+  abstract String name();
+  abstract int numberOfLegs();
+}
+```
+
+The constructor parameters correspond, in order, to the abstract accessor
+methods.
+
+**For a nested class**, see ["How do I use AutoValue with a nested class"](howto.md#nested).
+
+Note that in real life, some classes and methods would presumably be public and
+have Javadoc. We're leaving these off in the User Guide only to keep the
+examples short and simple.
+
+
+### With Maven
+
+You will need `auto-value-annotations-${auto-value.version}.jar` in your
+compile-time classpath, and you will need `auto-value-${auto-value.version}.jar`
+in your annotation-processor classpath.
+
+For `auto-value-annotations`, you can write this in `pom.xml`:
+
+```xml
+<dependencies>
+  <dependency>
+    <groupId>com.google.auto.value</groupId>
+    <artifactId>auto-value-annotations</artifactId>
+    <version>${auto-value.version}</version>
+  </dependency>
+</dependencies>
+```
+
+For `auto-value` (the annotation processor), you can write this:
+
+```xml
+<build>
+  <plugins>
+    <plugin>
+      <artifactId>maven-compiler-plugin</artifactId>
+      <configuration>
+        <annotationProcessorPaths>
+          <path>
+            <groupId>com.google.auto.value</groupId>
+            <artifactId>auto-value</artifactId>
+            <version>${auto-value.version}</version>
+          </path>
+        </annotationProcessorPaths>
+      </configuration>
+    </plugin>
+  </plugins>
+</build>
+```
+
+Alternatively, you can include the processor itself in your compile-time
+classpath. Doing so may pull unnecessary classes into your runtime classpath.
+
+```xml
+<dependencies>
+  <dependency>
+    <groupId>com.google.auto.value</groupId>
+    <artifactId>auto-value</artifactId>
+    <version>${auto-value.version}</version>
+    <optional>true</optional>
+  </dependency>
+</dependencies>
+```
+
+### With Gradle
+
+Gradle users can declare the dependencies in their `build.gradle` script:
+
+```groovy
+dependencies {
+  // Use 'api' rather than 'compile' for Android or java-library projects.
+  compile             "com.google.auto.value:auto-value-annotations:${autoValueVersion}"
+  annotationProcessor "com.google.auto.value:auto-value:${autoValueVersion}"
+}
+```
+
+Note: If you are using a version of Gradle prior to 4.6, you must apply an
+annotation processing plugin [as described in these instructions][tbroyer-apt].
+
+[tbroyer-apt]: https://plugins.gradle.org/plugin/net.ltgt.apt
+
+
+### <a name="usage"></a>Usage
+
+Your choice to use AutoValue is essentially *API-invisible*. This means that, to
+the consumer of your class, your class looks and functions like any other. The
+simple test below illustrates that behavior. Note that in real life, you would
+write tests that actually *do something interesting* with the object, instead of
+only checking field values going in and out.
+
+```java
+public void testAnimal() {
+  Animal dog = Animal.create("dog", 4);
+  assertEquals("dog", dog.name());
+  assertEquals(4, dog.numberOfLegs());
+
+  // You probably don't need to write assertions like these; just illustrating.
+  assertTrue(Animal.create("dog", 4).equals(dog));
+  assertFalse(Animal.create("cat", 4).equals(dog));
+  assertFalse(Animal.create("dog", 2).equals(dog));
+
+  assertEquals("Animal{name=dog, numberOfLegs=4}", dog.toString());
+}
+```
+
+### <a name="whats_going_on"></a>What's going on here?
+
+AutoValue runs inside `javac` as a standard annotation processor. It reads your
+abstract class and infers what the implementation class should look like. It
+generates source code, in your package, of a concrete implementation class
+which extends your abstract class, having:
+
+*   package visibility (non-public)
+*   one field for each of your abstract accessor methods
+*   a constructor that sets these fields
+*   a concrete implementation of each accessor method returning the associated
+    field value
+*   an `equals` implementation that compares these values in the usual way
+*   an appropriate corresponding `hashCode`
+*   a `toString` implementation returning a useful (but unspecified) string
+    representation of the instance
+
+Your hand-written code, as shown above, delegates its factory method call to the
+generated constructor and voilà!
+
+For the `Animal` example shown above, here is [typical code AutoValue might
+generate](generated-example.md).
+
+Note that *consumers* of your value class *don't need to know any of this*. They
+just invoke your provided factory method and get a well-behaved instance back.
+
+## <a name="warnings"></a>Warnings
+
+Be careful that you don't accidentally pass parameters to the generated
+constructor in the wrong order. You must ensure that **your tests are
+sufficient** to catch any field ordering problem. In most cases this should be
+the natural outcome from testing whatever actual purpose this value class was
+created for! In other cases a very simple test like the one shown above is
+enough. Consider switching to use the [builder option](builders.md) to avoid
+this problem.
+
+We reserve the right to **change the `hashCode` implementation** at any time.
+Never persist the result of `hashCode` or use it for any other unintended
+purpose, and be careful never to depend on the order your values appear in
+unordered collections like `HashSet`.
+
+## <a name="why"></a>Why should I use AutoValue?
+
+See [Why AutoValue?](why.md).
+
+## <a name="versions"></a>What Java versions does it work with?
+
+AutoValue requires that your compiler be at least Java 8. However, the code that
+it generates is compatible with Java 7. That means that you can use it with
+`-source 7 -target 7` or (for Java 9+) `--release 7`.
+
+## <a name="more_howto"></a>How do I...
+
+How do I...
+
+*   ... [also generate a **builder** for my value class?](howto.md#builder)
+*   ... [use AutoValue with a **nested** class?](howto.md#nested)
+*   ... [use (or not use) JavaBeans-style name **prefixes**?](howto.md#beans)
+*   ... [use **nullable** properties?](howto.md#nullable)
+*   ... [perform other **validation**?](howto.md#validate)
+*   ... [use a property of a **mutable** type?](howto.md#mutable_property)
+*   ... [use a **custom** implementation of `equals`, etc.?](howto.md#custom)
+*   ... [have AutoValue implement a concrete or default
+    method?](howto.md#concrete)
+*   ... [have multiple **`create`** methods, or name it/them
+    differently?](howto.md#create)
+*   ... [**ignore** certain properties in `equals`, etc.?](howto.md#ignore)
+*   ... [have AutoValue also implement abstract methods from my
+    **supertypes**?](howto.md#supertypes)
+*   ... [use AutoValue with a **generic** class?](howto.md#generic)
+*   ... [make my class Java- or GWT\-**serializable**?](howto.md#serialize)
+*   ... [use AutoValue to **implement** an **annotation**
+    type?](howto.md#annotation)
+*   ... [also include **setter** (mutator) methods?](howto.md#setters)
+*   ... [also generate **`compareTo`**?](howto.md#compareTo)
+*   ... [use a **primitive array** for a property
+    value?](howto.md#primitive_array)
+*   ... [use an **object array** for a property value?](howto.md#object_array)
+*   ... [have one `@AutoValue` class **extend** another?](howto.md#inherit)
+*   ... [keep my accessor methods **private**?](howto.md#private_accessors)
+*   ... [expose a **constructor**, not factory method, as my public creation
+    API?](howto.md#public_constructor)
+*   ... [use AutoValue on an **interface**, not abstract
+    class?](howto.md#interface)
+*   ... [**memoize** ("cache") derived properties?](howto.md#memoize)
+*   ... [memoize the result of `hashCode` or
+    `toString`?](howto.md#memoize_hash_tostring)
+*   ... [make a class where only one of its properties is ever
+    set?](howto.md#oneof)
+*   ... [copy annotations from a class/method to the implemented
+    class/method/field?](howto.md#copy_annotations)
+
+<!-- TODO(kevinb): should the above be only a selected subset? -->
+
+## <a name="more"></a>More information
+
+See the links in the sidebar at the top left.
+
+<!-- TODO(kevinb): there are some tidbits of information that don't seem to
+     belong anywhere yet; such as how it implements floating-point equality -->
+
diff --git a/value/userguide/performance.md b/value/userguide/performance.md
new file mode 100644
index 0000000..ee7a8fc
--- /dev/null
+++ b/value/userguide/performance.md
@@ -0,0 +1,9 @@
+# Performance notes
+
+
+TODO: write a real page
+
+*   should perform like a hand-written class after HotSpot compiles it
+    (generated accessors can be inlined)
+*   what does proguard do with it
+*   hash codes are not cached
diff --git a/value/userguide/practices.md b/value/userguide/practices.md
new file mode 100644
index 0000000..0121bd8
--- /dev/null
+++ b/value/userguide/practices.md
@@ -0,0 +1,64 @@
+# Best practices
+
+
+
+## <a name="interchangeable"></a>"Equals means interchangeable"
+
+Don't use AutoValue to implement value semantics unless you really want value
+semantics. In particular, you should never care about the difference between two
+equal instances.
+
+## <a name="mutable_properties"></a>Avoid mutable property types
+
+Avoid mutable types, including arrays, for your properties, especially if you
+make your accessor methods `public`. The generated accessors don't copy the
+field value on its way out, so you'd be exposing your internal state.
+
+Note that this doesn't mean your factory method can't *accept* mutable types as
+input parameters. Example:
+
+```java
+@AutoValue
+public abstract class ListExample {
+  abstract ImmutableList<String> names();
+
+  public static ListExample create(List<String> mutableNames) {
+    return new AutoValue_ListExample(ImmutableList.copyOf(mutableNames));
+  }
+}
+```
+
+## <a name="simple"></a>Keep behavior simple and dependency-free
+
+Your class can (and should) contain *simple* intrinsic behavior. But it
+shouldn't require complex dependencies and shouldn't access static state.
+
+You should essentially *never* need an alternative implementation of your
+hand-written abstract class, whether hand-written or generated by a mocking
+framework. If your behavior has enough complexity (or dependencies) that it
+actually needs to be mocked or faked, split it into a separate type that is
+*not* a value type. Otherwise it permits an instance with "real" behavior and
+one with "mock/fake" behavior to be `equals`, which does not make sense.
+
+## <a name="one_reference"></a>One reference only
+
+Other code in the same package will be able to directly access the generated
+class, but *should not*. It's best if each generated class has one and only one
+reference from your source code: the call from your static factory method to the
+generated constructor. If you have multiple factory methods, have them all
+delegate to the same hand-written method, so there is still only one point of
+contact with the generated code. This way, you have only one place to insert
+precondition checks or other pre- or postprocessing.
+
+## <a name="final"></a>Mark all concrete methods `final`
+
+Consider that other developers will try to read and understand your value class
+while looking only at your hand-written class, not the actual (generated)
+implementation class. If you mark your concrete methods `final`, they won't have
+to wonder whether the generated subclass might be overriding them. This is
+especially helpful if you are *[underriding](howto.md#custom)* `equals`,
+`hashCode` or `toString`!
+
+## <a name="constructor"></a>Maybe add an explicit, inaccessible constructor
+
+There are a few small advantages to adding a package-private, parameterless constructor to your abstract class. It prevents unwanted subclasses, and prevents an undocumented public constructor showing up in your generated API documentation. Whether these benefits are worth the extra noise in the file is a matter of your judgment.
diff --git a/value/userguide/trouble.md b/value/userguide/trouble.md
new file mode 100644
index 0000000..162041f
--- /dev/null
+++ b/value/userguide/trouble.md
@@ -0,0 +1,12 @@
+# Troubleshooting
+
+
+TODO
+
+## `equals()` is not returning what I expect
+
+This is usually a sign that one of your field types is not implementing `equals`
+as you expect. A typical offending class is [`JSONObject`]
+(https://developer.android.com/reference/org/json/JSONObject.html), which
+doesn't override `Object.equals()` and thus compromises your class's `equals`
+behavior as well.
diff --git a/value/userguide/why.md b/value/userguide/why.md
new file mode 100644
index 0000000..603abb8
--- /dev/null
+++ b/value/userguide/why.md
@@ -0,0 +1,19 @@
+# Why use AutoValue?
+
+
+AutoValue is the only solution to the value class problem in Java having all of
+the following characteristics:
+
+*   **API-invisible** (callers cannot become dependent on your choice to use it)
+*   No runtime dependencies
+*   Negligible cost to performance
+*   Very few limitations on what your class can do
+*   Extralinguistic "magic" kept to an absolute minimum (uses only standard Java
+    platform technologies, in the manner they were intended)
+
+This
+[slide presentation] compares AutoValue to numerous alternatives and explains
+why we think it is better.
+
+
+[slide presentation]: https://docs.google.com/presentation/d/14u_h-lMn7f1rXE1nDiLX0azS3IkgjGl5uxp5jGJ75RE/edit