package com.ryanharter.auto.value.gson;

import com.google.auto.value.processor.AutoValueProcessor;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import static com.google.common.truth.Truth.assertAbout;
import static com.google.common.truth.Truth.assertThat;
import static com.google.testing.compile.CompilationSubject.compilations;
import static com.google.testing.compile.Compiler.javac;
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
import static javax.tools.JavaFileObject.Kind.OTHER;

@RunWith(JUnit4.class)
public class AutoValueGsonExtensionTest {

  private JavaFileObject nullable;
  private JavaFileObject typeTargetNullable;

  @Before
  public void setup() {
    nullable = JavaFileObjects.forSourceString("com.ryanharter.auto.value.gson.Nullable", ""
        + "package com.ryanharter.auto.value.gson;\n"
        + "import java.lang.annotation.Retention;\n"
        + "import java.lang.annotation.Target;\n"
        + "import static java.lang.annotation.ElementType.METHOD;\n"
        + "import static java.lang.annotation.ElementType.PARAMETER;\n"
        + "import static java.lang.annotation.ElementType.FIELD;\n"
        + "import static java.lang.annotation.RetentionPolicy.SOURCE;\n"
        + "@Retention(SOURCE)\n"
        + "@Target({METHOD, PARAMETER, FIELD})\n"
        + "public @interface Nullable {\n"
        + "}");
    typeTargetNullable = JavaFileObjects.forSourceString("com.ryanharter.auto.value.gson.Nullable",
        ""
        + "package com.ryanharter.auto.value.gson;\n"
        + "import java.lang.annotation.Retention;\n"
        + "import java.lang.annotation.Target;\n"
        + "import static java.lang.annotation.ElementType.TYPE_USE;\n"
        + "import static java.lang.annotation.RetentionPolicy.SOURCE;\n"
        + "@Retention(SOURCE)\n"
        + "@Target(TYPE_USE)\n"
        + "public @interface Nullable {\n"
        + "}");
  }

  @Test
  public void simple() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.gson.annotations.SerializedName;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.common.collect.ImmutableMap;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.TypeAdapterFactory;\n"
        + "import com.google.gson.reflect.TypeToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import java.io.IOException;\n"
        + "import java.util.List;\n"
        + "import java.util.Map;\n"
        + "import java.util.Set;\n"
        + "@AutoValue abstract class Test {\n"
        + "  static TypeAdapter<Test> typeAdapter(Gson gson) {\n"
        + "    return new AutoValue_Test.GsonTypeAdapter(gson);\n"
        + "  }\n"
        // Reference type
        + "abstract String a();\n"
        // Array type
        + "abstract int[] b();\n"
        // Primitive type
        + "abstract int c();\n"
        // SerializedName
        + "@SerializedName(\"_D\") abstract String d();\n"
        // Nullable type
        + "@Nullable abstract String e();\n"
        // Parametrized type, multiple parameters
        + "abstract ImmutableMap<String, Number> f();\n"
        // Parametrized type, single parameter
        + "abstract Set<String> g();\n"
        // Nested parameterized type
        + "abstract Map<String, Set<String>> h();\n"
        // SerializedName with alternate
        + "@SerializedName(value = \"_I\", alternate = {\"_I_1\", \"_I_2\"}) abstract String i();\n"
        // Nullable collection type
        + "@Nullable abstract List<? extends String> j();\n"
        // Deeply nested parameterized type
        + "abstract Map<String, Map<String, Map<String, Map<String, Map<String, ? extends String>>>>> o();\n" +
        "  static class TestTypeAdapter extends TypeAdapter<String> {\n" +
        "    @Override public void write(JsonWriter out, String value) throws IOException {}\n" +
        "    @Override public String read(JsonReader in) throws IOException { return null; }\n" +
        "  }\n" +
        "  static class TestListTypeAdapter extends TypeAdapter<List<String>> {\n" +
        "    @Override public void write(JsonWriter out, List<String> value) throws IOException {}\n" +
        "    @Override public List<String> read(JsonReader in) throws IOException { return null; }\n" +
        "  }\n" +
        "  static class TestTypeAdapterFactory implements TypeAdapterFactory {\n" +
        "    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { return null; }\n" +
        "  }\n"
        + "}\n"
    );

    JavaFileObject expected = JavaFileObjects.forSourceString("test/AutoValue_Test", "package test;\n"
        + "\n"
        + "import com.google.common.collect.ImmutableMap;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.reflect.TypeToken;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import com.google.gson.stream.JsonToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "import com.ryanharter.auto.value.gson.internal.Util;\n"
        + "import com.ryanharter.auto.value.gson.internal.WildcardUtil;\n"
        + "import java.io.IOException;\n"
        + "import java.lang.Integer;\n"
        + "import java.lang.Number;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.String;\n"
        + "import java.lang.StringBuilder;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "import java.util.ArrayList;\n"
        + "import java.util.List;\n"
        + "import java.util.Map;\n"
        + "import java.util.Set;\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\n"
        + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
        + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
        + ")\n"
        + "final class AutoValue_Test extends $AutoValue_Test {\n"
        + "  AutoValue_Test(String a, int[] b, int c, String d, @Nullable String e,\n"
        + "      ImmutableMap<String, Number> f, Set<String> g, Map<String, Set<String>> h, "
        + "String i,\n"
        + "      @Nullable List<? extends String> j,\n"
        + "      Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
        + "String>>>>> o) {\n"
        + "    super(a, b, c, d, e, f, g, h, i, j, o);\n"
        + "  }\n"
        + "\n"
        + "  static final class GsonTypeAdapter extends TypeAdapter<Test> {\n"
        + "    private volatile TypeAdapter<String> string_adapter;\n"
        + "    private volatile TypeAdapter<int[]> array__int_adapter;\n"
        + "    private volatile TypeAdapter<Integer> int__adapter;\n"
        + "    private volatile TypeAdapter<ImmutableMap<String, Number>> "
        + "immutableMap__string_number_adapter;\n"
        + "    private volatile TypeAdapter<Set<String>> set__string_adapter;\n"
        + "    private volatile TypeAdapter<Map<String, Set<String>>> "
        + "map__string_set__string_adapter;\n"
        + "    private volatile TypeAdapter<List<? extends String>> "
        + "list__wildcard__string_adapter;\n"
        + "    private volatile TypeAdapter<Map<String, Map<String, Map<String, Map<String, "
        + "Map<String, ? extends String>>>>>> "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter;\n"
        + "    private final Map<String, String> realFieldNames;\n"
        + "    private final Gson gson;\n"
        + "    GsonTypeAdapter(Gson gson) {\n"
        + "      ArrayList<String> fields = new ArrayList<String>();\n"
        + "      fields.add(\"a\");\n"
        + "      fields.add(\"b\");\n"
        + "      fields.add(\"c\");\n"
        + "      fields.add(\"d\");\n"
        + "      fields.add(\"e\");\n"
        + "      fields.add(\"f\");\n"
        + "      fields.add(\"g\");\n"
        + "      fields.add(\"h\");\n"
        + "      fields.add(\"i\");\n"
        + "      fields.add(\"j\");\n"
        + "      fields.add(\"o\");\n"
        + "      this.gson = gson;\n"
        + "      this.realFieldNames = Util.renameFields($AutoValue_Test.class, fields, gson"
        + ".fieldNamingStrategy());\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public void write(JsonWriter jsonWriter, Test object) throws IOException {\n"
        + "      if (object == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "        return;\n"
        + "      }\n"
        + "      jsonWriter.beginObject();\n"
        + "      jsonWriter.name(realFieldNames.get(\"a\"));\n"
        + "      if (object.a() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.a());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"b\"));\n"
        + "      if (object.b() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<int[]> array__int_adapter = this.array__int_adapter;\n"
        + "        if (array__int_adapter == null) {\n"
        + "          this.array__int_adapter = array__int_adapter = gson.getAdapter(int[].class);\n"
        + "        }\n"
        + "        array__int_adapter.write(jsonWriter, object.b());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"c\"));\n"
        + "      {\n"
        + "        TypeAdapter<Integer> int__adapter = this.int__adapter;\n"
        + "        if (int__adapter == null) {\n"
        + "          this.int__adapter = int__adapter = gson.getAdapter(Integer.class);\n"
        + "        }\n"
        + "        int__adapter.write(jsonWriter, object.c());\n"
        + "      }\n"
        + "      jsonWriter.name(\"_D\");\n"
        + "      if (object.d() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.d());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"e\"));\n"
        + "      if (object.e() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.e());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"f\"));\n"
        + "      if (object.f() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<ImmutableMap<String, Number>> immutableMap__string_number_adapter "
        + "= this.immutableMap__string_number_adapter;\n"
        + "        if (immutableMap__string_number_adapter == null) {\n"
        + "          this.immutableMap__string_number_adapter = "
        + "immutableMap__string_number_adapter = (TypeAdapter<ImmutableMap<String, Number>>) gson"
        + ".getAdapter(TypeToken.getParameterized(ImmutableMap.class, String.class, Number.class)"
        + ");\n"
        + "        }\n"
        + "        immutableMap__string_number_adapter.write(jsonWriter, object.f());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"g\"));\n"
        + "      if (object.g() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Set<String>> set__string_adapter = this.set__string_adapter;\n"
        + "        if (set__string_adapter == null) {\n"
        + "          this.set__string_adapter = set__string_adapter = (TypeAdapter<Set<String>>) "
        + "gson.getAdapter(TypeToken.getParameterized(Set.class, String.class));\n"
        + "        }\n"
        + "        set__string_adapter.write(jsonWriter, object.g());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"h\"));\n"
        + "      if (object.h() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Map<String, Set<String>>> map__string_set__string_adapter = this"
        + ".map__string_set__string_adapter;\n"
        + "        if (map__string_set__string_adapter == null) {\n"
        + "          this.map__string_set__string_adapter = map__string_set__string_adapter = "
        + "(TypeAdapter<Map<String, Set<String>>>) gson.getAdapter(TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(Set.class, String.class).getType()));\n"
        + "        }\n"
        + "        map__string_set__string_adapter.write(jsonWriter, object.h());\n"
        + "      }\n"
        + "      jsonWriter.name(\"_I\");\n"
        + "      if (object.i() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.i());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"j\"));\n"
        + "      if (object.j() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<List<? extends String>> list__wildcard__string_adapter = this"
        + ".list__wildcard__string_adapter;\n"
        + "        if (list__wildcard__string_adapter == null) {\n"
        + "          this.list__wildcard__string_adapter = list__wildcard__string_adapter = "
        + "(TypeAdapter<List<? extends String>>) gson.getAdapter(TypeToken.getParameterized(List"
        + ".class, WildcardUtil.subtypeOf(String.class)));\n"
        + "        }\n"
        + "        list__wildcard__string_adapter.write(jsonWriter, object.j());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"o\"));\n"
        + "      if (object.o() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? "
        + "extends String>>>>>> "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter ="
        + " this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter;\n"
        + "        if "
        + "(map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "== null) {\n"
        + "          this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "= map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + " = (TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
        + "String>>>>>>) gson.getAdapter(TypeToken.getParameterized(Map.class, String.class, "
        + "TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(Map.class, String.class, TypeToken"
        + ".getParameterized(Map.class, String.class, WildcardUtil.subtypeOf(String.class))"
        + ".getType()).getType()).getType()).getType()));\n"
        + "        }\n"
        + "        "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + ".write(jsonWriter, object.o());\n"
        + "      }\n"
        + "      jsonWriter.endObject();\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public Test read(JsonReader jsonReader) throws IOException {\n"
        + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "        jsonReader.nextNull();\n"
        + "        return null;\n"
        + "      }\n"
        + "      jsonReader.beginObject();\n"
        + "      String a = null;\n"
        + "      int[] b = null;\n"
        + "      int c = 0;\n"
        + "      String d = null;\n"
        + "      String e = null;\n"
        + "      ImmutableMap<String, Number> f = null;\n"
        + "      Set<String> g = null;\n"
        + "      Map<String, Set<String>> h = null;\n"
        + "      String i = null;\n"
        + "      List<? extends String> j = null;\n"
        + "      Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
        + "String>>>>> o = null;\n"
        + "      while (jsonReader.hasNext()) {\n"
        + "        String _name = jsonReader.nextName();\n"
        + "        if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "          jsonReader.nextNull();\n"
        + "          continue;\n"
        + "        }\n"
        + "        switch (_name) {\n"
        + "          case \"_D\": {\n"
        + "            TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "            if (string_adapter == null) {\n"
        + "              this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "            }\n"
        + "            d = string_adapter.read(jsonReader);\n"
        + "            break;\n"
        + "          }\n"
        + "          case \"_I_1\":\n"
        + "          case \"_I_2\":\n"
        + "          case \"_I\": {\n"
        + "            TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "            if (string_adapter == null) {\n"
        + "              this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "            }\n"
        + "            i = string_adapter.read(jsonReader);\n"
        + "            break;\n"
        + "          }\n"
        + "          default: {\n"
        + "            if (realFieldNames.get(\"a\").equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              a = string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"b\").equals(_name)) {\n"
        + "              TypeAdapter<int[]> array__int_adapter = this.array__int_adapter;\n"
        + "              if (array__int_adapter == null) {\n"
        + "                this.array__int_adapter = array__int_adapter = gson.getAdapter(int[]"
        + ".class);\n"
        + "              }\n"
        + "              b = array__int_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"c\").equals(_name)) {\n"
        + "              TypeAdapter<Integer> int__adapter = this.int__adapter;\n"
        + "              if (int__adapter == null) {\n"
        + "                this.int__adapter = int__adapter = gson.getAdapter(Integer.class);\n"
        + "              }\n"
        + "              c = int__adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"e\").equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              e = string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"f\").equals(_name)) {\n"
        + "              TypeAdapter<ImmutableMap<String, Number>> "
        + "immutableMap__string_number_adapter = this.immutableMap__string_number_adapter;\n"
        + "              if (immutableMap__string_number_adapter == null) {\n"
        + "                this.immutableMap__string_number_adapter = "
        + "immutableMap__string_number_adapter = (TypeAdapter<ImmutableMap<String, Number>>) gson"
        + ".getAdapter(TypeToken.getParameterized(ImmutableMap.class, String.class, Number.class)"
        + ");\n"
        + "              }\n"
        + "              f = immutableMap__string_number_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"g\").equals(_name)) {\n"
        + "              TypeAdapter<Set<String>> set__string_adapter = this.set__string_adapter;\n"
        + "              if (set__string_adapter == null) {\n"
        + "                this.set__string_adapter = set__string_adapter = "
        + "(TypeAdapter<Set<String>>) gson.getAdapter(TypeToken.getParameterized(Set.class, "
        + "String.class));\n"
        + "              }\n"
        + "              g = set__string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"h\").equals(_name)) {\n"
        + "              TypeAdapter<Map<String, Set<String>>> map__string_set__string_adapter = "
        + "this.map__string_set__string_adapter;\n"
        + "              if (map__string_set__string_adapter == null) {\n"
        + "                this.map__string_set__string_adapter = map__string_set__string_adapter"
        + " = (TypeAdapter<Map<String, Set<String>>>) gson.getAdapter(TypeToken.getParameterized"
        + "(Map.class, String.class, TypeToken.getParameterized(Set.class, String.class).getType"
        + "()));\n"
        + "              }\n"
        + "              h = map__string_set__string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"j\").equals(_name)) {\n"
        + "              TypeAdapter<List<? extends String>> list__wildcard__string_adapter = "
        + "this.list__wildcard__string_adapter;\n"
        + "              if (list__wildcard__string_adapter == null) {\n"
        + "                this.list__wildcard__string_adapter = list__wildcard__string_adapter ="
        + " (TypeAdapter<List<? extends String>>) gson.getAdapter(TypeToken.getParameterized(List"
        + ".class, WildcardUtil.subtypeOf(String.class)));\n"
        + "              }\n"
        + "              j = list__wildcard__string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"o\").equals(_name)) {\n"
        + "              TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, "
        + "? extends String>>>>>> "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter ="
        + " this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter;\n"
        + "              if "
        + "(map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "== null) {\n"
        + "                this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "= map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + " = (TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
        + "String>>>>>>) gson.getAdapter(TypeToken.getParameterized(Map.class, String.class, "
        + "TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(Map.class, String.class, TypeToken"
        + ".getParameterized(Map.class, String.class, WildcardUtil.subtypeOf(String.class))"
        + ".getType()).getType()).getType()).getType()));\n"
        + "              }\n"
        + "              o = "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + ".read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            jsonReader.skipValue();\n"
        + "          }\n"
        + "        }\n"
        + "      }\n"
        + "      jsonReader.endObject();\n"
        + "      return new AutoValue_Test(a, b, c, d, e, f, g, h, i, j, o);\n"
        + "    }\n"
        + "\n"
        + "    @Override\n"
        + "    public String toString() {\n"
        + "      return new StringBuilder().append(\"TypeAdapter(\").append(\"Test\").append(\")"
        + "\").toString();\n"
        + "    }\n"
        + "  }\n"
        + "}"
    );

    assertAbout(javaSources())
        .that(Arrays.asList(nullable, source))
        .withCompilerOptions("-A" + AutoValueGsonExtension.USE_FIELD_NAME_POLICY)
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test
  public void simpleExternal() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.ryanharter.auto.value.gson.GenerateTypeAdapter;\n"
        + "@GenerateTypeAdapter\n"
        + "@AutoValue\n"
        + "abstract class Test {\n"
        + "  abstract String a();\n"
        + "}\n"
    );

    JavaFileObject expected = JavaFileObjects.forSourceString("test/Test_GsonTypeAdapter",
        "package test;\n"
        + "\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import com.google.gson.stream.JsonToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import java.io.IOException;\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\n"
        + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
        + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
        + ")\n"
        + "final class Test_GsonTypeAdapter extends TypeAdapter<Test> {\n"
        + "  private volatile TypeAdapter<String> string_adapter;\n"
        + "\n"
        + "  private final Gson gson;\n"
        + "\n"
        + "  Test_GsonTypeAdapter(Gson gson) {\n"
        + "    this.gson = gson;\n"
        + "  }\n"
        + "\n"
        + "  @Override\n"
        + "  @SuppressWarnings(\"unchecked\")\n"
        + "  public void write(JsonWriter jsonWriter, Test object) throws IOException {\n"
        + "    if (object == null) {\n"
        + "      jsonWriter.nullValue();\n"
        + "      return;\n"
        + "    }\n"
        + "    jsonWriter.beginObject();\n"
        + "    jsonWriter.name(\"a\");\n"
        + "    if (object.a() == null) {\n"
        + "      jsonWriter.nullValue();\n"
        + "    } else {\n"
        + "      TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "      if (string_adapter == null) {\n"
        + "        this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "      }\n"
        + "      string_adapter.write(jsonWriter, object.a());\n"
        + "    }\n"
        + "    jsonWriter.endObject();\n"
        + "  }\n"
        + "\n"
        + "  @Override\n"
        + "  @SuppressWarnings(\"unchecked\")\n"
        + "  public Test read(JsonReader jsonReader) throws IOException {\n"
        + "    if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "      jsonReader.nextNull();\n"
        + "      return null;\n"
        + "    }\n"
        + "    jsonReader.beginObject();\n"
        + "    String a = null;\n"
        + "    while (jsonReader.hasNext()) {\n"
        + "      String _name = jsonReader.nextName();\n"
        + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "        jsonReader.nextNull();\n"
        + "        continue;\n"
        + "      }\n"
        + "      switch (_name) {\n"
        + "        default: {\n"
        + "          if (\"a\".equals(_name)) {\n"
        + "            TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "            if (string_adapter == null) {\n"
        + "              this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "            }\n"
        + "            a = string_adapter.read(jsonReader);\n"
        + "            continue;\n"
        + "          }\n"
        + "          jsonReader.skipValue();\n"
        + "        }\n"
        + "      }\n"
        + "    }\n"
        + "    jsonReader.endObject();\n"
        + "    return new AutoValue_Test(a);\n"
        + "  }\n"
        + "\n"
        + "  @Override\n"
        + "  public String toString() {\n"
        + "    return new StringBuilder().append(\"TypeAdapter(\").append(\"Test\").append(\")\").toString();\n"
        + "  }\n"
        + "}\n"
    );

    JavaFileObject expectedProguard = proguardResource(
        "META-INF/proguard/avg-test.Test.pro",
        "-if class test.Test\n"
            + "-keepnames class test.Test\n"
            + "-if class test.Test\n"
            + "-keep class test.Test_GsonTypeAdapter {\n"
            + "    <init>(com.google.gson.Gson);\n"
            + "}\n");

    assertAbout(javaSources())
        .that(ImmutableList.of(source))
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .and()
        .generatesSources(expected)
        .and()
        .generatesFiles(expectedProguard);
  }

  @Test
  public void simpleExternalGeneric() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.ryanharter.auto.value.gson.GenerateTypeAdapter;\n"
        + "@GenerateTypeAdapter\n"
        + "@AutoValue\n"
        + "abstract class Test<T> {\n"
        + "  abstract T a();\n"
        + "}\n"
    );

    JavaFileObject expected = JavaFileObjects.forSourceString("test/Test_GsonTypeAdapter",
            "package test;\n"
            + "\n"
            + "import com.google.gson.Gson;\n"
            + "import com.google.gson.TypeAdapter;\n"
            + "import com.google.gson.reflect.TypeToken;\n"
            + "import com.google.gson.stream.JsonReader;\n"
            + "import com.google.gson.stream.JsonToken;\n"
            + "import com.google.gson.stream.JsonWriter;\n"
            + "import java.io.IOException;\n"
            + "import java.lang.reflect.Type;\n"
            + "import javax.annotation.Generated;\n"
            + "\n"
            + "@Generated(\n"
            + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
            + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
            + ")\n"
            + "final class Test_GsonTypeAdapter<T> extends TypeAdapter<Test<T>> {\n"
            + "  private volatile TypeAdapter<T> T_adapter;\n"
            + "\n"
            + "  private final Gson gson;\n"
            + "\n"
            + "  private final Type[] typeArgs;\n"
            + "\n"
            + "  Test_GsonTypeAdapter(Gson gson, Type[] types) {\n"
            + "    typeArgs = types;\n"
            + "    this.gson = gson;\n"
            + "  }\n"
            + "\n"
            + "  @Override\n"
            + "  @SuppressWarnings(\"unchecked\")\n"
            + "  public void write(JsonWriter jsonWriter, Test<T> object) throws IOException {\n"
            + "    if (object == null) {\n"
            + "      jsonWriter.nullValue();\n"
            + "      return;\n"
            + "    }\n"
            + "    jsonWriter.beginObject();\n"
            + "    jsonWriter.name(\"a\");\n"
            + "    if (object.a() == null) {\n"
            + "      jsonWriter.nullValue();\n"
            + "    } else {\n"
            + "      TypeAdapter<T> T_adapter = this.T_adapter;\n"
            + "      if (T_adapter == null) {\n"
            + "        this.T_adapter = T_adapter = (TypeAdapter<T>) gson.getAdapter(TypeToken"
            + ".get(typeArgs[0]));\n"
            + "      }\n"
            + "      T_adapter.write(jsonWriter, object.a());\n"
            + "    }\n"
            + "    jsonWriter.endObject();\n"
            + "  }\n"
            + "\n"
            + "  @Override\n"
            + "  @SuppressWarnings(\"unchecked\")\n"
            + "  public Test<T> read(JsonReader jsonReader) throws IOException {\n"
            + "    if (jsonReader.peek() == JsonToken.NULL) {\n"
            + "      jsonReader.nextNull();\n"
            + "      return null;\n"
            + "    }\n"
            + "    jsonReader.beginObject();\n"
            + "    T a = null;\n"
            + "    while (jsonReader.hasNext()) {\n"
            + "      String _name = jsonReader.nextName();\n"
            + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
            + "        jsonReader.nextNull();\n"
            + "        continue;\n"
            + "      }\n"
            + "      switch (_name) {\n"
            + "        default: {\n"
            + "          if (\"a\".equals(_name)) {\n"
            + "            TypeAdapter<T> T_adapter = this.T_adapter;\n"
            + "            if (T_adapter == null) {\n"
            + "              this.T_adapter = T_adapter = (TypeAdapter<T>) gson.getAdapter(TypeToken.get(typeArgs[0]));\n"
            + "            }\n"
            + "            a = T_adapter.read(jsonReader);\n"
            + "            continue;\n"
            + "          }\n"
            + "          jsonReader.skipValue();\n"
            + "        }\n"
            + "      }\n"
            + "    }\n"
            + "    jsonReader.endObject();\n"
            + "    return new AutoValue_Test<>(a);\n"
            + "  }\n"
            + "\n"
            + "  @Override\n"
            + "  public String toString() {\n"
            + "    return new StringBuilder().append(\"TypeAdapter(\").append(\"Test\").append(\")\").toString();\n"
            + "  }\n"
            + "}"
    );

    JavaFileObject expectedProguard = proguardResource(
        "META-INF/proguard/avg-test.Test.pro",
        "-if class test.Test\n"
            + "-keepnames class test.Test\n"
            + "-if class test.Test\n"
            + "-keep class test.Test_GsonTypeAdapter {\n"
            + "    <init>(com.google.gson.Gson,java.lang.reflect.Type[]);\n"
            + "}\n");

    assertAbout(javaSources())
        .that(ImmutableList.of(source))
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .and()
        .generatesSources(expected)
        .and()
        .generatesFiles(expectedProguard);
  }

  @Test
  public void simpleNoFieldNamePolicy() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.gson.annotations.SerializedName;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.common.collect.ImmutableMap;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.TypeAdapterFactory;\n"
        + "import com.google.gson.reflect.TypeToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import java.io.IOException;\n"
        + "import java.util.List;\n"
        + "import java.util.Map;\n"
        + "import java.util.Set;\n"
        + "@AutoValue abstract class Test {\n"
        + "  static TypeAdapter<Test> typeAdapter(Gson gson) {\n"
        + "    return new AutoValue_Test.GsonTypeAdapter(gson);\n"
        + "  }\n"
        // Reference type
        + "abstract String a();\n"
        // Array type
        + "abstract int[] b();\n"
        // Primitive type
        + "abstract int c();\n"
        // SerializedName
        + "@SerializedName(\"_D\") abstract String d();\n"
        // Nullable type
        + "@Nullable abstract String e();\n"
        // Parametrized type, multiple parameters
        + "abstract ImmutableMap<String, Number> f();\n"
        // Parametrized type, single parameter
        + "abstract Set<String> g();\n"
        // Nested parameterized type
        + "abstract Map<String, Set<String>> h();\n"
        // SerializedName with alternate
        + "@SerializedName(value = \"_I\", alternate = {\"_I_1\", \"_I_2\"}) abstract String i();\n"
        // Nullable collection type
        + "@Nullable abstract List<? extends String> j();\n"
        // Deeply nested parameterized type
        + "abstract Map<String, Map<String, Map<String, Map<String, Map<String, ? extends String>>>>> o();\n" +
        "  static class TestTypeAdapter extends TypeAdapter<String> {\n" +
        "    @Override public void write(JsonWriter out, String value) throws IOException {}\n" +
        "    @Override public String read(JsonReader in) throws IOException { return null; }\n" +
        "  }\n" +
        "  static class TestListTypeAdapter extends TypeAdapter<List<String>> {\n" +
        "    @Override public void write(JsonWriter out, List<String> value) throws IOException {}\n" +
        "    @Override public List<String> read(JsonReader in) throws IOException { return null; }\n" +
        "  }\n" +
        "  static class TestTypeAdapterFactory implements TypeAdapterFactory {\n" +
        "    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { return null; }\n" +
        "  }\n"
        + "}\n"
    );

    JavaFileObject expected = JavaFileObjects.forSourceString("test/AutoValue_Test", "package "
            + "test;\n"
        + "\n"
        + "import com.google.common.collect.ImmutableMap;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.reflect.TypeToken;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import com.google.gson.stream.JsonToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "import com.ryanharter.auto.value.gson.internal.WildcardUtil;\n"
        + "import java.io.IOException;\n"
        + "import java.lang.Integer;\n"
        + "import java.lang.Number;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.String;\n"
        + "import java.lang.StringBuilder;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "import java.util.List;\n"
        + "import java.util.Map;\n"
        + "import java.util.Set;\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\n"
        + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
        + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
        + ")\n"
        + "final class AutoValue_Test extends $AutoValue_Test {\n"
        + "  AutoValue_Test(String a, int[] b, int c, String d, @Nullable String e,\n"
        + "      ImmutableMap<String, Number> f, Set<String> g, Map<String, Set<String>> h, "
            + "String i,\n"
        + "      @Nullable List<? extends String> j,\n"
        + "      Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
            + "String>>>>> o) {\n"
        + "    super(a, b, c, d, e, f, g, h, i, j, o);\n"
        + "  }\n"
        + "\n"
        + "  static final class GsonTypeAdapter extends TypeAdapter<Test> {\n"
        + "    private volatile TypeAdapter<String> string_adapter;\n"
        + "    private volatile TypeAdapter<int[]> array__int_adapter;\n"
        + "    private volatile TypeAdapter<Integer> int__adapter;\n"
        + "    private volatile TypeAdapter<ImmutableMap<String, Number>> "
        + "immutableMap__string_number_adapter;\n"
        + "    private volatile TypeAdapter<Set<String>> set__string_adapter;\n"
        + "    private volatile TypeAdapter<Map<String, Set<String>>> "
        + "map__string_set__string_adapter;\n"
        + "    private volatile TypeAdapter<List<? extends String>> "
        + "list__wildcard__string_adapter;\n"
        + "    private volatile TypeAdapter<Map<String, Map<String, Map<String, Map<String, "
        + "Map<String, ? extends String>>>>>> "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter;\n"
        + "    private final Gson gson;\n"
        + "    GsonTypeAdapter(Gson gson) {\n"
        + "      this.gson = gson;\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public void write(JsonWriter jsonWriter, Test object) throws IOException {\n"
        + "      if (object == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "        return;\n"
        + "      }\n"
        + "      jsonWriter.beginObject();\n"
        + "      jsonWriter.name(\"a\");\n"
        + "      if (object.a() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.a());\n"
        + "      }\n"
        + "      jsonWriter.name(\"b\");\n"
        + "      if (object.b() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<int[]> array__int_adapter = this.array__int_adapter;\n"
        + "        if (array__int_adapter == null) {\n"
        + "          this.array__int_adapter = array__int_adapter = gson.getAdapter(int[].class);\n"
        + "        }\n"
        + "        array__int_adapter.write(jsonWriter, object.b());\n"
        + "      }\n"
        + "      jsonWriter.name(\"c\");\n"
        + "      {\n"
        + "        TypeAdapter<Integer> int__adapter = this.int__adapter;\n"
        + "        if (int__adapter == null) {\n"
        + "          this.int__adapter = int__adapter = gson.getAdapter(Integer.class);\n"
        + "        }\n"
        + "        int__adapter.write(jsonWriter, object.c());\n"
        + "      }\n"
        + "      jsonWriter.name(\"_D\");\n"
        + "      if (object.d() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.d());\n"
        + "      }\n"
        + "      jsonWriter.name(\"e\");\n"
        + "      if (object.e() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.e());\n"
        + "      }\n"
        + "      jsonWriter.name(\"f\");\n"
        + "      if (object.f() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<ImmutableMap<String, Number>> immutableMap__string_number_adapter "
        + "= this.immutableMap__string_number_adapter;\n"
        + "        if (immutableMap__string_number_adapter == null) {\n"
        + "          this.immutableMap__string_number_adapter = "
            + "immutableMap__string_number_adapter = (TypeAdapter<ImmutableMap<String, Number>>) gson"
        + ".getAdapter(TypeToken.getParameterized(ImmutableMap.class, String.class, Number.class)"
        + ");\n"
        + "        }\n"
        + "        immutableMap__string_number_adapter.write(jsonWriter, object.f());\n"
        + "      }\n"
        + "      jsonWriter.name(\"g\");\n"
        + "      if (object.g() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Set<String>> set__string_adapter = this.set__string_adapter;\n"
        + "        if (set__string_adapter == null) {\n"
        + "          this.set__string_adapter = set__string_adapter = (TypeAdapter<Set<String>>) "
            + "gson.getAdapter(TypeToken.getParameterized(Set.class, String.class));\n"
        + "        }\n"
        + "        set__string_adapter.write(jsonWriter, object.g());\n"
        + "      }\n"
        + "      jsonWriter.name(\"h\");\n"
        + "      if (object.h() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Map<String, Set<String>>> map__string_set__string_adapter = this"
        + ".map__string_set__string_adapter;\n"
        + "        if (map__string_set__string_adapter == null) {\n"
        + "          this.map__string_set__string_adapter = map__string_set__string_adapter = "
        + "(TypeAdapter<Map<String, Set<String>>>) gson.getAdapter(TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(Set.class, String.class).getType()));\n"
        + "        }\n"
        + "        map__string_set__string_adapter.write(jsonWriter, object.h());\n"
        + "      }\n"
        + "      jsonWriter.name(\"_I\");\n"
        + "      if (object.i() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.i());\n"
        + "      }\n"
        + "      jsonWriter.name(\"j\");\n"
        + "      if (object.j() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<List<? extends String>> list__wildcard__string_adapter = this"
        + ".list__wildcard__string_adapter;\n"
        + "        if (list__wildcard__string_adapter == null) {\n"
        + "          this.list__wildcard__string_adapter = list__wildcard__string_adapter = "
        + "(TypeAdapter<List<? extends String>>) gson.getAdapter(TypeToken.getParameterized(List"
        + ".class, WildcardUtil.subtypeOf(String.class)));\n"
        + "        }\n"
        + "        list__wildcard__string_adapter.write(jsonWriter, object.j());\n"
        + "      }\n"
        + "      jsonWriter.name(\"o\");\n"
        + "      if (object.o() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? "
        + "extends String>>>>>> "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter ="
        + " this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter;\n"
        + "        if "
        + "(map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "== null) {\n"
        + "          this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "= map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + " = (TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
        + "String>>>>>>) gson.getAdapter(TypeToken.getParameterized(Map.class, String.class, "
        + "TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(Map.class, String.class, TypeToken"
        + ".getParameterized(Map.class, String.class, WildcardUtil.subtypeOf(String.class))"
        + ".getType()).getType()).getType()).getType()));\n"
        + "        }\n"
        + "        "
 + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + ".write(jsonWriter, object.o());\n"
        + "      }\n"
        + "      jsonWriter.endObject();\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public Test read(JsonReader jsonReader) throws IOException {\n"
        + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "        jsonReader.nextNull();\n"
        + "        return null;\n"
        + "      }\n"
        + "      jsonReader.beginObject();\n"
        + "      String a = null;\n"
        + "      int[] b = null;\n"
        + "      int c = 0;\n"
        + "      String d = null;\n"
        + "      String e = null;\n"
        + "      ImmutableMap<String, Number> f = null;\n"
        + "      Set<String> g = null;\n"
        + "      Map<String, Set<String>> h = null;\n"
        + "      String i = null;\n"
        + "      List<? extends String> j = null;\n"
        + "      Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
        + "String>>>>> o = null;\n"
        + "      while (jsonReader.hasNext()) {\n"
        + "        String _name = jsonReader.nextName();\n"
        + "        if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "          jsonReader.nextNull();\n"
        + "          continue;\n"
        + "        }\n"
        + "        switch (_name) {\n"
        + "          case \"_D\": {\n"
        + "            TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "            if (string_adapter == null) {\n"
        + "              this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "            }\n"
        + "            d = string_adapter.read(jsonReader);\n"
        + "            break;\n"
        + "          }\n"
        + "          case \"_I_1\":\n"
        + "          case \"_I_2\":\n"
        + "          case \"_I\": {\n"
        + "            TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "            if (string_adapter == null) {\n"
        + "              this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "            }\n"
        + "            i = string_adapter.read(jsonReader);\n"
        + "            break;\n"
        + "          }\n"
        + "          default: {\n"
        + "            if (\"a\".equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              a = string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (\"b\".equals(_name)) {\n"
        + "              TypeAdapter<int[]> array__int_adapter = this.array__int_adapter;\n"
        + "              if (array__int_adapter == null) {\n"
        + "                this.array__int_adapter = array__int_adapter = gson.getAdapter(int[]"
        + ".class);\n"
        + "              }\n"
        + "              b = array__int_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (\"c\".equals(_name)) {\n"
        + "              TypeAdapter<Integer> int__adapter = this.int__adapter;\n"
        + "              if (int__adapter == null) {\n"
        + "                this.int__adapter = int__adapter = gson.getAdapter(Integer.class);\n"
        + "              }\n"
        + "              c = int__adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (\"e\".equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              e = string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (\"f\".equals(_name)) {\n"
        + "              TypeAdapter<ImmutableMap<String, Number>> "
        + "immutableMap__string_number_adapter = this.immutableMap__string_number_adapter;\n"
        + "              if (immutableMap__string_number_adapter == null) {\n"
        + "                this.immutableMap__string_number_adapter = "
        + "immutableMap__string_number_adapter = (TypeAdapter<ImmutableMap<String, Number>>) gson"
        + ".getAdapter(TypeToken.getParameterized(ImmutableMap.class, String.class, Number.class)"
        + ");\n"
        + "              }\n"
        + "              f = immutableMap__string_number_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (\"g\".equals(_name)) {\n"
        + "              TypeAdapter<Set<String>> set__string_adapter = this.set__string_adapter;\n"
        + "              if (set__string_adapter == null) {\n"
        + "                this.set__string_adapter = set__string_adapter = "
        + "(TypeAdapter<Set<String>>) gson.getAdapter(TypeToken.getParameterized(Set.class, "
        + "String.class));\n"
        + "              }\n"
        + "              g = set__string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (\"h\".equals(_name)) {\n"
        + "              TypeAdapter<Map<String, Set<String>>> map__string_set__string_adapter = "
        + "this.map__string_set__string_adapter;\n"
        + "              if (map__string_set__string_adapter == null) {\n"
        + "                this.map__string_set__string_adapter = map__string_set__string_adapter"
            + " = (TypeAdapter<Map<String, Set<String>>>) gson.getAdapter(TypeToken.getParameterized"
        + "(Map.class, String.class, TypeToken.getParameterized(Set.class, String.class).getType"
        + "()));\n"
        + "              }\n"
        + "              h = map__string_set__string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (\"j\".equals(_name)) {\n"
        + "              TypeAdapter<List<? extends String>> list__wildcard__string_adapter = "
        + "this.list__wildcard__string_adapter;\n"
        + "              if (list__wildcard__string_adapter == null) {\n"
        + "                this.list__wildcard__string_adapter = list__wildcard__string_adapter ="
        + " (TypeAdapter<List<? extends String>>) gson.getAdapter(TypeToken.getParameterized(List"
        + ".class, WildcardUtil.subtypeOf(String.class)));\n"
        + "              }\n"
        + "              j = list__wildcard__string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (\"o\".equals(_name)) {\n"
        + "              TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, "
        + "? extends String>>>>>> "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter ="
        + " this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter;\n"
        + "              if "
        + "(map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "== null) {\n"
        + "                this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "= map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + " = (TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
        + "String>>>>>>) gson.getAdapter(TypeToken.getParameterized(Map.class, String.class, "
        + "TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(Map.class, String.class, TypeToken"
        + ".getParameterized(Map.class, String.class, WildcardUtil.subtypeOf(String.class))"
        + ".getType()).getType()).getType()).getType()));\n"
        + "              }\n"
        + "              o = "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + ".read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            jsonReader.skipValue();\n"
        + "          }\n"
        + "        }\n"
        + "      }\n"
        + "      jsonReader.endObject();\n"
        + "      return new AutoValue_Test(a, b, c, d, e, f, g, h, i, j, o);\n"
        + "    }\n"
        + "    @Override\n"
        + "    public String toString() {\n"
        + "      return new StringBuilder().append(\"TypeAdapter(\").append(\"Test\").append(\")"
        + "\").toString();\n"
        + "    }\n"
        + "  }\n"
        + "}"
    );

    assertAbout(javaSources())
        .that(Arrays.asList(nullable, source))
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test public void privateMethod() {
    // Private methods exclude them from AVGson consideration
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.Gson;\n"
        + "import java.util.Map;\n"
        + "import java.util.Set;\n"
        + "@AutoValue abstract class Test {\n"
        + "  private static TypeAdapter<Test> typeAdapter(Gson gson) {\n"
        + "    return null;\n"
        + "  }\n"
        // Reference type
        + "  public abstract String a();\n"
        // Array type
        + "  public abstract int[] b();\n"
        // Primitive type
        + "  public abstract int c();\n"
        // Parametrized type, multiple parameters
        + "  public abstract Map<String, Number> e();\n"
        // Parametrized type, single parameter
        + "  public abstract Set<? extends String> f();\n"
        // Nested parameterized type
        + "  public abstract Map<String, Set<? super String>> g();\n"
        // Nullable type
        + "  @Nullable abstract String i();\n"
        + "}\n"
    );

    Compilation compilation = javac()
        .withProcessors(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compile(nullable, source);
    assertAbout(compilations())
        .that(compilation)
        .succeeded();

    assertThat(compilation.generatedSourceFiles()).hasSize(1);
    assertThat(compilation.generatedSourceFiles().get(0).getName()).endsWith("AutoValue_Test.java");
  }

  @Test
  public void simpleWithBuilder() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.gson.annotations.SerializedName;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.common.collect.ImmutableMap;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.TypeAdapterFactory;\n"
        + "import com.google.gson.reflect.TypeToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import java.io.IOException;\n"
        + "import java.util.List;\n"
        + "import java.util.Map;\n"
        + "import java.util.Set;\n"
        + "@AutoValue abstract class Test {\n"
        + "  static TypeAdapter<Test> typeAdapter(Gson gson) {\n"
        + "    return new AutoValue_Test.GsonTypeAdapter(gson);\n"
        + "  }\n"
        // Reference type
        + "abstract String a();\n"
        // Array type
        + "abstract int[] b();\n"
        // Primitive type
        + "abstract int c();\n"
        // SerializedName
        + "@SerializedName(\"_D\") abstract String d();\n"
        // Nullable type
        + "@Nullable abstract String e();\n"
        // Parametrized type, multiple parameters
        + "abstract ImmutableMap<String, Number> f();\n"
        // Parametrized type, single parameter
        + "abstract Set<String> g();\n"
        // Nested parameterized type
        + "abstract Map<String, Set<String>> h();\n"
        // SerializedName with alternate
        + "@SerializedName(value = \"_I\", alternate = {\"_I_1\", \"_I_2\"}) abstract String i();\n"
        // Nullable collection type
        + "@Nullable abstract List<? extends String> j();\n"
        // Deeply nested parameterized type
        + "abstract Map<String, Map<String, Map<String, Map<String, Map<String, ? extends String>>>>> o();\n" +
        "  @AutoValue.Builder static abstract class Builder {\n" +
        "    abstract Builder a(String a);\n" +
        "    abstract Builder b(int[] b);\n" +
        "    abstract Builder c(int c);\n" +
        "    abstract Builder d(String d);\n" +
        "    abstract Builder e(String e);\n" +
        "    abstract Builder f(ImmutableMap<String, Number> f);\n" +
        "    abstract Builder g(Set<String> g);\n" +
        "    abstract Builder h(Map<String, Set<String>> h);\n" +
        "    abstract Builder i(String i);\n" +
        "    abstract Builder j(List<? extends String> j);\n" +
        "    abstract Builder o(Map<String, Map<String, Map<String, Map<String, Map<String, ? extends String>>>>> o);\n" +
        "    abstract Test build();\n" +
        "  }\n" +
        "  static class TestTypeAdapter extends TypeAdapter<String> {\n" +
        "    @Override public void write(JsonWriter out, String value) throws IOException {}\n" +
        "    @Override public String read(JsonReader in) throws IOException { return null; }\n" +
        "  }\n" +
        "  static class TestListTypeAdapter extends TypeAdapter<List<String>> {\n" +
        "    @Override public void write(JsonWriter out, List<String> value) throws IOException {}\n" +
        "    @Override public List<String> read(JsonReader in) throws IOException { return null; }\n" +
        "  }\n" +
        "  static class TestTypeAdapterFactory implements TypeAdapterFactory {\n" +
        "    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { return null; }\n" +
        "  }\n"
        + "}\n"
    );

    JavaFileObject expected = JavaFileObjects.forSourceString("test/AutoValue_Test", "package test;\n"
        + "\n"
        + "import com.google.common.collect.ImmutableMap;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.reflect.TypeToken;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import com.google.gson.stream.JsonToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "import com.ryanharter.auto.value.gson.internal.Util;\n"
        + "import com.ryanharter.auto.value.gson.internal.WildcardUtil;\n"
        + "import java.io.IOException;\n"
        + "import java.lang.Integer;\n"
        + "import java.lang.Number;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.String;\n"
        + "import java.lang.StringBuilder;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "import java.util.ArrayList;\n"
        + "import java.util.List;\n"
        + "import java.util.Map;\n"
        + "import java.util.Set;\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\n"
        + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
        + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
        + ")\n"
        + "final class AutoValue_Test extends $AutoValue_Test {\n"
        + "  AutoValue_Test(String a, int[] b, int c, String d, @Nullable String e,\n"
        + "      ImmutableMap<String, Number> f, Set<String> g, Map<String, Set<String>> h, "
        + "String i,\n"
        + "      @Nullable List<? extends String> j,\n"
        + "      Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
        + "String>>>>> o) {\n"
        + "    super(a, b, c, d, e, f, g, h, i, j, o);\n"
        + "  }\n"
        + "\n"
        + "  static final class GsonTypeAdapter extends TypeAdapter<Test> {\n"
        + "    private volatile TypeAdapter<String> string_adapter;\n"
        + "    private volatile TypeAdapter<int[]> array__int_adapter;\n"
        + "    private volatile TypeAdapter<Integer> int__adapter;\n"
        + "    private volatile TypeAdapter<ImmutableMap<String, Number>> "
        + "immutableMap__string_number_adapter;\n"
        + "    private volatile TypeAdapter<Set<String>> set__string_adapter;\n"
        + "    private volatile TypeAdapter<Map<String, Set<String>>> "
        + "map__string_set__string_adapter;\n"
        + "    private volatile TypeAdapter<List<? extends String>> "
        + "list__wildcard__string_adapter;\n"
        + "    private volatile TypeAdapter<Map<String, Map<String, Map<String, Map<String, "
        + "Map<String, ? extends String>>>>>> "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter;\n"
        + "    private final Map<String, String> realFieldNames;\n"
        + "    private final Gson gson;\n"
        + "    GsonTypeAdapter(Gson gson) {\n"
        + "      ArrayList<String> fields = new ArrayList<String>();\n"
        + "      fields.add(\"a\");\n"
        + "      fields.add(\"b\");\n"
        + "      fields.add(\"c\");\n"
        + "      fields.add(\"d\");\n"
        + "      fields.add(\"e\");\n"
        + "      fields.add(\"f\");\n"
        + "      fields.add(\"g\");\n"
        + "      fields.add(\"h\");\n"
        + "      fields.add(\"i\");\n"
        + "      fields.add(\"j\");\n"
        + "      fields.add(\"o\");\n"
        + "      this.gson = gson;\n"
        + "      this.realFieldNames = Util.renameFields($AutoValue_Test.class, fields, gson"
        + ".fieldNamingStrategy());\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public void write(JsonWriter jsonWriter, Test object) throws IOException {\n"
        + "      if (object == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "        return;\n"
        + "      }\n"
        + "      jsonWriter.beginObject();\n"
        + "      jsonWriter.name(realFieldNames.get(\"a\"));\n"
        + "      if (object.a() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.a());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"b\"));\n"
        + "      if (object.b() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<int[]> array__int_adapter = this.array__int_adapter;\n"
        + "        if (array__int_adapter == null) {\n"
        + "          this.array__int_adapter = array__int_adapter = gson.getAdapter(int[].class);\n"
        + "        }\n"
        + "        array__int_adapter.write(jsonWriter, object.b());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"c\"));\n"
        + "      {\n"
        + "        TypeAdapter<Integer> int__adapter = this.int__adapter;\n"
        + "        if (int__adapter == null) {\n"
        + "          this.int__adapter = int__adapter = gson.getAdapter(Integer.class);\n"
        + "        }\n"
        + "        int__adapter.write(jsonWriter, object.c());\n"
        + "      }\n"
        + "      jsonWriter.name(\"_D\");\n"
        + "      if (object.d() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.d());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"e\"));\n"
        + "      if (object.e() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.e());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"f\"));\n"
        + "      if (object.f() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<ImmutableMap<String, Number>> immutableMap__string_number_adapter "
        + "= this.immutableMap__string_number_adapter;\n"
        + "        if (immutableMap__string_number_adapter == null) {\n"
        + "          this.immutableMap__string_number_adapter = "
        + "immutableMap__string_number_adapter = (TypeAdapter<ImmutableMap<String, Number>>) gson"
        + ".getAdapter(TypeToken.getParameterized(ImmutableMap.class, String.class, Number.class)"
        + ");\n"
        + "        }\n"
        + "        immutableMap__string_number_adapter.write(jsonWriter, object.f());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"g\"));\n"
        + "      if (object.g() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Set<String>> set__string_adapter = this.set__string_adapter;\n"
        + "        if (set__string_adapter == null) {\n"
        + "          this.set__string_adapter = set__string_adapter = (TypeAdapter<Set<String>>) "
        + "gson.getAdapter(TypeToken.getParameterized(Set.class, String.class));\n"
        + "        }\n"
        + "        set__string_adapter.write(jsonWriter, object.g());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"h\"));\n"
        + "      if (object.h() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Map<String, Set<String>>> map__string_set__string_adapter = this"
        + ".map__string_set__string_adapter;\n"
        + "        if (map__string_set__string_adapter == null) {\n"
        + "          this.map__string_set__string_adapter = map__string_set__string_adapter = "
        + "(TypeAdapter<Map<String, Set<String>>>) gson.getAdapter(TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(Set.class, String.class).getType()));\n"
        + "        }\n"
        + "        map__string_set__string_adapter.write(jsonWriter, object.h());\n"
        + "      }\n"
        + "      jsonWriter.name(\"_I\");\n"
        + "      if (object.i() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.i());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"j\"));\n"
        + "      if (object.j() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<List<? extends String>> list__wildcard__string_adapter = this"
        + ".list__wildcard__string_adapter;\n"
        + "        if (list__wildcard__string_adapter == null) {\n"
        + "          this.list__wildcard__string_adapter = list__wildcard__string_adapter = "
        + "(TypeAdapter<List<? extends String>>) gson.getAdapter(TypeToken.getParameterized(List"
        + ".class, WildcardUtil.subtypeOf(String.class)));\n"
        + "        }\n"
        + "        list__wildcard__string_adapter.write(jsonWriter, object.j());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"o\"));\n"
        + "      if (object.o() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? "
        + "extends String>>>>>> "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter ="
        + " this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter;\n"
        + "        if "
        + "(map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "== null) {\n"
        + "          this"
        + ".map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter "
        + "= map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + " = (TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? extends "
        + "String>>>>>>) gson.getAdapter(TypeToken.getParameterized(Map.class, String.class, "
        + "TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(Map.class, String.class, TypeToken"
        + ".getParameterized(Map.class, String.class, WildcardUtil.subtypeOf(String.class))"
        + ".getType()).getType()).getType()).getType()));\n"
        + "        }\n"
        + "        "
        + "map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter"
        + ".write(jsonWriter, object.o());\n"
        + "      }\n"
        + "      jsonWriter.endObject();\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public Test read(JsonReader jsonReader) throws IOException {\n"
        + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "        jsonReader.nextNull();\n"
        + "        return null;\n"
        + "      }\n"
        + "      jsonReader.beginObject();\n"
        + "      Test.Builder builder = new AutoValue_Test.Builder();\n"
        + "      while (jsonReader.hasNext()) {\n"
        + "        String _name = jsonReader.nextName();\n"
        + "        if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "          jsonReader.nextNull();\n"
        + "          continue;\n"
        + "        }\n"
        + "        switch (_name) {\n"
        + "          case \"_D\": {\n"
        + "            TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "            if (string_adapter == null) {\n"
        + "              this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "            }\n"
        + "            builder.d(string_adapter.read(jsonReader));\n"
        + "            break;\n"
        + "          }\n"
        + "          case \"_I_1\":\n"
        + "          case \"_I_2\":\n"
        + "          case \"_I\": {\n"
        + "            TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "            if (string_adapter == null) {\n"
        + "              this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "            }\n"
        + "            builder.i(string_adapter.read(jsonReader));\n"
        + "            break;\n"
        + "          }\n"
        + "          default: {\n"
        + "            if (realFieldNames.get(\"a\").equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              builder.a(string_adapter.read(jsonReader));\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"b\").equals(_name)) {\n"
        + "              TypeAdapter<int[]> array__int_adapter = this.array__int_adapter;\n"
        + "              if (array__int_adapter == null) {\n"
        + "                this.array__int_adapter = array__int_adapter = gson.getAdapter(int[].class);\n"
        + "              }\n"
        + "              builder.b(array__int_adapter.read(jsonReader));\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"c\").equals(_name)) {\n"
        + "              TypeAdapter<Integer> int__adapter = this.int__adapter;\n"
        + "              if (int__adapter == null) {\n"
        + "                this.int__adapter = int__adapter = gson.getAdapter(Integer.class);\n"
        + "              }\n"
        + "              builder.c(int__adapter.read(jsonReader));\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"e\").equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              builder.e(string_adapter.read(jsonReader));\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"f\").equals(_name)) {\n"
        + "              TypeAdapter<ImmutableMap<String, Number>> immutableMap__string_number_adapter = this.immutableMap__string_number_adapter;\n"
        + "              if (immutableMap__string_number_adapter == null) {\n"
        + "                this.immutableMap__string_number_adapter = immutableMap__string_number_adapter = (TypeAdapter<ImmutableMap<String, Number>>) gson.getAdapter(TypeToken.getParameterized(ImmutableMap.class, String.class, Number.class));\n"
        + "              }\n"
        + "              builder.f(immutableMap__string_number_adapter.read(jsonReader));\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"g\").equals(_name)) {\n"
        + "              TypeAdapter<Set<String>> set__string_adapter = this.set__string_adapter;\n"
        + "              if (set__string_adapter == null) {\n"
        + "                this.set__string_adapter = set__string_adapter = (TypeAdapter<Set<String>>) gson.getAdapter(TypeToken.getParameterized(Set.class, String.class));\n"
        + "              }\n"
        + "              builder.g(set__string_adapter.read(jsonReader));\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"h\").equals(_name)) {\n"
        + "              TypeAdapter<Map<String, Set<String>>> map__string_set__string_adapter = this.map__string_set__string_adapter;\n"
        + "              if (map__string_set__string_adapter == null) {\n"
        + "                this.map__string_set__string_adapter = map__string_set__string_adapter = (TypeAdapter<Map<String, Set<String>>>) gson.getAdapter(TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Set.class, String.class).getType()));\n"
        + "              }\n"
        + "              builder.h(map__string_set__string_adapter.read(jsonReader));\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"j\").equals(_name)) {\n"
        + "              TypeAdapter<List<? extends String>> list__wildcard__string_adapter = this.list__wildcard__string_adapter;\n"
        + "              if (list__wildcard__string_adapter == null) {\n"
        + "                this.list__wildcard__string_adapter = list__wildcard__string_adapter = (TypeAdapter<List<? extends String>>) gson.getAdapter(TypeToken.getParameterized(List.class, WildcardUtil.subtypeOf(String.class)));\n"
        + "              }\n"
        + "              builder.j(list__wildcard__string_adapter.read(jsonReader));\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"o\").equals(_name)) {\n"
        + "              TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? extends String>>>>>> map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter = this.map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter;\n"
        + "              if (map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter == null) {\n"
        + "                this.map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter = map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter = (TypeAdapter<Map<String, Map<String, Map<String, Map<String, Map<String, ? extends String>>>>>>) gson.getAdapter(TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Map.class, String.class, TypeToken.getParameterized(Map.class, String.class, WildcardUtil.subtypeOf(String.class)).getType()).getType()).getType()).getType()));\n"
        + "              }\n"
        + "              builder.o(map__string_map__string_map__string_map__string_map__string_wildcard__string_adapter.read(jsonReader));\n"
        + "              continue;\n"
        + "            }\n"
        + "            jsonReader.skipValue();\n"
        + "          }\n"
        + "        }\n"
        + "      }\n"
        + "      jsonReader.endObject();\n"
        + "      return builder.build();\n"
        + "    }\n"
        + "\n"
        + "    @Override\n"
        + "    public String toString() {\n"
        + "      return new StringBuilder().append(\"TypeAdapter(\").append(\"Test\").append(\")"
        + "\").toString();\n"
        + "    }\n"
        + "  }\n"
        + "}"
    );

    assertAbout(javaSources())
        .that(Arrays.asList(nullable, source))
        .withCompilerOptions("-A" + AutoValueGsonExtension.USE_FIELD_NAME_POLICY)
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test public void propertyMethodReferencedWithPrefix() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "@AutoValue public abstract class Test {\n"
        + "  public static TypeAdapter<Test> typeAdapter(Gson gson) {\n"
        + "    return new AutoValue_Test.GsonTypeAdapter(gson);\n"
        + "  }\n"
        + "  public abstract String getName();\n"
        + "  public abstract boolean isAwesome();\n"
        + "}"
    );
    JavaFileObject expected = JavaFileObjects.forSourceString("test/AutoValue_Test", "package test;\n"
        + "\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import com.google.gson.stream.JsonToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.ryanharter.auto.value.gson.internal.Util;\n"
        + "import java.io.IOException;\n"
        + "import java.lang.Boolean;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.String;\n"
        + "import java.lang.StringBuilder;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "import java.util.ArrayList;\n"
        + "import java.util.Map;\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\n"
        + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
        + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
        + ")\n"
        + "final class AutoValue_Test extends $AutoValue_Test {\n"
        + "  AutoValue_Test(String name, boolean awesome) {\n"
        + "    super(name, awesome);\n"
        + "  }\n"
        + "\n"
        + "  static final class GsonTypeAdapter extends TypeAdapter<Test> {\n"
        + "    private volatile TypeAdapter<String> string_adapter;\n"
        + "    private volatile TypeAdapter<Boolean> boolean__adapter;\n"
        + "    private final Map<String, String> realFieldNames;\n"
        + "    private final Gson gson;\n"
        + "    GsonTypeAdapter(Gson gson) {\n"
        + "      ArrayList<String> fields = new ArrayList<String>();\n"
        + "      fields.add(\"name\");\n"
        + "      fields.add(\"awesome\");\n"
        + "      this.gson = gson;\n"
        + "      this.realFieldNames = Util.renameFields($AutoValue_Test.class, fields, gson"
        + ".fieldNamingStrategy());\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public void write(JsonWriter jsonWriter, Test object) throws IOException {\n"
        + "      if (object == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "        return;\n"
        + "      }\n"
        + "      jsonWriter.beginObject();\n"
        + "      jsonWriter.name(realFieldNames.get(\"name\"));\n"
        + "      if (object.getName() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.getName());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"awesome\"));\n"
        + "      {\n"
        + "        TypeAdapter<Boolean> boolean__adapter = this.boolean__adapter;\n"
        + "        if (boolean__adapter == null) {\n"
        + "          this.boolean__adapter = boolean__adapter = gson.getAdapter(Boolean.class);\n"
        + "        }\n"
        + "        boolean__adapter.write(jsonWriter, object.isAwesome());\n"
        + "      }\n"
        + "      jsonWriter.endObject();\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public Test read(JsonReader jsonReader) throws IOException {\n"
        + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "        jsonReader.nextNull();\n"
        + "        return null;\n"
        + "      }\n"
        + "      jsonReader.beginObject();\n"
        + "      String name = null;\n"
        + "      boolean awesome = false;\n"
        + "      while (jsonReader.hasNext()) {\n"
        + "        String _name = jsonReader.nextName();\n"
        + "        if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "          jsonReader.nextNull();\n"
        + "          continue;\n"
        + "        }\n"
        + "        switch (_name) {\n"
        + "          default: {\n"
        + "            if (realFieldNames.get(\"name\").equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              name = string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"awesome\").equals(_name)) {\n"
        + "              TypeAdapter<Boolean> boolean__adapter = this.boolean__adapter;\n"
        + "              if (boolean__adapter == null) {\n"
        + "                this.boolean__adapter = boolean__adapter = gson.getAdapter(Boolean"
        + ".class);\n"
        + "              }\n"
        + "              awesome = boolean__adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            jsonReader.skipValue();\n"
        + "          }\n"
        + "        }\n"
        + "      }\n"
        + "      jsonReader.endObject();\n"
        + "      return new AutoValue_Test(name, awesome);\n"
        + "    }\n"
        + "\n"
        + "    @Override\n"
        + "    public String toString() {\n"
        + "      return new StringBuilder().append(\"TypeAdapter(\").append(\"Test\").append(\")"
        + "\").toString();\n"
        + "    }\n"
        + "  }\n"
        + "}");

    assertAbout(javaSource())
        .that(source)
        .withCompilerOptions("-A" + AutoValueGsonExtension.USE_FIELD_NAME_POLICY)
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }
  @Test public void handlesDefaultAccessTypeAdapterMethod() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
            + "package test;\n"
            + "import com.google.auto.value.AutoValue;\n"
            + "import com.google.gson.Gson;\n"
            + "import com.google.gson.TypeAdapter;\n"
            + "@AutoValue abstract class Test {\n"
            + "  static TypeAdapter<Test> typeAdapter(Gson gson) {\n"
            + "    return new AutoValue_Test.GsonTypeAdapter(gson);\n"
            + "  }\n"
            + "  abstract String getName();\n"
            + "  abstract boolean isAwesome();\n"
            + "}"
    );
    JavaFileObject expected = JavaFileObjects.forSourceString("test/AutoValue_Test", "package test;\n"
        + "\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import com.google.gson.stream.JsonToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.ryanharter.auto.value.gson.internal.Util;\n"
        + "import java.io.IOException;\n"
        + "import java.lang.Boolean;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.String;\n"
        + "import java.lang.StringBuilder;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "import java.util.ArrayList;\n"
        + "import java.util.Map;\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\n"
        + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
        + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
        + ")\n"
        + "final class AutoValue_Test extends $AutoValue_Test {\n"
        + "  AutoValue_Test(String name, boolean awesome) {\n"
        + "    super(name, awesome);\n"
        + "  }\n"
        + "\n"
        + "  static final class GsonTypeAdapter extends TypeAdapter<Test> {\n"
        + "    private volatile TypeAdapter<String> string_adapter;\n"
        + "    private volatile TypeAdapter<Boolean> boolean__adapter;\n"
        + "    private final Map<String, String> realFieldNames;\n"
        + "    private final Gson gson;\n"
        + "    GsonTypeAdapter(Gson gson) {\n"
        + "      ArrayList<String> fields = new ArrayList<String>();\n"
        + "      fields.add(\"name\");\n"
        + "      fields.add(\"awesome\");\n"
        + "      this.gson = gson;\n"
        + "      this.realFieldNames = Util.renameFields($AutoValue_Test.class, fields, gson"
        + ".fieldNamingStrategy());\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public void write(JsonWriter jsonWriter, Test object) throws IOException {\n"
        + "      if (object == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "        return;\n"
        + "      }\n"
        + "      jsonWriter.beginObject();\n"
        + "      jsonWriter.name(realFieldNames.get(\"name\"));\n"
        + "      if (object.getName() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.getName());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"awesome\"));\n"
        + "      {\n"
        + "        TypeAdapter<Boolean> boolean__adapter = this.boolean__adapter;\n"
        + "        if (boolean__adapter == null) {\n"
        + "          this.boolean__adapter = boolean__adapter = gson.getAdapter(Boolean.class);\n"
        + "        }\n"
        + "        boolean__adapter.write(jsonWriter, object.isAwesome());\n"
        + "      }\n"
        + "      jsonWriter.endObject();\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public Test read(JsonReader jsonReader) throws IOException {\n"
        + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "        jsonReader.nextNull();\n"
        + "        return null;\n"
        + "      }\n"
        + "      jsonReader.beginObject();\n"
        + "      String name = null;\n"
        + "      boolean awesome = false;\n"
        + "      while (jsonReader.hasNext()) {\n"
        + "        String _name = jsonReader.nextName();\n"
        + "        if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "          jsonReader.nextNull();\n"
        + "          continue;\n"
        + "        }\n"
        + "        switch (_name) {\n"
        + "          default: {\n"
        + "            if (realFieldNames.get(\"name\").equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              name = string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"awesome\").equals(_name)) {\n"
        + "              TypeAdapter<Boolean> boolean__adapter = this.boolean__adapter;\n"
        + "              if (boolean__adapter == null) {\n"
        + "                this.boolean__adapter = boolean__adapter = gson.getAdapter(Boolean"
        + ".class);\n"
        + "              }\n"
        + "              awesome = boolean__adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            jsonReader.skipValue();\n"
        + "          }\n"
        + "        }\n"
        + "      }\n"
        + "      jsonReader.endObject();\n"
        + "      return new AutoValue_Test(name, awesome);\n"
        + "    }\n"
        + "\n"
        + "    @Override\n"
        + "    public String toString() {\n"
        + "      return new StringBuilder().append(\"TypeAdapter(\").append(\"Test\").append(\")"
        + "\").toString();\n"
        + "    }\n"
        + "  }\n"
        + "}");

    assertAbout(javaSource())
            .that(source)
            .withCompilerOptions("-A" + AutoValueGsonExtension.USE_FIELD_NAME_POLICY)
            .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
            .compilesWithoutError()
            .and()
            .generatesSources(expected);
  }

  @Test public void generatesNothingWithoutTypeAdapterMethod() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "@AutoValue public abstract class Test {\n"
        + "  public abstract String a();\n"
        + "  public abstract boolean b();\n"
        + "}"
    );
    JavaFileObject expected = JavaFileObjects.forSourceString("test/AutoValue_Test", ""
        + "package test;\n"
        + "\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")\n"
        + " final class AutoValue_Test extends Test {\n"
        + "\n"
        + "  private final String a;\n"
        + "  private final boolean b;\n"
        + "\n"
        + "  AutoValue_Test(\n"
        + "      String a,\n"
        + "      boolean b) {\n"
        + "    if (a == null) {\n"
        + "      throw new NullPointerException(\"Null a\");\n"
        + "    }\n"
        + "    this.a = a;\n"
        + "    this.b = b;\n"
        + "  }\n"
        + "\n"
        + "  @Override\n"
        + "  public String a() {\n"
        + "    return a;\n"
        + "  }\n"
        + "\n"
        + "  @Override\n"
        + "  public boolean b() {\n"
        + "    return b;\n"
        + "  }\n"
        + "\n"
        + "  @Override\n"
        + "  public String toString() {\n"
        + "    return \"Test{\"\n"
        + "        + \"a=\" + a + \", \"\n"
        + "        + \"b=\" + b\n"
        + "        + \"}\";\n"
        + "  }\n"
        + "\n"
        + "  @Override\n"
        + "  public boolean equals(Object o) {\n"
        + "    if (o == this) {\n"
        + "      return true;\n"
        + "    }\n"
        + "    if (o instanceof Test) {\n"
        + "      Test that = (Test) o;\n"
        + "      return this.a.equals(that.a())\n"
        + "           && this.b == that.b();\n"
        + "    }\n"
        + "    return false;\n"
        + "  }\n"
        + "\n"
        + "  @Override\n"
        + "  public int hashCode() {\n"
        + "    int h$ = 1;\n"
        + "    h$ *= 1000003;\n"
        + "    h$ ^= a.hashCode();\n"
        + "    h$ *= 1000003;\n"
        + "    h$ ^= b ? 1231 : 1237;\n"
        + "    return h$;\n"
        + "  }\n"
        + "\n"
        + "}");

    assertAbout(javaSource())
        .that(source)
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .withWarningCount(2)
        .and()
        .generatesSources(expected);
  }

  @Test public void emitsWarningForWrongTypeAdapterTypeArgument() {
    JavaFileObject source1 = JavaFileObjects.forSourceString("test.Foo", ""
        + "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "@AutoValue public abstract class Foo {\n"
        + "  public static TypeAdapter<Bar> typeAdapter(Gson gson) {\n"
        + "    return null;"
        + "  }\n"
        + "  public abstract String a();\n"
        + "  public abstract boolean b();\n"
        + "}"
    );

    JavaFileObject source2 = JavaFileObjects.forSourceString("test.Bar", ""
        + "package test;\n"
        + "public class Bar {\n"
        + "}");

    assertAbout(javaSources())
        .that(ImmutableSet.of(source1, source2))
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .withWarningContaining("Found static method returning TypeAdapter<test.Bar> on "
            + "test.Foo class. Skipping GsonTypeAdapter generation.");
  }

  @Test public void emitsWarningForNoTypeAdapterTypeArgument() {
    JavaFileObject source1 = JavaFileObjects.forSourceString("test.Foo", ""
        + "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "@AutoValue public abstract class Foo {\n"
        + "  public static TypeAdapter typeAdapter(Gson gson) {\n"
        + "    return null;"
        + "  }\n"
        + "  public abstract String a();\n"
        + "  public abstract boolean b();\n"
        + "}"
    );

    assertAbout(javaSource())
        .that(source1)
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .withWarningContaining("Found static method returning TypeAdapter with no type "
            + "arguments, skipping GsonTypeAdapter generation.");
  }

  @Test public void compilesWithCapitalPackageName() {
    JavaFileObject source1 = JavaFileObjects.forSourceString("MyPackage.Foo", ""
        + "package MyPackage;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "@AutoValue public abstract class Foo {\n"
        + "  public static TypeAdapter<Foo> typeAdapter(Gson gson) {\n"
        + "    return new AutoValue_Foo.GsonTypeAdapter(gson);"
        + "  }\n"
        + "  public abstract String a();\n"
        + "  public abstract boolean b();\n"
        + "}"
    );

    assertAbout(javaSource())
        .that(source1)
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .withWarningCount(2);
  }

  @Test public void generatesCorrectDefaultCharPrimitiveValue() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "@AutoValue public abstract class Test {\n"
        + "  public static TypeAdapter<Test> typeAdapter(Gson gson) {\n"
        + "    return new AutoValue_Test.GsonTypeAdapter(gson);\n"
        + "  }\n"
        + "public abstract char c();\n"
        + "}\n"
    );

    JavaFileObject expected = JavaFileObjects.forSourceString("test/AutoValue_Test", "package test;\n"
        + "\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import com.google.gson.stream.JsonToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.ryanharter.auto.value.gson.internal.Util;\n"
        + "import java.io.IOException;\n"
        + "import java.lang.Character;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.String;\n"
        + "import java.lang.StringBuilder;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "import java.util.ArrayList;\n"
        + "import java.util.Map;\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\n"
        + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
        + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
        + ")\n"
        + "final class AutoValue_Test extends $AutoValue_Test {\n"
        + "  AutoValue_Test(char c) {\n"
        + "    super(c);\n"
        + "  }\n"
        + "\n"
        + "  static final class GsonTypeAdapter extends TypeAdapter<Test> {\n"
        + "    private volatile TypeAdapter<Character> char__adapter;\n"
        + "    private final Map<String, String> realFieldNames;\n"
        + "    private final Gson gson;\n"
        + "    GsonTypeAdapter(Gson gson) {\n"
        + "      ArrayList<String> fields = new ArrayList<String>();\n"
        + "      fields.add(\"c\");\n"
        + "      this.gson = gson;\n"
        + "      this.realFieldNames = Util.renameFields($AutoValue_Test.class, fields, gson"
        + ".fieldNamingStrategy());\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public void write(JsonWriter jsonWriter, Test object) throws IOException {\n"
        + "      if (object == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "        return;\n"
        + "      }\n"
        + "      jsonWriter.beginObject();\n"
        + "      jsonWriter.name(realFieldNames.get(\"c\"));\n"
        + "      {\n"
        + "        TypeAdapter<Character> char__adapter = this.char__adapter;\n"
        + "        if (char__adapter == null) {\n"
        + "          this.char__adapter = char__adapter = gson.getAdapter(Character.class);\n"
        + "        }\n"
        + "        char__adapter.write(jsonWriter, object.c());\n"
        + "      }\n"
        + "      jsonWriter.endObject();\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public Test read(JsonReader jsonReader) throws IOException {\n"
        + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "        jsonReader.nextNull();\n"
        + "        return null;\n"
        + "      }\n"
        + "      jsonReader.beginObject();\n"
        + "      char c = '\u0000';\n"
        + "      while (jsonReader.hasNext()) {\n"
        + "        String _name = jsonReader.nextName();\n"
        + "        if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "          jsonReader.nextNull();\n"
        + "          continue;\n"
        + "        }\n"
        + "        switch (_name) {\n"
        + "          default: {\n"
        + "            if (realFieldNames.get(\"c\").equals(_name)) {\n"
        + "              TypeAdapter<Character> char__adapter = this.char__adapter;\n"
        + "              if (char__adapter == null) {\n"
        + "                this.char__adapter = char__adapter = gson.getAdapter(Character.class);\n"
        + "              }\n"
        + "              c = char__adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            jsonReader.skipValue();\n"
        + "          }\n"
        + "        }\n"
        + "      }\n"
        + "      jsonReader.endObject();\n"
        + "      return new AutoValue_Test(c);\n"
        + "    }\n"
        + "\n"
        + "    @Override\n"
        + "    public String toString() {\n"
        + "      return new StringBuilder().append(\"TypeAdapter(\").append(\"Test\").append(\")"
        + "\").toString();\n"
        + "    }\n"
        + "  }\n"
        + "}"
    );

    assertAbout(javaSources())
      .that(Arrays.asList(nullable, source))
      .withCompilerOptions("-A" + AutoValueGsonExtension.USE_FIELD_NAME_POLICY)
      .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
      .compilesWithoutError()
      .and()
      .generatesSources(expected);
  }

  @Test public void handlesGenericTypes() {
    JavaFileObject source1 = JavaFileObjects.forSourceString("test.Foo", "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.reflect.TypeToken;\n"
        + "import java.lang.reflect.Type;\n"
        + "import java.util.List;\n"
        + "import java.util.Map;\n"
        + "@AutoValue public abstract class Foo<A, B, C> {\n"
        + "  public static <A, B, C> TypeAdapter<Foo<A, B, C>> typeAdapter(Gson gson, Type[] "
        + "types) {\n"
        + "    return new AutoValue_Foo.GsonTypeAdapter<>(gson, types);  }\n"
        + "  public abstract C c();\n"
        + "  public abstract A a();\n"
        + "  public abstract B b();\n"
        + "  public abstract List<A> list();\n"
        + "  public abstract Map<String, List<C>> map();\n"
        + "  public abstract String d();\n"
        + "}"
    );

    JavaFileObject expected = JavaFileObjects.forSourceString("test.AutoValue_Test", "package test;\n"
        + "\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.reflect.TypeToken;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import com.google.gson.stream.JsonToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.ryanharter.auto.value.gson.internal.Util;\n"
        + "import java.io.IOException;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.String;\n"
        + "import java.lang.StringBuilder;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "import java.lang.reflect.Type;\n"
        + "import java.util.ArrayList;\n"
        + "import java.util.List;\n"
        + "import java.util.Map;\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\n"
        + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
        + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
        + ")\n"
        + "final class AutoValue_Foo<A, B, C> extends $AutoValue_Foo<A, B, C> {\n"
        + "  AutoValue_Foo(C c, A a, B b, List<A> list, Map<String, List<C>> map, String d) {\n"
        + "    super(c, a, b, list, map, d);\n"
        + "  }\n"
        + "\n"
        + "  static final class GsonTypeAdapter<A, B, C> extends TypeAdapter<Foo<A, B, C>>"
        + " {\n"
        + "    private volatile TypeAdapter<C> C_adapter;\n"
        + "    private volatile TypeAdapter<A> A_adapter;\n"
        + "    private volatile TypeAdapter<B> B_adapter;\n"
        + "    private volatile TypeAdapter<List<A>> list__A_adapter;\n"
        + "    private volatile TypeAdapter<Map<String, List<C>>> map__string_list__C_adapter;\n"
        + "    private volatile TypeAdapter<String> string_adapter;\n"
        + "    private final Map<String, String> realFieldNames;\n"
        + "    private final Gson gson;\n"
        + "    private final Type[] typeArgs;\n"
        + "    GsonTypeAdapter(Gson gson, Type[] types) {\n"
        + "      typeArgs = types;\n"
        + "      ArrayList<String> fields = new ArrayList<String>();\n"
        + "      fields.add(\"c\");\n"
        + "      fields.add(\"a\");\n"
        + "      fields.add(\"b\");\n"
        + "      fields.add(\"list\");\n"
        + "      fields.add(\"map\");\n"
        + "      fields.add(\"d\");\n"
        + "      this.gson = gson;\n"
        + "      this.realFieldNames = Util.renameFields($AutoValue_Foo.class, fields, gson"
        + ".fieldNamingStrategy());\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public void write(JsonWriter jsonWriter, Foo<A, B, C> object) throws IOException {\n"
        + "      if (object == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "        return;\n"
        + "      }\n"
        + "      jsonWriter.beginObject();\n"
        + "      jsonWriter.name(realFieldNames.get(\"c\"));\n"
        + "      if (object.c() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<C> C_adapter = this.C_adapter;\n"
        + "        if (C_adapter == null) {\n"
        + "          this.C_adapter = C_adapter = (TypeAdapter<C>) gson.getAdapter(TypeToken.get"
        + "(typeArgs[2]));\n"
        + "        }\n"
        + "        C_adapter.write(jsonWriter, object.c());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"a\"));\n"
        + "      if (object.a() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<A> A_adapter = this.A_adapter;\n"
        + "        if (A_adapter == null) {\n"
        + "          this.A_adapter = A_adapter = (TypeAdapter<A>) gson.getAdapter(TypeToken.get"
        + "(typeArgs[0]));\n"
        + "        }\n"
        + "        A_adapter.write(jsonWriter, object.a());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"b\"));\n"
        + "      if (object.b() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<B> B_adapter = this.B_adapter;\n"
        + "        if (B_adapter == null) {\n"
        + "          this.B_adapter = B_adapter = (TypeAdapter<B>) gson.getAdapter(TypeToken.get"
        + "(typeArgs[1]));\n"
        + "        }\n"
        + "        B_adapter.write(jsonWriter, object.b());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"list\"));\n"
        + "      if (object.list() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<List<A>> list__A_adapter = this.list__A_adapter;\n"
        + "        if (list__A_adapter == null) {\n"
        + "          this.list__A_adapter = list__A_adapter = (TypeAdapter<List<A>>) gson"
        + ".getAdapter(TypeToken.getParameterized(List.class, typeArgs[0]));\n"
        + "        }\n"
        + "        list__A_adapter.write(jsonWriter, object.list());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"map\"));\n"
        + "      if (object.map() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<Map<String, List<C>>> map__string_list__C_adapter = this"
        + ".map__string_list__C_adapter;\n"
        + "        if (map__string_list__C_adapter == null) {\n"
        + "          this.map__string_list__C_adapter = map__string_list__C_adapter = "
        + "(TypeAdapter<Map<String, List<C>>>) gson.getAdapter(TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(List.class, typeArgs[2]).getType()));\n"
        + "        }\n"
        + "        map__string_list__C_adapter.write(jsonWriter, object.map());\n"
        + "      }\n"
        + "      jsonWriter.name(realFieldNames.get(\"d\"));\n"
        + "      if (object.d() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.d());\n"
        + "      }\n"
        + "      jsonWriter.endObject();\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public Foo<A, B, C> read(JsonReader jsonReader) throws IOException {\n"
        + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "        jsonReader.nextNull();\n"
        + "        return null;\n"
        + "      }\n"
        + "      jsonReader.beginObject();\n"
        + "      C c = null;\n"
        + "      A a = null;\n"
        + "      B b = null;\n"
        + "      List<A> list = null;\n"
        + "      Map<String, List<C>> map = null;\n"
        + "      String d = null;\n"
        + "      while (jsonReader.hasNext()) {\n"
        + "        String _name = jsonReader.nextName();\n"
        + "        if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "          jsonReader.nextNull();\n"
        + "          continue;\n"
        + "        }\n"
        + "        switch (_name) {\n"
        + "          default: {\n"
        + "            if (realFieldNames.get(\"c\").equals(_name)) {\n"
        + "              TypeAdapter<C> C_adapter = this.C_adapter;\n"
        + "              if (C_adapter == null) {\n"
        + "                this.C_adapter = C_adapter = (TypeAdapter<C>) gson.getAdapter"
        + "(TypeToken.get(typeArgs[2]));\n"
        + "              }\n"
        + "              c = C_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"a\").equals(_name)) {\n"
        + "              TypeAdapter<A> A_adapter = this.A_adapter;\n"
        + "              if (A_adapter == null) {\n"
        + "                this.A_adapter = A_adapter = (TypeAdapter<A>) gson.getAdapter"
        + "(TypeToken.get(typeArgs[0]));\n"
        + "              }\n"
        + "              a = A_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"b\").equals(_name)) {\n"
        + "              TypeAdapter<B> B_adapter = this.B_adapter;\n"
        + "              if (B_adapter == null) {\n"
        + "                this.B_adapter = B_adapter = (TypeAdapter<B>) gson.getAdapter"
        + "(TypeToken.get(typeArgs[1]));\n"
        + "              }\n"
        + "              b = B_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"list\").equals(_name)) {\n"
        + "              TypeAdapter<List<A>> list__A_adapter = this.list__A_adapter;\n"
        + "              if (list__A_adapter == null) {\n"
        + "                this.list__A_adapter = list__A_adapter = (TypeAdapter<List<A>>) gson"
        + ".getAdapter(TypeToken.getParameterized(List.class, typeArgs[0]));\n"
        + "              }\n"
        + "              list = list__A_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"map\").equals(_name)) {\n"
        + "              TypeAdapter<Map<String, List<C>>> map__string_list__C_adapter = this"
        + ".map__string_list__C_adapter;\n"
        + "              if (map__string_list__C_adapter == null) {\n"
        + "                this.map__string_list__C_adapter = map__string_list__C_adapter = "
        + "(TypeAdapter<Map<String, List<C>>>) gson.getAdapter(TypeToken.getParameterized(Map"
        + ".class, String.class, TypeToken.getParameterized(List.class, typeArgs[2]).getType()));\n"
        + "              }\n"
        + "              map = map__string_list__C_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            if (realFieldNames.get(\"d\").equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              d = string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            jsonReader.skipValue();\n"
        + "          }\n"
        + "        }\n"
        + "      }\n"
        + "      jsonReader.endObject();\n"
        + "      return new AutoValue_Foo<>(c, a, b, list, map, d);\n"
        + "    }\n"
        + "\n"
        + "    @Override\n"
        + "    public String toString() {\n"
        + "      return new StringBuilder().append(\"TypeAdapter(\").append(\"Foo\").append(\")"
        + "\").toString();\n"
        + "    }\n"
        + "  }\n"
        + "}");

    assertAbout(javaSource())
        .that(source1)
        .withCompilerOptions("-A" + AutoValueGsonExtension.USE_FIELD_NAME_POLICY)
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .and()
        .generatesSources(expected);
  }

  @Test public void transientProperties() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import io.sweers.autotransient.AutoTransient;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "@AutoValue public abstract class Test {\n"
        // a is not ignored, must be present in the JSON
        + "  public abstract String a();\n"
        // transientProperty is nullable and ignored, so a default value is not required
        + "  @Nullable @AutoTransient public abstract String transientProperty();\n"
        + "  public static TypeAdapter<Test> typeAdapter(Gson gson) {\n"
        + "    return new AutoValue_Test.GsonTypeAdapter(gson);\n"
        + "  }\n"
        + "}");

    JavaFileObject expected = JavaFileObjects.forSourceString("test/AutoValue_Test", "package test;\n"
        + "\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import com.google.gson.stream.JsonReader;\n"
        + "import com.google.gson.stream.JsonToken;\n"
        + "import com.google.gson.stream.JsonWriter;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "import com.ryanharter.auto.value.gson.internal.Util;\n"
        + "import java.io.IOException;\n"
        + "import java.lang.Override;\n"
        + "import java.lang.String;\n"
        + "import java.lang.StringBuilder;\n"
        + "import java.lang.SuppressWarnings;\n"
        + "import java.util.ArrayList;\n"
        + "import java.util.Map;\n"
        + "import javax.annotation.Generated;\n"
        + "\n"
        + "@Generated(\n"
        + "    value = \"com.ryanharter.auto.value.gson.AutoValueGsonExtension\",\n"
        + "    comments = \"https://github.com/rharter/auto-value-gson\"\n"
        + ")\n"
        + "final class AutoValue_Test extends $AutoValue_Test {\n"
        + "  AutoValue_Test(String a, @Nullable String transientProperty) {\n"
        + "    super(a, transientProperty);\n"
        + "  }\n"
        + "\n"
        + "  static final class GsonTypeAdapter extends TypeAdapter<Test> {\n"
        + "    private volatile TypeAdapter<String> string_adapter;\n"
        + "    private final Map<String, String> realFieldNames;\n"
        + "    private final Gson gson;\n"
        + "    GsonTypeAdapter(Gson gson) {\n"
        + "      ArrayList<String> fields = new ArrayList<String>();\n"
        + "      fields.add(\"a\");\n"
        + "      fields.add(\"transientProperty\");\n"
        + "      this.gson = gson;\n"
        + "      this.realFieldNames = Util.renameFields($AutoValue_Test.class, fields, gson"
        + ".fieldNamingStrategy());\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public void write(JsonWriter jsonWriter, Test object) throws IOException {\n"
        + "      if (object == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "        return;\n"
        + "      }\n"
        + "      jsonWriter.beginObject();\n"
        + "      jsonWriter.name(realFieldNames.get(\"a\"));\n"
        + "      if (object.a() == null) {\n"
        + "        jsonWriter.nullValue();\n"
        + "      } else {\n"
        + "        TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "        if (string_adapter == null) {\n"
        + "          this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "        }\n"
        + "        string_adapter.write(jsonWriter, object.a());\n"
        + "      }\n"
        + "      jsonWriter.endObject();\n"
        + "    }\n"
        + "    @Override\n"
        + "    @SuppressWarnings(\"unchecked\")\n"
        + "    public Test read(JsonReader jsonReader) throws IOException {\n"
        + "      if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "        jsonReader.nextNull();\n"
        + "        return null;\n"
        + "      }\n"
        + "      jsonReader.beginObject();\n"
        + "      String a = null;\n"
        + "      String transientProperty = null;\n"
        + "      while (jsonReader.hasNext()) {\n"
        + "        String _name = jsonReader.nextName();\n"
        + "        if (jsonReader.peek() == JsonToken.NULL) {\n"
        + "          jsonReader.nextNull();\n"
        + "          continue;\n"
        + "        }\n"
        + "        switch (_name) {\n"
        + "          default: {\n"
        + "            if (realFieldNames.get(\"a\").equals(_name)) {\n"
        + "              TypeAdapter<String> string_adapter = this.string_adapter;\n"
        + "              if (string_adapter == null) {\n"
        + "                this.string_adapter = string_adapter = gson.getAdapter(String.class);\n"
        + "              }\n"
        + "              a = string_adapter.read(jsonReader);\n"
        + "              continue;\n"
        + "            }\n"
        + "            jsonReader.skipValue();\n"
        + "          }\n"
        + "        }\n"
        + "      }\n"
        + "      jsonReader.endObject();\n"
        + "      return new AutoValue_Test(a, transientProperty);\n"
        + "    }\n"
        + "\n"
        + "    @Override\n"
        + "    public String toString() {\n"
        + "      return new StringBuilder().append(\"TypeAdapter(\").append(\"Test\").append(\")"
        + "\").toString();\n"
        + "    }\n"
        + "  }\n"
        + "}");

    Map<String, JavaFileObject> nullables = new TreeMap<>();
    nullables.put("using method-target @Nullable", nullable);
    nullables.put("using type-target @Nullable", typeTargetNullable);
    nullables.forEach(
        (nullableName, nullableObject) -> {
          assertAbout(javaSources())
              .that(Arrays.asList(nullableObject, source))
              .named(nullableName)
              .withCompilerOptions("-A" + AutoValueGsonExtension.USE_FIELD_NAME_POLICY)
              .processedWith(
                  new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
              .compilesWithoutError()
              .and()
              .generatesSources(expected);
        });
  }

  @Test public void transientRequiredProperty_shouldFail() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
        + "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.google.gson.Gson;\n"
        + "import com.google.gson.TypeAdapter;\n"
        + "import io.sweers.autotransient.AutoTransient;\n"
        + "import com.ryanharter.auto.value.gson.Nullable;\n"
        + "@AutoValue public abstract class Test {\n"
        + "  @AutoTransient public abstract String transientProperty();\n"
        + "  public static TypeAdapter<Test> typeAdapter(Gson gson) {\n"
        + "    return new AutoValue_Test.GsonTypeAdapter(gson);\n"
        + "  }\n"
        + "}");

    assertAbout(javaSources())
        .that(Arrays.asList(nullable, source))
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .failsToCompile()
        .withErrorContaining("Required property cannot be transient!");
  }

  @Test public void proguardRulesShouldUseReflectionName() {
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", "package test;\n"
        + "import com.google.auto.value.AutoValue;\n"
        + "import com.ryanharter.auto.value.gson.GenerateTypeAdapter;\n"
        + "\n"
        + "public abstract class Test {\n"
        + "  @GenerateTypeAdapter\n"
        + "  @AutoValue\n"
        + "  public static abstract class InnerClass {\n"
        + "    abstract String property();\n"
        + "  }\n"
        + "}");


    JavaFileObject expectedProguard = proguardResource(
        "META-INF/proguard/avg-test.Test.InnerClass.pro",
        "-if class test.Test$InnerClass\n"
            + "-keepnames class test.Test$InnerClass\n"
            + "-if class test.Test$InnerClass\n"
            + "-keep class test.Test_InnerClass_GsonTypeAdapter {\n"
            + "    <init>(com.google.gson.Gson);\n"
            + "}\n");

    assertAbout(javaSources())
        .that(ImmutableList.of(source))
        .processedWith(new AutoValueProcessor(Lists.newArrayList(new AutoValueGsonExtension())))
        .compilesWithoutError()
        .and()
        .generatesFiles(expectedProguard);
  }

  private static JavaFileObject proguardResource(String path, String source) {
    return new ResourceFile(path, source);
  }

  private static class ResourceFile extends SimpleJavaFileObject {

    final String source;
    final long lastModified;

    ResourceFile(String path, String source) {
      super(URI.create(path), OTHER);
      this.source = source;
      this.lastModified = System.currentTimeMillis();
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
      return source;
    }

    @Override
    public OutputStream openOutputStream() {
      throw new IllegalStateException();
    }

    @Override
    public InputStream openInputStream() {
      return new ByteArrayInputStream(source.getBytes(Charset.defaultCharset()));
    }

    @Override
    public Writer openWriter() {
      throw new IllegalStateException();
    }

    @Override
    public Reader openReader(boolean ignoreEncodingErrors) {
      return new StringReader(source);
    }

    @Override
    public long getLastModified() {
      return lastModified;
    }
  }
}
