/*
 * Copyright 2012 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.auto.value;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.common.base.MoreObjects;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Ordering;
import com.google.common.testing.EqualsTester;
import com.google.common.testing.SerializableTester;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.annotation.Nullable;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** @author emcmanus@google.com (Éamonn McManus) */
@RunWith(JUnit4.class)
@SuppressWarnings({"AutoValueImmutableFields", "AutoValueFinalMethods", "TypeNameShadowing"})
public class AutoValueTest {
  private static boolean omitIdentifiers;

  @BeforeClass
  public static void initOmitIdentifiers() {
    omitIdentifiers = System.getProperty("OmitIdentifiers") != null;
  }

  @AutoValue
  abstract static class Simple {
    public abstract String publicString();

    protected abstract int protectedInt();

    abstract Map<String, Long> packageMap();

    public static Simple create(String s, int i, Map<String, Long> m) {
      return new AutoValue_AutoValueTest_Simple(s, i, m);
    }
  }

  @Test
  public void testSimple() throws Exception {
    Simple instance1a = Simple.create("example", 23, ImmutableMap.of("twenty-three", 23L));
    Simple instance1b = Simple.create("example", 23, ImmutableMap.of("twenty-three", 23L));
    Simple instance2 = Simple.create("", 0, ImmutableMap.<String, Long>of());
    assertEquals("example", instance1a.publicString());
    assertEquals(23, instance1a.protectedInt());
    assertEquals(ImmutableMap.of("twenty-three", 23L), instance1a.packageMap());
    MoreObjects.ToStringHelper toStringHelper = MoreObjects.toStringHelper(Simple.class);
    toStringHelper.add("publicString", "example");
    toStringHelper.add("protectedInt", 23);
    toStringHelper.add("packageMap", ImmutableMap.of("twenty-three", 23L));
    String expectedString =
        omitIdentifiers ? "{example, 23, {twenty-three=23}}" : toStringHelper.toString();
    assertThat(instance1a.toString()).isEqualTo(expectedString);
    new EqualsTester()
        .addEqualityGroup(instance1a, instance1b)
        .addEqualityGroup(instance2)
        .testEquals();
  }

  @AutoValue
  abstract static class Empty {
    public static Empty create() {
      return new AutoValue_AutoValueTest_Empty();
    }
  }

  @Test
  public void testEmpty() throws Exception {
    Empty instance = Empty.create();
    String expectedString = omitIdentifiers ? "{}" : "Empty{}";
    assertThat(instance.toString()).isEqualTo(expectedString);
    assertEquals(instance, instance);
    assertEquals(instance, Empty.create());
  }

  @AutoValue
  abstract static class SimpleWithGetters {
    abstract int getFoo();

    abstract boolean isBar();

    abstract boolean getOtherBar();

    abstract String getPackage(); // package is a reserved word

    abstract String getPackage0();

    abstract String getHTMLPage();

    static SimpleWithGetters create(
        int foo, boolean bar, boolean otherBar, String pkg, String pkg0, String htmlPage) {
      return new AutoValue_AutoValueTest_SimpleWithGetters(foo, bar, otherBar, pkg, pkg0, htmlPage);
    }
  }

  @Test
  public void testGetters() {
    SimpleWithGetters instance = SimpleWithGetters.create(23, true, false, "foo", "bar", "<html>");
    String expectedString =
        omitIdentifiers
            ? "{23, true, false, foo, bar, <html>}"
            : "SimpleWithGetters{"
                + "foo=23, bar=true, otherBar=false, package=foo, package0=bar, HTMLPage=<html>}";
    assertThat(instance.toString()).isEqualTo(expectedString);
  }

  @AutoValue
  abstract static class NotAllGetters {
    abstract int getFoo();

    abstract boolean bar();

    static NotAllGetters create(int foo, boolean bar) {
      return new AutoValue_AutoValueTest_NotAllGetters(foo, bar);
    }
  }

  @Test
  public void testNotGetters() {
    NotAllGetters instance = NotAllGetters.create(23, true);
    String expectedString = omitIdentifiers ? "{23, true}" : "NotAllGetters{getFoo=23, bar=true}";
    assertThat(instance.toString()).isEqualTo(expectedString);
  }

  @AutoValue
  abstract static class StrangeGetters {
    abstract int get1st();
    abstract int get_1st(); // by default we'll use _1st where identifiers are needed, so foil that.

    @AutoValue.Builder
    abstract static class Builder {
      abstract Builder set1st(int x);
      abstract Builder set_1st(int x);
      abstract StrangeGetters build();
    }

    static Builder builder() {
      return new AutoValue_AutoValueTest_StrangeGetters.Builder();
    }
  }

  @Test
  public void testStrangeGetters() {
    StrangeGetters instance = StrangeGetters.builder().set1st(17).set_1st(23).build();
    String expectedString = omitIdentifiers ? "{17, 23}" : "StrangeGetters{1st=17, _1st=23}";
    assertThat(instance.toString()).isEqualTo(expectedString);
  }

  @AutoValue
  abstract static class GettersAndConcreteNonGetters {
    abstract int getFoo();

    @SuppressWarnings("mutable")
    abstract byte[] getBytes();

    boolean hasNoBytes() {
      return getBytes().length == 0;
    }

    static GettersAndConcreteNonGetters create(int foo, byte[] bytes) {
      return new AutoValue_AutoValueTest_GettersAndConcreteNonGetters(foo, bytes);
    }
  }

  @Test
  public void testGettersAndConcreteNonGetters() {
    GettersAndConcreteNonGetters instance = GettersAndConcreteNonGetters.create(23, new byte[] {1});
    assertFalse(instance.hasNoBytes());
    String expectedString =
        omitIdentifiers ? "{23, [1]}" : "GettersAndConcreteNonGetters{foo=23, bytes=[1]}";
    assertThat(instance.toString()).isEqualTo(expectedString);
  }

  @AutoValue
  abstract static class ClassProperty {
    abstract Class<?> theClass();

    static ClassProperty create(Class<?> theClass) {
      return new AutoValue_AutoValueTest_ClassProperty(theClass);
    }
  }

  @Test
  public void testClassProperty() {
    ClassProperty instance = ClassProperty.create(Thread.class);
    assertThat(instance.theClass()).isEqualTo(Thread.class);

    try {
      ClassProperty.create(null);
      fail();
    } catch (NullPointerException expected) {
    }
  }

  @AutoValue
  abstract static class ClassPropertyWithBuilder {
    abstract Class<? extends Number> numberClass();

    static Builder builder() {
      return new AutoValue_AutoValueTest_ClassPropertyWithBuilder.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder {
      abstract Builder setNumberClass(Class<? extends Number> x);

      abstract ClassPropertyWithBuilder build();
    }
  }

  @Test
  public void testClassPropertyWithBuilder() {
    ClassPropertyWithBuilder instance =
        ClassPropertyWithBuilder.builder().setNumberClass(Integer.class).build();
    assertThat(instance.numberClass()).isEqualTo(Integer.class);

    try {
      ClassPropertyWithBuilder.builder().build();
      fail();
    } catch (IllegalStateException expected) {
    }

    try {
      ClassPropertyWithBuilder.builder().setNumberClass(null);
      fail();
    } catch (NullPointerException expected) {
    }
  }

  @AutoValue
  public abstract static class Serialize implements Serializable {
    private static final long serialVersionUID = 1L;

    public abstract int integer();

    public abstract String string();

    public abstract BigInteger bigInteger();

    public static Serialize create(int integer, String string, BigInteger bigInteger) {
      return new AutoValue_AutoValueTest_Serialize(integer, string, bigInteger);
    }
  }

  @Test
  public void testSerialize() throws Exception {
    Serialize instance = Serialize.create(23, "23", BigInteger.valueOf(23));
    assertEquals(instance, SerializableTester.reserialize(instance));
  }

  @AutoValue
  public abstract static class SerializeWithVersionUID implements Serializable {
    private static final long serialVersionUID = 4294967297L;

    public abstract int integer();

    public abstract String string();

    public static SerializeWithVersionUID create(int integer, String string) {
      return new AutoValue_AutoValueTest_SerializeWithVersionUID(integer, string);
    }
  }

  @Test
  public void testSerializeWithVersionUID() throws Exception {
    SerializeWithVersionUID instance = SerializeWithVersionUID.create(23, "23");
    assertEquals(instance, SerializableTester.reserialize(instance));

    long serialVersionUID =
        ObjectStreamClass.lookup(AutoValue_AutoValueTest_SerializeWithVersionUID.class)
            .getSerialVersionUID();
    assertEquals(4294967297L, serialVersionUID);
  }

  @AutoValue
  abstract static class LongProperty {
    public abstract long longProperty();

    public static LongProperty create(long longProperty) {
      return new AutoValue_AutoValueTest_LongProperty(longProperty);
    }
  }

  @Test
  public void testLongHashCode() {
    long longValue = 0x1234567887654321L;
    LongProperty longProperty = LongProperty.create(longValue);
    assertEquals(singlePropertyHash(longValue), longProperty.hashCode());
  }

  @AutoValue
  abstract static class IntProperty {
    public abstract int intProperty();

    public static IntProperty create(int intProperty) {
      return new AutoValue_AutoValueTest_IntProperty(intProperty);
    }
  }

  @Test
  public void testIntHashCode() {
    int intValue = 0x12345678;
    IntProperty intProperty = IntProperty.create(intValue);
    assertEquals(singlePropertyHash(intValue), intProperty.hashCode());
  }

  @AutoValue
  abstract static class ShortProperty {
    public abstract short shortProperty();

    public static ShortProperty create(short shortProperty) {
      return new AutoValue_AutoValueTest_ShortProperty(shortProperty);
    }
  }

  @Test
  public void testShortHashCode() {
    short shortValue = 0x1234;
    ShortProperty shortProperty = ShortProperty.create(shortValue);
    assertEquals(singlePropertyHash(shortValue), shortProperty.hashCode());
  }

  @AutoValue
  abstract static class ByteProperty {
    public abstract byte byteProperty();

    public static ByteProperty create(byte byteProperty) {
      return new AutoValue_AutoValueTest_ByteProperty(byteProperty);
    }
  }

  @Test
  public void testByteHashCode() {
    byte byteValue = 123;
    ByteProperty byteProperty = ByteProperty.create(byteValue);
    assertEquals(singlePropertyHash(byteValue), byteProperty.hashCode());
  }

  @AutoValue
  abstract static class CharProperty {
    public abstract char charProperty();

    public static CharProperty create(char charProperty) {
      return new AutoValue_AutoValueTest_CharProperty(charProperty);
    }
  }

  @Test
  public void testCharHashCode() {
    char charValue = 123;
    CharProperty charProperty = CharProperty.create(charValue);
    assertEquals(singlePropertyHash(charValue), charProperty.hashCode());
  }

  @AutoValue
  abstract static class BooleanProperty {
    public abstract boolean booleanProperty();

    public static BooleanProperty create(boolean booleanProperty) {
      return new AutoValue_AutoValueTest_BooleanProperty(booleanProperty);
    }
  }

  @Test
  public void testBooleanHashCode() {
    for (boolean booleanValue : new boolean[] {false, true}) {
      BooleanProperty booleanProperty = BooleanProperty.create(booleanValue);
      assertEquals(singlePropertyHash(booleanValue), booleanProperty.hashCode());
    }
  }

  @AutoValue
  abstract static class FloatProperty {
    public abstract float floatProperty();

    public static FloatProperty create(float floatProperty) {
      return new AutoValue_AutoValueTest_FloatProperty(floatProperty);
    }
  }

  @Test
  public void testFloatHashCode() {
    float floatValue = 123456f;
    FloatProperty floatProperty = FloatProperty.create(floatValue);
    assertEquals(singlePropertyHash(floatValue), floatProperty.hashCode());
  }

  @AutoValue
  abstract static class DoubleProperty {
    public abstract double doubleProperty();

    public static DoubleProperty create(double doubleProperty) {
      return new AutoValue_AutoValueTest_DoubleProperty(doubleProperty);
    }
  }

  @Test
  public void testDoubleHashCode() {
    double doubleValue = 1234567890123456d;
    DoubleProperty doubleProperty = DoubleProperty.create(doubleValue);
    assertEquals(singlePropertyHash(doubleValue), doubleProperty.hashCode());
  }

  @Test
  public void testFloatingEquality() {
    FloatProperty floatZero = FloatProperty.create(0.0f);
    FloatProperty floatMinusZero = FloatProperty.create(-0.0f);
    FloatProperty floatNaN = FloatProperty.create(Float.NaN);
    DoubleProperty doubleZero = DoubleProperty.create(0.0);
    DoubleProperty doubleMinusZero = DoubleProperty.create(-0.0);
    DoubleProperty doubleNaN = DoubleProperty.create(Double.NaN);
    new EqualsTester()
        .addEqualityGroup(floatZero)
        .addEqualityGroup(floatMinusZero)
        .addEqualityGroup(floatNaN)
        .addEqualityGroup(doubleZero)
        .addEqualityGroup(doubleMinusZero)
        .addEqualityGroup(doubleNaN)
        .testEquals();
  }

  private static int singlePropertyHash(Object property) {
    return 1000003 ^ property.hashCode();
  }

  abstract static class Super {
    public abstract Object superObject();

    public abstract boolean superBoolean();
    // The above two are out of alphabetical order to test EclipseHack.
  }

  @AutoValue
  public abstract static class Sub extends Super {
    public abstract int subInt();

    public static Sub create(Object superObject, boolean superBoolean, int subInt) {
      return new AutoValue_AutoValueTest_Sub(superObject, superBoolean, subInt);
    }
  }

  // The @AutoValue class can inherit abstract methods from its superclass.
  @Test
  public void testSuperclass() throws Exception {
    Sub instance = Sub.create("blim", true, 1729);
    assertEquals("blim", instance.superObject());
    assertTrue(instance.superBoolean());
    assertEquals(1729, instance.subInt());
    assertEquals(instance, instance);
    assertEqualsNullIsFalse(instance);
  }

  abstract static class NonPublicSuper {
    abstract Object superObject();
  }

  // The properties in this subclass are not in alphabetical order, which enables us to test that
  // everything works correctly when Eclipse sorts them into the order
  // [superObject, subInt, subString], since it sorts per class.
  @AutoValue
  abstract static class NonPublicSub extends NonPublicSuper {
    abstract String subString();

    abstract int subInt();

    static NonPublicSub create(Object superObject, String subString, int subInt) {
      return new AutoValue_AutoValueTest_NonPublicSub(superObject, subString, subInt);
    }
  }

  @Test
  public void testNonPublicInheritedGetters() throws Exception {
    NonPublicSub instance = NonPublicSub.create("blim", "blam", 1729);
    assertEquals("blim", instance.superObject());
    assertEquals("blam", instance.subString());
    assertEquals(1729, instance.subInt());
    assertEquals(instance, instance);
    assertEqualsNullIsFalse(instance);
  }

  @SuppressWarnings("ObjectEqualsNull")
  private void assertEqualsNullIsFalse(Object instance) {
    assertFalse(instance.equals(null));
  }

  @AutoValue
  abstract static class NullableProperties {
    @Nullable
    abstract String nullableString();

    abstract int randomInt();

    static NullableProperties create(@Nullable String nullableString, int randomInt) {
      return new AutoValue_AutoValueTest_NullableProperties(nullableString, randomInt);
    }
  }

  @Test
  public void testNullablePropertiesCanBeNull() {
    NullableProperties instance = NullableProperties.create(null, 23);
    assertNull(instance.nullableString());
    assertThat(instance.randomInt()).isEqualTo(23);
    String expectedString =
        omitIdentifiers ? "{null, 23}" : "NullableProperties{nullableString=null, randomInt=23}";
    assertThat(instance.toString()).isEqualTo(expectedString);
  }

  @AutoAnnotation
  static Nullable nullable() {
    return new AutoAnnotation_AutoValueTest_nullable();
  }

  @Test
  public void testNullablePropertyConstructorParameterIsNullable() throws NoSuchMethodException {
    Constructor<?> constructor =
        AutoValue_AutoValueTest_NullableProperties.class.getDeclaredConstructor(
            String.class, int.class);
    assertThat(constructor.getParameterAnnotations()[0]).asList().contains(nullable());
  }

  @AutoValue
  abstract static class AlternativeNullableProperties {
    @interface Nullable {}

    @AlternativeNullableProperties.Nullable
    abstract String nullableString();

    abstract int randomInt();

    static AlternativeNullableProperties create(@Nullable String nullableString, int randomInt) {
      return new AutoValue_AutoValueTest_AlternativeNullableProperties(nullableString, randomInt);
    }
  }

  @Test
  public void testNullableCanBeFromElsewhere() throws Exception {
    AlternativeNullableProperties instance = AlternativeNullableProperties.create(null, 23);
    assertNull(instance.nullableString());
    assertThat(instance.randomInt()).isEqualTo(23);
    String expectedString =
        omitIdentifiers
            ? "{null, 23}"
            : "AlternativeNullableProperties{nullableString=null, randomInt=23}";
    assertThat(instance.toString()).isEqualTo(expectedString);
  }

  @AutoValue
  abstract static class NonNullableProperties {
    abstract String nonNullableString();

    abstract int randomInt();

    static NonNullableProperties create(String nonNullableString, int randomInt) {
      return new AutoValue_AutoValueTest_NonNullableProperties(nonNullableString, randomInt);
    }
  }

  @Test
  public void testNonNullablePropertiesCannotBeNull() throws Exception {
    try {
      NonNullableProperties.create(null, 23);
      fail("Object creation succeeded but should not have");
    } catch (NullPointerException expected) {
    }
    NonNullableProperties instance = NonNullableProperties.create("nonnull", 23);
    assertEquals("nonnull", instance.nonNullableString());
    assertEquals(23, instance.randomInt());
  }

  @AutoValue
  abstract static class NullableListProperties {
    @Nullable
    abstract ImmutableList<String> nullableStringList();

    static NullableListProperties create(@Nullable ImmutableList<String> nullableStringList) {
      return new AutoValue_AutoValueTest_NullableListProperties(nullableStringList);
    }
  }

  @Test
  public void testNullableListPropertiesCanBeNonNull() {
    NullableListProperties instance = NullableListProperties.create(ImmutableList.of("foo", "bar"));
    assertEquals(ImmutableList.of("foo", "bar"), instance.nullableStringList());
  }

  @Test
  public void testNullableListPropertiesCanBeNull() {
    NullableListProperties instance = NullableListProperties.create(null);
    assertNull(instance.nullableStringList());
  }

  @AutoValue
  abstract static class NullableListPropertiesWithBuilder {
    @Nullable
    abstract ImmutableList<String> nullableStringList();

    static Builder builder() {
      return new AutoValue_AutoValueTest_NullableListPropertiesWithBuilder.Builder();
    }

    @AutoValue.Builder
    interface Builder {
      Builder nullableStringList(List<String> nullableStringList);

      NullableListPropertiesWithBuilder build();
    }
  }

  @Test
  public void testNullableListPropertiesWithBuilderCanBeNonNull() {
    NullableListPropertiesWithBuilder instance =
        NullableListPropertiesWithBuilder.builder()
            .nullableStringList(ImmutableList.of("foo", "bar"))
            .build();
    assertEquals(ImmutableList.of("foo", "bar"), instance.nullableStringList());
  }

  @Test
  public void testNullableListPropertiesWithBuilderCanBeUnset() {
    NullableListPropertiesWithBuilder instance =
        NullableListPropertiesWithBuilder.builder().build();
    assertNull(instance.nullableStringList());
  }

  @Test
  public void testNullableListPropertiesWithBuilderCanBeNull() {
    NullableListPropertiesWithBuilder instance =
        NullableListPropertiesWithBuilder.builder().nullableStringList(null).build();
    assertNull(instance.nullableStringList());
  }

  static class Nested {
    @AutoValue
    abstract static class Doubly {
      @Nullable
      abstract String nullableString();

      abstract int randomInt();

      static Doubly create(String nullableString, int randomInt) {
        return new AutoValue_AutoValueTest_Nested_Doubly(nullableString, randomInt);
      }
    }
  }

  @Test
  public void testDoublyNestedClass() throws Exception {
    Nested.Doubly instance = Nested.Doubly.create(null, 23);
    assertNull(instance.nullableString());
    assertThat(instance.randomInt()).isEqualTo(23);
    String expectedString =
        omitIdentifiers ? "{null, 23}" : "Doubly{nullableString=null, randomInt=23}";
    assertThat(instance.toString()).isEqualTo(expectedString);
  }

  static interface NestedInInterface {
    @AutoValue
    abstract class Doubly {
      abstract String string();

      abstract Map<String, Integer> map();

      static Doubly create(String string, Map<String, Integer> map) {
        return new AutoValue_AutoValueTest_NestedInInterface_Doubly(string, map);
      }
    }
  }

  @Test
  public void testClassNestedInInterface() throws Exception {
    Map<String, Integer> map = ImmutableMap.of("vingt-et-un", 21);
    NestedInInterface.Doubly instance = NestedInInterface.Doubly.create("foo", map);
    assertEquals("foo", instance.string());
    assertEquals(map, instance.map());
  }

  @AutoValue
  abstract static class NullableNonNullable {
    @Nullable
    abstract String nullableString();

    @Nullable
    abstract String otherNullableString();

    abstract String nonNullableString();

    static NullableNonNullable create(
        String nullableString, String otherNullableString, String nonNullableString) {
      return new AutoValue_AutoValueTest_NullableNonNullable(
          nullableString, otherNullableString, nonNullableString);
    }
  }

  @Test
  public void testEqualsWithNullable() throws Exception {
    NullableNonNullable everythingNull =
        NullableNonNullable.create(null, null, "nonNullableString");
    NullableNonNullable somethingNull =
        NullableNonNullable.create(null, "otherNullableString", "nonNullableString");
    NullableNonNullable nothingNull =
        NullableNonNullable.create("nullableString", "otherNullableString", "nonNullableString");
    NullableNonNullable nothingNullAgain =
        NullableNonNullable.create("nullableString", "otherNullableString", "nonNullableString");
    new EqualsTester()
        .addEqualityGroup(everythingNull)
        .addEqualityGroup(somethingNull)
        .addEqualityGroup(nothingNull, nothingNullAgain)
        .testEquals();
  }

  @AutoValue
  abstract static class GenericProperties {
    abstract Map<String, Integer> simpleMap();

    abstract Map<String, Map<String, Integer>> hairyMap();

    static GenericProperties create(
        Map<String, Integer> simpleMap, Map<String, Map<String, Integer>> hairyMap) {
      return new AutoValue_AutoValueTest_GenericProperties(simpleMap, hairyMap);
    }
  }

  @Test
  public void testGenericProperties() throws Exception {
    GenericProperties instance1 =
        GenericProperties.create(
            ImmutableMap.of("twenty-three", 23),
            ImmutableMap.of("very", (Map<String, Integer>) ImmutableMap.of("hairy", 17)));
    GenericProperties instance2 =
        GenericProperties.create(
            ImmutableMap.of("seventeen", 17),
            ImmutableMap.of("very", (Map<String, Integer>) ImmutableMap.of("hairy", 23)));
    new EqualsTester().addEqualityGroup(instance1).addEqualityGroup(instance2).testEquals();
    assertEquals(
        ImmutableMap.of("very", (Map<String, Integer>) ImmutableMap.of("hairy", 23)),
        instance2.hairyMap());
  }

  @AutoValue
  abstract static class GenericClass<K, V> {
    abstract K key();

    abstract Map<K, V> map();

    static <K, V> GenericClass<K, V> create(K key, Map<K, V> map) {
      return new AutoValue_AutoValueTest_GenericClass<K, V>(key, map);
    }
  }

  @Test
  public void testGenericClass() throws Exception {
    GenericClass<String, Boolean> instance =
        GenericClass.create("whatever", ImmutableMap.of("no", false));
    assertEquals(instance, instance);
    assertEquals("whatever", instance.key());
    assertEquals(ImmutableMap.of("no", false), instance.map());
  }

  @AutoValue
  abstract static class GenericClassSimpleBounds<K extends Number, V extends K> {
    abstract K key();

    abstract Map<K, V> map();

    static <K extends Number, V extends K> GenericClassSimpleBounds<K, V> create(
        K key, Map<K, V> map) {
      return new AutoValue_AutoValueTest_GenericClassSimpleBounds<K, V>(key, map);
    }
  }

  @Test
  public void testGenericClassWithSimpleBounds() throws Exception {
    GenericClassSimpleBounds<Integer, Integer> instance =
        GenericClassSimpleBounds.create(23, ImmutableMap.of(17, 23));
    assertEquals(instance, instance);
    assertEquals(23, (int) instance.key());
    assertEquals(ImmutableMap.of(17, 23), instance.map());
  }

  @AutoValue
  abstract static class GenericClassHairyBounds<K extends List<V> & Comparable<K>, V> {
    abstract K key();

    abstract Map<K, V> map();

    static <K extends List<V> & Comparable<K>, V> GenericClassHairyBounds<K, V> create(
        K key, Map<K, V> map) {
      return new AutoValue_AutoValueTest_GenericClassHairyBounds<K, V>(key, map);
    }
  }

  @Test
  public void testGenericClassWithHairyBounds() throws Exception {
    class ComparableList<E> extends ArrayList<E> implements Comparable<ComparableList<E>> {
      private static final long serialVersionUID = 1L;

      @Override
      public int compareTo(ComparableList<E> list) {
        throw new UnsupportedOperationException();
      }
    }
    ComparableList<String> emptyList = new ComparableList<String>();
    GenericClassHairyBounds<ComparableList<String>, String> instance =
        GenericClassHairyBounds.create(emptyList, ImmutableMap.of(emptyList, "23"));
    assertEquals(instance, instance);
    assertEquals(emptyList, instance.key());
    assertEquals(ImmutableMap.of(emptyList, "23"), instance.map());
  }

  interface Mergeable<M extends Mergeable<M>> {
    M merge(M other);
  }

  @AutoValue
  abstract static class Delta<M extends Mergeable<M>> {
    abstract M meta();

    static <M extends Mergeable<M>> Delta<M> create(M meta) {
      return new AutoValue_AutoValueTest_Delta<M>(meta);
    }
  }

  @Test
  public void testRecursiveGeneric() {
    class MergeableImpl implements Mergeable<MergeableImpl> {
      @Override
      public MergeableImpl merge(MergeableImpl other) {
        return this;
      }
    }
    MergeableImpl mergeable = new MergeableImpl();
    Delta<MergeableImpl> instance = Delta.create(mergeable);
    assertSame(mergeable, instance.meta());
  }

  static class NodeType<O> {}

  abstract static class NodeExpressionClass<O> {
    abstract NodeType<O> getType();
  }

  @AutoValue
  abstract static class NotNodeExpression extends NodeExpressionClass<Boolean> {
    static NotNodeExpression create() {
      return new AutoValue_AutoValueTest_NotNodeExpression(new NodeType<Boolean>());
    }
  }

  interface NodeExpressionInterface<O> {
    NodeType<O> getType();
  }

  @AutoValue
  abstract static class NotNodeExpression2 implements NodeExpressionInterface<Boolean> {
    static NotNodeExpression2 create() {
      return new AutoValue_AutoValueTest_NotNodeExpression2(new NodeType<Boolean>());
    }
  }

  @Test
  public void testConcreteWithGenericParent() {
    NotNodeExpression instance = NotNodeExpression.create();
    assertThat(instance.getType()).isInstanceOf(NodeType.class);
    NotNodeExpression2 instance2 = NotNodeExpression2.create();
    assertThat(instance2.getType()).isInstanceOf(NodeType.class);
  }

  @AutoValue
  abstract static class ExplicitToString {
    abstract String string();

    static ExplicitToString create(String string) {
      return new AutoValue_AutoValueTest_ExplicitToString(string);
    }

    @Override
    public String toString() {
      return "Bazinga{" + string() + "}";
    }
  }

  // We should not generate a toString() method if there already is a non-default one.
  @Test
  public void testExplicitToString() throws Exception {
    ExplicitToString instance = ExplicitToString.create("foo");
    assertEquals("Bazinga{foo}", instance.toString());
  }

  abstract static class NonAutoExplicitToString {
    abstract String string();

    @Override
    public String toString() {
      return "Bazinga{" + string() + "}";
    }
  }

  @AutoValue
  abstract static class InheritedExplicitToString extends NonAutoExplicitToString {
    static InheritedExplicitToString create(String string) {
      return new AutoValue_AutoValueTest_InheritedExplicitToString(string);
    }
  }

  // We should not generate a toString() method if we already inherit a non-default one.
  @Test
  public void testInheritedExplicitToString() throws Exception {
    InheritedExplicitToString instance = InheritedExplicitToString.create("foo");
    assertEquals("Bazinga{foo}", instance.toString());
  }

  @AutoValue
  abstract static class AbstractToString {
    abstract String string();

    static AbstractToString create(String string) {
      return new AutoValue_AutoValueTest_AbstractToString(string);
    }

    @Override
    public abstract String toString();
  }

  // We should generate a toString() method if the parent class has an abstract one.
  // That allows users to cancel a toString() from a parent class if they want.
  @Test
  public void testAbstractToString() throws Exception {
    AbstractToString instance = AbstractToString.create("foo");
    String expectedString = omitIdentifiers ? "{foo}" : "AbstractToString{string=foo}";
    assertThat(instance.toString()).isEqualTo(expectedString);
  }

  abstract static class NonAutoAbstractToString {
    abstract String string();

    @Override
    public abstract String toString();
  }

  @AutoValue
  abstract static class SubAbstractToString extends NonAutoAbstractToString {
    static SubAbstractToString create(String string) {
      return new AutoValue_AutoValueTest_SubAbstractToString(string);
    }
  }

  // We should generate a toString() method if the parent class inherits an abstract one.
  @Test
  public void testInheritedAbstractToString() throws Exception {
    SubAbstractToString instance = SubAbstractToString.create("foo");
    String expectedString = omitIdentifiers ? "{foo}" : "SubAbstractToString{string=foo}";
    assertThat(instance.toString()).isEqualTo(expectedString);
  }

  @AutoValue
  abstract static class ExplicitHashCode {
    abstract String string();

    static ExplicitHashCode create(String string) {
      return new AutoValue_AutoValueTest_ExplicitHashCode(string);
    }

    @Override
    public int hashCode() {
      return 1234;
    }
  }

  @Test
  public void testExplicitHashCode() throws Exception {
    ExplicitHashCode instance = ExplicitHashCode.create("foo");
    assertEquals(1234, instance.hashCode());
  }

  @AutoValue
  @SuppressWarnings("EqualsHashCode")
  abstract static class ExplicitEquals {
    int equalsCount;

    static ExplicitEquals create() {
      return new AutoValue_AutoValueTest_ExplicitEquals();
    }

    @Override
    public boolean equals(Object o) {
      equalsCount++;
      return super.equals(o);
    }
  }

  @SuppressWarnings("SelfEquals")
  @Test
  public void testExplicitEquals() throws Exception {
    ExplicitEquals instance = ExplicitEquals.create();
    assertEquals(0, instance.equalsCount);
    assertTrue(instance.equals(instance));
    assertEquals(1, instance.equalsCount);
    Method equals = instance.getClass().getMethod("equals", Object.class);
    assertNotSame(ExplicitEquals.class, instance.getClass());
    assertSame(ExplicitEquals.class, equals.getDeclaringClass());
  }

  @Retention(RetentionPolicy.RUNTIME)
  @interface MyAnnotation {
    String value();
  }

  @AutoAnnotation
  private static MyAnnotation myAnnotation(String value) {
    return new AutoAnnotation_AutoValueTest_myAnnotation(value);
  }

  @AutoValue
  abstract static class PrimitiveArrays {
    @SuppressWarnings("mutable")
    abstract boolean[] booleans();

    @SuppressWarnings("mutable")
    @Nullable
    abstract int[] ints();

    static PrimitiveArrays create(boolean[] booleans, int[] ints) {
      // Real code would likely clone these parameters, but here we want to check that the
      // generated constructor rejects a null value for booleans.
      return new AutoValue_AutoValueTest_PrimitiveArrays(booleans, ints);
    }
  }

  @Test
  public void testPrimitiveArrays() {
    PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], new int[0]);
    boolean[] booleans = {false, true, true, false};
    int[] ints = {6, 28, 496, 8128, 33550336};
    PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), ints.clone());
    PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), ints.clone());
    new EqualsTester().addEqualityGroup(object1, object2).addEqualityGroup(object0).testEquals();
    // EqualsTester also exercises hashCode(). We clone the arrays above to ensure that using the
    // default Object.hashCode() will fail.

    String expectedString =
        omitIdentifiers
            ? ("{" + Arrays.toString(booleans) + ", " + Arrays.toString(ints) + "}")
            : ("PrimitiveArrays{booleans="
                + Arrays.toString(booleans)
                + ", "
                + "ints="
                + Arrays.toString(ints)
                + "}");
    assertThat(object1.toString()).isEqualTo(expectedString);
    assertThat(object1.ints()).isSameInstanceAs(object1.ints());
  }

  @Test
  public void testNullablePrimitiveArrays() {
    PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], null);
    boolean[] booleans = {false, true, true, false};
    PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), null);
    PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), null);
    new EqualsTester().addEqualityGroup(object1, object2).addEqualityGroup(object0).testEquals();

    String expectedString =
        omitIdentifiers
            ? ("{" + Arrays.toString(booleans) + ", null}")
            : ("PrimitiveArrays{booleans=" + Arrays.toString(booleans) + ", " + "ints=null}");
    assertThat(object1.toString()).isEqualTo(expectedString);

    assertThat(object1.booleans()).isSameInstanceAs(object1.booleans());
    assertThat(object1.booleans()).isEqualTo(booleans);
    object1.booleans()[0] ^= true;
    assertThat(object1.booleans()).isNotEqualTo(booleans);
  }

  @Test
  public void testNotNullablePrimitiveArrays() {
    try {
      PrimitiveArrays.create(null, new int[0]);
      fail("Construction with null value for non-@Nullable array should have failed");
    } catch (NullPointerException e) {
      if (omitIdentifiers) {
        assertThat(e).hasMessageThat().isNull();
      } else {
        assertThat(e).hasMessageThat().contains("booleans");
      }
    }
  }

  // If users are mad enough to define their own Arrays class and have some properties of that
  // class and others of primitive array type, then we can't import java.util.Arrays.
  // This is unlikely.
  @AutoValue
  abstract static class AmbiguousArrays {
    static class Arrays {}

    abstract Arrays arrays();

    @SuppressWarnings("mutable")
    abstract int[] ints();

    static AmbiguousArrays create(Arrays arrays, int[] ints) {
      return new AutoValue_AutoValueTest_AmbiguousArrays(arrays, ints);
    }
  }

  @Test
  public void testAmbiguousArrays() {
    // If this test compiles at all then we presumably don't have the import problem above.
    AmbiguousArrays object1 = AmbiguousArrays.create(new AmbiguousArrays.Arrays(), new int[0]);
    assertNotNull(object1.arrays());
    assertEquals(0, object1.ints().length);
  }

  static final class HashCodeObserver {
    int hashCodeCount;

    @Override
    public boolean equals(Object obj) {
      return obj instanceof HashCodeObserver;
    }

    @Override
    public int hashCode() {
      hashCodeCount++;
      return 23;
    }
  }

  @AutoValue
  abstract static class MaybeCachedHashCode {
    abstract HashCodeObserver hashCodeObserver();

    abstract int randomInt();

    static MaybeCachedHashCode create(HashCodeObserver hashCodeObserver, int randomInt) {
      return new AutoValue_AutoValueTest_MaybeCachedHashCode(hashCodeObserver, randomInt);
    }
  }

  @Test
  public void testHashCodeNotCached() {
    HashCodeObserver observer = new HashCodeObserver();
    MaybeCachedHashCode maybeCached = MaybeCachedHashCode.create(observer, 17);
    int hash1 = maybeCached.hashCode();
    int hash2 = maybeCached.hashCode();
    assertEquals(hash1, hash2);
    assertEquals(2, observer.hashCodeCount);
  }

  @AutoValue
  abstract static class Version implements Comparable<Version> {
    abstract int major();

    abstract int minor();

    static Version create(int major, int minor) {
      return new AutoValue_AutoValueTest_Version(major, minor);
    }

    @Override
    public int compareTo(Version that) {
      return ComparisonChain.start()
          .compare(this.major(), that.major())
          .compare(this.minor(), that.minor())
          .result();
    }
  }

  @Test
  public void testComparisonChain() {
    assertEquals(Version.create(1, 2), Version.create(1, 2));
    Version[] versions = {Version.create(1, 2), Version.create(1, 3), Version.create(2, 1)};
    for (int i = 0; i < versions.length; i++) {
      for (int j = 0; j < versions.length; j++) {
        int actual = Integer.signum(versions[i].compareTo(versions[j]));
        int expected = Integer.signum(i - j);
        assertEquals(expected, actual);
      }
    }
  }

  abstract static class LukesBase {
    interface LukesVisitor<T> {
      T visit(LukesSub s);
    }

    abstract <T> T accept(LukesVisitor<T> visitor);

    @AutoValue
    abstract static class LukesSub extends LukesBase {
      static LukesSub create() {
        return new AutoValue_AutoValueTest_LukesBase_LukesSub();
      }

      @Override
      <T> T accept(LukesVisitor<T> visitor) {
        return visitor.visit(this);
      }
    }
  }

  @Test
  public void testVisitor() {
    LukesBase.LukesVisitor<String> visitor =
        new LukesBase.LukesVisitor<String>() {
          @Override
          public String visit(LukesBase.LukesSub s) {
            return s.toString();
          }
        };
    LukesBase.LukesSub sub = LukesBase.LukesSub.create();
    assertEquals(sub.toString(), sub.accept(visitor));
  }

  @AutoValue
  public abstract static class ComplexInheritance extends AbstractBase implements IntfA, IntfB {
    public static ComplexInheritance create(String name) {
      return new AutoValue_AutoValueTest_ComplexInheritance(name);
    }

    abstract String name();
  }

  static class AbstractBase implements Base {
    @Override
    public int answer() {
      return 42;
    }
  }

  interface IntfA extends Base {}

  interface IntfB extends Base {}

  interface Base {
    int answer();
  }

  @Test
  public void testComplexInheritance() {
    ComplexInheritance complex = ComplexInheritance.create("fred");
    assertEquals("fred", complex.name());
    assertEquals(42, complex.answer());
  }

  // This tests the case where we inherit abstract methods on more than one path. AbstractList
  // extends AbstractCollection, which implements Collection; and AbstractList also implements List,
  // which extends Collection. So the class here inherits the methods of Collection on more than
  // one path. In an earlier version of the logic for handling inheritance, this confused us into
  // thinking that the methods from Collection were still abstract and therefore candidates for
  // implementation, even though we inherit concrete implementations of them from AbstractList.
  @AutoValue
  public static class MoreComplexInheritance extends AbstractList<String> {
    @Override
    public String get(int index) {
      throw new NoSuchElementException(String.valueOf(index));
    }

    @Override
    public int size() {
      return 0;
    }

    public static MoreComplexInheritance create() {
      return new AutoValue_AutoValueTest_MoreComplexInheritance();
    }
  }

  @Test
  public void testMoreComplexInheritance() {
    MoreComplexInheritance instance1 = MoreComplexInheritance.create();
    MoreComplexInheritance instance2 = MoreComplexInheritance.create();
    assertThat(instance1).isEqualTo(instance2);
    assertThat(instance1).isNotSameInstanceAs(instance2);
  }

  // Test that we are not misled by the privateness of an ancestor into thinking that its methods
  // are invisible to descendants.
  public abstract static class PublicGrandparent {
    public abstract String foo();
  }

  private static class PrivateParent extends PublicGrandparent {
    @Override
    public String foo() {
      return "foo";
    }
  }

  @AutoValue
  static class EffectiveVisibility extends PrivateParent {
    static EffectiveVisibility create() {
      return new AutoValue_AutoValueTest_EffectiveVisibility();
    }
  }

  @Test
  public void testEffectiveVisibility() {
    EffectiveVisibility instance1 = EffectiveVisibility.create();
    EffectiveVisibility instance2 = EffectiveVisibility.create();
    assertThat(instance1).isEqualTo(instance2);
    assertThat(instance1).isNotSameInstanceAs(instance2);
  }

  @AutoValue
  public abstract static class InheritTwice implements IntfA, IntfB {
    public static InheritTwice create(int answer) {
      return new AutoValue_AutoValueTest_InheritTwice(answer);
    }
  }

  @Test
  public void testInheritTwice() {
    InheritTwice inheritTwice = InheritTwice.create(42);
    assertEquals(42, inheritTwice.answer());
  }

  @AutoValue
  public abstract static class Optional {
    public abstract com.google.common.base.Optional<Object> getOptional();

    public static Optional create(com.google.common.base.Optional<Object> opt) {
      return new AutoValue_AutoValueTest_Optional(opt);
    }
  }

  @Test
  public void testAmbiguityFromAutoValueType() {
    Optional autoOptional = Optional.create(com.google.common.base.Optional.absent());
    assertEquals(com.google.common.base.Optional.absent(), autoOptional.getOptional());
  }

  static class BaseWithNestedType {
    static class Optional {}
  }

  @AutoValue
  public abstract static class InheritsNestedType extends BaseWithNestedType {
    public abstract com.google.common.base.Optional<Object> getOptional();

    public static InheritsNestedType create(com.google.common.base.Optional<Object> opt) {
      return new AutoValue_AutoValueTest_InheritsNestedType(opt);
    }
  }

  @Test
  public void testAmbiguityFromInheritedType() {
    InheritsNestedType inheritsNestedType =
        InheritsNestedType.create(com.google.common.base.Optional.absent());
    assertEquals(com.google.common.base.Optional.absent(), inheritsNestedType.getOptional());
  }

  abstract static class AbstractParent {
    abstract int foo();
  }

  @AutoValue
  abstract static class AbstractChild extends AbstractParent {
    // The main point of this test is to ensure that we don't try to copy this @Override into the
    // generated implementation alongside the @Override that we put on all implementation methods.
    @Override
    abstract int foo();

    static AbstractChild create(int foo) {
      return new AutoValue_AutoValueTest_AbstractChild(foo);
    }
  }

  @Test
  public void testOverrideNotDuplicated() {
    AbstractChild instance = AbstractChild.create(23);
    assertEquals(23, instance.foo());
  }

  @AutoValue
  public abstract static class BasicWithBuilder {
    public abstract int foo();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_BasicWithBuilder.Builder();
    }

    @AutoValue.Builder
    public interface Builder {
      Builder foo(int foo);

      BasicWithBuilder build();
    }
  }

  @Test
  public void testBasicWithBuilder() {
    BasicWithBuilder x = BasicWithBuilder.builder().foo(23).build();
    assertEquals(23, x.foo());
    try {
      BasicWithBuilder.builder().build();
      fail("Expected exception for missing property");
    } catch (IllegalStateException e) {
      if (omitIdentifiers) {
        assertThat(e).hasMessageThat().isNull();
      } else {
        assertThat(e).hasMessageThat().contains("foo");
      }
    }
  }

  @Test
  public void testBasicWithBuilderHasOnlyOneConstructor() throws Exception {
    Class<?> builderClass = AutoValue_AutoValueTest_BasicWithBuilder.Builder.class;
    Constructor<?>[] constructors = builderClass.getDeclaredConstructors();
    assertThat(constructors).hasLength(1);
    Constructor<?> constructor = constructors[0];
    assertThat(constructor.getParameterTypes()).isEmpty();
  }

  @AutoValue
  public abstract static class EmptyWithBuilder {
    public static Builder builder() {
      return new AutoValue_AutoValueTest_EmptyWithBuilder.Builder();
    }

    @AutoValue.Builder
    public interface Builder {
      EmptyWithBuilder build();
    }
  }

  @Test
  public void testEmptyWithBuilder() {
    EmptyWithBuilder x = EmptyWithBuilder.builder().build();
    EmptyWithBuilder y = EmptyWithBuilder.builder().build();
    assertEquals(x, y);
  }

  @AutoValue
  public abstract static class TwoPropertiesWithBuilderClass {
    public abstract String string();

    public abstract int integer();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_TwoPropertiesWithBuilderClass.Builder();
    }

    public static Builder builder(String string) {
      return new AutoValue_AutoValueTest_TwoPropertiesWithBuilderClass.Builder().string(string);
    }

    @AutoValue.Builder
    public abstract static class Builder {
      public abstract Builder string(String x);

      public abstract Builder integer(int x);

      public abstract TwoPropertiesWithBuilderClass build();
    }
  }

  @Test
  public void testTwoPropertiesWithBuilderClass() {
    TwoPropertiesWithBuilderClass a1 =
        TwoPropertiesWithBuilderClass.builder().string("23").integer(17).build();
    TwoPropertiesWithBuilderClass a2 =
        TwoPropertiesWithBuilderClass.builder("23").integer(17).build();
    TwoPropertiesWithBuilderClass a3 =
        TwoPropertiesWithBuilderClass.builder().integer(17).string("23").build();
    TwoPropertiesWithBuilderClass b =
        TwoPropertiesWithBuilderClass.builder().string("17").integer(17).build();
    new EqualsTester().addEqualityGroup(a1, a2, a3).addEqualityGroup(b).testEquals();

    try {
      TwoPropertiesWithBuilderClass.builder().string(null);
      fail("Did not get expected exception");
    } catch (NullPointerException expected) {
    }
  }

  @AutoValue
  public abstract static class NullablePropertyWithBuilder {
    public abstract String notNullable();

    @Nullable
    public abstract String nullable();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_NullablePropertyWithBuilder.Builder();
    }

    @AutoValue.Builder
    public interface Builder {
      Builder notNullable(String s);

      Builder nullable(@Nullable String s);

      NullablePropertyWithBuilder build();
    }
  }

  @Test
  public void testOmitNullableWithBuilder() {
    NullablePropertyWithBuilder instance1 =
        NullablePropertyWithBuilder.builder().notNullable("hello").build();
    assertThat(instance1.notNullable()).isEqualTo("hello");
    assertThat(instance1.nullable()).isNull();

    NullablePropertyWithBuilder instance2 =
        NullablePropertyWithBuilder.builder().notNullable("hello").nullable(null).build();
    assertThat(instance2.notNullable()).isEqualTo("hello");
    assertThat(instance2.nullable()).isNull();
    assertThat(instance1).isEqualTo(instance2);

    NullablePropertyWithBuilder instance3 =
        NullablePropertyWithBuilder.builder().notNullable("hello").nullable("world").build();
    assertThat(instance3.notNullable()).isEqualTo("hello");
    assertThat(instance3.nullable()).isEqualTo("world");

    try {
      NullablePropertyWithBuilder.builder().build();
      fail("Expected IllegalStateException for unset non-@Nullable property");
    } catch (IllegalStateException e) {
      if (omitIdentifiers) {
        assertThat(e).hasMessageThat().isNull();
      } else {
        assertThat(e).hasMessageThat().contains("notNullable");
      }
    }
  }

  @AutoValue
  public abstract static class PrimitiveAndBoxed {
    public abstract int anInt();

    @Nullable
    public abstract Integer aNullableInteger();

    public abstract Integer aNonNullableInteger();

    public abstract Builder toBuilder();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_PrimitiveAndBoxed.Builder();
    }

    @AutoValue.Builder
    public interface Builder {
      Builder setAnInt(Integer x);

      Builder setANullableInteger(int x);

      Builder setANonNullableInteger(int x);

      PrimitiveAndBoxed build();
    }
  }

  @Test
  public void testPrimitiveAndBoxed() {
    PrimitiveAndBoxed instance1 =
        PrimitiveAndBoxed.builder().setAnInt(17).setANonNullableInteger(23).build();
    assertThat(instance1.anInt()).isEqualTo(17);
    assertThat(instance1.aNullableInteger()).isNull();
    assertThat(instance1.aNonNullableInteger()).isEqualTo(23);

    PrimitiveAndBoxed instance2 = instance1.toBuilder().setANullableInteger(5).build();
    assertThat(instance2.aNullableInteger()).isEqualTo(5);

    try {
      instance1.toBuilder().setAnInt(null);
      fail();
    } catch (NullPointerException expected) {
    }
  }

  @AutoValue
  public abstract static class OptionalPropertiesWithBuilder {
    public abstract com.google.common.base.Optional<String> optionalString();

    public abstract com.google.common.base.Optional<Integer> optionalInteger();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_OptionalPropertiesWithBuilder.Builder();
    }

    @AutoValue.Builder
    public interface Builder {
      Builder setOptionalString(com.google.common.base.Optional<String> s);

      Builder setOptionalString(String s);

      Builder setOptionalInteger(com.google.common.base.Optional<Integer> i);

      Builder setOptionalInteger(int i);

      OptionalPropertiesWithBuilder build();
    }
  }

  @Test
  public void testOmitOptionalWithBuilder() {
    OptionalPropertiesWithBuilder omitted = OptionalPropertiesWithBuilder.builder().build();
    assertThat(omitted.optionalString()).isAbsent();
    assertThat(omitted.optionalInteger()).isAbsent();

    OptionalPropertiesWithBuilder supplied =
        OptionalPropertiesWithBuilder.builder()
            .setOptionalString(com.google.common.base.Optional.of("foo"))
            .build();
    assertThat(supplied.optionalString()).hasValue("foo");
    assertThat(omitted.optionalInteger()).isAbsent();

    OptionalPropertiesWithBuilder suppliedDirectly =
        OptionalPropertiesWithBuilder.builder()
            .setOptionalString("foo")
            .setOptionalInteger(23)
            .build();
    assertThat(suppliedDirectly.optionalString()).hasValue("foo");
    assertThat(suppliedDirectly.optionalInteger()).hasValue(23);
  }

  @AutoValue
  public abstract static class OptionalPropertyWithNullableBuilder {
    public abstract String notOptional();

    public abstract com.google.common.base.Optional<String> optional();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_OptionalPropertyWithNullableBuilder.Builder();
    }

    @AutoValue.Builder
    public interface Builder {
      Builder notOptional(String s);

      Builder optional(@Nullable String s);

      OptionalPropertyWithNullableBuilder build();
    }
  }

  @Test
  public void testOmitOptionalWithNullableBuilder() {
    OptionalPropertyWithNullableBuilder instance1 =
        OptionalPropertyWithNullableBuilder.builder().notOptional("hello").build();
    assertThat(instance1.notOptional()).isEqualTo("hello");
    assertThat(instance1.optional()).isAbsent();

    OptionalPropertyWithNullableBuilder instance2 =
        OptionalPropertyWithNullableBuilder.builder().notOptional("hello").optional(null).build();
    assertThat(instance2.notOptional()).isEqualTo("hello");
    assertThat(instance2.optional()).isAbsent();
    assertThat(instance1).isEqualTo(instance2);

    OptionalPropertyWithNullableBuilder instance3 =
        OptionalPropertyWithNullableBuilder.builder()
            .notOptional("hello")
            .optional("world")
            .build();
    assertThat(instance3.notOptional()).isEqualTo("hello");
    assertThat(instance3.optional()).hasValue("world");

    try {
      OptionalPropertyWithNullableBuilder.builder().build();
      fail("Expected IllegalStateException for unset non-Optional property");
    } catch (IllegalStateException expected) {
    }
  }

  @AutoValue
  public abstract static class NullableOptionalPropertiesWithBuilder {
    @Nullable
    public abstract com.google.common.base.Optional<String> optionalString();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_NullableOptionalPropertiesWithBuilder.Builder();
    }

    @AutoValue.Builder
    public interface Builder {
      Builder setOptionalString(com.google.common.base.Optional<String> s);

      NullableOptionalPropertiesWithBuilder build();
    }
  }

  @Test
  public void testOmitNullableOptionalWithBuilder() {
    NullableOptionalPropertiesWithBuilder omitted =
        NullableOptionalPropertiesWithBuilder.builder().build();
    assertThat(omitted.optionalString()).isNull();

    NullableOptionalPropertiesWithBuilder supplied =
        NullableOptionalPropertiesWithBuilder.builder()
            .setOptionalString(com.google.common.base.Optional.of("foo"))
            .build();
    assertThat(supplied.optionalString()).hasValue("foo");
  }

  @AutoValue
  public abstract static class OptionalPropertiesWithBuilderSimpleSetter {
    public abstract com.google.common.base.Optional<String> optionalString();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_OptionalPropertiesWithBuilderSimpleSetter.Builder();
    }

    @AutoValue.Builder
    public interface Builder {
      Builder setOptionalString(String s);

      OptionalPropertiesWithBuilderSimpleSetter build();
    }
  }

  @Test
  public void testOptionalPropertySimpleSetter() {
    OptionalPropertiesWithBuilderSimpleSetter omitted =
        OptionalPropertiesWithBuilderSimpleSetter.builder().build();
    assertThat(omitted.optionalString()).isAbsent();

    OptionalPropertiesWithBuilderSimpleSetter supplied =
        OptionalPropertiesWithBuilderSimpleSetter.builder().setOptionalString("foo").build();
    assertThat(supplied.optionalString()).hasValue("foo");
  }

  @AutoValue
  public abstract static class PropertyWithOptionalGetter {
    public abstract String getString();

    public abstract int getInt();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_PropertyWithOptionalGetter.Builder();
    }

    @AutoValue.Builder
    public interface Builder {
      Builder setString(String s);

      com.google.common.base.Optional<String> getString();

      Builder setInt(int x);

      com.google.common.base.Optional<Integer> getInt();

      PropertyWithOptionalGetter build();
    }
  }

  @Test
  public void testOptionalGetter() {
    PropertyWithOptionalGetter.Builder omitted = PropertyWithOptionalGetter.builder();
    assertThat(omitted.getString()).isAbsent();
    assertThat(omitted.getInt()).isAbsent();

    PropertyWithOptionalGetter.Builder supplied =
        PropertyWithOptionalGetter.builder().setString("foo").setInt(23);
    assertThat(supplied.getString()).hasValue("foo");
    assertThat(supplied.getInt()).hasValue(23);
  }

  @AutoValue
  public abstract static class PropertyNamedMissing {
    public abstract String missing();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_PropertyNamedMissing.Builder();
    }

    @AutoValue.Builder
    public abstract static class Builder {
      public abstract Builder setMissing(String x);

      public abstract PropertyNamedMissing build();
    }
  }

  // https://github.com/google/auto/issues/412
  @Test
  public void testPropertyNamedMissing() {
    try {
      PropertyNamedMissing.builder().build();
      fail();
    } catch (IllegalStateException expected) {
    }
    PropertyNamedMissing x = PropertyNamedMissing.builder().setMissing("foo").build();
    assertThat(x.missing()).isEqualTo("foo");
  }

  @AutoValue
  public abstract static class GenericsWithBuilder<T extends Number & Comparable<T>, U extends T> {
    public abstract List<T> list();

    public abstract U u();

    public static <T extends Number & Comparable<T>, U extends T> Builder<T, U> builder() {
      return new AutoValue_AutoValueTest_GenericsWithBuilder.Builder<T, U>();
    }

    public abstract Builder<T, U> toBuilderGenerated();

    @AutoValue.Builder
    public interface Builder<T extends Number & Comparable<T>, U extends T> {
      Builder<T, U> list(List<T> list);

      Builder<T, U> u(U u);

      GenericsWithBuilder<T, U> build();
    }
  }

  @Test
  public void testBuilderGenerics() {
    List<Integer> integers = ImmutableList.of(1, 2, 3);
    GenericsWithBuilder<Integer, Integer> instance =
        GenericsWithBuilder.<Integer, Integer>builder().list(integers).u(23).build();
    assertEquals(integers, instance.list());
    assertEquals((Integer) 23, instance.u());

    GenericsWithBuilder<Integer, Integer> instance2 = instance.toBuilderGenerated().build();
    assertEquals(instance, instance2);
    assertNotSame(instance, instance2);

    GenericsWithBuilder<Integer, Integer> instance3 = instance.toBuilderGenerated().u(17).build();
    assertEquals(integers, instance3.list());
    assertEquals((Integer) 17, instance3.u());
  }

  public interface ToBuilder<BuilderT> {
    BuilderT toBuilder();
  }

  @AutoValue
  public abstract static class InheritedToBuilder<T, U>
      implements ToBuilder<InheritedToBuilder.Builder<T, U>> {

    public abstract T t();
    public abstract U u();

    public static <T, U> Builder<T, U> builder() {
      return new AutoValue_AutoValueTest_InheritedToBuilder.Builder<T, U>();
    }

    @AutoValue.Builder
    public abstract static class Builder<T, U> {
      public abstract Builder<T, U> setT(T t);
      public abstract Builder<T, U> setU(U u);
      public abstract InheritedToBuilder<T, U> build();
    }
  }

  @Test
  public void testInheritedToBuilder() {
    InheritedToBuilder<Integer, String> x =
        InheritedToBuilder.<Integer, String>builder().setT(17).setU("wibble").build();
    InheritedToBuilder<Integer, String> y = x.toBuilder().setT(23).build();
    assertThat(y.u()).isEqualTo("wibble");
  }

  @AutoValue
  public abstract static class BuilderWithSet<T extends Comparable<T>> {
    public abstract List<T> list();

    public abstract T t();

    public static <T extends Comparable<T>> Builder<T> builder() {
      return new AutoValue_AutoValueTest_BuilderWithSet.Builder<T>();
    }

    @AutoValue.Builder
    public interface Builder<T extends Comparable<T>> {
      Builder<T> setList(List<T> list);

      Builder<T> setT(T t);

      BuilderWithSet<T> build();
    }
  }

  @Test
  public void testBuilderWithSet() {
    List<Integer> integers = ImmutableList.of(1, 2, 3);
    BuilderWithSet<Integer> instance =
        BuilderWithSet.<Integer>builder().setList(integers).setT(23).build();
    assertEquals(integers, instance.list());
    assertEquals((Integer) 23, instance.t());
  }

  @AutoValue
  public abstract static class BuilderWithSetAndGet {
    public abstract List<Integer> getAList();

    public abstract int getAnInt();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_BuilderWithSetAndGet.Builder();
    }

    public abstract Builder toBuilder();

    @AutoValue.Builder
    public interface Builder {
      Builder setAList(List<Integer> list);

      Builder setAnInt(int i);

      BuilderWithSetAndGet build();
    }
  }

  @Test
  public void testBuilderWithSetAndGet() {
    List<Integer> integers = ImmutableList.of(1, 2, 3);
    BuilderWithSetAndGet instance =
        BuilderWithSetAndGet.builder().setAList(integers).setAnInt(23).build();
    assertEquals(integers, instance.getAList());
    assertEquals(23, instance.getAnInt());

    BuilderWithSetAndGet instance2 = instance.toBuilder().build();
    assertEquals(instance, instance2);
    assertNotSame(instance, instance2);

    BuilderWithSetAndGet instance3 = instance.toBuilder().setAnInt(17).build();
    assertEquals(integers, instance3.getAList());
    assertEquals(17, instance3.getAnInt());
  }

  @AutoValue
  public abstract static class BuilderWithUnprefixedGetters<T extends Comparable<T>> {
    public abstract ImmutableList<T> list();

    @Nullable
    public abstract T t();

    @SuppressWarnings("mutable")
    public abstract int[] ints();

    public abstract int noGetter();

    public abstract String oAuth();

    public abstract String oBrien();

    public static <T extends Comparable<T>> Builder<T> builder() {
      return new AutoValue_AutoValueTest_BuilderWithUnprefixedGetters.Builder<T>();
    }

    @AutoValue.Builder
    public interface Builder<T extends Comparable<T>> {
      Builder<T> setList(ImmutableList<T> list);

      Builder<T> setT(T t);

      Builder<T> setInts(int[] ints);

      Builder<T> setNoGetter(int x);

      Builder<T> setoAuth(String x); // this ugly spelling is for compatibility

      Builder<T> setOBrien(String x);

      ImmutableList<T> list();

      T t();

      int[] ints();

      String oAuth();

      String oBrien();

      BuilderWithUnprefixedGetters<T> build();
    }
  }

  @Test
  public void testBuilderWithUnprefixedGetter() {
    ImmutableList<String> names = ImmutableList.of("fred", "jim");
    int[] ints = {6, 28, 496, 8128, 33550336};
    int noGetter = -1;

    BuilderWithUnprefixedGetters.Builder<String> builder = BuilderWithUnprefixedGetters.builder();
    assertNull(builder.t());
    try {
      builder.list();
      fail("Attempt to retrieve unset list property should have failed");
    } catch (IllegalStateException e) {
      if (omitIdentifiers) {
        assertThat(e).hasMessageThat().isNull();
      } else {
        assertThat(e).hasMessageThat().isEqualTo("Property \"list\" has not been set");
      }
    }
    try {
      builder.ints();
      fail("Attempt to retrieve unset ints property should have failed");
    } catch (IllegalStateException e) {
      if (omitIdentifiers) {
        assertThat(e).hasMessageThat().isNull();
      } else {
        assertThat(e).hasMessageThat().isEqualTo("Property \"ints\" has not been set");
      }
    }

    builder.setList(names);
    assertThat(builder.list()).isSameInstanceAs(names);
    builder.setInts(ints);
    assertThat(builder.ints()).isEqualTo(ints);
    builder.setoAuth("OAuth");
    assertThat(builder.oAuth()).isEqualTo("OAuth");
    builder.setOBrien("Flann");
    assertThat(builder.oBrien()).isEqualTo("Flann");
    // The array is not cloned by the getter, so the client can modify it (but shouldn't).
    ints[0] = 0;
    assertThat(builder.ints()[0]).isEqualTo(0);
    ints[0] = 6;

    BuilderWithUnprefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
    assertThat(instance.list()).isSameInstanceAs(names);
    assertThat(instance.t()).isNull();
    assertThat(instance.ints()).isEqualTo(ints);
    assertThat(instance.noGetter()).isEqualTo(noGetter);
    assertThat(instance.oAuth()).isEqualTo("OAuth");
    assertThat(instance.oBrien()).isEqualTo("Flann");
  }

  @AutoValue
  public abstract static class BuilderWithPrefixedGetters<T extends Comparable<T>> {
    public abstract ImmutableList<T> getList();

    public abstract T getT();

    @SuppressWarnings("mutable")
    @Nullable
    public abstract int[] getInts();

    public abstract String getOAuth();

    public abstract int getNoGetter();

    public static <T extends Comparable<T>> Builder<T> builder() {
      return new AutoValue_AutoValueTest_BuilderWithPrefixedGetters.Builder<T>();
    }

    @AutoValue.Builder
    public abstract static class Builder<T extends Comparable<T>> {
      public abstract Builder<T> setList(ImmutableList<T> list);

      public abstract Builder<T> setT(T t);

      public abstract Builder<T> setInts(int[] ints);

      public abstract Builder<T> setNoGetter(int x);

      public abstract Builder<T> setOAuth(String x);

      abstract ImmutableList<T> getList();

      abstract T getT();

      abstract int[] getInts();

      public abstract BuilderWithPrefixedGetters<T> build();
    }
  }

  @Test
  public void testBuilderWithPrefixedGetter() {
    ImmutableList<String> names = ImmutableList.of("fred", "jim");
    String name = "sheila";
    int noGetter = -1;

    BuilderWithPrefixedGetters.Builder<String> builder = BuilderWithPrefixedGetters.builder();
    assertThat(builder.getInts()).isNull();
    try {
      builder.getList();
      fail("Attempt to retrieve unset list property should have failed");
    } catch (IllegalStateException e) {
      if (omitIdentifiers) {
        assertThat(e).hasMessageThat().isNull();
      } else {
        assertThat(e).hasMessageThat().isEqualTo("Property \"list\" has not been set");
      }
    }

    builder.setList(names);
    assertThat(builder.getList()).isSameInstanceAs(names);
    builder.setT(name);
    assertThat(builder.getInts()).isNull();
    builder.setOAuth("OAuth");

    BuilderWithPrefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
    assertThat(instance.getList()).isSameInstanceAs(names);
    assertThat(instance.getT()).isEqualTo(name);
    assertThat(instance.getInts()).isNull();
    assertThat(instance.getNoGetter()).isEqualTo(noGetter);
    assertThat(instance.getOAuth()).isEqualTo("OAuth");
  }

  @AutoValue
  public abstract static class BuilderWithPrefixedGettersAndUnprefixedSetters {
    public abstract String getOAuth();
    public abstract String getOBrien();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_BuilderWithPrefixedGettersAndUnprefixedSetters.Builder();
    }

    @AutoValue.Builder
    public abstract static class Builder {
      public abstract Builder oAuth(String x);
      public abstract Builder OBrien(String x);
      public abstract BuilderWithPrefixedGettersAndUnprefixedSetters build();
    }
  }

  @Test
  public void testBuilderWithPrefixedGetterAndUnprefixedSetter() {
    BuilderWithPrefixedGettersAndUnprefixedSetters x =
        BuilderWithPrefixedGettersAndUnprefixedSetters.builder()
        .oAuth("OAuth")
        .OBrien("Flann")
        .build();
    assertThat(x.getOAuth()).isEqualTo("OAuth");
    assertThat(x.getOBrien()).isEqualTo("Flann");
  }

  @AutoValue
  public abstract static class BuilderWithPropertyBuilders<FooT extends Comparable<FooT>> {
    public abstract ImmutableList<FooT> getFoos();

    public abstract ImmutableSet<String> getStrings();

    public abstract BuilderWithPropertyBuilders.Builder<FooT> toBuilder();

    public static <FooT extends Comparable<FooT>> Builder<FooT> builder() {
      return new AutoValue_AutoValueTest_BuilderWithPropertyBuilders.Builder<FooT>();
    }

    @AutoValue.Builder
    public abstract static class Builder<FooT extends Comparable<FooT>> {
      public abstract ImmutableList<FooT> getFoos();

      public Builder<FooT> addFoos(Iterable<FooT> foos) {
        foosBuilder().addAll(foos);
        return this;
      }

      abstract ImmutableList.Builder<FooT> foosBuilder();

      public Builder<FooT> addToTs(FooT element) {
        foosBuilder().add(element);
        return this;
      }

      abstract Builder<FooT> setStrings(ImmutableList<String> strings);

      abstract ImmutableSet.Builder<String> stringsBuilder();

      public Builder<FooT> addToStrings(String element) {
        stringsBuilder().add(element);
        return this;
      }

      public abstract BuilderWithPropertyBuilders<FooT> build();
    }
  }

  @Test
  public void testBuilderWithPropertyBuilders() {
    ImmutableList<Integer> numbers = ImmutableList.of(1, 1, 2, 6, 24);
    ImmutableSet<String> names = ImmutableSet.of("one", "two", "six", "twenty-four");

    BuilderWithPropertyBuilders<Integer> a =
        BuilderWithPropertyBuilders.<Integer>builder()
            .addFoos(numbers)
            .addToStrings("one")
            .addToStrings("two")
            .addToStrings("six")
            .addToStrings("twenty-four")
            .build();

    assertEquals(numbers, a.getFoos());
    assertEquals(names, a.getStrings());

    BuilderWithPropertyBuilders.Builder<Integer> bBuilder = BuilderWithPropertyBuilders.builder();
    bBuilder.stringsBuilder().addAll(names);
    bBuilder.foosBuilder().addAll(numbers);

    assertEquals(numbers, bBuilder.getFoos());

    BuilderWithPropertyBuilders<Integer> b = bBuilder.build();
    assertEquals(a, b);

    BuilderWithPropertyBuilders.Builder<Integer> cBuilder = a.toBuilder();
    cBuilder.addToStrings("one hundred and twenty");
    cBuilder.addToTs(120);
    BuilderWithPropertyBuilders<Integer> c = cBuilder.build();
    assertEquals(
        ImmutableSet.of("one", "two", "six", "twenty-four", "one hundred and twenty"),
        c.getStrings());
    assertEquals(ImmutableList.of(1, 1, 2, 6, 24, 120), c.getFoos());

    BuilderWithPropertyBuilders.Builder<Integer> dBuilder = a.toBuilder();
    dBuilder.addFoos(ImmutableList.of(120, 720));
    BuilderWithPropertyBuilders<Integer> d = dBuilder.build();
    assertEquals(ImmutableList.of(1, 1, 2, 6, 24, 120, 720), d.getFoos());
    assertEquals(names, d.getStrings());

    BuilderWithPropertyBuilders<Integer> empty =
        BuilderWithPropertyBuilders.<Integer>builder().build();
    assertEquals(ImmutableList.of(), empty.getFoos());
    assertEquals(ImmutableSet.of(), empty.getStrings());

    try {
      BuilderWithPropertyBuilders.<Integer>builder().setStrings(null).build();
      fail("Did not get expected exception");
    } catch (RuntimeException expected) {
      // We don't specify whether you get the exception on setStrings(null) or on build(), nor
      // which exception it is exactly.
    }
  }

  interface ImmutableListOf<T> {
    ImmutableList<T> list();
  }

  @AutoValue
  abstract static class PropertyBuilderInheritsType implements ImmutableListOf<String> {
    static Builder builder() {
      return new AutoValue_AutoValueTest_PropertyBuilderInheritsType.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder {
      abstract ImmutableList.Builder<String> listBuilder();
      abstract PropertyBuilderInheritsType build();
    }
  }

  @Test
  public void propertyBuilderInheritsType() {
    PropertyBuilderInheritsType.Builder builder = PropertyBuilderInheritsType.builder();
    builder.listBuilder().add("foo", "bar");
    PropertyBuilderInheritsType x = builder.build();
    assertThat(x.list()).containsExactly("foo", "bar").inOrder();
  }

  @AutoValue
  public abstract static class BuilderWithExoticPropertyBuilders<
      K extends Number, V extends Comparable<K>> {
    public abstract ImmutableMap<String, V> map();

    public abstract ImmutableTable<String, K, V> table();

    public static <K extends Number, V extends Comparable<K>> Builder<K, V> builder() {
      return new AutoValue_AutoValueTest_BuilderWithExoticPropertyBuilders.Builder<K, V>();
    }

    @AutoValue.Builder
    public abstract static class Builder<K extends Number, V extends Comparable<K>> {
      public Builder<K, V> putAll(Map<String, V> map) {
        mapBuilder().putAll(map);
        return this;
      }

      public abstract ImmutableMap.Builder<String, V> mapBuilder();

      public Builder<K, V> putAll(ImmutableTable<String, K, V> table) {
        tableBuilder().putAll(table);
        return this;
      }

      public abstract ImmutableTable.Builder<String, K, V> tableBuilder();

      public abstract BuilderWithExoticPropertyBuilders<K, V> build();
    }
  }

  @Test
  public void testBuilderWithExoticPropertyBuilders() {
    ImmutableMap<String, Integer> map = ImmutableMap.of("one", 1);
    ImmutableTable<String, Integer, Integer> table = ImmutableTable.of("one", 1, -1);

    BuilderWithExoticPropertyBuilders<Integer, Integer> a =
        BuilderWithExoticPropertyBuilders.<Integer, Integer>builder()
            .putAll(map)
            .putAll(table)
            .build();
    assertEquals(map, a.map());
    assertEquals(table, a.table());

    BuilderWithExoticPropertyBuilders.Builder<Integer, Integer> bBuilder =
        BuilderWithExoticPropertyBuilders.builder();
    bBuilder.mapBuilder().putAll(map);
    bBuilder.tableBuilder().putAll(table);
    BuilderWithExoticPropertyBuilders<Integer, Integer> b = bBuilder.build();
    assertEquals(a, b);

    BuilderWithExoticPropertyBuilders<Integer, Integer> empty =
        BuilderWithExoticPropertyBuilders.<Integer, Integer>builder().build();
    assertEquals(ImmutableMap.of(), empty.map());
    assertEquals(ImmutableTable.of(), empty.table());
  }

  @AutoValue
  public abstract static class BuilderWithCopyingSetters<T extends Number> {
    public abstract ImmutableSet<? extends T> things();

    public abstract ImmutableList<Number> numbers();

    public abstract ImmutableMap<String, T> map();

    public static <T extends Number> Builder<T> builder(T value) {
      return new AutoValue_AutoValueTest_BuilderWithCopyingSetters.Builder<T>()
          .setNumbers(ImmutableSet.of(17, 23.0))
          .setMap(Collections.singletonMap("foo", value));
    }

    @AutoValue.Builder
    public interface Builder<T extends Number> {
      Builder<T> setThings(ImmutableSet<T> things);

      Builder<T> setThings(Iterable<? extends T> things);

      Builder<T> setThings(T... things);

      Builder<T> setNumbers(Collection<? extends Number> strings);

      Builder<T> setMap(Map<String, T> map);

      BuilderWithCopyingSetters<T> build();
    }
  }

  @Test
  public void testBuilderWithCopyingSetters() {
    BuilderWithCopyingSetters.Builder<Integer> builder = BuilderWithCopyingSetters.builder(23);

    BuilderWithCopyingSetters<Integer> a = builder.setThings(ImmutableSet.of(1, 2)).build();
    assertThat(a.things()).containsExactly(1, 2);
    assertThat(a.numbers()).containsExactly(17, 23.0).inOrder();
    assertThat(a.map()).containsExactly("foo", 23);

    BuilderWithCopyingSetters<Integer> b = builder.setThings(Arrays.asList(1, 2)).build();
    assertThat(b).isEqualTo(a);

    BuilderWithCopyingSetters<Integer> c = builder.setThings(1, 2).build();
    assertThat(c).isEqualTo(a);
  }

  @AutoValue
  public abstract static class BuilderWithImmutableSorted<T extends Comparable<T>> {
    public abstract ImmutableSortedSet<T> sortedSet();

    public abstract ImmutableSortedMap<T, Integer> sortedMap();

    public static <T extends Comparable<T>> Builder<T> builder() {
      return new AutoValue_AutoValueTest_BuilderWithImmutableSorted.Builder<T>()
          .setSortedSet(new TreeSet<T>())
          .setSortedMap(new TreeMap<T, Integer>());
    }

    @AutoValue.Builder
    public interface Builder<T extends Comparable<T>> {
      @SuppressWarnings("unchecked")
      Builder<T> setSortedSet(T... x);

      Builder<T> setSortedSet(NavigableSet<T> x);

      ImmutableSortedSet.Builder<T> sortedSetBuilder();

      Builder<T> setSortedMap(SortedMap<T, Integer> x);

      Builder<T> setSortedMap(NavigableMap<T, Integer> x);

      ImmutableSortedMap.Builder<T, Integer> sortedMapBuilder();

      BuilderWithImmutableSorted<T> build();
    }
  }

  @Test
  public void testBuilderWithImmutableSorted_Varargs() {
    BuilderWithImmutableSorted<String> x =
        BuilderWithImmutableSorted.<String>builder().setSortedSet("foo", "bar", "baz").build();
    assertThat(x.sortedSet()).containsExactly("bar", "baz", "foo").inOrder();
  }

  @Test
  public void testBuilderWithImmutableSorted_SetSet() {
    BuilderWithImmutableSorted<String> x =
        BuilderWithImmutableSorted.<String>builder()
            .setSortedSet(new TreeSet<String>(String.CASE_INSENSITIVE_ORDER))
            .build();
    assertThat(x.sortedSet().comparator()).isEqualTo(String.CASE_INSENSITIVE_ORDER);
  }

  @Test
  public void testBuilderWithImmutableSorted_SetMap() {
    BuilderWithImmutableSorted<String> x =
        BuilderWithImmutableSorted.<String>builder()
            .setSortedMap(new TreeMap<String, Integer>(String.CASE_INSENSITIVE_ORDER))
            .build();
    assertThat(x.sortedMap().comparator()).isEqualTo(String.CASE_INSENSITIVE_ORDER);
  }

  @Test
  public void testBuilderWithImmutableSorted_SetCollectionBuilder() {
    BuilderWithImmutableSorted.Builder<String> builder =
        BuilderWithImmutableSorted.<String>builder();
    builder.sortedSetBuilder().add("is", "ea", "id");
    BuilderWithImmutableSorted<String> x = builder.build();
    assertThat(x.sortedSet()).containsExactly("ea", "id", "is").inOrder();
  }

  @Test
  public void testBuilderWithImmutableSorted_MapCollectionBuilder() {
    BuilderWithImmutableSorted.Builder<String> builder =
        BuilderWithImmutableSorted.<String>builder();
    builder.sortedMapBuilder().put("two", 2).put("one", 1);
    BuilderWithImmutableSorted<String> x = builder.build();
    assertThat(x.sortedMap()).containsExactly("one", 1, "two", 2).inOrder();
  }

  @AutoValue
  public abstract static class BuilderWithCollectionBuilderAndSetter<T extends Number> {
    public abstract ImmutableList<T> things();

    public static <T extends Number> Builder<T> builder() {
      return new AutoValue_AutoValueTest_BuilderWithCollectionBuilderAndSetter.Builder<T>();
    }

    @AutoValue.Builder
    public interface Builder<T extends Number> {
      Builder<T> setThings(List<T> things);

      ImmutableList<T> things();

      ImmutableList.Builder<T> thingsBuilder();

      BuilderWithCollectionBuilderAndSetter<T> build();
    }
  }

  @Test
  public void testBuilderAndSetterDefaultsEmpty() {
    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
        BuilderWithCollectionBuilderAndSetter.<Integer>builder();
    assertThat(builder.things()).isEmpty();
    assertThat(builder.build().things()).isEmpty();
  }

  @Test
  public void testBuilderAndSetterUsingBuilder() {
    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
        BuilderWithCollectionBuilderAndSetter.builder();
    builder.thingsBuilder().add(17, 23);
    BuilderWithCollectionBuilderAndSetter<Integer> x = builder.build();
    assertThat(x.things()).isEqualTo(ImmutableList.of(17, 23));
  }

  @Test
  public void testBuilderAndSetterUsingSetter() {
    ImmutableList<Integer> things = ImmutableList.of(17, 23);
    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
        BuilderWithCollectionBuilderAndSetter.<Integer>builder().setThings(things);
    assertThat(builder.things()).isSameInstanceAs(things);
    assertThat(builder.build().things()).isSameInstanceAs(things);

    List<Integer> moreThings = Arrays.asList(5, 17, 23);
    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder2 =
        BuilderWithCollectionBuilderAndSetter.<Integer>builder().setThings(moreThings);
    assertThat(builder2.things()).isEqualTo(moreThings);
    assertThat(builder2.build().things()).isEqualTo(moreThings);
  }

  @Test
  public void testBuilderAndSetterUsingSetterThenBuilder() {
    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
        BuilderWithCollectionBuilderAndSetter.builder();
    builder.setThings(ImmutableList.of(5));
    builder.thingsBuilder().add(17, 23);
    List<Integer> expectedThings = ImmutableList.of(5, 17, 23);
    assertThat(builder.things()).isEqualTo(expectedThings);
    assertThat(builder.build().things()).isEqualTo(expectedThings);
  }

  @Test
  public void testBuilderAndSetterCannotSetAfterBuilder() {
    BuilderWithCollectionBuilderAndSetter.Builder<Integer> builder =
        BuilderWithCollectionBuilderAndSetter.builder();
    builder.setThings(ImmutableList.of(5));
    builder.thingsBuilder().add(17, 23);
    try {
      builder.setThings(ImmutableList.of(1729));
      fail("Setting list after retrieving builder should provoke an exception");
    } catch (IllegalStateException e) {
      if (omitIdentifiers) {
        assertThat(e).hasMessageThat().isNull();
      } else {
        assertThat(e).hasMessageThat().isEqualTo("Cannot set things after calling thingsBuilder()");
      }
    }
  }

  abstract static class AbstractParentWithBuilder {
    abstract String foo();

    abstract static class Builder<B extends Builder<B>> {
      abstract B foo(String s);
    }
  }

  @AutoValue
  abstract static class ChildWithBuilder extends AbstractParentWithBuilder {
    abstract String bar();

    static Builder builder() {
      return new AutoValue_AutoValueTest_ChildWithBuilder.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder extends AbstractParentWithBuilder.Builder<Builder> {
      abstract Builder bar(String s);

      abstract ChildWithBuilder build();
    }
  }

  @Test
  public void testInheritedBuilder() {
    ChildWithBuilder x = ChildWithBuilder.builder().foo("foo").bar("bar").build();
    assertThat(x.foo()).isEqualTo("foo");
    assertThat(x.bar()).isEqualTo("bar");
  }

  @Retention(RetentionPolicy.RUNTIME)
  @interface GwtCompatible {
    boolean funky() default false;
  }

  @AutoValue
  @GwtCompatible(funky = true)
  abstract static class GwtCompatibleTest {
    abstract int foo();

    static GwtCompatibleTest create(int foo) {
      return new AutoValue_AutoValueTest_GwtCompatibleTest(foo);
    }
  }

  @AutoValue
  @GwtCompatible
  abstract static class GwtCompatibleTestNoArgs {
    abstract String bar();

    static GwtCompatibleTestNoArgs create(String bar) {
      return new AutoValue_AutoValueTest_GwtCompatibleTestNoArgs(bar);
    }
  }

  @Test
  public void testGwtCompatibleInherited() {
    GwtCompatibleTest test = GwtCompatibleTest.create(23);
    GwtCompatible gwtCompatible = test.getClass().getAnnotation(GwtCompatible.class);
    assertNotNull(gwtCompatible);
    assertTrue(gwtCompatible.funky());

    GwtCompatibleTestNoArgs testNoArgs = GwtCompatibleTestNoArgs.create("23");
    GwtCompatible gwtCompatibleNoArgs = testNoArgs.getClass().getAnnotation(GwtCompatible.class);
    assertNotNull(gwtCompatibleNoArgs);
    assertFalse(gwtCompatibleNoArgs.funky());
  }

  @interface NestedAnnotation {
    int anInt();

    Class<?>[] aClassArray();
  }

  @Retention(RetentionPolicy.RUNTIME)
  @interface HairyAnnotation {
    String aString();

    Class<? extends Number> aClass();

    RetentionPolicy anEnum();

    NestedAnnotation anAnnotation();
  }

  @Retention(RetentionPolicy.RUNTIME)
  @interface CopiedAnnotation {}

  @Retention(RetentionPolicy.RUNTIME)
  @interface ExcludedAnnotation {}

  @Retention(RetentionPolicy.RUNTIME)
  @Inherited
  @interface InheritedAnnotation {}

  @CopiedAnnotation
  @ExcludedAnnotation
  @InheritedAnnotation
  @AutoValue
  @AutoValue.CopyAnnotations(exclude = {ExcludedAnnotation.class})
  abstract static class CopyAnnotation {
    @HairyAnnotation(
        aString = "hello",
        aClass = Integer.class,
        anEnum = RetentionPolicy.RUNTIME,
        anAnnotation =
            @NestedAnnotation(
                anInt = 73,
                aClassArray = {String.class, Object.class}))
    abstract String field1();

    @CopiedAnnotation
    @ExcludedAnnotation
    @InheritedAnnotation
    @AutoValue.CopyAnnotations(exclude = {ExcludedAnnotation.class})
    abstract String field2();

    static CopyAnnotation create() {
      return new AutoValue_AutoValueTest_CopyAnnotation("field1", "field2");
    }
  }

  @Test
  public void testCopyClassAnnotations() throws Exception {
    CopyAnnotation x = CopyAnnotation.create();
    Class<?> c = x.getClass();
    assertNotSame(CopyAnnotation.class, c);

    // Sanity check: if these don't appear on CopyAnnotation, it makes no sense to assert that they
    // don't appear on the AutoValue_ subclass.
    {
      List<Class<? extends Annotation>> annotationsOnSuperclass =
          new ArrayList<Class<? extends Annotation>>();
      for (Annotation annotation : CopyAnnotation.class.getDeclaredAnnotations()) {
        annotationsOnSuperclass.add(annotation.annotationType());
      }
      assertThat(annotationsOnSuperclass)
          .containsAtLeast(
              CopiedAnnotation.class, ExcludedAnnotation.class, InheritedAnnotation.class);
    }

    {
      List<Class<? extends Annotation>> annotationsOnSubclass =
          new ArrayList<Class<? extends Annotation>>();
      for (Annotation annotation : c.getDeclaredAnnotations()) {
        annotationsOnSubclass.add(annotation.annotationType());
      }
      assertThat(annotationsOnSubclass).containsExactly(CopiedAnnotation.class);
    }
  }

  @Test
  public void testCopyMethodAnnotations() throws Exception {
    CopyAnnotation x = CopyAnnotation.create();
    Class<?> c = x.getClass();
    assertNotSame(CopyAnnotation.class, c);

    Method methodInSubclass = c.getDeclaredMethod("field2");
    Method methodInSuperclass = CopyAnnotation.class.getDeclaredMethod("field2");

    // Sanity check: if these don't appear on CopyAnnotation, it makes no sense to assert that they
    // don't appear on the AutoValue_ subclass.
    assertThat(methodInSuperclass.isAnnotationPresent(CopiedAnnotation.class)).isTrue();
    assertThat(methodInSuperclass.isAnnotationPresent(ExcludedAnnotation.class)).isTrue();
    assertThat(methodInSuperclass.isAnnotationPresent(InheritedAnnotation.class)).isTrue();

    assertThat(methodInSubclass.isAnnotationPresent(CopiedAnnotation.class)).isTrue();
    assertThat(methodInSubclass.isAnnotationPresent(ExcludedAnnotation.class)).isFalse();
    assertThat(methodInSubclass.isAnnotationPresent(InheritedAnnotation.class)).isTrue();
  }

  @Test
  public void testCopyMethodAnnotationsByDefault() throws Exception {
    CopyAnnotation x = CopyAnnotation.create();
    Class<?> c = x.getClass();
    assertNotSame(CopyAnnotation.class, c);
    Method methodInSubclass = c.getDeclaredMethod("field1");
    Method methodInSuperclass = CopyAnnotation.class.getDeclaredMethod("field1");
    assertNotSame(methodInSuperclass, methodInSubclass);
    HairyAnnotation annotationInSubclass = methodInSubclass.getAnnotation(HairyAnnotation.class);
    HairyAnnotation annotationInSuperclass =
        methodInSuperclass.getAnnotation(HairyAnnotation.class);
    assertEquals(annotationInSuperclass, annotationInSubclass);
  }

  @AutoValue
  abstract static class HProperty {
    public abstract Object h();

    public static HProperty create(Object h) {
      return new AutoValue_AutoValueTest_HProperty(h);
    }
  }

  @Test
  public void testHProperty() throws Exception {
    // Checks that we can have a property called `h`. The generated hashCode() method has
    // a local variable of that name and can cause the error `int cannot be dereferenced`
    HProperty.create(new Object());
  }

  interface Parent1 {
    int something();
  }

  interface Parent2 {
    int something();
  }

  @AutoValue
  abstract static class InheritSameMethodTwice implements Parent1, Parent2 {
    static InheritSameMethodTwice create(int something) {
      return new AutoValue_AutoValueTest_InheritSameMethodTwice(something);
    }
  }

  @Test
  public void testInheritSameMethodTwice() {
    InheritSameMethodTwice x = InheritSameMethodTwice.create(23);
    assertThat(x.something()).isEqualTo(23);
  }

  // Make sure we behave correctly when we inherit the same method definition from more than
  // one parent interface. We expect methods to appear in the order they are seen, with parents
  // preceding children, the superclass of a class preceding interfaces that class implements,
  // and an interface mentioned earlier in the "implements" clause preceding one mentioned later.
  // https://github.com/google/auto/issues/372
  interface OneTwoThreeFour {
    String one();

    String two();

    boolean three();

    long four();
  }

  interface TwoFour {
    String two();

    long four();
  }

  @AutoValue
  abstract static class OneTwoThreeFourImpl implements OneTwoThreeFour, TwoFour {
    static OneTwoThreeFourImpl create(String one, String two, boolean three, long four) {
      return new AutoValue_AutoValueTest_OneTwoThreeFourImpl(one, two, three, four);
    }
  }

  @Test
  public void testOneTwoThreeFour() {
    OneTwoThreeFour x = OneTwoThreeFourImpl.create("one", "two", false, 4);
    String expectedString =
        omitIdentifiers
            ? "{one, two, false, 4}"
            : "OneTwoThreeFourImpl{one=one, two=two, three=false, four=4}";
    assertThat(x.toString()).isEqualTo(expectedString);
  }

  @AutoValue
  abstract static class OuterWithBuilder {
    abstract String foo();

    abstract InnerWithBuilder inner();

    abstract Builder toBuilder();

    static Builder builder() {
      return new AutoValue_AutoValueTest_OuterWithBuilder.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder {
      abstract Builder foo(String x);

      abstract Builder inner(InnerWithBuilder x);

      abstract InnerWithBuilder.Builder innerBuilder();

      abstract OuterWithBuilder build();
    }
  }

  @AutoValue
  abstract static class InnerWithBuilder {
    abstract int bar();

    abstract Builder toBuilder();

    static Builder builder() {
      return new AutoValue_AutoValueTest_InnerWithBuilder.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder {
      abstract Builder setBar(int x);

      abstract InnerWithBuilder build();
    }
  }

  @Test
  public void testBuilderWithinBuilder() {
    OuterWithBuilder x =
        OuterWithBuilder.builder()
            .inner(InnerWithBuilder.builder().setBar(23).build())
            .foo("yes")
            .build();
    String expectedStringX =
        omitIdentifiers
            ? "{yes, {23}}"
            : "OuterWithBuilder{foo=yes, inner=InnerWithBuilder{bar=23}}";
    assertThat(x.toString()).isEqualTo(expectedStringX);

    OuterWithBuilder.Builder xBuilder = x.toBuilder();
    xBuilder.innerBuilder().setBar(17);
    OuterWithBuilder y = xBuilder.build();
    String expectedStringY =
        omitIdentifiers
            ? "{yes, {17}}"
            : "OuterWithBuilder{foo=yes, inner=InnerWithBuilder{bar=17}}";
    assertThat(y.toString()).isEqualTo(expectedStringY);
  }

  public static class MyMap<K, V> extends HashMap<K, V> {
    private static final long serialVersionUID = 1L;

    public MyMap() {}

    public MyMap(Map<K, V> map) {
      super(map);
    }
  }

  public static class MyMapBuilder<K, V> extends LinkedHashMap<K, V> {
    private static final long serialVersionUID = 1L;

    public MyMapBuilder() {}

    public MyMapBuilder(Map<K, V> map) {
      super(map);
    }

    public MyMap<K, V> build() {
      return new MyMap<K, V>(this);
    }
  }

  @AutoValue
  abstract static class BuildMyMap<K, V> {
    abstract MyMap<K, V> map();

    abstract Builder<K, V> toBuilder();

    static <K, V> Builder<K, V> builder() {
      return new AutoValue_AutoValueTest_BuildMyMap.Builder<K, V>();
    }

    @AutoValue.Builder
    abstract static class Builder<K, V> {
      abstract MyMapBuilder<K, V> mapBuilder();

      abstract BuildMyMap<K, V> build();
    }
  }

  @Test
  public void testMyMapBuilder() {
    BuildMyMap.Builder<String, Integer> builder = BuildMyMap.builder();
    MyMapBuilder<String, Integer> mapBuilder = builder.mapBuilder();
    mapBuilder.put("23", 23);
    BuildMyMap<String, Integer> built = builder.build();
    assertThat(built.map()).containsExactly("23", 23);

    BuildMyMap.Builder<String, Integer> builder2 = built.toBuilder();
    MyMapBuilder<String, Integer> mapBuilder2 = builder2.mapBuilder();
    mapBuilder2.put("17", 17);
    BuildMyMap<String, Integer> built2 = builder2.build();
    assertThat(built2.map()).containsExactly("23", 23, "17", 17);
  }

  public static class MyStringMap<V> extends MyMap<String, V> {
    private static final long serialVersionUID = 1L;

    public MyStringMap() {}

    public MyStringMap(Map<String, V> map) {
      super(map);
    }

    public MyStringMapBuilder<V> toBuilder() {
      return new MyStringMapBuilder<V>(this);
    }
  }

  public static class MyStringMapBuilder<V> extends MyMapBuilder<String, V> {
    private static final long serialVersionUID = 1L;

    public MyStringMapBuilder() {}

    public MyStringMapBuilder(Map<String, V> map) {
      super(map);
    }

    @Override
    public MyStringMap<V> build() {
      return new MyStringMap<V>(this);
    }
  }

  @AutoValue
  abstract static class BuildMyStringMap<V> {
    abstract MyStringMap<V> map();

    abstract Builder<V> toBuilder();

    static <V> Builder<V> builder() {
      return new AutoValue_AutoValueTest_BuildMyStringMap.Builder<V>();
    }

    @AutoValue.Builder
    abstract static class Builder<V> {
      abstract MyStringMapBuilder<V> mapBuilder();

      abstract BuildMyStringMap<V> build();
    }
  }

  @Test
  public void testMyStringMapBuilder() {
    BuildMyStringMap.Builder<Integer> builder = BuildMyStringMap.builder();
    MyStringMapBuilder<Integer> mapBuilder = builder.mapBuilder();
    mapBuilder.put("23", 23);
    BuildMyStringMap<Integer> built = builder.build();
    assertThat(built.map()).containsExactly("23", 23);

    BuildMyStringMap.Builder<Integer> builder2 = built.toBuilder();
    MyStringMapBuilder<Integer> mapBuilder2 = builder2.mapBuilder();
    mapBuilder2.put("17", 17);
    BuildMyStringMap<Integer> built2 = builder2.build();
    assertThat(built2.map()).containsExactly("17", 17, "23", 23);
  }

  @AutoValue
  abstract static class BuilderOfManyAccessLevels {
    public abstract int publicGetterProtectedBuilderGetterPackageProtectedSetterInt();

    protected abstract int protectedGetterPackageProtectedBuilderGetterPublicSetterInt();

    abstract int packageProtectedGetterPublicBuilderGetterProtectedSetterInt();

    @AutoValue.Builder
    public abstract static class Builder {
      protected abstract int publicGetterProtectedBuilderGetterPackageProtectedSetterInt();

      abstract int protectedGetterPackageProtectedBuilderGetterPublicSetterInt();

      public abstract int packageProtectedGetterPublicBuilderGetterProtectedSetterInt();

      abstract Builder setPublicGetterProtectedBuilderGetterPackageProtectedSetterInt(int x);

      public abstract Builder setProtectedGetterPackageProtectedBuilderGetterPublicSetterInt(int x);

      protected abstract Builder setPackageProtectedGetterPublicBuilderGetterProtectedSetterInt(
          int x);

      public abstract BuilderOfManyAccessLevels build();
    }
  }

  @Test
  public void testBuilderOfManyAccessLevels_accessLevels() throws NoSuchMethodException {
    Class<?> builderClass = AutoValue_AutoValueTest_BuilderOfManyAccessLevels.Builder.class;

    testMethodAccess(
        Access.PROTECTED,
        builderClass,
        "publicGetterProtectedBuilderGetterPackageProtectedSetterInt");
    testMethodAccess(
        Access.PACKAGE,
        builderClass,
        "protectedGetterPackageProtectedBuilderGetterPublicSetterInt");
    testMethodAccess(
        Access.PUBLIC, builderClass, "packageProtectedGetterPublicBuilderGetterProtectedSetterInt");

    testMethodAccess(
        Access.PACKAGE,
        builderClass,
        "setPublicGetterProtectedBuilderGetterPackageProtectedSetterInt",
        int.class);
    testMethodAccess(
        Access.PUBLIC,
        builderClass,
        "setProtectedGetterPackageProtectedBuilderGetterPublicSetterInt",
        int.class);
    testMethodAccess(
        Access.PROTECTED,
        builderClass,
        "setPackageProtectedGetterPublicBuilderGetterProtectedSetterInt",
        int.class);
  }

  private enum Access {
    PRIVATE,
    PACKAGE,
    PROTECTED,
    PUBLIC
  }

  private static final ImmutableMap<Integer, Access> MODIFIER_BITS_TO_ACCESS =
      ImmutableMap.of(
          Modifier.PUBLIC,
          Access.PUBLIC,
          Modifier.PROTECTED,
          Access.PROTECTED,
          Modifier.PRIVATE,
          Access.PRIVATE,
          0,
          Access.PACKAGE);

  private static void testMethodAccess(
      Access expectedAccess, Class<?> clazz, String methodName, Class<?>... parameterTypes)
      throws NoSuchMethodException {
    Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
    int modBits = method.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE);
    Access actualAccess = MODIFIER_BITS_TO_ACCESS.get(modBits);
    assertWithMessage("Wrong access for %s", methodName)
        .that(actualAccess)
        .isEqualTo(expectedAccess);
  }

  static class VersionId {}

  static class ItemVersionId extends VersionId {}

  interface VersionedPersistent {
    VersionId getVersionId();
  }

  interface Item extends VersionedPersistent {
    @Override
    ItemVersionId getVersionId();
  }

  @AutoValue
  abstract static class FakeItem implements Item {
    static Builder builder() {
      return new AutoValue_AutoValueTest_FakeItem.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder {
      abstract Builder setVersionId(ItemVersionId x);

      abstract FakeItem build();
    }
  }

  @Test
  public void testParentInterfaceOverridesGrandparent() {
    ItemVersionId version = new ItemVersionId();
    FakeItem fakeItem = FakeItem.builder().setVersionId(version).build();
    assertThat(fakeItem.getVersionId()).isSameInstanceAs(version);
  }

  /** Fake ApkVersionCode class. */
  public static class ApkVersionCode {}

  /**
   * Illustrates a potential problem that showed up while generalizing builders. If our imports are
   * not accurate we may end up importing ImmutableList.Builder, which won't work because the
   * generated Builder subclass of ReleaseInfoBuilder will supersede it. Normally we wouldn't import
   * ImmutableList.Builder because the nested Builder class in the {@code @AutoValue} class would
   * prevent us trying. But in this case the nested class is called ReleaseInfoBuilder so we might
   * import anyway if we're not careful. This is one reason why we moved away from importing nested
   * classes to only importing top-level classes.
   */
  @AutoValue
  public abstract static class ReleaseInfo {
    public static ReleaseInfoBuilder newBuilder() {
      return new AutoValue_AutoValueTest_ReleaseInfo.Builder();
    }

    public abstract ImmutableList<ApkVersionCode> apkVersionCodes();

    ReleaseInfo() {}

    /** Notice that this is called ReleaseInfoBuilder and not Builder. */
    @AutoValue.Builder
    public abstract static class ReleaseInfoBuilder {
      public ReleaseInfoBuilder addApkVersionCode(ApkVersionCode code) {
        apkVersionCodesBuilder().add(code);
        return this;
      }

      abstract ImmutableList.Builder<ApkVersionCode> apkVersionCodesBuilder();

      public abstract ReleaseInfo build();
    }
  }

  @Test
  public void testUnusualBuilderName() {
    ApkVersionCode apkVersionCode = new ApkVersionCode();
    ReleaseInfo x = ReleaseInfo.newBuilder().addApkVersionCode(apkVersionCode).build();
    assertThat(x.apkVersionCodes()).containsExactly(apkVersionCode);
  }

  @AutoValue
  public abstract static class OuterWithDefaultableInner {
    public abstract ImmutableList<String> names();

    public abstract DefaultableInner inner();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_OuterWithDefaultableInner.Builder();
    }

    @AutoValue.Builder
    public abstract static class Builder {
      public abstract ImmutableList<String> names();

      public abstract ImmutableList.Builder<String> namesBuilder();

      public abstract DefaultableInner inner();

      public abstract DefaultableInner.Builder innerBuilder();

      public abstract OuterWithDefaultableInner build();
    }
  }

  @AutoValue
  public abstract static class DefaultableInner {
    public abstract int bar();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_DefaultableInner.Builder().setBar(23);
    }

    @AutoValue.Builder
    public abstract static class Builder {
      public abstract Builder setBar(int x);

      public abstract DefaultableInner build();
    }
  }

  @Test
  public void testOuterWithDefaultableInner_Defaults() {
    DefaultableInner defaultInner = DefaultableInner.builder().build();
    OuterWithDefaultableInner x = OuterWithDefaultableInner.builder().build();
    assertThat(x.names()).isEmpty();
    assertThat(x.inner()).isEqualTo(defaultInner);
  }

  @Test
  public void testOuterWithDefaultableInner_Getters() {
    DefaultableInner defaultInner = DefaultableInner.builder().build();

    OuterWithDefaultableInner.Builder builder = OuterWithDefaultableInner.builder();
    assertThat(builder.names()).isEmpty();
    assertThat(builder.inner()).isEqualTo(defaultInner);

    OuterWithDefaultableInner x1 = builder.build();
    assertThat(x1.names()).isEmpty();
    assertThat(x1.inner()).isEqualTo(defaultInner);

    builder.namesBuilder().add("Fred");
    builder.innerBuilder().setBar(17);
    OuterWithDefaultableInner x2 = builder.build();
    assertThat(x2.names()).containsExactly("Fred");
    assertThat(x2.inner().bar()).isEqualTo(17);
  }

  @AutoValue
  public abstract static class OuterWithNonDefaultableInner<T> {
    public abstract int foo();

    public abstract NonDefaultableInner<T> inner();

    public static <T> Builder<T> builder() {
      return new AutoValue_AutoValueTest_OuterWithNonDefaultableInner.Builder<T>();
    }

    @AutoValue.Builder
    public abstract static class Builder<T> {
      public abstract Builder<T> setFoo(int x);

      public abstract NonDefaultableInner.Builder<T> innerBuilder();

      public abstract OuterWithNonDefaultableInner<T> build();
    }
  }

  @AutoValue
  public abstract static class NonDefaultableInner<E> {
    public abstract E bar();

    public static <E> Builder<E> builder() {
      return new AutoValue_AutoValueTest_NonDefaultableInner.Builder<E>();
    }

    @AutoValue.Builder
    public abstract static class Builder<E> {
      public abstract Builder<E> setBar(E x);

      public abstract NonDefaultableInner<E> build();
    }
  }

  @Test
  public void testOuterWithNonDefaultableInner() {
    OuterWithNonDefaultableInner.Builder<String> builder = OuterWithNonDefaultableInner.builder();
    builder.setFoo(23);
    try {
      builder.build();
      fail("Did not get expected exception for unbuilt inner instance");
    } catch (IllegalStateException expected) {
    }
  }

  @SuppressWarnings("JavaLangClash")
  @AutoValue
  public abstract static class RedeclareJavaLangClasses {
    // If you really really want to do this, we have you covered.

    public static class Object {}

    public static class String {}

    public abstract Object alienObject();

    public abstract String alienString();

    public static Builder builder() {
      return new AutoValue_AutoValueTest_RedeclareJavaLangClasses.Builder();
    }

    @AutoValue.Builder
    public abstract static class Builder {
      public abstract Builder setAlienObject(Object x);

      public abstract Builder setAlienString(String x);

      public abstract RedeclareJavaLangClasses build();
    }
  }

  @Test
  public void testRedeclareJavaLangClasses() {
    RedeclareJavaLangClasses x =
        RedeclareJavaLangClasses.builder()
            .setAlienObject(new RedeclareJavaLangClasses.Object())
            .setAlienString(new RedeclareJavaLangClasses.String())
            .build();
    assertThat(x).isNotNull();
  }

  // b/28382293
  @AutoValue
  abstract static class GenericExtends {
    abstract ImmutableSet<Number> metrics();

    static Builder builder() {
      return new AutoValue_AutoValueTest_GenericExtends.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder {
      abstract Builder setMetrics(ImmutableSet<? extends Number> metrics);
      abstract GenericExtends build();
    }
  }

  @Test
  public void testGenericExtends() {
    ImmutableSet<Integer> ints = ImmutableSet.of(1, 2, 3);
    GenericExtends g = GenericExtends.builder().setMetrics(ints).build();
    assertThat(g.metrics()).isEqualTo(ints);
  }

  abstract static class Parent<T> {
    abstract List<T> getList();
  }

  @AutoValue
  abstract static class Child extends Parent<String> {
    static Builder builder() {
      return new AutoValue_AutoValueTest_Child.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder {
      abstract Builder setList(List<String> list);
      abstract Child build();
    }
  }

  @Test
  public void nonGenericExtendsGeneric() {
    List<String> list = ImmutableList.of("foo", "bar", "baz");
    Child child = Child.builder().setList(list).build();
    assertThat(child.getList()).containsExactlyElementsIn(list).inOrder();
  }

  abstract static class AbstractGenericParentWithBuilder<T> {
    abstract T foo();

    abstract static class Builder<T, B extends Builder<T, B>> {
      abstract B foo(T s);
    }
  }

  @AutoValue
  abstract static class ChildOfAbstractGenericParentWithBuilder<T>
      extends AbstractGenericParentWithBuilder<T> {
    static <T> Builder<T> builder() {
      return new AutoValue_AutoValueTest_ChildOfAbstractGenericParentWithBuilder.Builder<T>();
    }

    @AutoValue.Builder
    abstract static class Builder<T>
        extends AbstractGenericParentWithBuilder.Builder<T, Builder<T>> {
      abstract ChildOfAbstractGenericParentWithBuilder<T> build();
    }
  }

  @Test
  public void genericExtendsGeneric() {
    ChildOfAbstractGenericParentWithBuilder<String> child =
        ChildOfAbstractGenericParentWithBuilder.<String>builder().foo("foo").build();
    assertThat(child.foo()).isEqualTo("foo");
  }

  @SuppressWarnings("ClassCanBeStatic")
  static class OuterWithTypeParam<T extends Number> {
    class InnerWithTypeParam<U> {}
    class InnerWithoutTypeParam {}
    static class Nested {}
  }

  @AutoValue
  abstract static class Nesty {
    abstract OuterWithTypeParam<Double>.InnerWithTypeParam<String> innerWithTypeParam();
    abstract OuterWithTypeParam<Double>.InnerWithoutTypeParam innerWithoutTypeParam();
    abstract OuterWithTypeParam.Nested nested();

    static Builder builder() {
      return new AutoValue_AutoValueTest_Nesty.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder {
      abstract Builder setInnerWithTypeParam(
          OuterWithTypeParam<Double>.InnerWithTypeParam<String> x);
      abstract Builder setInnerWithoutTypeParam(OuterWithTypeParam<Double>.InnerWithoutTypeParam x);
      abstract Builder setNested(OuterWithTypeParam.Nested x);
      abstract Nesty build();
    }
  }

  @Test
  public void outerWithTypeParam() throws ReflectiveOperationException {
    @SuppressWarnings("UseDiamond") // Currently we compile this with -source 6 in the Eclipse test.
    OuterWithTypeParam<Double> outer = new OuterWithTypeParam<Double>();
    Nesty nesty = Nesty.builder()
        .setInnerWithTypeParam(outer.new InnerWithTypeParam<String>())
        .setInnerWithoutTypeParam(outer.new InnerWithoutTypeParam())
        .setNested(new OuterWithTypeParam.Nested())
        .build();
    Type originalReturnType =
        Nesty.class.getDeclaredMethod("innerWithTypeParam").getGenericReturnType();
    Type generatedReturnType =
        nesty.getClass().getDeclaredMethod("innerWithTypeParam").getGenericReturnType();
    assertThat(generatedReturnType).isEqualTo(originalReturnType);
    Type generatedBuilderParamType =
        Nesty.builder()
            .getClass()
            .getDeclaredMethod("setInnerWithTypeParam", OuterWithTypeParam.InnerWithTypeParam.class)
            .getGenericParameterTypes()[0];
    assertThat(generatedBuilderParamType).isEqualTo(originalReturnType);
  }

  @AutoValue
  abstract static class BuilderAnnotationsNotCopied {
    abstract String foo();

    static Builder builder() {
      return new AutoValue_AutoValueTest_BuilderAnnotationsNotCopied.Builder();
    }

    @AutoValue.Builder
    @MyAnnotation("thing")
    abstract static class Builder {
      abstract Builder setFoo(String x);
      abstract BuilderAnnotationsNotCopied build();
    }
  }

  @Test
  public void builderAnnotationsNotCopiedByDefault() {
    BuilderAnnotationsNotCopied.Builder builder = BuilderAnnotationsNotCopied.builder();
    assertThat(builder.getClass().getAnnotations()).isEmpty();
    assertThat(builder.setFoo("foo").build().foo()).isEqualTo("foo");
  }

  @AutoValue
  abstract static class BuilderAnnotationsCopied {
    abstract String foo();

    static Builder builder() {
      return new AutoValue_AutoValueTest_BuilderAnnotationsCopied.Builder();
    }

    @AutoValue.Builder
    @AutoValue.CopyAnnotations
    @MyAnnotation("thing")
    abstract static class Builder {
      abstract Builder setFoo(String x);
      abstract BuilderAnnotationsCopied build();
    }
  }

  @Test
  public void builderAnnotationsCopiedIfRequested() {
    BuilderAnnotationsCopied.Builder builder = BuilderAnnotationsCopied.builder();
    assertThat(builder.getClass().getAnnotations()).asList().containsExactly(myAnnotation("thing"));
    assertThat(builder.setFoo("foo").build().foo()).isEqualTo("foo");
  }

  @AutoValue
  @AutoValue.CopyAnnotations
  @SuppressWarnings({"rawtypes", "unchecked"}) // deliberately checking handling of raw types
  abstract static class DataWithSortedCollectionBuilders<K, V> {
    abstract ImmutableSortedMap<K, V> anImmutableSortedMap();
    abstract ImmutableSortedSet<V> anImmutableSortedSet();
    abstract ImmutableSortedMap<Integer, V> nonGenericImmutableSortedMap();
    abstract ImmutableSortedSet rawImmutableSortedSet();
    abstract DataWithSortedCollectionBuilders.Builder<K, V> toBuilder();

    static <K, V> DataWithSortedCollectionBuilders.Builder<K, V> builder() {
      return new AutoValue_AutoValueTest_DataWithSortedCollectionBuilders.Builder<K, V>();
    }

    @AutoValue.Builder
    abstract static class Builder<K, V> {
      abstract DataWithSortedCollectionBuilders.Builder<K, V> anImmutableSortedMap(
          SortedMap<K, V> anImmutableSortedMap);
      abstract ImmutableSortedMap.Builder<K, V> anImmutableSortedMapBuilder(
          Comparator<K> keyComparator);
      abstract DataWithSortedCollectionBuilders.Builder<K, V> anImmutableSortedSet(
          SortedSet<V> anImmutableSortedSet);
      abstract ImmutableSortedSet.Builder<V> anImmutableSortedSetBuilder(Comparator<V> comparator);
      abstract ImmutableSortedMap.Builder<Integer, V> nonGenericImmutableSortedMapBuilder(
          Comparator<Integer> keyComparator);
      abstract ImmutableSortedSet.Builder rawImmutableSortedSetBuilder(Comparator comparator);
      abstract DataWithSortedCollectionBuilders<K, V> build();
    }
  }

  @Test
  @SuppressWarnings({"rawtypes", "unchecked"}) // deliberately checking handling of raw types
  public void shouldGenerateBuildersWithComparators() {
    Comparator<String> stringComparator = new Comparator<String>() {
      @Override
      public int compare(String left, String right) {
        return left.compareTo(right);
      }
    };

    Comparator<Integer> intComparator = new Comparator<Integer>() {
      @Override
      public int compare(Integer o1, Integer o2) {
        return o1 - o2;
      }
    };

    Comparator comparator = new Comparator() {
      @Override
      public int compare(Object left, Object right) {
        return String.valueOf(left).compareTo(String.valueOf(right));
      }
    };

    AutoValueTest.DataWithSortedCollectionBuilders.Builder<String, Integer> builder =
        AutoValueTest.DataWithSortedCollectionBuilders.builder();

    builder.anImmutableSortedMapBuilder(stringComparator)
        .put("Charlie", 1).put("Alfa", 2).put("Bravo", 3);
    builder.anImmutableSortedSetBuilder(intComparator)
        .add(1,5,9,3);
    builder.nonGenericImmutableSortedMapBuilder(intComparator)
        .put(9, 99).put(1, 11).put(3, 33);
    builder.rawImmutableSortedSetBuilder(comparator)
        .add("Bravo", "Charlie", "Alfa");

    AutoValueTest.DataWithSortedCollectionBuilders<String, Integer> data = builder.build();

    AutoValueTest.DataWithSortedCollectionBuilders.Builder<String, Integer> copiedBuilder =
        data.toBuilder();
    AutoValueTest.DataWithSortedCollectionBuilders<String, Integer> copiedData =
        copiedBuilder.build();

    assertThat(data.anImmutableSortedMap().keySet())
        .containsExactly("Alfa", "Bravo", "Charlie")
        .inOrder();
    assertThat(data.anImmutableSortedSet()).containsExactly(1, 3, 5, 9).inOrder();
    assertThat(data.nonGenericImmutableSortedMap().keySet()).containsExactly(1, 3, 9).inOrder();
    assertThat(data.rawImmutableSortedSet()).containsExactly("Alfa", "Bravo", "Charlie").inOrder();

    assertThat(copiedData).isEqualTo(data);

    try {
      builder.anImmutableSortedMapBuilder(Ordering.from(stringComparator).reverse());
      fail("Calling property builder method a second time should have failed");
    } catch (IllegalStateException expected) {
    }
  }
}
