aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--common/pom.xml4
-rw-r--r--factory/pom.xml4
-rw-r--r--factory/src/it/functional/pom.xml6
-rw-r--r--service/pom.xml2
-rw-r--r--value/CHANGES.md42
-rw-r--r--value/pom.xml2
-rw-r--r--value/src/it/functional-java8/invoker.properties6
-rw-r--r--value/src/it/functional-java8/pom.xml92
-rw-r--r--value/src/it/functional-java8/src/main/java/PackagelessNestedValueType.java33
-rw-r--r--value/src/it/functional-java8/src/main/java/PackagelessValueType.java58
-rw-r--r--value/src/it/functional-java8/src/main/java/com/google/auto/value/NestedValueType.java17
-rw-r--r--value/src/it/functional-java8/src/main/java/com/google/auto/value/SimpleValueType.java57
-rw-r--r--value/src/it/functional-java8/src/main/java/com/google/auto/value/annotation/Nullable.java16
-rw-r--r--value/src/it/functional-java8/src/test/java/PackagelessValueTypeTest.java59
-rw-r--r--value/src/it/functional-java8/src/test/java/com/google/auto/value/AutoValueTest.java1760
-rw-r--r--value/src/it/functional-java8/src/test/java/com/google/auto/value/SimpleValueTypeTest.java59
-rw-r--r--value/src/it/functional-java8/src/test/java/com/google/auto/value/enums/MyEnum.java23
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java146
-rw-r--r--value/src/it/gwtserializer/pom.xml4
-rw-r--r--value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java120
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java5
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java349
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java17
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java139
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderSpec.java62
-rw-r--r--value/src/main/java/com/google/auto/value/processor/ErrorReporter.java10
-rw-r--r--value/src/main/java/com/google/auto/value/processor/ExtensionContext.java14
-rw-r--r--value/src/main/java/com/google/auto/value/processor/Java8Support.java51
-rw-r--r--value/src/main/java/com/google/auto/value/processor/Optionalish.java116
-rw-r--r--value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java14
-rw-r--r--value/src/main/java/com/google/auto/value/processor/autovalue.vm28
-rw-r--r--value/src/main/java/com/google/auto/value/processor/escapevelocity/README.md339
-rw-r--r--value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java36
-rw-r--r--value/src/test/java/com/google/auto/value/processor/CompilationErrorsTest.java2
-rw-r--r--value/src/test/java/com/google/auto/value/processor/CompilationTest.java94
-rw-r--r--value/src/test/java/com/google/auto/value/processor/ExtensionTest.java394
-rw-r--r--value/userguide/builders-howto.md83
-rw-r--r--value/userguide/howto.md6
39 files changed, 4033 insertions, 237 deletions
diff --git a/.travis.yml b/.travis.yml
index 4ff93c01..7b02cf80 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,6 +11,7 @@ script:
jdk:
- oraclejdk7
- openjdk7
+ - oraclejdk8
env:
global:
diff --git a/common/pom.xml b/common/pom.xml
index 2bed6e01..e442dd57 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -42,13 +42,13 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
- <version>18.0</version>
+ <version>19.0</version>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
- <version>18.0</version>
+ <version>19.0</version>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/factory/pom.xml b/factory/pom.xml
index 484e83c0..446483a3 100644
--- a/factory/pom.xml
+++ b/factory/pom.xml
@@ -60,12 +60,12 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
- <version>18.0</version>
+ <version>19.0</version>
</dependency>
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
- <version>1.5.1</version>
+ <version>1.6.1</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
diff --git a/factory/src/it/functional/pom.xml b/factory/src/it/functional/pom.xml
index f8ffa52a..30cfce2d 100644
--- a/factory/src/it/functional/pom.xml
+++ b/factory/src/it/functional/pom.xml
@@ -42,12 +42,12 @@
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger</artifactId>
- <version>2.0</version>
+ <version>2.4</version>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger-compiler</artifactId>
- <version>2.0</version>
+ <version>2.4</version>
<optional>true</optional>
</dependency>
<dependency>
@@ -65,7 +65,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
- <version>18.0</version>
+ <version>19.0</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/service/pom.xml b/service/pom.xml
index f581fb3f..8a730767 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -48,7 +48,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
- <version>18.0</version>
+ <version>19.0</version>
</dependency>
<!-- test dependencies -->
<dependency>
diff --git a/value/CHANGES.md b/value/CHANGES.md
index 61d3af8f..35e6a785 100644
--- a/value/CHANGES.md
+++ b/value/CHANGES.md
@@ -1,4 +1,4 @@
-# Auto-Value Changes
+# AutoValue Changes
## 1.1 → 1.2
@@ -9,13 +9,13 @@
[AutoValueExtension] class.
* Properties of primitive array type (e.g. `byte[]`) are no longer cloned
- when read. If your @AutoValue class includes an array property, by default
+ when read. If your `@AutoValue` class includes an array property, by default
it will get a compiler warning, which can be suppressed with
`@SuppressWarnings("mutable")`.
- * An AutoValue.Builder type can now define both the setter and builder
+ * An `@AutoValue.Builder` type can now define both the setter and builder
methods like so:
-
+
```
...
abstract void setStrings(ImmutableList<String>);
@@ -34,7 +34,7 @@
### Bugs fixed
- * Explicit check for nested @AutoValue classes being private, or not being
+ * Explicit check for nested `@AutoValue` classes being private, or not being
static. Otherwise the compiler errors could be hard to understand,
especially in IDEs.
@@ -46,8 +46,8 @@
that parameter. For example `StringIterator implements Iterator<String>`,
where the type of `next()` is String, not `T`.
- * In AutoValueProcessor, fixed an exception that happened if the same abstract
- method was inherited from more than one parent (Github Issue #267).
+ * In `AutoValueProcessor`, fixed an exception that happened if the same
+ abstract method was inherited from more than one parent (Github Issue #267).
* AutoValue now works correctly in an environment where
`@javax.annotation.Generated` does not exist.
@@ -55,5 +55,33 @@
* Properties marked `@Nullable` now get `@Nullable` on the corresponding
constructor parameters in the generated class.
+## 1.0 → 1.1
+
+### Functional changes
+
+ * Adds builders to AutoValue. Builders are nested classes annotated with
+ `@AutoValue.Builder`.
+
+ * Annotates constructor parameters with `@Nullable` if the corresponding
+ property methods are `@Nullable`.
+
+ * Changes Maven shading so org.apache.commons is shaded.
+
+ * Copies a `@GwtCompatible` annotation from the `@AutoValue` class to its
+ implementation subclass.
+
+### Bugs fixed
+
+ * Works around a bug in the Eclipse compiler that meant that annotations
+ would be incorrectly copied from `@AutoValue` methods to their
+ implementations.
+
+## 1.0 (Initial Release)
+
+ * Allows automatic generation of value type implementations
+
+ See [the AutoValue User's Guide](userguide/index.md)
+
+
[AutoValueExtension]: src/main/java/com/google/auto/value/extension/AutoValueExtension.java
diff --git a/value/pom.xml b/value/pom.xml
index 52ae4642..cf8e5424 100644
--- a/value/pom.xml
+++ b/value/pom.xml
@@ -79,7 +79,7 @@
<dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
- <version>0.6</version>
+ <version>0.9</version>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/value/src/it/functional-java8/invoker.properties b/value/src/it/functional-java8/invoker.properties
new file mode 100644
index 00000000..fd4c6206
--- /dev/null
+++ b/value/src/it/functional-java8/invoker.properties
@@ -0,0 +1,6 @@
+invoker.goals = clean verify
+
+invoker.profiles.1 =
+invoker.profiles.2 = eclipse
+
+invoker.java.version=1.8+
diff --git a/value/src/it/functional-java8/pom.xml b/value/src/it/functional-java8/pom.xml
new file mode 100644
index 00000000..dd95af36
--- /dev/null
+++ b/value/src/it/functional-java8/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2013 Google, Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- TODO(gak): see if we can manage these dependencies any better -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.google.auto.value.it.functional</groupId>
+ <artifactId>functional-java8</artifactId>
+ <version>HEAD-SNAPSHOT</version>
+ <name>Auto-Value Functional Integration Test (Java 8)</name>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.auto.value</groupId>
+ <artifactId>auto-value</artifactId>
+ <version>@auto.version@</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.11</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ <version>0.28</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-testlib</artifactId>
+ <version>18.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <compilerArgument>-Xlint:all</compilerArgument>
+ <showWarnings>true</showWarnings>
+ <showDeprecation>true</showDeprecation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>eclipse</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-Acom.google.auto.value.EclipseHackTest=1</arg>
+ </compilerArgs>
+ <showWarnings>true</showWarnings>
+ <showDeprecation>true</showDeprecation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/value/src/it/functional-java8/src/main/java/PackagelessNestedValueType.java b/value/src/it/functional-java8/src/main/java/PackagelessNestedValueType.java
new file mode 100644
index 00000000..135416e1
--- /dev/null
+++ b/value/src/it/functional-java8/src/main/java/PackagelessNestedValueType.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Map;
+
+/**
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+public class PackagelessNestedValueType {
+ @AutoValue
+ public abstract static class Nested {
+ abstract Map<Integer, String> numberNames();
+
+ public static Nested create(Map<Integer, String> numberNames) {
+ return new AutoValue_PackagelessNestedValueType_Nested(numberNames);
+ }
+ }
+}
diff --git a/value/src/it/functional-java8/src/main/java/PackagelessValueType.java b/value/src/it/functional-java8/src/main/java/PackagelessValueType.java
new file mode 100644
index 00000000..9b77ed1d
--- /dev/null
+++ b/value/src/it/functional-java8/src/main/java/PackagelessValueType.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Map;
+
+import com.google.auto.value.annotation.Nullable;
+
+/**
+ * Simple package-less value type for tests.
+ *
+ * @see PackagelessValueTypeTest
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@AutoValue
+public abstract class PackagelessValueType {
+ // The getters here are formatted as an illustration of what getters typically look in real
+ // classes. In particular they have doc comments.
+
+ /**
+ * @return A string that is a nullable string.
+ */
+ @Nullable public abstract String string();
+
+ /**
+ * @return An integer that is an integer.
+ */
+ public abstract int integer();
+
+ /**
+ * @return A non-null map where the keys are strings and the values are longs.
+ */
+ public abstract Map<String, Long> map();
+
+ public static PackagelessValueType create(
+ @Nullable String string, int integer, Map<String, Long> map) {
+ // The subclass AutoValue_PackagelessValueType is created by the annotation processor that is
+ // triggered by the presence of the @AutoValue annotation. It has a constructor for each
+ // of the abstract getter methods here, in order. The constructor stashes the values here
+ // in private final fields, and each method is implemented to return the value of the
+ // corresponding field.
+ return new AutoValue_PackagelessValueType(string, integer, map);
+ }
+}
diff --git a/value/src/it/functional-java8/src/main/java/com/google/auto/value/NestedValueType.java b/value/src/it/functional-java8/src/main/java/com/google/auto/value/NestedValueType.java
new file mode 100644
index 00000000..9c83e312
--- /dev/null
+++ b/value/src/it/functional-java8/src/main/java/com/google/auto/value/NestedValueType.java
@@ -0,0 +1,17 @@
+package com.google.auto.value;
+
+import java.util.Map;
+
+/**
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+public class NestedValueType {
+ @AutoValue
+ public abstract static class Nested {
+ abstract Map<Integer, String> numberNames();
+
+ public static Nested create(Map<Integer, String> numberNames) {
+ return new AutoValue_NestedValueType_Nested(numberNames);
+ }
+ }
+} \ No newline at end of file
diff --git a/value/src/it/functional-java8/src/main/java/com/google/auto/value/SimpleValueType.java b/value/src/it/functional-java8/src/main/java/com/google/auto/value/SimpleValueType.java
new file mode 100644
index 00000000..0ad4a99c
--- /dev/null
+++ b/value/src/it/functional-java8/src/main/java/com/google/auto/value/SimpleValueType.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value;
+
+import java.util.Map;
+
+import com.google.auto.value.annotation.Nullable;
+
+/**
+ * Simple value type for tests.
+ *
+ * @see SimpleValueTypeTest
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@AutoValue
+public abstract class SimpleValueType {
+ // The getters here are formatted as an illustration of what getters typically look in real
+ // classes. In particular they have doc comments.
+
+ /**
+ * @return A string that is a nullable string.
+ */
+ @Nullable public abstract String string();
+
+ /**
+ * @return An integer that is an integer.
+ */
+ public abstract int integer();
+
+ /**
+ * @return A non-null map where the keys are strings and the values are longs.
+ */
+ public abstract Map<String, Long> map();
+
+ public static SimpleValueType create(
+ @Nullable String string, int integer, Map<String, Long> map) {
+ // The subclass AutoValue_SimpleValueType is created by the annotation processor that is
+ // triggered by the presence of the @AutoValue annotation. It has a constructor for each
+ // of the abstract getter methods here, in order. The constructor stashes the values here
+ // in private final fields, and each method is implemented to return the value of the
+ // corresponding field.
+ return new AutoValue_SimpleValueType(string, integer, map);
+ }
+} \ No newline at end of file
diff --git a/value/src/it/functional-java8/src/main/java/com/google/auto/value/annotation/Nullable.java b/value/src/it/functional-java8/src/main/java/com/google/auto/value/annotation/Nullable.java
new file mode 100644
index 00000000..7aaf1f89
--- /dev/null
+++ b/value/src/it/functional-java8/src/main/java/com/google/auto/value/annotation/Nullable.java
@@ -0,0 +1,16 @@
+package com.google.auto.value.annotation;
+
+/**
+ * NOTE: Testing with org.eclipse.jdt.annotation.Nullable (v2) doesn't work because it has
+ * RetentionPolicy.CLASS, but RUNTIME is needed for
+ * com.google.auto.value.AutoValueTest.testNullablePropertyConstructorParameterIsNullable()
+ */
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE_USE)
+public @interface Nullable {
+}
diff --git a/value/src/it/functional-java8/src/test/java/PackagelessValueTypeTest.java b/value/src/it/functional-java8/src/test/java/PackagelessValueTypeTest.java
new file mode 100644
index 00000000..743b891d
--- /dev/null
+++ b/value/src/it/functional-java8/src/test/java/PackagelessValueTypeTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.NullPointerTester;
+
+import junit.framework.TestCase;
+
+import java.util.Map;
+
+import org.junit.Ignore;
+
+/**
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+public class PackagelessValueTypeTest extends TestCase {
+ public void testPackagelessValueType() {
+ final String happy = "happy";
+ final int testInt = 23;
+ final Map<String, Long> testMap = ImmutableMap.of("happy", 23L);
+ PackagelessValueType simple = PackagelessValueType.create(happy, testInt, testMap);
+ assertSame(happy, simple.string());
+ assertEquals(testInt, simple.integer());
+ assertSame(testMap, simple.map());
+ assertEquals("PackagelessValueType{string=happy, integer=23, map={happy=23}}",
+ simple.toString());
+ int expectedHashCode = 1;
+ expectedHashCode = (expectedHashCode * 1000003) ^ happy.hashCode();
+ expectedHashCode = (expectedHashCode * 1000003) ^ ((Object) testInt).hashCode();
+ expectedHashCode = (expectedHashCode * 1000003) ^ testMap.hashCode();
+ assertEquals(expectedHashCode, simple.hashCode());
+ }
+
+ public void testNestedValueType() {
+ ImmutableMap<Integer, String> numberNames = ImmutableMap.of(1, "un", 2, "deux");
+ PackagelessNestedValueType.Nested nested =
+ PackagelessNestedValueType.Nested.create(numberNames);
+ assertEquals(numberNames, nested.numberNames());
+ }
+
+ @Ignore("NullPointerTester doesn't support type annotations yet")
+ public void ignoredTestNull() {
+ NullPointerTester tester = new NullPointerTester();
+ tester.testAllPublicStaticMethods(PackagelessValueType.class);
+ }
+}
diff --git a/value/src/it/functional-java8/src/test/java/com/google/auto/value/AutoValueTest.java b/value/src/it/functional-java8/src/test/java/com/google/auto/value/AutoValueTest.java
new file mode 100644
index 00000000..fbe0ec22
--- /dev/null
+++ b/value/src/it/functional-java8/src/test/java/com/google/auto/value/AutoValueTest.java
@@ -0,0 +1,1760 @@
+/*
+ * Copyright (C) 2012 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 com.google.common.base.Objects;
+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.ImmutableTable;
+import com.google.common.testing.EqualsTester;
+import com.google.common.testing.SerializableTester;
+
+import junit.framework.TestCase;
+
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+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.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import com.google.auto.value.annotation.Nullable;
+
+/**
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+public class AutoValueTest extends TestCase {
+
+ // TODO(emcmanus): add tests for exotic locales
+
+ @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);
+ }
+ }
+ 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());
+ Objects.ToStringHelper toStringHelper = Objects.toStringHelper(Simple.class);
+ toStringHelper.add("publicString", "example");
+ toStringHelper.add("protectedInt", 23);
+ toStringHelper.add("packageMap", ImmutableMap.of("twenty-three", 23L));
+ assertEquals(toStringHelper.toString(), instance1a.toString());
+ new EqualsTester()
+ .addEqualityGroup(instance1a, instance1b)
+ .addEqualityGroup(instance2)
+ .testEquals();
+ }
+
+ @AutoValue
+ abstract static class Empty {
+ public static Empty create() {
+ return new AutoValue_AutoValueTest_Empty();
+ }
+ }
+
+ public void testEmpty() throws Exception {
+ Empty instance = Empty.create();
+ assertEquals("Empty{}", instance.toString());
+ 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);
+ }
+ }
+
+ public void testGetters() {
+ SimpleWithGetters instance = SimpleWithGetters.create(23, true, false, "foo", "bar", "<html>");
+ assertEquals(
+ "SimpleWithGetters{"
+ + "foo=23, bar=true, otherBar=false, package=foo, package0=bar, HTMLPage=<html>}",
+ instance.toString());
+ }
+
+ @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);
+ }
+ }
+
+ public void testNotGetters() {
+ NotAllGetters instance = NotAllGetters.create(23, true);
+ assertEquals("NotAllGetters{getFoo=23, bar=true}", instance.toString());
+ }
+
+ @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);
+ }
+ }
+
+ public void testGettersAndConcreteNonGetters() {
+ GettersAndConcreteNonGetters instance = GettersAndConcreteNonGetters.create(23, new byte[] {1});
+ assertFalse(instance.hasNoBytes());
+ assertEquals("GettersAndConcreteNonGetters{foo=23, bytes=[1]}", instance.toString());
+ }
+
+ @AutoValue
+ public abstract static class Serialize implements Serializable {
+ public abstract int integer();
+ public abstract String string();
+ public abstract BigInteger bigInteger();
+ public static Serialize create(int integer, String string, BigInteger bigInteger) {
+ return new AutoValue_AutoValueTest_Serialize(integer, string, bigInteger);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ public void testDoubleHashCode() {
+ double doubleValue = 12345678901234567890d;
+ DoubleProperty doubleProperty = DoubleProperty.create(doubleValue);
+ assertEquals(singlePropertyHash(doubleValue), doubleProperty.hashCode());
+ }
+
+ 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.
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ public void testNullablePropertiesCanBeNull() {
+ NullableProperties instance = NullableProperties.create(null, 23);
+ assertNull(instance.nullableString());
+ assertEquals(23, instance.randomInt());
+ assertEquals("NullableProperties{nullableString=null, randomInt=23}", instance.toString());
+ }
+
+ @AutoAnnotation
+ static Nullable nullable() {
+ return new AutoAnnotation_AutoValueTest_nullable();
+ }
+
+ public void testNullablePropertyConstructorParameterIsNullable() throws NoSuchMethodException {
+ Constructor<?> constructor =
+ AutoValue_AutoValueTest_NullableProperties.class.getDeclaredConstructor(
+ String.class, int.class);
+ assertThat(constructor.getAnnotatedParameterTypes()[0].getAnnotations()).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);
+ }
+ }
+
+ public void testNullableCanBeFromElsewhere() throws Exception {
+ AlternativeNullableProperties instance = AlternativeNullableProperties.create(null, 23);
+ assertNull(instance.nullableString());
+ assertEquals(23, instance.randomInt());
+ assertEquals(
+ "AlternativeNullableProperties{nullableString=null, randomInt=23}", instance.toString());
+ }
+
+ @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);
+ }
+ }
+
+ 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());
+ }
+
+ 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);
+ }
+ }
+ }
+
+ public void testDoublyNestedClass() throws Exception {
+ Nested.Doubly instance = Nested.Doubly.create(null, 23);
+ assertNull(instance.nullableString());
+ assertEquals(23, instance.randomInt());
+ assertEquals("Doubly{nullableString=null, randomInt=23}", instance.toString());
+ }
+
+ 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);
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+ public void testGenericClassWithHairyBounds() throws Exception {
+ class ComparableList<E> extends ArrayList<E> implements Comparable<ComparableList<E>> {
+ @Override public int compareTo(ComparableList<E> list) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ ComparableList<String> emptyList = new ComparableList<String>();
+ GenericClassHairyBounds<ComparableList<String>, String> instance =
+ GenericClassHairyBounds.create(emptyList, ImmutableMap.of(emptyList, "23"));
+ assertEquals(instance, instance);
+ assertEquals(emptyList, instance.key());
+ assertEquals(ImmutableMap.of(emptyList, "23"), instance.map());
+ }
+
+ interface Mergeable<M extends Mergeable<M>> {
+ M merge(M other);
+ }
+
+ @AutoValue
+ abstract static class Delta<M extends Mergeable<M>> {
+ abstract M meta();
+
+ static <M extends Mergeable<M>> Delta<M> create(M meta) {
+ return new AutoValue_AutoValueTest_Delta<M>(meta);
+ }
+ }
+
+ 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());
+ }
+
+ @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.
+ 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.
+ 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.
+ public void testAbstractToString() throws Exception {
+ AbstractToString instance = AbstractToString.create("foo");
+ assertEquals("AbstractToString{string=foo}", instance.toString());
+ }
+
+ 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.
+ public void testInheritedAbstractToString() throws Exception {
+ SubAbstractToString instance = SubAbstractToString.create("foo");
+ assertEquals("SubAbstractToString{string=foo}", instance.toString());
+ }
+
+ @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;
+ }
+ }
+
+ public void testExplicitHashCode() throws Exception {
+ ExplicitHashCode instance = ExplicitHashCode.create("foo");
+ assertEquals(1234, instance.hashCode());
+ }
+
+ @AutoValue
+ 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);
+ }
+ }
+
+ 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();
+ }
+
+ @AutoValue
+ abstract static class PrimitiveArrays {
+ @SuppressWarnings("mutable")
+ abstract boolean[] booleans();
+ @SuppressWarnings("mutable")
+ abstract int @Nullable[] ints();
+
+ static PrimitiveArrays create(boolean[] booleans, int[] ints) {
+ // Real code would likely clone these parameters, but here we want to check that the
+ // generated constructor rejects a null value for booleans.
+ return new AutoValue_AutoValueTest_PrimitiveArrays(booleans, ints);
+ }
+ }
+
+ public void testPrimitiveArrays() {
+ PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], new int[0]);
+ boolean[] booleans = {false, true, true, false};
+ int[] ints = {6, 28, 496, 8128, 33550336};
+ PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), ints.clone());
+ PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), ints.clone());
+ new EqualsTester()
+ .addEqualityGroup(object1, object2)
+ .addEqualityGroup(object0)
+ .testEquals();
+ // EqualsTester also exercises hashCode(). We clone the arrays above to ensure that using the
+ // default Object.hashCode() will fail.
+
+ String expectedString = "PrimitiveArrays{booleans=" + Arrays.toString(booleans) + ", "
+ + "ints=" + Arrays.toString(ints) + "}";
+ assertEquals(expectedString, object1.toString());
+
+ assertThat(object1.ints()).isSameAs(object1.ints());
+ }
+
+ // problem with current javac: annotations on array are not visible to AnnotationProcessor
+ public void ignoredTestNullablePrimitiveArrays() {
+ PrimitiveArrays object0 = PrimitiveArrays.create(new boolean[0], null);
+ boolean[] booleans = {false, true, true, false};
+ PrimitiveArrays object1 = PrimitiveArrays.create(booleans.clone(), null);
+ PrimitiveArrays object2 = PrimitiveArrays.create(booleans.clone(), null);
+ new EqualsTester()
+ .addEqualityGroup(object1, object2)
+ .addEqualityGroup(object0)
+ .testEquals();
+
+ String expectedString = "PrimitiveArrays{booleans=" + Arrays.toString(booleans) + ", "
+ + "ints=null}";
+ assertEquals(expectedString, object1.toString());
+
+ assertThat(object1.booleans()).isSameAs(object1.booleans());
+ assertThat(object1.booleans()).isEqualTo(booleans);
+ object1.booleans()[0] ^= true;
+ assertThat(object1.booleans()).isNotEqualTo(booleans);
+ }
+
+ 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) {
+ assertTrue(e.getMessage().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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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(actual, expected);
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+
+ public void testVisitor() {
+ LukesBase.LukesVisitor<String> visitor = new LukesBase.LukesVisitor<String>() {
+ @Override public String visit(LukesBase.LukesSub s) {
+ return s.toString();
+ }
+ };
+ LukesBase.LukesSub sub = LukesBase.LukesSub.create();
+ assertEquals(sub.toString(), sub.accept(visitor));
+ }
+
+ @AutoValue
+ public abstract static class ComplexInheritance extends AbstractBase implements A, B {
+ public static ComplexInheritance create(String name) {
+ return new AutoValue_AutoValueTest_ComplexInheritance(name);
+ }
+
+ abstract String name();
+ }
+
+ static class AbstractBase implements Base {
+ @Override
+ public int answer() {
+ return 42;
+ }
+ }
+
+ interface A extends Base {}
+ interface B extends Base {}
+
+ interface Base {
+ int answer();
+ }
+
+ 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();
+ }
+ }
+
+ public void testMoreComplexInheritance() {
+ MoreComplexInheritance instance1 = MoreComplexInheritance.create();
+ MoreComplexInheritance instance2 = MoreComplexInheritance.create();
+ assertThat(instance1).isEqualTo(instance2);
+ assertThat(instance1).isNotSameAs(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();
+ }
+ }
+
+ public void testEffectiveVisibility() {
+ EffectiveVisibility instance1 = EffectiveVisibility.create();
+ EffectiveVisibility instance2 = EffectiveVisibility.create();
+ assertThat(instance1).isEqualTo(instance2);
+ assertThat(instance1).isNotSameAs(instance2);
+ }
+
+ @AutoValue
+ public abstract static class InheritTwice implements A, B {
+ public static InheritTwice create(int answer) {
+ return new AutoValue_AutoValueTest_InheritTwice(answer);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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) {
+ assertThat(e.getMessage()).contains("foo");
+ }
+ }
+
+ @AutoValue
+ public abstract static class EmptyWithBuilder {
+ public static Builder builder() {
+ return new AutoValue_AutoValueTest_EmptyWithBuilder.Builder();
+ }
+
+ @AutoValue.Builder
+ public interface Builder {
+ EmptyWithBuilder build();
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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();
+ }
+
+ @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();
+ }
+ }
+
+ public void testOmitNullableWithBuilder() {
+ NullablePropertyWithBuilder instance1 = NullablePropertyWithBuilder.builder()
+ .notNullable("hello")
+ .build();
+ assertThat(instance1.notNullable()).isEqualTo("hello");
+ assertThat(instance1.nullable()).isNull();
+
+ NullablePropertyWithBuilder instance2 = NullablePropertyWithBuilder.builder()
+ .notNullable("hello")
+ .nullable(null)
+ .build();
+ assertThat(instance2.notNullable()).isEqualTo("hello");
+ assertThat(instance2.nullable()).isNull();
+ assertThat(instance1).isEqualTo(instance2);
+
+ NullablePropertyWithBuilder instance3 = NullablePropertyWithBuilder.builder()
+ .notNullable("hello")
+ .nullable("world")
+ .build();
+ assertThat(instance3.notNullable()).isEqualTo("hello");
+ assertThat(instance3.nullable()).isEqualTo("world");
+
+ try {
+ NullablePropertyWithBuilder.builder().build();
+ fail("Expected IllegalStateException for unset non-@Nullable property");
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage()).contains("notNullable");
+ }
+ }
+
+ @AutoValue
+ public abstract static class PropertyWithOptionalGetters {
+ public abstract String getString();
+ public abstract int getInt();
+ public abstract long getLong();
+ public abstract double getDouble();
+
+ public static Builder builder() {
+ return new AutoValue_AutoValueTest_PropertyWithOptionalGetters.Builder();
+ }
+
+ @AutoValue.Builder
+ public interface Builder {
+ Builder setString(String s);
+ java.util.Optional<String> getString();
+ Builder setInt(int x);
+ java.util.OptionalInt getInt();
+ Builder setLong(long x);
+ java.util.OptionalLong getLong();
+ Builder setDouble(double d);
+ java.util.OptionalDouble getDouble();
+ PropertyWithOptionalGetters build();
+ }
+ }
+
+ public void testOptionalGetter() {
+ PropertyWithOptionalGetters.Builder omitted =
+ PropertyWithOptionalGetters.builder();
+ assertThat(omitted.getString().isPresent()).isFalse();
+ assertThat(omitted.getInt().isPresent()).isFalse();
+ assertThat(omitted.getLong().isPresent()).isFalse();
+ assertThat(omitted.getDouble().isPresent()).isFalse();
+
+ PropertyWithOptionalGetters.Builder supplied =
+ PropertyWithOptionalGetters
+ .builder()
+ .setString("foo")
+ .setInt(23)
+ .setLong(17L)
+ .setDouble(5.0);
+ assertThat(supplied.getString().get()).isEqualTo("foo");
+ assertThat(supplied.getInt().getAsInt()).isEqualTo(23);
+ assertThat(supplied.getLong().getAsLong()).isEqualTo(17L);
+ assertThat(supplied.getDouble().getAsDouble()).isWithin(0.0).of(5.0);
+ }
+
+ @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 Builder<T, U> toBuilderManual() {
+ return new AutoValue_AutoValueTest_GenericsWithBuilder.Builder<T, U>(this);
+ }
+
+ 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();
+ }
+ }
+
+ 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.toBuilderManual().build();
+ assertEquals(instance, instance2);
+ assertNotSame(instance, instance2);
+
+ GenericsWithBuilder<Integer, Integer> instance3 = instance.toBuilderManual().u(17).build();
+ assertEquals(integers, instance3.list());
+ assertEquals((Integer) 17, instance3.u());
+
+ GenericsWithBuilder<Integer, Integer> instance4 = instance.toBuilderGenerated().build();
+ assertEquals(instance, instance4);
+ assertNotSame(instance, instance4);
+
+ GenericsWithBuilder<Integer, Integer> instance5 = instance.toBuilderManual().u(17).build();
+ assertEquals(integers, instance5.list());
+ assertEquals((Integer) 17, instance5.u());
+ }
+
+ @AutoValue
+ public abstract static class BuilderWithSet<T extends Comparable<T>> {
+ public abstract List<T> list();
+ public abstract T t();
+
+ public static <T extends Comparable<T>> Builder<T> builder() {
+ return new AutoValue_AutoValueTest_BuilderWithSet.Builder<T>();
+ }
+
+ @AutoValue.Builder
+ public interface Builder<T extends Comparable<T>> {
+ Builder<T> setList(List<T> list);
+ Builder<T> setT(T t);
+ BuilderWithSet<T> build();
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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 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);
+
+ ImmutableList<T> list();
+ T t();
+ int[] ints();
+
+ BuilderWithUnprefixedGetters<T> build();
+ }
+ }
+
+ // problem with current javac: annotations on T are not visible to AnnotationProcessor
+ public void ignoredTestBuilderWithUnprefixedGetter() {
+ 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) {
+ assertThat(e).hasMessage("Property \"list\" has not been set");
+ }
+ try {
+ builder.ints();
+ fail("Attempt to retrieve unset ints property should have failed");
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Property \"ints\" has not been set");
+ }
+
+ builder.setList(names);
+ assertThat(builder.list()).isSameAs(names);
+ builder.setInts(ints);
+ assertThat(builder.ints()).isEqualTo(ints);
+ // The array is not cloned by the getter, so the client can modify it (but shouldn't).
+ ints[0] = 0;
+ assertThat(builder.ints()[0]).isEqualTo(0);
+ ints[0] = 6;
+
+ BuilderWithUnprefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
+ assertThat(instance.list()).isSameAs(names);
+ assertThat(instance.t()).isNull();
+ assertThat(instance.ints()).isEqualTo(ints);
+ assertThat(instance.noGetter()).isEqualTo(noGetter);
+ }
+
+ @AutoValue
+ public abstract static class BuilderWithPrefixedGetters<T extends Comparable<T>> {
+ public abstract ImmutableList<T> getList();
+ public abstract T getT();
+ @SuppressWarnings("mutable")
+ public abstract int @Nullable [] getInts();
+ public abstract int getNoGetter();
+
+ public static <T extends Comparable<T>> Builder<T> builder() {
+ return new AutoValue_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);
+
+ abstract ImmutableList<T> getList();
+ abstract T getT();
+ abstract int[] getInts();
+
+ public abstract BuilderWithPrefixedGetters<T> build();
+ }
+ }
+
+ // problem with current javac: annotations on array are not visible to AnnotationProcessor
+ public void ignoredTestBuilderWithPrefixedGetter() {
+ ImmutableList<String> names = ImmutableList.of("fred", "jim");
+ String name = "sheila";
+ int noGetter = -1;
+
+ BuilderWithPrefixedGetters.Builder<String> builder = BuilderWithPrefixedGetters.builder();
+ assertThat(builder.getInts()).isNull();
+ try {
+ builder.getList();
+ fail("Attempt to retrieve unset list property should have failed");
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Property \"list\" has not been set");
+ }
+
+ builder.setList(names);
+ assertThat(builder.getList()).isSameAs(names);
+ builder.setT(name);
+ assertThat(builder.getInts()).isNull();
+
+ BuilderWithPrefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
+ assertThat(instance.getList()).isSameAs(names);
+ assertThat(instance.getT()).isEqualTo(name);
+ assertThat(instance.getInts()).isNull();
+ assertThat(instance.getNoGetter()).isEqualTo(noGetter);
+ }
+
+ @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 ImmutableSet.Builder<String> stringsBuilder();
+
+ public Builder<FooT> addToStrings(String element) {
+ stringsBuilder().add(element);
+ return this;
+ }
+
+ public abstract BuilderWithPropertyBuilders<FooT> build();
+ }
+ }
+
+ 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());
+ }
+
+ @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();
+ }
+ }
+
+ 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<String> strings();
+ public abstract ImmutableMap<String, T> map();
+
+ public static <T extends Number> Builder<T> builder(T value) {
+ return new AutoValue_AutoValueTest_BuilderWithCopyingSetters.Builder<T>()
+ .setStrings(ImmutableSet.of("foo", "bar"))
+ .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> setStrings(Collection<String> strings);
+ Builder<T> setMap(Map<String, T> map);
+ BuilderWithCopyingSetters<T> build();
+ }
+ }
+
+ public void testBuilderWithCopyingSetters() {
+ BuilderWithCopyingSetters.Builder<Integer> builder = BuilderWithCopyingSetters.builder(23);
+
+ BuilderWithCopyingSetters<Integer> a = builder.setThings(ImmutableSet.of(1, 2)).build();
+ assertEquals(ImmutableSet.of(1, 2), a.things());
+ assertEquals(ImmutableList.of("foo", "bar"), a.strings());
+ assertEquals(ImmutableMap.of("foo", 23), a.map());
+
+ BuilderWithCopyingSetters<Integer> b = builder.setThings(Arrays.asList(1, 2)).build();
+ assertEquals(a, b);
+
+ BuilderWithCopyingSetters<Integer> c = builder.setThings(1, 2).build();
+ assertEquals(a, c);
+ }
+
+ @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);
+ }
+ }
+
+ 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();
+ }
+
+ @AutoValue
+ abstract static class CopyAnnotation {
+ @HairyAnnotation(
+ aString = "hello",
+ aClass = Integer.class,
+ anEnum = RetentionPolicy.RUNTIME,
+ anAnnotation = @NestedAnnotation(
+ anInt = 73,
+ aClassArray = {String.class, Object.class}))
+ abstract String id();
+
+ static CopyAnnotation create(String id) {
+ return new AutoValue_AutoValueTest_CopyAnnotation(id);
+ }
+ }
+
+ public void testCopyAnnotations() throws Exception {
+ CopyAnnotation x = CopyAnnotation.create("id");
+ Class<?> c = x.getClass();
+ assertNotSame(CopyAnnotation.class, c);
+ Method methodInSubclass = c.getDeclaredMethod("id");
+ Method methodInSuperclass = CopyAnnotation.class.getDeclaredMethod("id");
+ 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);
+ }
+ }
+ 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);
+ }
+ }
+
+ public void testInheritSameMethodTwice() {
+ InheritSameMethodTwice x = InheritSameMethodTwice.create(23);
+ assertThat(x.something()).isEqualTo(23);
+ }
+}
diff --git a/value/src/it/functional-java8/src/test/java/com/google/auto/value/SimpleValueTypeTest.java b/value/src/it/functional-java8/src/test/java/com/google/auto/value/SimpleValueTypeTest.java
new file mode 100644
index 00000000..969ce952
--- /dev/null
+++ b/value/src/it/functional-java8/src/test/java/com/google/auto/value/SimpleValueTypeTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.NullPointerTester;
+
+import junit.framework.TestCase;
+
+import org.junit.Ignore;
+
+import java.util.Map;
+
+/**
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+public class SimpleValueTypeTest extends TestCase {
+ public void testSimpleValueType() {
+ final String happy = "happy";
+ final int testInt = 23;
+ final Map<String, Long> testMap = ImmutableMap.of("happy", 23L);
+ SimpleValueType simple = SimpleValueType.create(happy, testInt, testMap);
+ assertSame(happy, simple.string());
+ assertEquals(testInt, simple.integer());
+ assertSame(testMap, simple.map());
+ assertEquals("SimpleValueType{string=happy, integer=23, map={happy=23}}",
+ simple.toString());
+ int expectedHashCode = 1;
+ expectedHashCode = (expectedHashCode * 1000003) ^ happy.hashCode();
+ expectedHashCode = (expectedHashCode * 1000003) ^ ((Object) testInt).hashCode();
+ expectedHashCode = (expectedHashCode * 1000003) ^ testMap.hashCode();
+ assertEquals(expectedHashCode, simple.hashCode());
+ }
+
+ public void testNestedValueType() {
+ ImmutableMap<Integer, String> numberNames = ImmutableMap.of(1, "un", 2, "deux");
+ NestedValueType.Nested nested = NestedValueType.Nested.create(numberNames);
+ assertEquals(numberNames, nested.numberNames());
+ }
+
+ @Ignore("NullPointerTester doesn't support type annotations yet")
+ public void ignoredTestNull() {
+ NullPointerTester tester = new NullPointerTester();
+ tester.testAllPublicStaticMethods(SimpleValueType.class);
+ }
+} \ No newline at end of file
diff --git a/value/src/it/functional-java8/src/test/java/com/google/auto/value/enums/MyEnum.java b/value/src/it/functional-java8/src/test/java/com/google/auto/value/enums/MyEnum.java
new file mode 100644
index 00000000..9ac28eb4
--- /dev/null
+++ b/value/src/it/functional-java8/src/test/java/com/google/auto/value/enums/MyEnum.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.enums;
+
+/**
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+public enum MyEnum {
+ ONE, TWO, BUCKLE_MY_SHOE
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
index ee54aaa0..49319441 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
@@ -1280,6 +1280,122 @@ public class AutoValueTest extends TestCase {
}
@AutoValue
+ public abstract static class OptionalPropertyWithBuilder {
+ public abstract com.google.common.base.Optional<String> optionalString();
+
+ public static Builder builder() {
+ return new AutoValue_AutoValueTest_OptionalPropertyWithBuilder.Builder();
+ }
+
+ @AutoValue.Builder
+ public interface Builder {
+ Builder setOptionalString(com.google.common.base.Optional<String> s);
+ Builder setOptionalString(String s);
+ OptionalPropertyWithBuilder build();
+ }
+ }
+
+ public void testOmitOptionalWithBuilder() {
+ OptionalPropertyWithBuilder omitted = OptionalPropertyWithBuilder.builder().build();
+ assertThat(omitted.optionalString()).isAbsent();
+
+ OptionalPropertyWithBuilder supplied = OptionalPropertyWithBuilder.builder()
+ .setOptionalString(com.google.common.base.Optional.of("foo"))
+ .build();
+ assertThat(supplied.optionalString()).hasValue("foo");
+
+ OptionalPropertyWithBuilder suppliedDirectly = OptionalPropertyWithBuilder.builder()
+ .setOptionalString("foo")
+ .build();
+ assertThat(suppliedDirectly.optionalString()).hasValue("foo");
+ }
+
+ @AutoValue
+ public abstract static class NullableOptionalPropertyWithBuilder {
+ @Nullable
+ public abstract com.google.common.base.Optional<String> optionalString();
+
+ public static Builder builder() {
+ return new AutoValue_AutoValueTest_NullableOptionalPropertyWithBuilder.Builder();
+ }
+
+ @AutoValue.Builder
+ public interface Builder {
+ Builder setOptionalString(com.google.common.base.Optional<String> s);
+ NullableOptionalPropertyWithBuilder build();
+ }
+ }
+
+ public void testOmitNullableOptionalWithBuilder() {
+ NullableOptionalPropertyWithBuilder omitted =
+ NullableOptionalPropertyWithBuilder.builder().build();
+ assertThat(omitted.optionalString()).isNull();
+
+ NullableOptionalPropertyWithBuilder supplied = NullableOptionalPropertyWithBuilder.builder()
+ .setOptionalString(com.google.common.base.Optional.of("foo"))
+ .build();
+ assertThat(supplied.optionalString()).hasValue("foo");
+ }
+
+ @AutoValue
+ public abstract static class OptionalPropertyWithBuilderSimpleSetter {
+ public abstract com.google.common.base.Optional<String> optionalString();
+
+ public static Builder builder() {
+ return new AutoValue_AutoValueTest_OptionalPropertyWithBuilderSimpleSetter.Builder();
+ }
+
+ @AutoValue.Builder
+ public interface Builder {
+ Builder setOptionalString(String s);
+ OptionalPropertyWithBuilderSimpleSetter build();
+ }
+ }
+
+ public void testOptionalPropertySimpleSetter() {
+ OptionalPropertyWithBuilderSimpleSetter omitted =
+ OptionalPropertyWithBuilderSimpleSetter.builder().build();
+ assertThat(omitted.optionalString()).isAbsent();
+
+ OptionalPropertyWithBuilderSimpleSetter supplied =
+ OptionalPropertyWithBuilderSimpleSetter.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();
+ }
+ }
+
+ 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 GenericsWithBuilder<T extends Number & Comparable<T>, U extends T> {
public abstract List<T> list();
public abstract U u();
@@ -1748,6 +1864,36 @@ public class AutoValueTest extends TestCase {
}
}
+ 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();
+ }
+ }
+
+ 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;
diff --git a/value/src/it/gwtserializer/pom.xml b/value/src/it/gwtserializer/pom.xml
index cdbdb3fa..f76a826c 100644
--- a/value/src/it/gwtserializer/pom.xml
+++ b/value/src/it/gwtserializer/pom.xml
@@ -53,13 +53,13 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
- <version>18.0</version>
+ <version>19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-gwt</artifactId>
- <version>18.0</version>
+ <version>19.0</version>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
index 6126659c..88f28c64 100644
--- a/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
+++ b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
@@ -14,19 +14,24 @@ import javax.lang.model.element.TypeElement;
* of an AutoValue class.
*
* <p>Extensions are discovered at compile time using the {@link java.util.ServiceLoader} APIs,
- * allowing them to run without any additional annotations.
+ * allowing them to run without any additional annotations. To be found by {@code ServiceLoader},
+ * an extension class must be public with a public no-arg constructor, and its fully-qualified
+ * name must appear in a file called
+ * {@code META-INF/services/com.google.auto.value.extension.AutoValueExtension} in a jar that
+ * is on the compiler's {@code -classpath} or {@code -processorpath}.
*
- * <p>Extensions can extend the AutoValue implementation by generating subclasses of the AutoValue
- * generated class. It is not guaranteed that an Extension's generated class will be the final
- * class in the inheritance hierarchy, unless its {@link #mustBeFinal(Context)} method returns true.
- * Only one Extension can return true for a given context. Only generated classes that will be the
- * final class in the inheritance hierarchy can be declared final. All others should be declared
- * abstract.
+ * <p>An Extension can extend the AutoValue implementation by generating a subclass of the
+ * AutoValue generated class. It is not guaranteed that an Extension's generated class will be the
+ * final class in the inheritance hierarchy, unless its {@link #mustBeFinal(Context)} method returns
+ * true. Only one Extension can return true for a given context. Only generated classes that will
+ * be the final class in the inheritance hierarchy can be declared final. All others should be
+ * declared abstract.
*
* <p>Each Extension must also be sure to generate a constructor with arguments corresponding to
* all properties in
- * {@link com.google.auto.value.extension.AutoValueExtension.Context#properties()}, in order. This
- * constructor must have at least package visibility.
+ * {@link com.google.auto.value.extension.AutoValueExtension.Context#properties()}, in order,
+ * and to call the superclass constructor with the same arguments. This constructor must have at
+ * least package visibility.
*/
public abstract class AutoValueExtension {
@@ -36,41 +41,49 @@ public abstract class AutoValueExtension {
public interface Context {
/**
- * The processing environment of this generation cycle.
- *
- * @return The ProcessingEnvironment of this generation cycle.
+ * Returns the processing environment of this generation cycle. This can be used, among other
+ * things, to produce compilation warnings or errors, using
+ * {@link ProcessingEnvironment#getMessager()}.
*/
ProcessingEnvironment processingEnvironment();
/**
- * The package name of the classes to be generated.
- *
- * @return The package name of the classes to be generated.
+ * Returns the package name of the classes to be generated.
*/
String packageName();
/**
- * The annotated class that this generation cycle is based on.
+ * Returns the annotated class that this generation cycle is based on.
*
* <p>Given {@code @AutoValue public class Foo {...}}, this will be {@code Foo}.
- *
- * @return The annotated class.
*/
TypeElement autoValueClass();
/**
- * The ordered collection of properties to be generated by AutoValue.
- *
- * @return The ordered collection of properties.
+ * Returns the ordered collection of properties to be generated by AutoValue. Each key is a
+ * property name, and the corresponding value is the getter method for that property. For
+ * example, if property {@code bar} is defined by {@code abstract String getBar()} then this
+ * map will have an entry mapping {@code "bar"} to the {@code ExecutableElement} for
+ * {@code getBar()}.
*/
Map<String, ExecutableElement> properties();
+
+ /**
+ * Returns the complete set of abstract methods defined in or inherited by the
+ * {@code @AutoValue} class. This includes all methods that define properties
+ * (like {@code abstract String getBar()}), any abstract {@code toBuilder()} method, and any
+ * other abstract method even if it has been consumed by this or another Extension.
+ */
+ Set<ExecutableElement> abstractMethods();
}
/**
- * Determines whether this extension applies to the given context.
+ * Determines whether this Extension applies to the given context.
*
* @param context The Context of the code generation for this class.
- * @return True if this extension should be applied in the given context.
+ * @return true if this Extension should be applied in the given context. If an Extension
+ * returns false for a given class, it will not be called again during the processing
+ * of that class.
*/
public boolean applicable(Context context) {
return false;
@@ -78,21 +91,25 @@ public abstract class AutoValueExtension {
/**
* Denotes that the class generated by this Extension must be the final class
- * in the inheritance hierarchy. Only one extension may be the final class, so
+ * in the inheritance hierarchy. Only one Extension may be the final class, so
* this should be used sparingly.
*
- * @param context The Context of the code generation for this class.
- * @return True if the resulting class must be the final class in the inheritance hierarchy.
+ * @param context the Context of the code generation for this class.
*/
public boolean mustBeFinal(Context context) {
return false;
}
/**
- * Returns a non-null set of property names from {@link Context#properties()} that this extension
- * intends to implement. This will prevent AutoValue from generating an implementation, and remove
- * the supplied properties from builders, constructors, {@code toString}, {@code equals},
- * and {@code hashCode}. The default set returned by this method is empty.
+ * Returns a possibly empty set of property names that this Extension intends to implement. This
+ * will prevent AutoValue from generating an implementation, and remove the supplied properties
+ * from builders, constructors, {@code toString}, {@code equals}, and {@code hashCode}. The
+ * default set returned by this method is empty.
+ *
+ * <p>Each returned string must be one of the property names in {@link Context#properties()}.
+ *
+ * <p>Returning a property name from this method is equivalent to returning the property's
+ * getter method from {@link #consumeMethods}.
*
* <p>For example, Android's {@code Parcelable} interface includes a
* <a href="http://developer.android.com/reference/android/os/Parcelable.html#describeContents()">method</a>
@@ -105,28 +122,49 @@ public abstract class AutoValueExtension {
* implementation and return a set containing {@code "describeContents"}. Then
* {@code describeContents} will be omitted from builders and the rest.
*
- * @param context The Context of the code generation for this class.
- * @return A collection of property names that this extension intends to implement.
+ * @param context the Context of the code generation for this class.
*/
public Set<String> consumeProperties(Context context) {
return Collections.emptySet();
}
/**
+ * Returns a possible empty set of abstract methods that this Extension intends to implement.
+ * This will prevent AutoValue from generating an implementation, in cases where it would have,
+ * and it will also avoid warnings about abstract methods that AutoValue doesn't expect. The
+ * default set returned by this method is empty.
+ *
+ * <p>Each returned method must be one of the abstract methods in
+ * {@link Context#abstractMethods()}.
+ *
+ * <p>For example, Android's {@code Parcelable} interface includes a
+ * <a href="http://developer.android.com/reference/android/os/Parcelable.html#writeToParcel(android.os.Parcel, int)">method</a>
+ * {@code void writeToParcel(Parcel, int)}. Normally AutoValue would not know what to do with that
+ * abstract method. But an {@code AutoValueExtension} that understands {@code Parcelable} can
+ * provide a useful implementation and return the {@code writeToParcel} method here. That will
+ * prevent a warning about the method from AutoValue.
+ *
+ * @param context the Context of the code generation for this class.
+ */
+ public Set<ExecutableElement> consumeMethods(Context context) {
+ return Collections.emptySet();
+ }
+
+ /**
* Generates the source code of the class named {@code className} to extend
- * {@code classToExtend}, with the original annotated class of
- * {@code classToImplement}. The generated class should be final if {@code isFinal}
- * is true; otherwise it should be abstract.
+ * {@code classToExtend}. The generated class should be final if {@code isFinal}
+ * is true; otherwise it should be abstract. The returned string should be a complete
+ * Java class definition of the class {@code className} in the package
+ * {@link Context#packageName() context.packageName()}.
*
* @param context The {@link Context} of the code generation for this class.
- * @param className The name of the resulting class. The returned code will be written to a
+ * @param className The simple name of the resulting class. The returned code will be written to a
* file named accordingly.
- * @param classToExtend The direct parent of the generated class. Could be the AutoValue
- * generated class, or a class generated as the result of another
- * extension.
+ * @param classToExtend The direct parent of the generated class. This could be the AutoValue
+ * generated class, or a class generated as the result of another Extension.
* @param isFinal True if this class is the last class in the chain, meaning it should be
- * marked as final, otherwise it should be marked as abstract.
- * @return The source code of the generated class
+ * marked as final. Otherwise it should be marked as abstract.
+ * @return The source code of the generated class.
*/
public abstract String generateClass(
Context context, String className, String classToExtend, boolean isFinal);
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
index a044ed0b..252758b6 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
@@ -15,6 +15,7 @@
*/
package com.google.auto.value.processor;
+import com.google.auto.common.MoreElements;
import com.google.auto.common.SuperficialValidation;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoAnnotation;
@@ -186,7 +187,9 @@ public class AutoAnnotationProcessor extends AbstractProcessor {
boolean overloaded = false;
Set<String> classNames = new HashSet<String>();
for (ExecutableElement method : methods) {
- if (!classNames.add(generatedClassName(method))) {
+ String qualifiedClassName = MoreElements.getPackage(method).getQualifiedName() + "."
+ + generatedClassName(method);
+ if (!classNames.add(qualifiedClassName)) {
overloaded = true;
reportError(method, "@AutoAnnotation methods cannot be overloaded");
}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
index b527b739..9fa363f2 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
@@ -21,6 +21,7 @@ import com.google.auto.common.MoreElements;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.AutoValueExtension;
+import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
@@ -31,6 +32,7 @@ import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -42,7 +44,7 @@ import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Iterator;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
@@ -109,10 +111,13 @@ public class AutoValueProcessor extends AbstractProcessor {
private Iterable<? extends AutoValueExtension> extensions;
+ private Types typeUtils;
+
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
errorReporter = new ErrorReporter(processingEnv);
+ typeUtils = processingEnv.getTypeUtils();
}
@Override
@@ -189,6 +194,7 @@ public class AutoValueProcessor extends AbstractProcessor {
private final ExecutableElement method;
private final String type;
private final ImmutableList<String> annotations;
+ private final Optionalish optional;
Property(
String name,
@@ -201,6 +207,9 @@ public class AutoValueProcessor extends AbstractProcessor {
this.method = method;
this.type = type;
this.annotations = buildAnnotations(typeSimplifier);
+ TypeMirror propertyType = method.getReturnType();
+ this.optional =
+ Optionalish.createIfOptional(propertyType, typeSimplifier.simplifyRaw(propertyType));
}
private ImmutableList<String> buildAnnotations(TypeSimplifier typeSimplifier) {
@@ -218,6 +227,11 @@ public class AutoValueProcessor extends AbstractProcessor {
builder.add(annotationOutput.sourceFormForAnnotation(annotationMirror));
}
+ for (AnnotationMirror annotationMirror :
+ Java8Support.getAnnotationMirrors(method.getReturnType())) {
+ AnnotationOutput annotationOutput = new AnnotationOutput(typeSimplifier);
+ builder.add(annotationOutput.sourceFormForAnnotation(annotationMirror));
+ }
return builder.build();
}
@@ -271,6 +285,14 @@ public class AutoValueProcessor extends AbstractProcessor {
}
/**
+ * Returns an {@link Optionalish} representing the kind of Optional that this property's type
+ * is, or null if the type is not an Optional of any kind.
+ */
+ public Optionalish getOptional() {
+ return optional;
+ }
+
+ /**
* Returns the string to use as an annotation to indicate the nullability of this property.
* It is either the empty string, if the property is not nullable, or an annotation string
* with a trailing space, such as {@code "@Nullable "} or {@code "@javax.annotation.Nullable "}.
@@ -359,54 +381,72 @@ public class AutoValueProcessor extends AbstractProcessor {
}
checkModifiersIfNested(type);
+ // We are going to classify the methods of the @AutoValue class into several categories.
+ // This covers the methods in the class itself and the ones it inherits from supertypes.
+ // First, the only concrete (non-abstract) methods we are interested in are overrides of
+ // Object methods (equals, hashCode, toString), which signal that we should not generate
+ // an implementation of those methods.
+ // Then, each abstract method is one of the following:
+ // (1) A property getter, like "abstract String foo()" or "abstract String getFoo()".
+ // (2) A toBuilder() method, which is any abstract no-arg method returning the Builder for
+ // this @AutoValue class.
+ // (3) An abstract method that will be consumed by an extension, such as
+ // Parcelable.describeContents() or Parcelable.writeToParcel(Parcel, int).
+ // The describeContents() example shows a quirk here: initially we will identify it as a
+ // property, which means that we need to reconstruct the list of properties after allowing
+ // extensions to consume abstract methods.
+ // If there are abstract methods that don't fit any of the categories above, that is an error
+ // which we signal explicitly to avoid confusion.
+
ImmutableSet<ExecutableElement> methods =
getLocalAndInheritedMethods(type, processingEnv.getElementUtils());
- ImmutableSet<ExecutableElement> methodsToImplement = methodsToImplement(type, methods);
- ImmutableBiMap<String, ExecutableElement> properties =
- propertyNameToMethodMap(methodsToImplement);
+ ImmutableSet<ExecutableElement> abstractMethods = abstractMethodsIn(methods);
- String fqExtClass = TypeSimplifier.classNameOf(type);
- List<AutoValueExtension> appliedExtensions = new ArrayList<AutoValueExtension>();
- ExtensionContext context = new ExtensionContext(processingEnv, type, properties);
- for (AutoValueExtension extension : extensions) {
- if (extension.applicable(context)) {
- if (extension.mustBeFinal(context)) {
- appliedExtensions.add(0, extension);
- } else {
- appliedExtensions.add(extension);
- }
- }
+ BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter);
+ Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder();
+ ImmutableSet<ExecutableElement> toBuilderMethods;
+ if (builder.isPresent()) {
+ toBuilderMethods = builder.get().toBuilderMethods(typeUtils, abstractMethods);
+ } else {
+ toBuilderMethods = ImmutableSet.of();
}
- if (!appliedExtensions.isEmpty()) {
- final Set<String> methodsToRemove = Sets.newHashSet();
- for (int i = appliedExtensions.size() - 1; i >= 0; i--) {
- AutoValueExtension extension = appliedExtensions.get(i);
- methodsToRemove.addAll(extension.consumeProperties(context));
- }
- if (!methodsToRemove.isEmpty()) {
- context.setProperties(newImmutableBiMapRemovingKeys(properties, methodsToRemove));
- Set<ExecutableElement> newMethods = Sets.newLinkedHashSet(methods);
- for (Iterator<ExecutableElement> it = newMethods.iterator(); it.hasNext(); ) {
- if (methodsToRemove.contains(it.next().getSimpleName().toString())) {
- it.remove();
- }
- }
- methods = ImmutableSet.copyOf(newMethods);
- }
+ ImmutableSet<ExecutableElement> propertyMethods =
+ propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods));
+ ImmutableBiMap<String, ExecutableElement> properties = propertyNameToMethodMap(propertyMethods);
+
+ ExtensionContext context =
+ new ExtensionContext(processingEnv, type, properties, abstractMethods);
+ ImmutableList<AutoValueExtension> applicableExtensions = applicableExtensions(type, context);
+ ImmutableSet<ExecutableElement> consumedMethods = methodsConsumedByExtensions(
+ type, applicableExtensions, context, abstractMethods, properties);
+
+ if (!consumedMethods.isEmpty()) {
+ ImmutableSet<ExecutableElement> allAbstractMethods = abstractMethods;
+ abstractMethods = immutableSetDifference(abstractMethods, consumedMethods);
+ toBuilderMethods = immutableSetDifference(toBuilderMethods, consumedMethods);
+ propertyMethods =
+ propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods));
+ properties = propertyNameToMethodMap(propertyMethods);
+ context = new ExtensionContext(processingEnv, type, properties, allAbstractMethods);
}
+ boolean extensionsPresent = !applicableExtensions.isEmpty();
+ validateMethods(type, abstractMethods, toBuilderMethods, propertyMethods, extensionsPresent);
+
String finalSubclass = generatedSubclassName(type, 0);
- String subclass = generatedSubclassName(type, appliedExtensions.size());
+ String subclass = generatedSubclassName(type, applicableExtensions.size());
AutoValueTemplateVars vars = new AutoValueTemplateVars();
vars.pkg = TypeSimplifier.packageNameOf(type);
- vars.origClass = fqExtClass;
+ vars.origClass = TypeSimplifier.classNameOf(type);
vars.simpleClassName = TypeSimplifier.simpleNameOf(vars.origClass);
vars.subclass = TypeSimplifier.simpleNameOf(subclass);
vars.finalSubclass = TypeSimplifier.simpleNameOf(finalSubclass);
- vars.isFinal = appliedExtensions.isEmpty();
+ vars.isFinal = applicableExtensions.isEmpty();
vars.types = processingEnv.getTypeUtils();
- defineVarsForType(type, vars, methods);
+ determineObjectMethodsToGenerate(methods, vars);
+ defineVarsForType(type, vars, toBuilderMethods, propertyMethods, builder);
+
GwtCompatibility gwtCompatibility = new GwtCompatibility(type);
vars.gwtCompatibleAnnotation = gwtCompatibility.gwtCompatibleAnnotationString();
String text = vars.toText();
@@ -415,9 +455,17 @@ public class AutoValueProcessor extends AbstractProcessor {
GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type);
gwtSerialization.maybeWriteGwtSerializer(vars);
+ writeExtensions(type, context, applicableExtensions, subclass);
+ }
+
+ private void writeExtensions(
+ TypeElement type,
+ ExtensionContext context,
+ ImmutableList<AutoValueExtension> applicableExtensions,
+ String subclass) {
String extClass = TypeSimplifier.simpleNameOf(subclass);
- for (int i = appliedExtensions.size() - 1; i >= 0; i--) {
- AutoValueExtension extension = appliedExtensions.remove(i);
+ for (int i = applicableExtensions.size() - 1; i >= 0; i--) {
+ AutoValueExtension extension = applicableExtensions.get(i);
String fqClassName = generatedSubclassName(type, i);
String className = TypeSimplifier.simpleNameOf(fqClassName);
boolean isFinal = (i == 0);
@@ -433,26 +481,128 @@ public class AutoValueProcessor extends AbstractProcessor {
}
}
- private static <K, V> ImmutableBiMap<K, V> newImmutableBiMapRemovingKeys(
- ImmutableBiMap<K, V> original, Set<K> keysToRemove) {
- ImmutableBiMap.Builder<K, V> builder = ImmutableBiMap.builder();
- for (Map.Entry<K, V> property : original.entrySet()) {
- if (!keysToRemove.contains(property.getKey())) {
- builder.put(property);
+ private ImmutableList<AutoValueExtension> applicableExtensions(
+ TypeElement type, ExtensionContext context) {
+ List<AutoValueExtension> applicableExtensions = Lists.newArrayList();
+ List<AutoValueExtension> finalExtensions = Lists.newArrayList();
+ for (AutoValueExtension extension : extensions) {
+ if (extension.applicable(context)) {
+ if (extension.mustBeFinal(context)) {
+ finalExtensions.add(extension);
+ } else {
+ applicableExtensions.add(extension);
+ }
}
}
- return builder.build();
+ switch (finalExtensions.size()) {
+ case 0:
+ break;
+ case 1:
+ applicableExtensions.add(0, finalExtensions.get(0));
+ break;
+ default:
+ errorReporter.reportError(
+ "More than one extension wants to generate the final class: "
+ + FluentIterable.from(finalExtensions).transform(ExtensionName.INSTANCE)
+ .join(Joiner.on(", ")),
+ type);
+ break;
+ }
+ return ImmutableList.copyOf(applicableExtensions);
+ }
+
+ private ImmutableSet<ExecutableElement> methodsConsumedByExtensions(
+ TypeElement type,
+ ImmutableList<AutoValueExtension> applicableExtensions,
+ ExtensionContext context,
+ ImmutableSet<ExecutableElement> abstractMethods,
+ ImmutableBiMap<String, ExecutableElement> properties) {
+ Set<ExecutableElement> consumed = Sets.newHashSet();
+ for (AutoValueExtension extension : applicableExtensions) {
+ Set<ExecutableElement> consumedHere = Sets.newHashSet();
+ for (String consumedProperty : extension.consumeProperties(context)) {
+ ExecutableElement propertyMethod = properties.get(consumedProperty);
+ if (propertyMethod == null) {
+ errorReporter.reportError(
+ "Extension " + extensionName(extension)
+ + " wants to consume a property that does not exist: " + consumedProperty,
+ type);
+ } else {
+ consumedHere.add(propertyMethod);
+ }
+ }
+ for (ExecutableElement consumedMethod : extension.consumeMethods(context)) {
+ if (!abstractMethods.contains(consumedMethod)) {
+ errorReporter.reportError(
+ "Extension " + extensionName(extension)
+ + " wants to consume a method that is not one of the abstract methods in this"
+ + " class: " + consumedMethod,
+ type);
+ } else {
+ consumedHere.add(consumedMethod);
+ }
+ }
+ for (ExecutableElement repeat : Sets.intersection(consumed, consumedHere)) {
+ errorReporter.reportError(
+ "Extension " + extensionName(extension) + " wants to consume a method that was already"
+ + " consumed by another extension", repeat);
+ }
+ consumed.addAll(consumedHere);
+ }
+ return ImmutableSet.copyOf(consumed);
+ }
+
+ private void validateMethods(
+ TypeElement type,
+ ImmutableSet<ExecutableElement> abstractMethods,
+ ImmutableSet<ExecutableElement> toBuilderMethods,
+ ImmutableSet<ExecutableElement> propertyMethods,
+ boolean extensionsPresent) {
+ boolean ok = true;
+ for (ExecutableElement method : abstractMethods) {
+ if (propertyMethods.contains(method)) {
+ ok &= checkReturnType(type, method);
+ } else if (!toBuilderMethods.contains(method)
+ && objectMethodToOverride(method) == ObjectMethodToOverride.NONE) {
+ // This could reasonably be an error, were it not for an Eclipse bug in
+ // ElementUtils.override that sometimes fails to recognize that one method overrides
+ // another, and therefore leaves us with both an abstract method and the subclass method
+ // that overrides it. This shows up in AutoValueTest.LukesBase for example.
+ String message = "Abstract method is neither a property getter nor a Builder converter";
+ if (extensionsPresent) {
+ message += ", and no extension consumed it";
+ }
+ errorReporter.reportWarning(message, method);
+ }
+ }
+ if (!ok) {
+ throw new AbortProcessingException();
+ }
+ }
+
+ private static String extensionName(AutoValueExtension extension) {
+ return extension.getClass().getName();
+ }
+
+ private enum ExtensionName implements Function<AutoValueExtension, String> {
+ INSTANCE;
+
+ @Override public String apply(AutoValueExtension input) {
+ return extensionName(input);
+ }
}
private void defineVarsForType(
TypeElement type,
AutoValueTemplateVars vars,
- Set<ExecutableElement> methods) {
- Types typeUtils = processingEnv.getTypeUtils();
- determineObjectMethodsToGenerate(methods, vars);
- ImmutableSet<ExecutableElement> methodsToImplement = methodsToImplement(type, methods);
+ ImmutableSet<ExecutableElement> toBuilderMethods,
+ ImmutableSet<ExecutableElement> propertyMethods,
+ Optional<BuilderSpec.Builder> builder) {
Set<TypeMirror> types = new TypeMirrorSet();
- types.addAll(returnTypesOf(methodsToImplement));
+ types.addAll(returnTypesOf(propertyMethods));
+ if (builder.isPresent()) {
+ types.addAll(builder.get().referencedTypes());
+ }
TypeElement generatedTypeElement =
processingEnv.getElementUtils().getTypeElement(Generated.class.getName());
if (generatedTypeElement != null) {
@@ -464,18 +614,8 @@ public class AutoValueProcessor extends AbstractProcessor {
// Arrange to import it unless that would introduce ambiguity.
types.add(javaUtilArrays);
}
- BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter);
- Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder();
- ImmutableSet<ExecutableElement> toBuilderMethods;
- if (builder.isPresent()) {
- toBuilderMethods = builder.get().toBuilderMethods(typeUtils, methodsToImplement);
- types.addAll(builder.get().referencedTypes());
- } else {
- toBuilderMethods = ImmutableSet.of();
- }
vars.toBuilderMethods =
FluentIterable.from(toBuilderMethods).transform(SimpleNameFunction.INSTANCE).toList();
- Set<ExecutableElement> propertyMethods = Sets.difference(methodsToImplement, toBuilderMethods);
types.addAll(allMethodAnnotationTypes(propertyMethods));
String pkg = TypeSimplifier.packageNameOf(type);
TypeSimplifier typeSimplifier = new TypeSimplifier(typeUtils, pkg, types, type.asType());
@@ -491,7 +631,7 @@ public class AutoValueProcessor extends AbstractProcessor {
List<Property> props = new ArrayList<Property>();
EclipseHack eclipseHack = eclipseHack();
ImmutableMap<ExecutableElement, TypeMirror> returnTypes =
- eclipseHack.methodReturnTypes(methods, type);
+ eclipseHack.methodReturnTypes(propertyMethods, type);
for (ExecutableElement method : propertyMethods) {
TypeMirror returnType = returnTypes.get(method);
String propertyType = typeSimplifier.simplify(returnType);
@@ -517,12 +657,12 @@ public class AutoValueProcessor extends AbstractProcessor {
}
private ImmutableBiMap<String, ExecutableElement> propertyNameToMethodMap(
- Iterable<ExecutableElement> propertyMethods) {
+ Set<ExecutableElement> propertyMethods) {
Map<String, ExecutableElement> map = Maps.newLinkedHashMap();
- boolean allGetters = allGetters(propertyMethods);
+ boolean allPrefixed = gettersAllPrefixed(propertyMethods);
for (ExecutableElement method : propertyMethods) {
String methodName = method.getSimpleName().toString();
- String name = allGetters ? nameWithoutPrefix(methodName) : methodName;
+ String name = allPrefixed ? nameWithoutPrefix(methodName) : methodName;
Object old = map.put(name, method);
if (old != null) {
errorReporter.reportError("More than one @AutoValue property called " + name, method);
@@ -531,18 +671,23 @@ public class AutoValueProcessor extends AbstractProcessor {
return ImmutableBiMap.copyOf(map);
}
- private static boolean allGetters(Iterable<ExecutableElement> methods) {
+ private static boolean gettersAllPrefixed(Set<ExecutableElement> methods) {
+ return prefixedGettersIn(methods).size() == methods.size();
+ }
+
+ static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) {
+ ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder();
for (ExecutableElement method : methods) {
String name = method.getSimpleName().toString();
// TODO(emcmanus): decide whether getfoo() (without a capital) is a getter. Currently it is.
boolean get = name.startsWith("get") && !name.equals("get");
boolean is = name.startsWith("is") && !name.equals("is")
&& method.getReturnType().getKind() == TypeKind.BOOLEAN;
- if (!get && !is) {
- return false;
+ if (get || is) {
+ getters.add(method);
}
}
- return true;
+ return getters.build();
}
private Set<TypeMirror> allMethodAnnotationTypes(Iterable<ExecutableElement> methods) {
@@ -551,6 +696,10 @@ public class AutoValueProcessor extends AbstractProcessor {
for (AnnotationMirror annotationMirror : method.getAnnotationMirrors()) {
annotationTypes.add(annotationMirror.getAnnotationType());
}
+ for (AnnotationMirror annotationMirror :
+ Java8Support.getAnnotationMirrors(method.getReturnType())) {
+ annotationTypes.add(annotationMirror.getAnnotationType());
+ }
}
return annotationTypes;
}
@@ -643,39 +792,46 @@ public class AutoValueProcessor extends AbstractProcessor {
case TO_STRING:
vars.toString = canGenerate;
break;
+ default:
+ // Not a method from Object, nothing to do.
}
}
}
- private ImmutableSet<ExecutableElement> methodsToImplement(
- TypeElement autoValueClass, Set<ExecutableElement> methods) {
- ImmutableSet.Builder<ExecutableElement> toImplement = ImmutableSet.builder();
- Set<Name> toImplementNames = Sets.newHashSet();
- boolean ok = true;
+ private ImmutableSet<ExecutableElement> abstractMethodsIn(
+ ImmutableSet<ExecutableElement> methods) {
+ Set<Name> noArgMethods = Sets.newHashSet();
+ ImmutableSet.Builder<ExecutableElement> abstracts = ImmutableSet.builder();
for (ExecutableElement method : methods) {
- if (method.getModifiers().contains(Modifier.ABSTRACT)
- && objectMethodToOverride(method) == ObjectMethodToOverride.NONE) {
- if (method.getParameters().isEmpty() && method.getReturnType().getKind() != TypeKind.VOID) {
- ok &= checkReturnType(autoValueClass, method);
- if (toImplementNames.add(method.getSimpleName())) {
- // If an abstract method with the same signature is inherited on more than one path,
- // we only add it once.
- toImplement.add(method);
- }
- } else {
- // This could reasonably be an error, were it not for an Eclipse bug in
- // ElementUtils.override that sometimes fails to recognize that one method overrides
- // another, and therefore leaves us with both an abstract method and the subclass method
- // that overrides it. This shows up in AutoValueTest.LukesBase for example.
- errorReporter.reportWarning("@AutoValue classes cannot have abstract methods other than"
- + " property getters and Builder converters", method);
+ if (method.getModifiers().contains(Modifier.ABSTRACT)) {
+ boolean hasArgs = !method.getParameters().isEmpty();
+ if (hasArgs || noArgMethods.add(method.getSimpleName())) {
+ // If an abstract method with the same signature is inherited on more than one path,
+ // we only add it once. At the moment we only do this check for no-arg methods. All
+ // methods that AutoValue will implement are either no-arg methods or equals(Object).
+ // The former is covered by this check and the latter will lead to vars.equals being
+ // set to true, regardless of how many times it appears. So the only case that is
+ // covered imperfectly here is that of a method that is inherited on more than one path
+ // and that will be consumed by an extension. We could check parameters as well, but that
+ // can be a bit tricky if any of the parameters are generic.
+ abstracts.add(method);
}
}
}
- if (!ok) {
- throw new AbortProcessingException();
+ return abstracts.build();
+ }
+
+ private ImmutableSet<ExecutableElement> propertyMethodsIn(
+ ImmutableSet<ExecutableElement> abstractMethods) {
+ ImmutableSet.Builder<ExecutableElement> properties = ImmutableSet.builder();
+ for (ExecutableElement method : abstractMethods) {
+ if (method.getParameters().isEmpty()
+ && method.getReturnType().getKind() != TypeKind.VOID
+ && objectMethodToOverride(method) == ObjectMethodToOverride.NONE) {
+ properties.add(method);
+ }
}
- return toImplement.build();
+ return properties.build();
}
private boolean checkReturnType(TypeElement autoValueClass, ExecutableElement getter) {
@@ -749,7 +905,6 @@ public class AutoValueProcessor extends AbstractProcessor {
if (parentMirror.getKind() == TypeKind.NONE) {
return false;
}
- Types typeUtils = processingEnv.getTypeUtils();
TypeElement parentElement = (TypeElement) typeUtils.asElement(parentMirror);
if (MoreElements.isAnnotationPresent(parentElement, AutoValue.class)) {
return true;
@@ -759,14 +914,12 @@ public class AutoValueProcessor extends AbstractProcessor {
}
private boolean implementsAnnotation(TypeElement type) {
- Types typeUtils = processingEnv.getTypeUtils();
return typeUtils.isAssignable(type.asType(), getTypeMirror(Annotation.class));
}
// Return a string like "1234L" if type instanceof Serializable and defines
// serialVersionUID = 1234L, otherwise "".
private String getSerialVersionUID(TypeElement type) {
- Types typeUtils = processingEnv.getTypeUtils();
TypeMirror serializable = getTypeMirror(Serializable.class);
if (typeUtils.isAssignable(type.asType(), serializable)) {
List<VariableElement> fields = ElementFilter.fieldsIn(type.getEnclosedElements());
@@ -807,6 +960,14 @@ public class AutoValueProcessor extends AbstractProcessor {
}
}
+ private static <E> ImmutableSet<E> immutableSetDifference(ImmutableSet<E> a, ImmutableSet<E> b) {
+ if (Collections.disjoint(a, b)) {
+ return a;
+ } else {
+ return ImmutableSet.copyOf(Sets.difference(a, b));
+ }
+ }
+
private EclipseHack eclipseHack() {
return new EclipseHack(processingEnv);
}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java
index 2d80ea85..3492a66a 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java
@@ -158,16 +158,23 @@ class AutoValueTemplateVars extends TemplateVars {
ImmutableMap.of();
/**
- * Properties that are required to be set. A property must be set explicitly unless it is either
- * {@code @Nullable} (in which case it defaults to null), or has a property-builder method
- * (in which case it defaults to empty).
+ * Properties that are required to be set. A property must be set explicitly except in the
+ * following cases:
+ * <ul>
+ * <li>it is {@code @Nullable} (in which case it defaults to null);
+ * <li>it is {@code Optional} (in which case it defaults to empty);
+ * <li>it has a property-builder method (in which case it defaults to empty).
+ * </ul>
*/
ImmutableSet<AutoValueProcessor.Property> builderRequiredProperties = ImmutableSet.of();
/**
- * Properties that have getters in the builder.
+ * A map from property names to information about the associated property getter. A property
+ * called foo (defined by a method foo() or getFoo()) can have a property getter method with
+ * the same name (foo() or getFoo()) and either the same return type or an Optional
+ * (or OptionalInt, etc) wrapping it.
*/
- ImmutableSet<String> propertiesWithBuilderGetters = ImmutableSet.of();
+ ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters = ImmutableMap.of();
/**
* The names of any {@code toBuilder()} methods, that is methods that return the builder type.
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
index 866632fd..0cd5d7c7 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
@@ -58,9 +58,11 @@ class BuilderMethodClassifier {
private final TypeElement builderType;
private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName;
private final ImmutableMap<String, ExecutableElement> getterNameToGetter;
+ private final TypeSimplifier typeSimplifier;
private final Set<ExecutableElement> buildMethods = Sets.newLinkedHashSet();
- private final Set<String> propertiesWithBuilderGetters = Sets.newLinkedHashSet();
+ private final Map<String, BuilderSpec.PropertyGetter> builderGetters =
+ Maps.newLinkedHashMap();
private final Multimap<String, ExecutableElement> propertyNameToPrefixedSetters =
LinkedListMultimap.create();
private final Multimap<String, ExecutableElement> propertyNameToUnprefixedSetters =
@@ -74,7 +76,8 @@ class BuilderMethodClassifier {
ProcessingEnvironment processingEnv,
TypeElement autoValueClass,
TypeElement builderType,
- ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
+ ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
+ TypeSimplifier typeSimplifier) {
this.errorReporter = errorReporter;
this.typeUtils = processingEnv.getTypeUtils();
this.autoValueClass = autoValueClass;
@@ -86,6 +89,7 @@ class BuilderMethodClassifier {
getterToPropertyNameBuilder.put(getter.getSimpleName().toString(), getter);
}
this.getterNameToGetter = getterToPropertyNameBuilder.build();
+ this.typeSimplifier = typeSimplifier;
}
/**
@@ -106,9 +110,15 @@ class BuilderMethodClassifier {
ProcessingEnvironment processingEnv,
TypeElement autoValueClass,
TypeElement builderType,
- ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
+ ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
+ TypeSimplifier typeSimplifier) {
BuilderMethodClassifier classifier = new BuilderMethodClassifier(
- errorReporter, processingEnv, autoValueClass, builderType, getterToPropertyName);
+ errorReporter,
+ processingEnv,
+ autoValueClass,
+ builderType,
+ getterToPropertyName,
+ typeSimplifier);
if (classifier.classifyMethods(methods)) {
return Optional.of(classifier);
} else {
@@ -138,8 +148,8 @@ class BuilderMethodClassifier {
* then the name of the property is {@code foo}, If the builder also has a method of the same name
* ({@code foo()} or {@code getFoo()}) then the set returned here will contain {@code foo}.
*/
- ImmutableSet<String> propertiesWithBuilderGetters() {
- return ImmutableSet.copyOf(propertiesWithBuilderGetters);
+ ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters() {
+ return ImmutableMap.copyOf(builderGetters);
}
/**
@@ -212,14 +222,17 @@ class BuilderMethodClassifier {
/**
* Classifies a method given that it has no arguments. Currently a method with no
- * arguments can only be a {@code build()} method, meaning that its return type must be the
- * {@code @AutoValue} class.
+ * arguments can be a {@code build()} method, meaning that its return type must be the
+ * {@code @AutoValue} class; it can be a getter, with the same signature as one of
+ * the property getters in the {@code @AutoValue} class; or it can be a property builder,
+ * like {@code ImmutableList.Builder<String> foosBuilder()} for the property defined by
+ * {@code ImmutableList<String> foos()} or {@code getFoos()}.
*
* @return true if the method was successfully classified, false if an error has been reported.
*/
private boolean classifyMethodNoArgs(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
- TypeMirror returnType = method.getReturnType();
+ TypeMirror returnType = builderMethodReturnType(method);
ExecutableElement getter = getterNameToGetter.get(methodName);
if (getter != null) {
@@ -248,25 +261,49 @@ class BuilderMethodClassifier {
private boolean classifyGetter(
ExecutableElement builderGetter, ExecutableElement originalGetter) {
- if (!TYPE_EQUIVALENCE.equivalent(
- builderGetter.getReturnType(), originalGetter.getReturnType())) {
- String error = String.format(
- "Method matches a property of %s but has return type %s instead of %s",
- autoValueClass, builderGetter.getReturnType(), originalGetter.getReturnType());
- errorReporter.reportError(error, builderGetter);
- return false;
+ String propertyName = getterToPropertyName.get(originalGetter);
+ TypeMirror builderGetterType = builderMethodReturnType(builderGetter);
+ String builderGetterTypeString = typeSimplifier.simplify(builderGetterType);
+ TypeMirror originalGetterType = originalGetter.getReturnType();
+ if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) {
+ builderGetters.put(
+ propertyName, new BuilderSpec.PropertyGetter(builderGetterTypeString, null));
+ return true;
}
- propertiesWithBuilderGetters.add(getterToPropertyName.get(originalGetter));
- return true;
+ Optionalish optional = Optionalish.createIfOptional(
+ builderGetterType, typeSimplifier.simplifyRaw(builderGetterType));
+ if (optional != null) {
+ TypeMirror containedType = optional.getContainedType(typeUtils);
+ // If the original method is int getFoo() then we allow Optional<Integer> here.
+ // boxedOriginalType is Integer, and containedType is also Integer.
+ // We don't need any special code for OptionalInt because containedType will be int then.
+ TypeMirror boxedOriginalType = (originalGetterType.getKind().isPrimitive())
+ ? typeUtils.boxedClass(MoreTypes.asPrimitiveType(originalGetterType))
+ .asType()
+ : null;
+ if (TYPE_EQUIVALENCE.equivalent(containedType, originalGetterType)
+ || TYPE_EQUIVALENCE.equivalent(containedType, boxedOriginalType)) {
+ builderGetters.put(
+ propertyName,
+ new BuilderSpec.PropertyGetter(builderGetterTypeString, optional));
+ return true;
+ }
+ }
+ String error = String.format(
+ "Method matches a property of %1$s but has return type %2$s instead of %3$s "
+ + "or an Optional wrapping of %3$s",
+ autoValueClass, builderGetterType, originalGetter.getReturnType());
+ errorReporter.reportError(error, builderGetter);
+ return false;
}
// Construct this string so it won't be found by Maven shading and renamed, which is not what
// we want.
private static final String COM_GOOGLE_COMMON_COLLECT_IMMUTABLE =
- new StringBuilder("com.").append("google.common.collect.Immutable").toString();
+ "com.".concat("google.common.collect.Immutable");
private boolean classifyPropertyBuilder(ExecutableElement method, String property) {
- TypeMirror builderTypeMirror = method.getReturnType();
+ TypeMirror builderTypeMirror = builderMethodReturnType(method);
TypeElement builderTypeElement = MoreTypes.asTypeElement(builderTypeMirror);
String builderTypeString = builderTypeElement.getQualifiedName().toString();
boolean isGuavaBuilder = (builderTypeString.startsWith(COM_GOOGLE_COMMON_COLLECT_IMMUTABLE)
@@ -328,11 +365,13 @@ class BuilderMethodClassifier {
// propertyNameToSetters can't be null when we call put on it below.
errorReporter.reportError(
"Method does not correspond to a property of " + autoValueClass, method);
+ checkForFailedJavaBean(method);
return false;
}
if (!checkSetterParameter(valueGetter, method)) {
return false;
- } else if (!TYPE_EQUIVALENCE.equivalent(method.getReturnType(), builderType.asType())) {
+ } else if (
+ !TYPE_EQUIVALENCE.equivalent(builderMethodReturnType(method), builderType.asType())) {
errorReporter.reportError(
"Setter methods must return " + builderType + typeParamsString(), method);
return false;
@@ -342,10 +381,29 @@ class BuilderMethodClassifier {
}
}
+ // A frequence source of problems is where the JavaBeans conventions have been followed for
+ // most but not all getters. Then AutoValue considers that they haven't been followed at all,
+ // so you might have a property called getFoo where you thought it was called just foo, and
+ // you might not understand why your setter called setFoo is rejected (it would have to be called
+ // setGetFoo).
+ private void checkForFailedJavaBean(ExecutableElement rejectedSetter) {
+ ImmutableSet<ExecutableElement> allGetters = getterToPropertyName.keySet();
+ ImmutableSet<ExecutableElement> prefixedGetters =
+ AutoValueProcessor.prefixedGettersIn(allGetters);
+ if (prefixedGetters.size() < allGetters.size()
+ && prefixedGetters.size() >= allGetters.size() / 2) {
+ String note =
+ "This might be because you are using the getFoo() convention"
+ + " for some but not all methods. These methods don't follow the convention: "
+ + Sets.difference(allGetters, prefixedGetters);
+ errorReporter.reportNote(note, rejectedSetter);
+ }
+ }
+
/**
* Checks that the given setter method has a parameter type that is compatible with the return
* type of the given getter. Compatible means either that it is the same, or that it is a type
- * that can be copied using a method like {@code ImmutableList.copyOf}.
+ * that can be copied using a method like {@code ImmutableList.copyOf} or {@code Optional.of}.
*
* @return true if the types correspond, false if an error has been reported.
*/
@@ -436,11 +494,12 @@ class BuilderMethodClassifier {
if (!targetType.getKind().equals(TypeKind.DECLARED)) {
return ImmutableList.of();
}
+ String copyOf = Optionalish.isOptional(targetType) ? "of" : "copyOf";
TypeElement immutableTargetType = MoreElements.asType(typeUtils.asElement(targetType));
ImmutableList.Builder<ExecutableElement> copyOfMethods = ImmutableList.builder();
for (ExecutableElement method :
ElementFilter.methodsIn(immutableTargetType.getEnclosedElements())) {
- if (method.getSimpleName().contentEquals("copyOf")
+ if (method.getSimpleName().contentEquals(copyOf)
&& method.getParameters().size() == 1
&& method.getModifiers().contains(Modifier.STATIC)) {
copyOfMethods.add(method);
@@ -449,6 +508,40 @@ class BuilderMethodClassifier {
return copyOfMethods.build();
}
+ /**
+ * Returns the return type of the given method from the builder. This should be the final type of
+ * the method when any bound type variables are substituted. Consider this example:
+ * <pre>{@code
+ * abstract static class ParentBuilder<B extends ParentBuilder> {
+ * B setFoo(String s);
+ * }
+ * abstract static class ChildBuilder extends ParentBuilder<ChildBuilder> {
+ * ...
+ * }
+ * }</pre>
+ * If the builder is {@code ChildBuilder} then the return type of {@code setFoo} is also
+ * {@code ChildBuilder}, and not {@code B} as its {@code getReturnType()} method would claim.
+ *
+ * <p>If the caller is in a version of Eclipse with
+ * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=382590">this bug</a> then the
+ * {@code asMemberOf} call will fail if the method is inherited from an interface. We work around
+ * that for methods in the {@code @AutoValue} class using {@link EclipseHack#methodReturnTypes}
+ * but we don't try to do so here because it should be much less likely. You might need to change
+ * {@code ParentBuilder} from an interface to an abstract class to make it work, but you'll often
+ * need to do that anyway.
+ */
+ private TypeMirror builderMethodReturnType(ExecutableElement builderMethod) {
+ DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
+ TypeMirror methodMirror;
+ try {
+ methodMirror = typeUtils.asMemberOf(builderTypeMirror, builderMethod);
+ } catch (IllegalArgumentException e) {
+ // Presumably we've hit the Eclipse bug cited.
+ return builderMethod.getReturnType();
+ }
+ return MoreTypes.asExecutable(methodMirror).getReturnType();
+ }
+
private String prefixWithSet(String propertyName) {
// This is not internationalizationally correct, but it corresponds to what
// Introspector.decapitalize does.
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
index cab26774..46f17cf0 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
@@ -186,7 +186,8 @@ class BuilderSpec {
processingEnv,
autoValueClass,
builderTypeElement,
- getterToPropertyName);
+ getterToPropertyName,
+ typeSimplifier);
if (!optionalClassifier.isPresent()) {
return;
}
@@ -210,7 +211,7 @@ class BuilderSpec {
vars.builderFormalTypes = typeSimplifier.formalTypeParametersString(builderTypeElement);
vars.builderActualTypes = TypeSimplifier.actualTypeParametersString(builderTypeElement);
vars.buildMethodName = buildMethod.getSimpleName().toString();
- vars.propertiesWithBuilderGetters = classifier.propertiesWithBuilderGetters();
+ vars.builderGetters = classifier.builderGetters();
ImmutableMultimap.Builder<String, PropertySetter> setterBuilder = ImmutableMultimap.builder();
for (Map.Entry<String, ExecutableElement> entry :
@@ -227,7 +228,9 @@ class BuilderSpec {
Set<Property> required = Sets.newLinkedHashSet(vars.props);
for (Property property : vars.props) {
- if (property.isNullable() || vars.builderPropertyBuilders.containsKey(property.getName())) {
+ if (property.isNullable()
+ || property.getOptional() != null
+ || vars.builderPropertyBuilders.containsKey(property.getName())) {
required.remove(property);
}
}
@@ -253,12 +256,53 @@ class BuilderSpec {
}
/**
+ * Information about a builder property getter, referenced from the autovalue.vm template. A
+ * property called foo (defined by a method {@code T foo()} or {@code T getFoo()}) can have a
+ * getter method in the builder with the same name ({@code foo()} or {@code getFoo()}) and a
+ * return type of either {@code T} or {@code Optional<T>}. The {@code Optional<T>} form can be
+ * used to tell whether the property has been set. Here, {@code Optional<T>} can be either
+ * {@code java.util.Optional} or {@code com.google.common.base.Optional}. If {@code T} is {@code
+ * int}, {@code long}, or {@code double}, then instead of {@code Optional<T>} we can have {@code
+ * OptionalInt} etc. If {@code T} is a primitive type (including these ones but also the other
+ * five) then {@code Optional<T>} can be the corresponding boxed type.
+ */
+ public static class PropertyGetter {
+ private final String type;
+ private final Optionalish optional;
+
+ /**
+ * Makes a new {@code PropertyGetter} instance.
+ *
+ * @param type the type that the getter returns. This is written to take imports into account,
+ * so it might be {@code List<String>} for example. It is either identical to the type
+ * of the corresponding getter in the {@code @AutoValue} class, or it is an optional
+ * wrapper, like {@code Optional<List<String>>}.
+ * @param optional a representation of the {@code Optional} type that the getter returns, if
+ * this is an optional getter, or null otherwise. An optional getter is one that returns
+ * {@code Optional<T>} rather than {@code T}, as explained above.
+ */
+ PropertyGetter(String type, Optionalish optional) {
+ this.type = type;
+ this.optional = optional;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Optionalish getOptional() {
+ return optional;
+ }
+ }
+
+ /**
* Information about a property setter, referenced from the autovalue.vm template. A property
* called foo (defined by a method {@code T foo()} or {@code T getFoo()}) can have a setter
* method {@code foo(T)} or {@code setFoo(T)} that returns the builder type. Additionally, it
* can have a setter with a type that can be copied to {@code T} through a {@code copyOf} method;
* for example a property {@code foo} of type {@code ImmutableSet<String>} can be set with a
- * method {@code setFoo(Collection<String> foos)}.
+ * method {@code setFoo(Collection<String> foos)}. And, if {@code T} is {@code Optional},
+ * it can have a setter with a type that can be copied to {@code T} through {@code Optional.of}.
*/
public class PropertySetter {
private final String name;
@@ -277,9 +321,13 @@ class BuilderSpec {
Types typeUtils = processingEnv.getTypeUtils();
TypeMirror erasedPropertyType = typeUtils.erasure(propertyType);
boolean sameType = typeUtils.isSameType(typeUtils.erasure(parameterType), erasedPropertyType);
- this.copyOf = sameType
- ? null
- : typeSimplifier.simplifyRaw(erasedPropertyType) + ".copyOf(%s)";
+ if (sameType) {
+ this.copyOf = null;
+ } else {
+ String rawTarget = typeSimplifier.simplifyRaw(erasedPropertyType);
+ String of = Optionalish.isOptional(propertyType) ? "of" : "copyOf";
+ this.copyOf = rawTarget + "." + of + "(%s)";
+ }
}
public String getName() {
diff --git a/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java
index 6e519213..23f7e5ce 100644
--- a/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java
+++ b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java
@@ -36,6 +36,16 @@ class ErrorReporter {
}
/**
+ * Issue a compilation note.
+ *
+ * @param msg the text of the note
+ * @param e the element to which it pertains
+ */
+ void reportNote(String msg, Element e) {
+ messager.printMessage(Diagnostic.Kind.NOTE, msg, e);
+ }
+
+ /**
* Issue a compilation warning.
*
* @param msg the text of the warning
diff --git a/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java b/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java
index e5133f0f..6b0fd701 100644
--- a/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java
+++ b/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java
@@ -2,8 +2,10 @@ package com.google.auto.value.processor;
import com.google.auto.value.extension.AutoValueExtension;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import java.util.Map;
+import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
@@ -13,15 +15,18 @@ class ExtensionContext implements AutoValueExtension.Context {
private final ProcessingEnvironment processingEnvironment;
private final TypeElement typeElement;
- private ImmutableMap<String, ExecutableElement> properties;
+ private final ImmutableMap<String, ExecutableElement> properties;
+ private final ImmutableSet<ExecutableElement> abstractMethods;
ExtensionContext(
ProcessingEnvironment processingEnvironment,
TypeElement typeElement,
- ImmutableMap<String, ExecutableElement> properties) {
+ ImmutableMap<String, ExecutableElement> properties,
+ ImmutableSet<ExecutableElement> abstractMethods) {
this.processingEnvironment = processingEnvironment;
this.typeElement = typeElement;
this.properties = properties;
+ this.abstractMethods = abstractMethods;
}
@Override
@@ -44,7 +49,8 @@ class ExtensionContext implements AutoValueExtension.Context {
return properties;
}
- public void setProperties(Map<String, ExecutableElement> properties) {
- this.properties = ImmutableMap.copyOf(properties);
+ @Override
+ public Set<ExecutableElement> abstractMethods() {
+ return abstractMethods;
}
}
diff --git a/value/src/main/java/com/google/auto/value/processor/Java8Support.java b/value/src/main/java/com/google/auto/value/processor/Java8Support.java
new file mode 100644
index 00000000..0e44b8a2
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/Java8Support.java
@@ -0,0 +1,51 @@
+package com.google.auto.value.processor;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Provides access to Java 8 type annotations via reflection, to allow running on
+ * older Java versions.
+ *
+ * @author Till Brychcy
+ */
+class Java8Support {
+ private static Method determineAnnotationsMirrorsMethod() {
+ try {
+ return Class.forName("javax.lang.model.AnnotatedConstruct").getMethod("getAnnotationMirrors");
+ } catch (Exception e) {
+ // method and type only exist on java 8 and later
+ return null;
+ }
+ }
+
+ static Method getAnnotationsMirrorsMethod = determineAnnotationsMirrorsMethod();
+
+ /**
+ * Provides access to {@link javax.lang.model.AnnotatedConstruct#getAnnotationMirrors()} via
+ * reflection.
+ *
+ * @param typeMirror the type whose annotations are to be returned.
+ * @return if possible, the result of {@code typeMirror.getAnnotationMirrors()},
+ * otherwise an empty list.
+ */
+ static List<? extends AnnotationMirror> getAnnotationMirrors(TypeMirror typeMirror) {
+ if (getAnnotationsMirrorsMethod == null) {
+ return Collections.emptyList();
+ }
+ try {
+ @SuppressWarnings("unchecked")
+ List<? extends AnnotationMirror> annotations =
+ (List<? extends AnnotationMirror>) getAnnotationsMirrorsMethod.invoke(typeMirror);
+ return annotations;
+ } catch (Exception e) {
+ throw new RuntimeException("exception during invocation of getAnnotationMirrors", e);
+ }
+ }
+
+ private Java8Support() {}
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/Optionalish.java b/value/src/main/java/com/google/auto/value/processor/Optionalish.java
new file mode 100644
index 00000000..012ca447
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/Optionalish.java
@@ -0,0 +1,116 @@
+package com.google.auto.value.processor;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.List;
+
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+
+/**
+ * A wrapper for properties of Optional-like classes. This can be com.google.common.base.Optional,
+ * or any of Optional, OptionalDouble, OptionalInt, OptionalLong in java.util.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+public class Optionalish {
+ private static final ImmutableSet<String> OPTIONAL_CLASS_NAMES = ImmutableSet.of(
+ "com.".concat("google.common.base.Optional"), // subterfuge to foil shading
+ "java.util.Optional",
+ "java.util.OptionalDouble",
+ "java.util.OptionalInt",
+ "java.util.OptionalLong");
+
+ private final DeclaredType optionalType;
+ private final String rawTypeSpelling;
+
+ private Optionalish(DeclaredType optionalType, String rawTypeSpelling) {
+ this.optionalType = optionalType;
+ this.rawTypeSpelling = rawTypeSpelling;
+ }
+
+ /**
+ * Returns an instance wrapping the given TypeMirror, or null if it is not any kind of Optional.
+ *
+ * @param type the TypeMirror for the original optional type, for example
+ * {@code Optional<String>}.
+ * @param rawTypeSpelling the representation of the base Optional type in source code, given
+ * the imports that will be present. Usually this will be {@code Optional},
+ * {@code OptionalInt}, etc. In cases of ambiguity it might be {@code java.util.Optional} etc.
+ */
+ static Optionalish createIfOptional(TypeMirror type, String rawTypeSpelling) {
+ if (isOptional(type)) {
+ return new Optionalish(
+ MoreTypes.asDeclared(type), Preconditions.checkNotNull(rawTypeSpelling));
+ } else {
+ return null;
+ }
+ }
+
+ static boolean isOptional(TypeMirror type) {
+ if (type.getKind() != TypeKind.DECLARED) {
+ return false;
+ }
+ DeclaredType declaredType = MoreTypes.asDeclared(type);
+ TypeElement typeElement = MoreElements.asType(declaredType.asElement());
+ return OPTIONAL_CLASS_NAMES.contains(typeElement.getQualifiedName().toString())
+ && typeElement.getTypeParameters().size() == declaredType.getTypeArguments().size();
+ }
+
+ /**
+ * Returns a string representing the raw type of this Optional. This will typically be just
+ * {@code "Optional"}, but it might be {@code "OptionalInt"} or {@code "java.util.Optional"}
+ * for example.
+ */
+ public String getRawType() {
+ return rawTypeSpelling;
+ }
+
+ /**
+ * Returns a string representing the method call to obtain the empty version of this Optional.
+ * This will be something like {@code "Optional.empty()"} or possibly
+ * {@code "java.util.Optional.empty()"}. It does not have a final semicolon.
+ *
+ * <p>This method is public so that it can be referenced as {@code p.optional.empty} from
+ * templates.
+ */
+ public String getEmpty() {
+ TypeElement typeElement = MoreElements.asType(optionalType.asElement());
+ String empty = typeElement.getQualifiedName().toString().startsWith("java.util.")
+ ? ".empty()"
+ : ".absent()";
+ return rawTypeSpelling + empty;
+ }
+
+ TypeMirror getContainedType(Types typeUtils) {
+ List<? extends TypeMirror> typeArguments = optionalType.getTypeArguments();
+ switch (typeArguments.size()) {
+ case 1:
+ return typeArguments.get(0);
+ case 0:
+ return getContainedPrimitiveType(typeUtils);
+ default:
+ throw new AssertionError("Wrong number of type arguments: " + optionalType);
+ }
+ }
+
+ private static final ImmutableMap<String, TypeKind> PRIMITIVE_TYPE_KINDS = ImmutableMap.of(
+ "OptionalDouble", TypeKind.DOUBLE,
+ "OptionalInt", TypeKind.INT,
+ "OptionalLong", TypeKind.LONG);
+
+ private TypeMirror getContainedPrimitiveType(Types typeUtils) {
+ String simpleName = optionalType.asElement().getSimpleName().toString();
+ TypeKind typeKind = PRIMITIVE_TYPE_KINDS.get(simpleName);
+ Verify.verifyNotNull(typeKind, "Could not get contained type of %s", optionalType);
+ return typeUtils.getPrimitiveType(typeKind);
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
index b2b2c4b9..e88e5a7c 100644
--- a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
+++ b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
@@ -29,6 +29,7 @@ import java.util.Map;
import java.util.Set;
import javax.lang.model.element.Element;
+import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
@@ -434,19 +435,26 @@ final class TypeSimplifier {
private static Set<String> ambiguousNames(Types typeUtils, Set<TypeMirror> types) {
Set<String> ambiguous = new HashSet<String>();
- Set<String> simpleNames = new HashSet<String>();
+ Map<String, Name> simpleNamesToQualifiedNames = new HashMap<String, Name>();
for (TypeMirror type : types) {
if (type.getKind() == TypeKind.ERROR) {
throw new MissingTypeException();
}
String simpleName = typeUtils.asElement(type).getSimpleName().toString();
- if (!simpleNames.add(simpleName)) {
+ /*
+ * Compare by qualified names, because in Eclipse JDT, if Java 8 type annotations are used,
+ * the same (unannotated) type may appear multiple times in the Set<TypeMirror>.
+ * TODO(emcmanus): investigate further, because this might cause problems elsewhere.
+ */
+ Name qualifiedName = ((TypeElement)typeUtils.asElement(type)).getQualifiedName();
+ Name previous = simpleNamesToQualifiedNames.put(simpleName, qualifiedName);
+ if (previous != null && !previous.equals(qualifiedName)) {
ambiguous.add(simpleName);
}
}
return ambiguous;
}
-
+
/**
* Returns true if casting to the given type will elicit an unchecked warning from the
* compiler. Only generic types such as {@code List<String>} produce such warnings. There will be
diff --git a/value/src/main/java/com/google/auto/value/processor/autovalue.vm b/value/src/main/java/com/google/auto/value/processor/autovalue.vm
index 31d557bc..3a679fc3 100644
--- a/value/src/main/java/com/google/auto/value/processor/autovalue.vm
+++ b/value/src/main/java/com/google/auto/value/processor/autovalue.vm
@@ -40,7 +40,7 @@ ${gwtCompatibleAnnotation}
${p.nullableAnnotation}$p.type $p #if ($foreach.hasNext) , #end
#end ) {
#foreach ($p in $props)
- #if (!$p.kind.primitive && !$p.nullable && !$builderPropertyBuilders[$p.name])
+ #if (!$p.kind.primitive && !$p.nullable && $builderTypeName == "")
if ($p == null) {
throw new NullPointerException("Null $p.name");
@@ -206,7 +206,7 @@ ${gwtCompatibleAnnotation}
#end
- private $p.type $p;
+ private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ;
#end
#end
@@ -276,28 +276,39 @@ ${gwtCompatibleAnnotation}
## Getter
- #if ($propertiesWithBuilderGetters.contains($p.name))
+ #if ($builderGetters[$p.name])
@Override
- ${p.nullableAnnotation}public $p.type ${p.getter}() {
- #if ($builderRequiredProperties.contains($p))
+ ${p.nullableAnnotation}public $builderGetters[$p.name].type ${p.getter}() {
+ #if ($builderGetters[$p.name].optional)
+
+ if ($p == null) {
+ return $builderGetters[$p.name].optional.empty;
+ } else {
+ return ${builderGetters[$p.name].optional.rawType}.of($p);
+ }
+
+ #else
+ #if ($builderRequiredProperties.contains($p))
if ($p == null) {
throw new IllegalStateException("Property \"$p.name\" has not been set");
}
- #end
+ #end
- #if ($builderPropertyBuilders[$p.name])
+ #if ($builderPropertyBuilders[$p.name])
if (${propertyBuilder.name} != null) {
return ${propertyBuilder.name}.build();
}
- #end
+ #end
return $p;
+ #end
+
}
#end
@@ -316,7 +327,6 @@ ${gwtCompatibleAnnotation}
#end
#end
-
#if (!$builderRequiredProperties.empty)
String missing = "";
diff --git a/value/src/main/java/com/google/auto/value/processor/escapevelocity/README.md b/value/src/main/java/com/google/auto/value/processor/escapevelocity/README.md
new file mode 100644
index 00000000..d0d6137d
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/escapevelocity/README.md
@@ -0,0 +1,339 @@
+# EscapeVelocity summary
+
+EscapeVelocity is a templating engine that can be used from Java. It is a reimplementation of a subset of
+functionality from [Apache Velocity](http://velocity.apache.org/).
+
+For a fuller explanation of Velocity's functioning, see its
+[User Guide](http://velocity.apache.org/engine/releases/velocity-1.7/user-guide.html)
+
+If EscapeVelocity successfully produces a result from a template evaluation, that result should be
+the exact same string that Velocity produces. If not, that is a bug.
+
+EscapeVelocity has no facilities for HTML escaping and it is not appropriate for producing
+HTML output that might include portions of untrusted input.
+
+
+## Motivation
+
+Velocity has a convenient templating language. It is easy to read, and it has widespread support
+from tools such as editors and coding websites. However, *using* Velocity can prove difficult.
+Its use to generate Java code in the [AutoValue][AutoValue] annotation processor required many
+[workarounds][VelocityHacks]. The way it dynamically loads classes as part of its standard operation
+makes it hard to [shade](https://maven.apache.org/plugins/maven-shade-plugin/) it, which in the case
+of AutoValue led to interference if Velocity was used elsewhere in a project.
+
+EscapeVelocity has a simple API that does not involve any class-loading or other sources of
+problems. It and its dependencies can be shaded with no difficulty.
+
+## Loading a template
+
+The entry point for EscapeVelocity is the `Template` class. To obtain an instance, use
+`Template.from(Reader)`. If a template is stored in a file, that file conventionally has the
+suffix `.vm` (for Velocity Macros). But since the argument is a `Reader`, you can also load
+a template directly from a Java string, using `StringReader`.
+
+Here's how you might make a `Template` instance from a template file that is packaged as a resource
+in the same package as the calling class:
+
+```java
+InputStream in = getClass().getResourceAsStream("foo.vm");
+if (in == null) {
+ throw new IllegalArgumentException("Could not find resource foo.vm");
+}
+Reader reader = new BufferedReader(new InputStreamReader(in));
+Template template = Template.from(reader);
+```
+
+## Expanding a template
+
+Once you have a `Template` object, you can use it to produce a string where the variables in the
+template are given the values you provide. You can do this any number of times, specifying the
+same or different values each time.
+
+Suppose you have this template:
+
+```
+The $language word for $original is $translated.
+```
+
+You might write this code:
+
+```java
+Map<String, String> vars = new HashMap<>();
+vars.put("language", "French");
+vars.put("original", "toe");
+vars.put("translated", "orteil");
+String result = template.evaluate(vars);
+```
+
+The `result` string would then be: `The French word for toe is orteil.`
+
+## Comments
+
+The characters `##` introduce a comment. Characters from `##` up to and including the following
+newline are omitted from the template. This template has comments:
+
+```
+Line 1 ## with a comment
+Line 2
+```
+
+It is the same as this template:
+```
+Line 1 Line 2
+```
+
+## References
+
+EscapeVelocity supports most of the reference types described in the
+[Velocity User Guide](http://velocity.apache.org/engine/releases/velocity-1.7/user-guide.html#References)
+
+### Variables
+
+A variable has an ASCII name that starts with a letter (a-z or A-Z) and where any other characters
+are also letters or digits or hyphens (-) or underscores (_). A variable reference can be written
+as `$foo` or as `${foo}`. The value of a variable can be of any Java type. If the value `v` of
+variable `foo` is not a String then the result of `$foo` in a template will be `String.valueOf(v)`.
+Variables must be defined before they are referenced; otherwise an `EvaluationException` will be
+thrown.
+
+Variable names are case-sensitive: `$foo` is not the same variable as `$Foo` or `$FOO`.
+
+Initially the values of variables come from the Map that is passed to `Template.evaluate`. Those
+values can be changed, and new ones defined, using the `#set` directive in the template:
+
+```
+#set ($foo = "bar")
+```
+
+Setting a variable affects later references to it in the template, but has no effect on the
+`Map` that was passed in or on later template evaluations.
+
+### Properties
+
+If a reference looks like `$purchase.Total` then the value of the `$purchase` variable must be a
+Java object that has a public method `getTotal()` or `gettotal()`, or a method called `isTotal()` or
+`istotal()` that returns `boolean`. The result of `$purchase.Total` is then the result of calling
+that method on the `$purchase` object.
+
+If you want to have a period (`.`) after a variable reference *without* it being a property
+reference, you can use braces like this: `${purchase}.Total`. If, after a property reference, you
+have a further period, you can put braces around the reference like this:
+`${purchase.Total}.nonProperty`.
+
+### Methods
+
+If a reference looks like `$purchase.addItem("scones", 23)` then the value of the `$purchase`
+variable must be a Java object that has a public method `addItem` with two parameters that match
+the given values. Unlike Velocity, EscapeVelocity requires that there be exactly one such method.
+It is OK if there are other `addItem` methods provided they are not compatible with the
+arguments provided.
+
+Properties are in fact a special case of methods: instead of writing `$purchase.Total` you could
+write `$purchase.getTotal()`. Braces can be used to make the method invocation explicit
+(`${purchase.getTotal()}`) or to prevent method invocation (`${purchase}.getTotal()`).
+
+### Indexing
+
+If a reference looks like `$indexme[$i]` then the value of the `$indexme` variable must be a Java
+object that has a public `get` method that takes one argument that is compatible with the index.
+For example, `$indexme` might be a `List` and `$i` might be an integer. Then the reference would
+be the result of `List.get(int)` for that list and that integer. Or, `$indexme` might be a `Map`,
+and the reference would be the result of `Map.get(Object)` for the object `$i`. In general,
+`$indexme[$i]` is equivalent to `$indexme.get($i)`.
+
+Unlike Velocity, EscapeVelocity does not allow `$indexme` to be a Java array.
+
+### Undefined references
+
+If a variable has not been given a value, either by being in the initial Map argument or by being
+set in the template, then referencing it will provoke an `EvaluationException`. There is
+a special case for `#if`: if you write `#if ($var)` then it is allowed for `$var` not to be defined,
+and it is treated as false.
+
+### Setting properties and indexes: not supported
+
+Unlke Velocity, EscapeVelocity does not allow `#set` assignments with properties or indexes:
+
+```
+#set ($data.User = "jon") ## Allowed in Velocity but not in EscapeVelocity
+#set ($map["apple"] = "orange") ## Allowed in Velocity but not in EscapeVelocity
+```
+
+## Expressions
+
+In certain contexts, such as the `#set` directive we have just seen or certain other directives,
+EscapeVelocity can evaluate expressions. An expression can be any of these:
+
+* A reference, of the kind we have just seen. The value is the value of the reference.
+* A string literal enclosed in double quotes, like `"this"`. A string literal must appear on
+ one line. EscapeVelocity does not support the characters `$` or `\\` in a string literal.
+* An integer literal such as `23` or `-100`. EscapeVelocity does not support floating-point
+ literals.
+* A Boolean literal, `true` or `false`.
+* Simpler expressions joined together with operators that have the same meaning as in Java:
+ `!`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `&&`, `||`, `+`, `-`, `*`, `/`, `%`. The operators have the
+ same precedence as in Java.
+* A simpler expression in parentheses, for example `(2 + 3)`.
+
+Velocity supports string literals with single quotes, like `'this`' and also references within
+strings, like `"a $reference in a string"`, but EscapeVelocity does not.
+
+## Directives
+
+A directive is introduced by a `#` character followed by a word. We have already seen the `#set`
+directive, which sets the value of a variable. The other directives are listed below.
+
+Directives can be spelled with or without braces, so `#set` or `#{set}`.
+
+### `#if`/`#elseif`/`#else`
+
+The `#if` directive selects parts of the template according as a condition is true or false.
+The simplest case looks like this:
+
+```
+#if ($condition) yes #end
+```
+
+This evaluates to the string ` yes ` if the variable `$condition` is defined and has a true value,
+and to the empty string otherwise. It is allowed for `$condition` not to be defined in this case,
+and then it is treated as false.
+
+The expression in `#if` (here `$condition`) is considered true if its value is not null and not
+equal to the Boolean value `false`.
+
+An `#if` directive can also have an `#else` part, for example:
+
+```
+#if ($condition) yes #else no #end
+```
+
+This evaluates to the string ` yes ` if the condition is true or the string ` no ` if it is not.
+
+An `#if` directive can have any number of `#elseif` parts. For example:
+
+```
+#if ($i == 0) zero #elseif ($i == 1) one #elseif ($i == 2) two #else many #end
+```
+
+### `#foreach`
+
+The `#foreach` directive repeats a part of the template once for each value in a list.
+
+```
+#foreach ($product in $allProducts)
+ ${product}!
+#end
+```
+
+This will produce one line for each value in the `$allProducts` variable. The value of
+`$allProducts` can be a Java `Iterable`, such as a `List` or `Set`; or it can be an object array;
+or it can be a Java `Map`. When it is a `Map` the `#foreach` directive loops over every *value*
+in the `Map`.
+
+If `$allProducts` is a `List` containing the strings `oranges` and `lemons` then the result of the
+`#foreach` would be this:
+
+```
+
+ oranges!
+
+
+ lemons!
+
+```
+
+When the `#foreach` completes, the loop variable (`$product` in the example) goes back to whatever
+value it had before, or to being undefined if it was undefined before.
+
+Within the `#foreach`, a special variable `$foreach` is defined, such that you can write
+`$foreach.hasNext`, which will be true if there are more values after this one or false if this
+is the last value. For example:
+
+```
+#foreach ($product in $allProducts)${product}#if ($foreach.hasNext), #end#end
+```
+
+This would produce the output `oranges, lemons` for the list above. (The example is scrunched up
+to avoid introducing extraneous spaces, as described in the [section](#spaces) on spaces
+below.)
+
+Velocity gives the `$foreach` variable other properties (`index` and `count`) but EscapeVelocity
+does not.
+
+### Macros
+
+A macro is a part of the template that can be reused in more than one place, potentially with
+different parameters each time. In the simplest case, a macro has no arguments:
+
+```
+#macro (hello) bonjour #end
+```
+
+Then the macro can be referenced by writing `#hello()` and the result will be the string ` bonjour `
+inserted at that point.
+
+Macros can also have parameters:
+
+```
+#macro (greet $hello $world) $hello, $world! #end
+```
+
+Then `#greet("bonjour", "monde")` would produce ` bonjour, monde! `. The comma is optional, so
+you could also write `#greet("bonjour" "monde")`.
+
+When a macro completes, the parameters (`$hello` and `$world` in the example) go back to whatever
+values they had before, or to being undefined if they were undefined before.
+
+All macro definitions take effect before the template is evaluated, so you can use a macro at a
+point in the template that is before the point where it is defined. This also means that you can't
+define a macro conditionally:
+
+```
+## This doesn't work!
+#if ($language == "French")
+#macro (hello) bonjour #end
+#else
+#macro (hello) hello #end
+#end
+```
+
+There is no particular reason to define the same macro more than once, but if you do it is the
+first definition that is retained. In the `#if` example just above, the `bonjour` version will
+always be used.
+
+Macros can make templates hard to understand. You may prefer to put the logic in a Java method
+rather than a macro, and call the method from the template using `$methods.doSomething("foo")`
+or whatever.
+
+## <a name="spaces"></a> Spaces
+
+For the most part, spaces and newlines in the template are preserved exactly in the output.
+To avoid unwanted newlines, you may end up using `##` comments. In the `#foreach` example above
+we had this:
+
+```
+#foreach ($product in $allProducts)${product}#if ($foreach.hasNext), #end#end
+```
+
+That was to avoid introducing unwanted spaces and newlines. A more readable way to achieve the same
+result is this:
+
+```
+#foreach ($product in $allProducts)##
+${product}##
+#if ($foreach.hasNext), #end##
+#end
+```
+
+Spaces are ignored between the `#` of a directive and the `)` that closes it, so there is no trace
+in the output of the spaces in `#foreach ($product in $allProducts)` or `#if ($foreach.hasNext)`.
+Spaces are also ignored inside references, such as `$indexme[ $i ]` or `$callme( $i , $j )`.
+
+If you are concerned about the detailed formatting of the text from the template, you may want to
+post-process it. For example, if it is Java code, you could use a formatter such as
+[google-java-format](https://github.com/google/google-java-format). Then you shouldn't have to
+worry about extraneous spaces.
+
+[VelocityHacks]: https://github.com/google/auto/blob/ca2384d5ad15a0c761b940384083cf5c50c6e839/value/src/main/java/com/google/auto/value/processor/TemplateVars.java#L54]
+[AutoValue]: https://github.com/google/auto/tree/master/value
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
index b3138591..1f4f3fd0 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
@@ -112,6 +112,42 @@ public class AutoAnnotationErrorsTest extends TestCase {
.withErrorContaining("@AutoAnnotation methods cannot be overloaded")
.in(testSource).onLine(11);
}
+
+ // Overload detection used to detect all @AutoAnnotation methods that resulted in
+ // annotation class of the same SimpleName as being an overload.
+ // This verifies that implementations in different packages work correctly.
+ public void testSameNameDifferentPackagesDoesNotTriggerOverload() {
+
+ JavaFileObject fooTestSource = JavaFileObjects.forSourceLines(
+ "com.foo.Test",
+ "package com.foo;",
+ "",
+ "import com.example.TestAnnotation;",
+ "import com.google.auto.value.AutoAnnotation;",
+ "",
+ "class Test {",
+ " @AutoAnnotation static TestAnnotation newTestAnnotation(int value) {",
+ " return new AutoAnnotation_Test_newTestAnnotation(value);",
+ " }",
+ "}");
+ JavaFileObject barTestSource = JavaFileObjects.forSourceLines(
+ "com.bar.Test",
+ "package com.bar;",
+ "",
+ "import com.example.TestAnnotation;",
+ "import com.google.auto.value.AutoAnnotation;",
+ "",
+ "class Test {",
+ " @AutoAnnotation static TestAnnotation newTestAnnotation(int value) {",
+ " return new AutoAnnotation_Test_newTestAnnotation(value);",
+ " }",
+ "}");
+
+ assert_().about(javaSources())
+ .that(ImmutableList.of(TEST_ANNOTATION, fooTestSource, barTestSource))
+ .processedWith(new AutoAnnotationProcessor())
+ .compilesWithoutError();
+ }
public void testWrongName() {
JavaFileObject testSource = JavaFileObjects.forSourceLines(
diff --git a/value/src/test/java/com/google/auto/value/processor/CompilationErrorsTest.java b/value/src/test/java/com/google/auto/value/processor/CompilationErrorsTest.java
index 68e87b98..f392c981 100644
--- a/value/src/test/java/com/google/auto/value/processor/CompilationErrorsTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/CompilationErrorsTest.java
@@ -127,7 +127,7 @@ public class CompilationErrorsTest extends TestCase {
}
private static final Pattern CANNOT_HAVE_NON_PROPERTIES = Pattern.compile(
- "@AutoValue classes cannot have abstract methods other than property getters");
+ "Abstract method is neither a property getter nor a Builder converter");
public void testAbstractVoid() throws Exception {
String testSourceCode = Joiner.on('\n').join(
diff --git a/value/src/test/java/com/google/auto/value/processor/CompilationTest.java b/value/src/test/java/com/google/auto/value/processor/CompilationTest.java
index ea9f37c9..aa2e6561 100644
--- a/value/src/test/java/com/google/auto/value/processor/CompilationTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/CompilationTest.java
@@ -587,6 +587,7 @@ public class CompilationTest {
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
+ "import com.google.common.base.Optional;",
"import com.google.common.collect.ImmutableList;",
"",
"import java.util.List;",
@@ -601,6 +602,7 @@ public class CompilationTest {
" @Nullable public abstract int[] aNullableIntArray();",
" public abstract List<T> aList();",
" public abstract ImmutableList<T> anImmutableList();",
+ " public abstract Optional<String> anOptionalString();",
"",
" public abstract Builder<T> toBuilder();",
"",
@@ -612,7 +614,10 @@ public class CompilationTest {
" Builder<T> aList(List<T> x);",
" Builder<T> anImmutableList(List<T> x);",
" ImmutableList.Builder<T> anImmutableListBuilder();",
+ " Builder<T> anOptionalString(Optional<String> s);",
+ " Builder<T> anOptionalString(String s);",
"",
+ " Optional<Integer> anInt();",
" List<T> aList();",
" ImmutableList<T> anImmutableList();",
"",
@@ -627,7 +632,8 @@ public class CompilationTest {
"foo.bar.AutoValue_Baz",
"package foo.bar;",
"",
- "import com.google.common.collect.ImmutableList",
+ "import com.google.common.base.Optional;",
+ "import com.google.common.collect.ImmutableList;",
"import java.util.Arrays;",
"import java.util.List;",
"import javax.annotation.Generated;",
@@ -640,24 +646,21 @@ public class CompilationTest {
" private final int[] aNullableIntArray;",
" private final List<T> aList;",
" private final ImmutableList<T> anImmutableList;",
+ " private final Optional<String> anOptionalString;",
"",
" private AutoValue_Baz(",
" int anInt,",
" byte[] aByteArray,",
" @Nullable int[] aNullableIntArray,",
" List<T> aList,",
- " ImmutableList<T> anImmutableList) {",
+ " ImmutableList<T> anImmutableList,",
+ " Optional<String> anOptionalString) {",
" this.anInt = anInt;",
- " if (aByteArray == null) {",
- " throw new NullPointerException(\"Null aByteArray\");",
- " }",
" this.aByteArray = aByteArray;",
" this.aNullableIntArray = aNullableIntArray;",
- " if (aList == null) {",
- " throw new NullPointerException(\"Null aList\");",
- " }",
" this.aList = aList;",
" this.anImmutableList = anImmutableList;",
+ " this.anOptionalString = anOptionalString;",
" }",
"",
" @Override public int anInt() {",
@@ -683,13 +686,18 @@ public class CompilationTest {
" return anImmutableList;",
" }",
"",
+ " @Override public Optional<String> anOptionalString() {",
+ " return anOptionalString;",
+ " }",
+ "",
" @Override public String toString() {",
" return \"Baz{\"",
" + \"anInt=\" + anInt + \", \"",
" + \"aByteArray=\" + Arrays.toString(aByteArray) + \", \"",
" + \"aNullableIntArray=\" + Arrays.toString(aNullableIntArray) + \", \"",
" + \"aList=\" + aList + \", \"",
- " + \"anImmutableList=\" + anImmutableList",
+ " + \"anImmutableList=\" + anImmutableList + \", \"",
+ " + \"anOptionalString=\" + anOptionalString",
" + \"}\";",
" }",
"",
@@ -707,7 +715,8 @@ public class CompilationTest {
+ "(that instanceof AutoValue_Baz) "
+ "? ((AutoValue_Baz) that).aNullableIntArray : that.aNullableIntArray()))",
" && (this.aList.equals(that.aList()))",
- " && (this.anImmutableList.equals(that.anImmutableList()));",
+ " && (this.anImmutableList.equals(that.anImmutableList()))",
+ " && (this.anOptionalString.equals(that.anOptionalString()));",
" }",
" return false;",
" }",
@@ -724,6 +733,8 @@ public class CompilationTest {
" h ^= this.aList.hashCode();",
" h *= 1000003;",
" h ^= this.anImmutableList.hashCode();",
+ " h *= 1000003;",
+ " h ^= this.anOptionalString.hashCode();",
" return h;",
" }",
"",
@@ -736,8 +747,9 @@ public class CompilationTest {
" private byte[] aByteArray;",
" private int[] aNullableIntArray;",
" private List<T> aList;",
- " private ImmutableList.Builder<T> anImmutableListBuilder$",
+ " private ImmutableList.Builder<T> anImmutableListBuilder$;",
" private ImmutableList<T> anImmutableList;",
+ " private Optional<String> anOptionalString = Optional.absent();",
"",
" Builder() {",
" this.anImmutableList = ImmutableList.of();",
@@ -749,6 +761,7 @@ public class CompilationTest {
" this.aNullableIntArray = source.aNullableIntArray();",
" this.aList = source.aList();",
" this.anImmutableList = source.anImmutableList();",
+ " this.anOptionalString = source.anOptionalString();",
" }",
"",
" @Override",
@@ -758,6 +771,15 @@ public class CompilationTest {
" }",
"",
" @Override",
+ " public Optional<Integer> anInt() {",
+ " if (anInt == null) {",
+ " return Optional.absent();",
+ " } else {",
+ " return Optional.of(anInt);",
+ " }",
+ " }",
+ "",
+ " @Override",
" public Baz.Builder<T> aByteArray(byte[] aByteArray) {",
" this.aByteArray = aByteArray;",
" return this;",
@@ -787,7 +809,7 @@ public class CompilationTest {
" public Baz.Builder<T> anImmutableList(List<T> anImmutableList) {",
" if (anImmutableListBuilder$ != null) {",
" throw new IllegalStateException("
- + "\"Cannot set anImmutableList after calling anImmutableListBuilder()\")",
+ + "\"Cannot set anImmutableList after calling anImmutableListBuilder()\");",
" }",
" this.anImmutableList = ImmutableList.copyOf(anImmutableList);",
" return this;",
@@ -812,6 +834,18 @@ public class CompilationTest {
" }",
"",
" @Override",
+ " public Baz.Builder<T> anOptionalString(Optional<String> anOptionalString) {",
+ " this.anOptionalString = anOptionalString;",
+ " return this;",
+ " }",
+ "",
+ " @Override",
+ " public Baz.Builder<T> anOptionalString(String anOptionalString) {",
+ " this.anOptionalString = Optional.of(anOptionalString);",
+ " return this;",
+ " }",
+ "",
+ " @Override",
" public Baz<T> build() {",
" if (anImmutableListBuilder$ != null) {",
" anImmutableList = anImmutableListBuilder$.build();",
@@ -834,7 +868,8 @@ public class CompilationTest {
" this.aByteArray,",
" this.aNullableIntArray,",
" this.aList,",
- " this.anImmutableList);",
+ " this.anImmutableList,",
+ " this.anOptionalString);",
" }",
" }",
"}");
@@ -1131,6 +1166,39 @@ public class CompilationTest {
.in(javaFileObject).onLine(12);
}
+ // Check that we get a helpful error message if some of your properties look like getters but
+ // others don't.
+ @Test
+ public void autoValueBuilderBeansConfusion() {
+ JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+ "foo.bar.Item",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public abstract class Item {",
+ " abstract String getTitle();",
+ " abstract boolean hasThumbnail();",
+ "",
+ " @AutoValue.Builder",
+ " public interface Builder {",
+ " Builder setTitle(String title);",
+ " Builder setHasThumbnail(boolean t);",
+ " Item build();",
+ " }",
+ "}");
+ assertAbout(javaSource())
+ .that(javaFileObject)
+ .processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+ .failsToCompile()
+ .withErrorContaining("Method does not correspond to a property of foo.bar.Item")
+ .in(javaFileObject).onLine(12)
+ .and()
+ .withNoteContaining("hasThumbnail")
+ .in(javaFileObject).onLine(12);
+ }
+
@Test
public void autoValueBuilderExtraSetter() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
diff --git a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
index ec55fc74..b0844935 100644
--- a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
@@ -1,11 +1,12 @@
package com.google.auto.value.processor;
-import static com.google.common.truth.Truth.assertAbout;
-import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
+import static com.google.testing.compile.JavaSourcesSubject.assertThat;
import com.google.auto.value.extension.AutoValueExtension;
+import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.truth.Truth;
import com.google.testing.compile.JavaFileObjects;
import junit.framework.TestCase;
@@ -15,6 +16,11 @@ import java.util.Map;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
import javax.tools.JavaFileObject;
public class ExtensionTest extends TestCase {
@@ -46,8 +52,7 @@ public class ExtensionTest extends TestCase {
" }",
"}"
);
- assertAbout(javaSource())
- .that(javaFileObject)
+ assertThat(javaFileObject)
.processedWith(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
.compilesWithoutError()
.and().generatesSources(expectedExtensionOutput);
@@ -118,14 +123,199 @@ public class ExtensionTest extends TestCase {
+ "\n"
+ "}"
);
- assertAbout(javaSource())
- .that(javaFileObject)
- .processedWith(
- new AutoValueProcessor(ImmutableList.<AutoValueExtension>of(new FooExtension())))
+ assertThat(javaFileObject)
+ .processedWith(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
.compilesWithoutError()
.and().generatesSources(expectedExtensionOutput);
}
+ public void testDoesntRaiseWarningForConsumedProperties() {
+ JavaFileObject impl = JavaFileObjects.forSourceLines("foo.bar.Baz",
+ "package foo.bar;",
+ "import com.google.auto.value.AutoValue;",
+ "@AutoValue public abstract class Baz {",
+ " abstract String foo();",
+ " abstract String dizzle();",
+ "",
+ " @AutoValue.Builder",
+ " public abstract static class Builder {",
+ " public abstract Builder foo(String s);",
+ " public abstract Baz build();",
+ " }",
+ "}");
+ assertThat(impl)
+ .withCompilerOptions("-Xlint:-processing")
+ .processedWith(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+ .compilesWithoutWarnings();
+ }
+
+ public void testDoesntRaiseWarningForToBuilder() {
+ JavaFileObject impl = JavaFileObjects.forSourceLines("foo.bar.Baz",
+ "package foo.bar;",
+ "import com.google.auto.value.AutoValue;",
+ "@AutoValue public abstract class Baz {",
+ " abstract String foo();",
+ " abstract String dizzle();",
+ " abstract Builder toBuilder();",
+ "",
+ " @AutoValue.Builder",
+ " public abstract static class Builder {",
+ " public abstract Builder foo(String s);",
+ " public abstract Baz build();",
+ " }",
+ "}");
+ assertThat(impl)
+ .withCompilerOptions("-Xlint:-processing")
+ .processedWith(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+ .compilesWithoutWarnings();
+ }
+
+ public void testCantConsumeTwice() throws Exception {
+ class ConsumeDizzle extends NonFinalExtension {
+ @Override public Set<String> consumeProperties(Context context) {
+ return ImmutableSet.of("dizzle");
+ }
+ }
+ class AlsoConsumeDizzle extends ConsumeDizzle {}
+ AutoValueExtension ext1 = new ConsumeDizzle();
+ AutoValueExtension ext2 = new AlsoConsumeDizzle();
+ Truth.assertThat(ext1).isNotEqualTo(ext2);
+ JavaFileObject impl = JavaFileObjects.forSourceLines("foo.bar.Baz",
+ "package foo.bar;",
+ "import com.google.auto.value.AutoValue;",
+ "@AutoValue public abstract class Baz {",
+ " abstract String foo();",
+ " abstract String dizzle();",
+ "}");
+ assertThat(impl)
+ .processedWith(new AutoValueProcessor(ImmutableList.of(ext1, ext2)))
+ .failsToCompile()
+ .withErrorContaining("wants to consume a method that was already consumed")
+ .in(impl).onLine(5);
+ }
+
+ public void testCantConsumeNonExistentProperty() throws Exception {
+ class ConsumeDizzle extends NonFinalExtension {
+ @Override public Set<String> consumeProperties(Context context) {
+ return ImmutableSet.of("dizzle");
+ }
+ }
+ JavaFileObject impl = JavaFileObjects.forSourceLines("foo.bar.Baz",
+ "package foo.bar;",
+ "import com.google.auto.value.AutoValue;",
+ "@AutoValue public abstract class Baz {",
+ " abstract String foo();",
+ "}");
+ assertThat(impl)
+ .processedWith(new AutoValueProcessor(ImmutableList.of(new ConsumeDizzle())))
+ .failsToCompile()
+ .withErrorContaining("wants to consume a property that does not exist: dizzle")
+ .in(impl).onLine(3);
+ }
+
+ public void testCantConsumeConcreteMethod() throws Exception {
+ class ConsumeConcreteMethod extends NonFinalExtension {
+ @Override public Set<ExecutableElement> consumeMethods(Context context) {
+ TypeElement autoValueClass = context.autoValueClass();
+ for (ExecutableElement method :
+ ElementFilter.methodsIn(autoValueClass.getEnclosedElements())) {
+ if (method.getSimpleName().contentEquals("frob")) {
+ return ImmutableSet.of(method);
+ }
+ }
+ throw new AssertionError("Could not find frob method");
+ }
+ }
+ JavaFileObject impl = JavaFileObjects.forSourceLines("foo.bar.Baz",
+ "package foo.bar;",
+ "import com.google.auto.value.AutoValue;",
+ "@AutoValue public abstract class Baz {",
+ " abstract String foo();",
+ " void frob(int x) {}",
+ "}");
+ assertThat(impl)
+ .processedWith(new AutoValueProcessor(ImmutableList.of(new ConsumeConcreteMethod())))
+ .failsToCompile()
+ .withErrorContaining(
+ "wants to consume a method that is not one of the abstract methods in this class")
+ .in(impl).onLine(3)
+ .and()
+ .withErrorContaining("frob")
+ .in(impl).onLine(3);
+ }
+
+ public void testCantConsumeNonExistentMethod() throws Exception {
+ class ConsumeBogusMethod extends NonFinalExtension {
+ @Override public Set<ExecutableElement> consumeMethods(Context context) {
+ // Find Integer.intValue() and try to consume that.
+ Elements elementUtils = context.processingEnvironment().getElementUtils();
+ TypeElement javaLangInteger = elementUtils.getTypeElement(Integer.class.getName());
+ for (ExecutableElement method :
+ ElementFilter.methodsIn(javaLangInteger.getEnclosedElements())) {
+ if (method.getSimpleName().contentEquals("intValue")) {
+ return ImmutableSet.of(method);
+ }
+ }
+ throw new AssertionError("Could not find Integer.intValue()");
+ }
+ }
+ JavaFileObject impl = JavaFileObjects.forSourceLines("foo.bar.Baz",
+ "package foo.bar;",
+ "import com.google.auto.value.AutoValue;",
+ "@AutoValue public abstract class Baz {",
+ " abstract String foo();",
+ "}");
+ assertThat(impl)
+ .processedWith(new AutoValueProcessor(ImmutableList.of(new ConsumeBogusMethod())))
+ .failsToCompile()
+ .withErrorContaining(
+ "wants to consume a method that is not one of the abstract methods in this class")
+ .in(impl).onLine(3)
+ .and()
+ .withErrorContaining("intValue")
+ .in(impl).onLine(3);
+ }
+
+ public void testExtensionWithoutConsumedPropertiesFails() throws Exception {
+ JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz {",
+ " abstract String foo();",
+ " abstract String dizzle();",
+ " abstract Double[] bad();",
+ "}");
+ assertThat(javaFileObject)
+ .processedWith(
+ new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+ .failsToCompile()
+ .withErrorContaining("An @AutoValue class cannot define an array-valued property unless "
+ + "it is a primitive array");
+ }
+
+ public void testConsumeMethodWithArguments() throws Exception {
+ JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz {",
+ " abstract String foo();",
+ " abstract void writeToParcel(Object parcel, int flags);",
+ "}");
+ assertThat(javaFileObject)
+ .withCompilerOptions("-Xlint:-processing")
+ .processedWith(
+ new AutoValueProcessor(ImmutableList.of(new FakeWriteToParcelExtension())))
+ .compilesWithoutWarnings();
+ }
+
public void testExtensionWithBuilderCompilation() throws Exception {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
@@ -160,14 +350,105 @@ public class ExtensionTest extends TestCase {
" return \"dizzle\";\n",
" }",
"}");
- assertAbout(javaSource())
- .that(javaFileObject)
+ assertThat(javaFileObject)
.processedWith(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
.compilesWithoutError()
.and().generatesSources(expectedExtensionOutput);
}
- static class FooExtension extends AutoValueExtension {
+ public void testTwoExtensionsBothWantToBeFinal() throws Exception {
+ JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz {",
+ " abstract String foo();",
+ "}");
+ assertThat(javaFileObject)
+ .processedWith(
+ new AutoValueProcessor(ImmutableList.of(new FooExtension(), new FinalExtension())))
+ .failsToCompile()
+ .withErrorContaining("More than one extension wants to generate the final class: "
+ + FooExtension.class.getName() + ", " + FinalExtension.class.getName())
+ .in(javaFileObject).onLine(6);
+ }
+
+ public void testNonFinalThenFinal() throws Exception {
+ JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz {",
+ " abstract String foo();",
+ "}");
+ FinalExtension finalExtension = new FinalExtension();
+ NonFinalExtension nonFinalExtension = new NonFinalExtension();
+ assertFalse(finalExtension.generated);
+ assertFalse(nonFinalExtension.generated);
+ assertThat(javaFileObject)
+ .processedWith(
+ new AutoValueProcessor(ImmutableList.of(finalExtension, nonFinalExtension)))
+ .compilesWithoutError();
+ assertTrue(finalExtension.generated);
+ assertTrue(nonFinalExtension.generated);
+ }
+
+ public void testFinalThenNonFinal() throws Exception {
+ JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz {",
+ " abstract String foo();",
+ "}");
+ FinalExtension finalExtension = new FinalExtension();
+ NonFinalExtension nonFinalExtension = new NonFinalExtension();
+ assertFalse(finalExtension.generated);
+ assertFalse(nonFinalExtension.generated);
+ assertThat(javaFileObject)
+ .processedWith(
+ new AutoValueProcessor(ImmutableList.of(nonFinalExtension, finalExtension)))
+ .compilesWithoutError();
+ assertTrue(finalExtension.generated);
+ assertTrue(nonFinalExtension.generated);
+ }
+
+ public void testUnconsumedMethod() throws Exception {
+ JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz {",
+ " abstract String foo();",
+ " abstract void writeToParcel(Object parcel, int flags);",
+ "}");
+ assertThat(javaFileObject)
+ .processedWith(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+ .failsToCompile()
+ .withErrorContaining("writeToParcel")
+ .and()
+ .withWarningContaining(
+ "Abstract method is neither a property getter nor a Builder converter, "
+ + "and no extension consumed it")
+ .in(javaFileObject).onLine(8);
+ // The error here comes from the Java compiler rather than AutoValue, so we don't assume
+ // much about what it looks like. On the other hand, the warning does come from AutoValue
+ // so we know what to expect.
+ }
+
+ private static class FooExtension extends AutoValueExtension {
@Override
public boolean applicable(Context context) {
@@ -234,4 +515,95 @@ public class ExtensionTest extends TestCase {
"}", context.packageName(), isFinal ? "final" : "abstract", className, classToExtend);
}
}
+
+ // Extension that generates a class that just forwards to the parent constructor.
+ // We will make subclasses that are respectively final and non-final.
+ private static abstract class EmptyExtension extends AutoValueExtension {
+ @Override
+ public boolean applicable(Context context) {
+ return true;
+ }
+
+ @Override
+ public abstract boolean mustBeFinal(Context context);
+
+ String extraText(Context context) {
+ return "";
+ }
+
+ boolean generated = false;
+
+ @Override
+ public String generateClass(
+ Context context, String className, String classToExtend, boolean isFinal) {
+ generated = true;
+
+ ImmutableList.Builder<String> typesAndNamesBuilder = ImmutableList.builder();
+ for (Map.Entry<String, ExecutableElement> entry : context.properties().entrySet()) {
+ typesAndNamesBuilder.add(entry.getValue().getReturnType() + " " + entry.getKey());
+ }
+ String typesAndNames = Joiner.on(", ").join(typesAndNamesBuilder.build());
+ String template = "package {pkg};\n"
+ + "\n"
+ + "{finalOrAbstract} class {className} extends {classToExtend} {\n"
+ + " {className}({propertyTypesAndNames}) {\n"
+ + " super({propertyNames});\n"
+ + " }\n"
+ + " {extraText}\n"
+ + "}\n";
+ return template
+ .replace("{pkg}", context.packageName())
+ .replace("{finalOrAbstract}", isFinal ? "final" : "abstract")
+ .replace("{className}", className)
+ .replace("{classToExtend}", classToExtend)
+ .replace("{propertyTypesAndNames}", typesAndNames)
+ .replace("{propertyNames}", Joiner.on(", ").join(context.properties().keySet()))
+ .replace("{extraText}", extraText(context));
+ }
+ }
+
+ private static class NonFinalExtension extends EmptyExtension {
+ @Override
+ public boolean mustBeFinal(Context context) {
+ return false;
+ }
+ }
+
+ private static class FinalExtension extends EmptyExtension {
+ @Override
+ public boolean mustBeFinal(Context context) {
+ return true;
+ }
+ }
+
+ private static class FakeWriteToParcelExtension extends NonFinalExtension {
+ private ExecutableElement writeToParcelMethod(Context context) {
+ for (ExecutableElement method : context.abstractMethods()) {
+ if (method.getSimpleName().contentEquals("writeToParcel")) {
+ return method;
+ }
+ }
+ throw new AssertionError("Did not see abstract method writeToParcel");
+ }
+
+ @Override
+ public Set<ExecutableElement> consumeMethods(Context context) {
+ return ImmutableSet.of(writeToParcelMethod(context));
+ }
+
+ @Override
+ String extraText(Context context) {
+ // This is perhaps overgeneral. It is simply going to generate this:
+ // @Override void writeToParcel(Object parcel, int flags) {}
+ ExecutableElement methodToImplement = writeToParcelMethod(context);
+ assertEquals(TypeKind.VOID, methodToImplement.getReturnType().getKind());
+ ImmutableList.Builder<String> typesAndNamesBuilder = ImmutableList.builder();
+ for (VariableElement p : methodToImplement.getParameters()) {
+ typesAndNamesBuilder.add(p.asType() + " " + p.getSimpleName());
+ }
+ return "@Override void "
+ + methodToImplement.getSimpleName()
+ + "(" + Joiner.on(", ").join(typesAndNamesBuilder.build()) + ") {}";
+ }
+ }
}
diff --git a/value/userguide/builders-howto.md b/value/userguide/builders-howto.md
index 847e5177..b3c1914e 100644
--- a/value/userguide/builders-howto.md
+++ b/value/userguide/builders-howto.md
@@ -22,6 +22,7 @@ How do I...
* ... [**validate** property values?](#validate)
* ... [**normalize** (modify) a property value at `build` time?](#normalize)
* ... [expose **both** a builder and a factory method?](#both)
+* ... [handle `Optional` properties?](#optional)
* ... [use a **collection**-valued property?](#collection)
* ... [let my builder **accumulate** values for a collection-valued
property (not require them all at once)?](#accumulate)
@@ -72,9 +73,11 @@ Use whichever names you like; AutoValue doesn't actually care.
What should happen when a caller does not supply a value for a property before
calling `build()`? If the property in question is [nullable](howto.md#nullable),
-it will simply default to `null` as you would expect. But if it isn't (including
-if it is a primitive-valued property, which *can't* be null), then `build()`
-will throw an unchecked exception.
+it will simply default to `null` as you would expect. And if it is
+[Optional](#optional) it will default to an empty `Optional` as you might
+also expect. But if it isn't either of those things (including if it is a
+primitive-valued property, which *can't* be null), then `build()` will throw an
+unchecked exception.
But this presents a problem, since one of the main *advantages* of a builder in
the first place is that callers can specify only the properties they care about!
@@ -105,6 +108,10 @@ abstract class Animal {
}
```
+Occasionally you may want to supply a default value, but only if the
+property is not set explicitly. This is covered in the section on
+[normalization](#normalize).
+
## <a name="to_builder"></a>... initialize a builder to the same property values as an existing value instance
Suppose your caller has an existing instance of your value class, and wants to
@@ -229,7 +236,7 @@ public abstract class Animal {
}
```
-The getter in your builder must have the exact same signature as the abstract
+The getter in your builder must have the same signature as the abstract
property accessor method in the value class. It will return the value that has
been set on the `Builder`. If no value has been set for a non-[nullable]
(howto.md#nullable) property, `IllegalStateException` is thrown.
@@ -237,6 +244,52 @@ been set on the `Builder`. If no value has been set for a non-[nullable]
Getters should generally only be used within the `Builder` as shown, so they are
not public.
+As an alternative to returning the same type as the property accessor method,
+the builder getter can return an Optional wrapping of that type. This can be
+used if you want to supply a default, but only if the property has not been set.
+(The [usual way](#default) of supplying defaults means that the property always
+appears to have been set.) For example, suppose you wanted the default name of
+your Animal to be something like "4-legged creature", where 4 is the
+`numberOfLegs()` property. You might write this:
+
+```java
+@AutoValue
+public abstract class Animal {
+ public abstract String name();
+ public abstract int numberOfLegs();
+
+ public static Builder builder() {
+ return new AutoValue_Animal.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setName(String value);
+ public abstract Builder setNumberOfLegs(int value);
+
+ abstract Optional<String> name();
+ abstract int numberOfLegs();
+
+ abstract Animal autoBuild(); // not public
+
+ public Animal build() {
+ if (!name().isPresent()) {
+ setName(numberOfLegs() + "-legged creature);
+ }
+ return autoBuild();
+ }
+ }
+}
+```
+
+Notice that this will throw `IllegalStateException` if the `numberOfLegs`
+property hasn't been set either.
+
+The Optional wrapping can be any of the Optional types mentioned in the
+[section](#optional) on `Optional` properties. If your property has type
+`int` it can be wrapped as either `Optional<Integer>` or `OptionalInt`,
+and likewise for `long` and `double`.
+
## <a name="both"></a>... expose *both* a builder *and* a factory method?
If you use the builder option, AutoValue will not generate a visible constructor
@@ -244,6 +297,28 @@ for the generated concrete value class. If it's important to offer your caller
the choice of a factory method as well as the builder, then your factory method
implementation will have to invoke the builder itself.
+## <a name="optional"></a>... handle `Optional` properties?
+
+Properties of type `Optional` benefit from special treatment. If you
+have a property of type `Optional<String>`, say, then it will default
+to an empty `Optional` without needing to [specify](#default) a default
+explicitly. And, instead of or as well as the normal `setFoo(Optional<String>)`
+method, you can have `setFoo(String)`. Then `setFoo(s)` is equivalent to
+`setFoo(Optional.of(s))`.
+
+Here, `Optional` means either [`java.util.Optional`] from Java (Java 8
+or later), or [`com.google.common.base.Optional`] from Guava. Java 8
+also introduced related classes in `java.util` called [`OptionalInt`],
+[`OptionalLong`], and [`OptionalDouble`]. You can use those in the same way. For
+example a property of type `OptionalInt` will default to `OptionalInt.empty()`
+and you can set it with either `setFoo(OptionalInt)` or `setFoo(int)`.
+
+[`java.util.Optional`]: https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
+[`com.google.common.base.Optional`]: http://google.github.io/guava/releases/snapshot/api/docs/com/google/common/base/Optional.html
+[`OptionalDouble`]: https://docs.oracle.com/javase/8/docs/api/java/util/OptionalDouble.html
+[`OptionalInt`]: https://docs.oracle.com/javase/8/docs/api/java/util/OptionalInt.html
+[`OptionalLong`]: https://docs.oracle.com/javase/8/docs/api/java/util/OptionalLong.html
+
## <a name="collection"></a>... use a collection-valued property?
Value objects should be immutable, so if a property of one is a collection then
diff --git a/value/userguide/howto.md b/value/userguide/howto.md
index 574d0aea..c58bd948 100644
--- a/value/userguide/howto.md
+++ b/value/userguide/howto.md
@@ -162,7 +162,7 @@ Remember when doing this that you are losing AutoValue's protections. Be careful
to follow the basic rules of hash codes: equal objects must have equal hash
codes *always*, and equal hash codes should imply equal objects *almost always*.
You should now test your class more thoroughly, ideally using [`EqualsTester`]
-(http://static.javadoc.io/com.google.guava/guava-testlib/18.0/com/google/common/testing/EqualsTester.html)
+(http://static.javadoc.io/com.google.guava/guava-testlib/19.0/com/google/common/testing/EqualsTester.html)
from [guava-testlib](http://github.com/google/guava).
Best practice: mark your underriding methods `final` to make it clear to future
@@ -208,7 +208,7 @@ abstract class DerivedExample {
private String derivedProperty;
- String derivedProperty() {
+ final String derivedProperty() {
// non-thread-safe example
if (derivedProperty == null) {
derivedProperty = realProperty().toLowerCase();
@@ -233,7 +233,7 @@ abstract class IgnoreExample {
private String ignoredProperty; // sadly, it can't be `final`
- String ignoredProperty() {
+ final String ignoredProperty() {
return ignoredProperty;
}
}