aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--factory/pom.xml13
-rw-r--r--factory/src/it/functional/pom.xml4
-rw-r--r--factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java2
-rw-r--r--factory/src/it/functional/src/main/java/com/google/auto/factory/FactoryComponent.java (renamed from factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessorComponent.java)16
-rw-r--r--factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java5
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java21
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java57
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java25
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java4
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java23
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/ProcessorModule.java66
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java9
-rw-r--r--value/README.md81
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java299
-rw-r--r--value/src/main/java/com/google/auto/value/AutoValue.java10
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java16
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java12
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java44
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java131
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderSpec.java156
-rw-r--r--value/src/main/java/com/google/auto/value/processor/GwtSerialization.java2
-rw-r--r--value/src/main/java/com/google/auto/value/processor/Reformatter.java4
-rw-r--r--value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java9
-rw-r--r--value/src/main/java/com/google/auto/value/processor/autovalue.vm109
-rw-r--r--value/src/test/java/com/google/auto/value/processor/CompilationTest.java382
-rw-r--r--value/src/test/java/com/google/auto/value/processor/GuavaCollectionBuildersTest.java99
26 files changed, 1095 insertions, 504 deletions
diff --git a/factory/pom.xml b/factory/pom.xml
index 4726732b..021c5c3f 100644
--- a/factory/pom.xml
+++ b/factory/pom.xml
@@ -44,7 +44,7 @@
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
- <version>0.3</version>
+ <version>0.4</version>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
@@ -62,17 +62,6 @@
<version>2.5.1</version>
</dependency>
<dependency>
- <groupId>com.google.dagger</groupId>
- <artifactId>dagger</artifactId>
- <version>2.0-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>com.google.dagger</groupId>
- <artifactId>dagger-compiler</artifactId>
- <version>2.0-SNAPSHOT</version>
- <optional>true</optional>
- </dependency>
- <dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
diff --git a/factory/src/it/functional/pom.xml b/factory/src/it/functional/pom.xml
index 59308e8d..7283a2ae 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-SNAPSHOT</version>
+ <version>2.0</version>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger-compiler</artifactId>
- <version>2.0-SNAPSHOT</version>
+ <version>2.0</version>
<optional>true</optional>
</dependency>
<dependency>
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java
index 4d079f51..2080d904 100644
--- a/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java
@@ -18,7 +18,7 @@ package com.google.auto.factory;
import dagger.Module;
import dagger.Provides;
-@Module(injects = FactoryGeneratedFactory.class)
+@Module
final class DaggerModule {
@Provides Dependency provideDependency(DependencyImpl impl) {
return impl;
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessorComponent.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/FactoryComponent.java
index 66aaf03f..a14530c9 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessorComponent.java
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/FactoryComponent.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Google, Inc.
+ * Copyright (C) 2015 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.auto.factory.processor;
+package com.google.auto.factory;
import dagger.Component;
-/**
- * The Dagger component that declares all of the bindings for {@link AutoFactoryProcessor}.
- *
- * @author Gregory Kick
- */
-@Component(modules = ProcessorModule.class)
-interface AutoFactoryProcessorComponent {
- void injectProcessor(AutoFactoryProcessor processor);
+/** A component to materialize the factory using Dagger 2 */
+@Component(modules = DaggerModule.class)
+interface FactoryComponent {
+ FactoryGeneratedFactory factory();
}
diff --git a/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java
index 3c4090fe..e5c7b1bb 100644
--- a/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java
+++ b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java
@@ -4,8 +4,6 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.inject.Guice;
-import dagger.ObjectGraph;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -13,8 +11,7 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class DependencyInjectionIntegrationTest {
@Test public void daggerInjectedFactory() {
- FactoryGeneratedFactory factoryGeneratedFactory =
- ObjectGraph.create(DaggerModule.class).get(FactoryGeneratedFactory.class);
+ FactoryGeneratedFactory factoryGeneratedFactory = DaggerFactoryComponent.create().factory();
FactoryGenerated one = factoryGeneratedFactory.create("A");
FactoryGenerated two = factoryGeneratedFactory.create("B");
assertThat(one.name()).isEqualTo("A");
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
index 73fd12e4..19d98b2c 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
@@ -21,10 +21,18 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static javax.tools.Diagnostic.Kind.ERROR;
+import com.google.auto.factory.AutoFactory;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
import java.util.Map;
import javax.annotation.processing.Messager;
-import javax.inject.Inject;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
@@ -35,15 +43,6 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
-import com.google.auto.factory.AutoFactory;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.base.Strings;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
/**
* This is a value object that mirrors the static declaration of an {@link AutoFactory} annotation.
*
@@ -116,7 +115,7 @@ final class AutoFactoryDeclaration {
private final Elements elements;
private final Messager messager;
- @Inject Factory(Elements elements, Messager messager) {
+ Factory(Elements elements, Messager messager) {
this.elements = elements;
this.messager = messager;
}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
index 2f1006aa..250b2070 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
@@ -15,6 +15,19 @@
*/
package com.google.auto.factory.processor;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import com.google.auto.service.AutoService;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimaps;
+
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
@@ -27,7 +40,6 @@ import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
-import javax.inject.Inject;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
@@ -39,19 +51,6 @@ import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
-import com.google.auto.factory.AutoFactory;
-import com.google.auto.factory.Provided;
-import com.google.auto.service.AutoService;
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSortedSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Multimaps;
-
/**
* The annotation processor that generates factories for {@link AutoFactory} annotations.
*
@@ -59,27 +58,31 @@ import com.google.common.collect.Multimaps;
*/
@AutoService(Processor.class)
public final class AutoFactoryProcessor extends AbstractProcessor {
- @Inject FactoryDescriptorGenerator factoryDescriptorGenerator;
- @Inject AutoFactoryDeclaration.Factory declarationFactory;
- @Inject ProvidedChecker providedChecker;
- @Inject Messager messager;
- @Inject Elements elements;
- @Inject Types types;
- @Inject FactoryWriter factoryWriter;
+ private FactoryDescriptorGenerator factoryDescriptorGenerator;
+ private AutoFactoryDeclaration.Factory declarationFactory;
+ private ProvidedChecker providedChecker;
+ private Messager messager;
+ private Elements elements;
+ private Types types;
+ private FactoryWriter factoryWriter;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
- Dagger_AutoFactoryProcessorComponent.builder()
- .processorModule(new ProcessorModule(processingEnv))
- .build()
- .injectProcessor(this);
+ elements = processingEnv.getElementUtils();
+ types = processingEnv.getTypeUtils();
+ messager = processingEnv.getMessager();
+ factoryWriter = new FactoryWriter(processingEnv.getFiler());
+ providedChecker = new ProvidedChecker(messager);
+ declarationFactory = new AutoFactoryDeclaration.Factory(elements, messager);
+ factoryDescriptorGenerator =
+ new FactoryDescriptorGenerator(messager, elements, declarationFactory);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
- doProcess(annotations, roundEnv);
+ doProcess(roundEnv);
} catch (Throwable e) {
messager.printMessage(Kind.ERROR, "Failed to process @AutoFactory annotations:\n"
+ Throwables.getStackTraceAsString(e));
@@ -87,7 +90,7 @@ public final class AutoFactoryProcessor extends AbstractProcessor {
return false;
}
- private void doProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ private void doProcess(RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(Provided.class)) {
providedChecker.checkProvidedParameter(element);
}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java
index 51f61fa3..8ede5d25 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java
@@ -21,18 +21,6 @@ import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.tools.Diagnostic.Kind.ERROR;
-import javax.annotation.processing.Messager;
-import javax.inject.Inject;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.Name;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.util.ElementKindVisitor6;
-import javax.lang.model.util.Elements;
-
import com.google.auto.factory.AutoFactory;
import com.google.auto.factory.Provided;
import com.google.common.base.Function;
@@ -44,6 +32,17 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimaps;
+import javax.annotation.processing.Messager;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.ElementKindVisitor6;
+import javax.lang.model.util.Elements;
+
/**
* A service that traverses an element and returns the set of factory methods defined therein.
*
@@ -54,7 +53,7 @@ final class FactoryDescriptorGenerator {
private final Elements elements;
private final AutoFactoryDeclaration.Factory declarationFactory;
- @Inject FactoryDescriptorGenerator(Messager messager, Elements elements,
+ FactoryDescriptorGenerator(Messager messager, Elements elements,
AutoFactoryDeclaration.Factory declarationFactory) {
this.messager = messager;
this.elements = elements;
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
index 4b40ca9e..7fb96bcb 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
@@ -18,14 +18,14 @@ package com.google.auto.factory.processor;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
-import java.util.Set;
-
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import java.util.Set;
+
/**
* A value object representing a factory method to be generated.
*
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
index 2cc782ba..ea4879eb 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
@@ -19,6 +19,17 @@ import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+
+import com.squareup.javawriter.JavaWriter;
+
import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
@@ -31,20 +42,10 @@ import javax.inject.Inject;
import javax.lang.model.element.Modifier;
import javax.tools.JavaFileObject;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Optional;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-import com.squareup.javawriter.JavaWriter;
-
final class FactoryWriter {
private final Filer filer;
- @Inject FactoryWriter(Filer filer) {
+ FactoryWriter(Filer filer) {
this.filer = filer;
}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProcessorModule.java b/factory/src/main/java/com/google/auto/factory/processor/ProcessorModule.java
deleted file mode 100644
index 441aca8b..00000000
--- a/factory/src/main/java/com/google/auto/factory/processor/ProcessorModule.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.
- */
-package com.google.auto.factory.processor;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.Filer;
-import javax.annotation.processing.Messager;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.annotation.processing.Processor;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * A Dagger {@link Module} that binds objects related to {@link Processor} instances. Most callers
- * will create an instance using the {@link ProcessingEnvironment} passed into
- * {@link AbstractProcessor#init}.
- *
- * @author Gregory Kick
- */
-// TODO(gak): move this some place more common so that it can be shared amongst processors
-@Module(library = true)
-final class ProcessorModule {
- private final ProcessingEnvironment processingEnvironment;
-
- ProcessorModule(ProcessingEnvironment processingEnvironment) {
- this.processingEnvironment = checkNotNull(processingEnvironment);
- }
-
- @Provides ProcessingEnvironment provideProcessingEnvironment() {
- return processingEnvironment;
- }
-
- @Provides Elements provideElements(ProcessingEnvironment processingEnvironment) {
- return processingEnvironment.getElementUtils();
- }
-
- @Provides Filer provideFiler(ProcessingEnvironment processingEnvironment) {
- return processingEnvironment.getFiler();
- }
-
- @Provides Messager provideMessager(ProcessingEnvironment processingEnvironment) {
- return processingEnvironment.getMessager();
- }
-
- @Provides Types provideTypes(ProcessingEnvironment processingEnvironment) {
- return processingEnvironment.getTypeUtils();
- }
-}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java
index ae26cb6a..06ca8e13 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java
@@ -18,20 +18,19 @@ package com.google.auto.factory.processor;
import static com.google.common.base.Preconditions.checkArgument;
import static javax.tools.Diagnostic.Kind.ERROR;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
import javax.annotation.processing.Messager;
-import javax.inject.Inject;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementKindVisitor6;
-import com.google.auto.factory.AutoFactory;
-import com.google.auto.factory.Provided;
-
final class ProvidedChecker {
private final Messager messager;
- @Inject ProvidedChecker(Messager messager) {
+ ProvidedChecker(Messager messager) {
this.messager = messager;
}
diff --git a/value/README.md b/value/README.md
index 06326b9a..0641fe7b 100644
--- a/value/README.md
+++ b/value/README.md
@@ -209,14 +209,13 @@ written using builders.
   abstract int numberOfLegs();
@AutoValue.Builder
- interface Builder {
- Builder name(String s);
- Builder numberOfLegs(int n);
- Animal build();
+ abstract static class Builder {
+ abstract Builder name(String s);
+ abstract Builder numberOfLegs(int n);
+ abstract Animal build();
}
 }
}
-
```
Now client code can look something like this:
@@ -260,7 +259,6 @@ how to define that animals have 4 legs by default:
// ...remainder as before...
 }
}
-
```
### Nullability
@@ -293,26 +291,69 @@ steps, insert the code to do so into your static factory method before
invoking the generated constructor. Remember that null checks are
already present in the generated constructor.
-When using builders, you can validate using a method annotated with
-`@AutoValue.Validate`. The method will be called immediately after a
-new instance is constructed by the build method and before that
-instance is returned to the client. It can throw an exception if
-validation fails. Here's how our example might be validated:
+When using builders, you can validate by implementing your own build()
+method that calls the generated build method, conventionally called
+autoBuild(). You can construct an object provisionally and inspect its
+properties before returning it. Here's how our example might be validated:
```java
class Example {
 @AutoValue
 abstract static class Animal {
- @AutoValue.Validate
- void validate() {
- if (numberOfLegs() < 0) {
- throw new IllegalStateException("Negative legs");
+ ...
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder name(String s);
+ abstract Builder numberOfLegs(int n);
+
+ abstract Animal autoBuild();
+ Animal build() {
+ Animal animal = autoBuild();
+ if (animal.numberOfLegs() < 0) {
+ throw new IllegalStateException("Negative legs");
+ }
+ return animal;
}
}
- // ...remainder as before...
 }
}
+```
+
+If the Builder class is public, typically `build()` will be too but
+`autoBuild()` will be package-private.
+
+Sometimes it may be more convenient to consult the value in the builder
+before calling `autoBuild()`. If the `Builder` class has an abstract method
+with the same name and type as one of the methods in the `@AutoValue` class,
+it will be implemented to return the value set on the builder, or throw an
+exception if no value has been set. Like `autoBuild()`, these getter methods
+will typically not be public.
+
+```java
+ class Example {
+  @AutoValue
+  abstract static class Animal {
+ abstract String name();
+ abstract int numberOfLegs();
+ ...
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder name(String s);
+ abstract Builder numberOfLegs(int n);
+ abstract int numberOfLegs();
+
+ abstract Animal autoBuild();
+ Animal build() {
+ if (numberOfLegs() < 0) {
+ throw new IllegalStateException("Negative legs");
+ }
+ return autoBuild();
+ }
+ }
+  }
+ }
```
### Custom implementations
@@ -361,10 +402,10 @@ or
return new AutoValue_MapEntry.Builder<K, V>();
}
- interface Builder<K extends Comparable<K>, V> {
- Builder setKey(K key);
- Builder setValue(V value);
- MapEntry<K, V> build();
+ abstract static class Builder<K extends Comparable<K>, V> {
+ abstract Builder setKey(K key);
+ abstract Builder setValue(V value);
+ abstract MapEntry<K, V> build();
}
...
}
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 8e856268..e9e65813 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
@@ -21,6 +21,8 @@ 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;
@@ -1118,53 +1120,6 @@ public class AutoValueTest extends TestCase {
}
@AutoValue
- public abstract static class ValidationWithBuilder {
- public abstract String string();
- public abstract int integer();
-
- public static Builder builder() {
- return new AutoValue_AutoValueTest_ValidationWithBuilder.Builder();
- }
-
- @AutoValue.Validate
- void validate() {
- if (string().isEmpty()) {
- throw new IllegalStateException("String is empty");
- }
- if (integer() < 0) {
- throw new IllegalStateException("Integer is negative");
- }
- }
-
- @AutoValue.Builder
- public interface Builder {
- Builder string(String string);
- Builder integer(int integer);
- ValidationWithBuilder build();
- }
- }
-
- public void testValidation() {
- ValidationWithBuilder ok = ValidationWithBuilder.builder().string("foo").integer(17).build();
- assertEquals("foo", ok.string());
- assertEquals(17, ok.integer());
-
- try {
- ValidationWithBuilder.builder().string("").integer(17).build();
- fail("Expected IllegalStateException for empty string");
- } catch (IllegalStateException expected) {
- assertThat(expected).hasMessage("String is empty");
- }
-
- try {
- ValidationWithBuilder.builder().string("foo").integer(-17).build();
- fail("Expected IllegalStateException for negative integer");
- } catch (IllegalStateException expected) {
- assertThat(expected).hasMessage("Integer is negative");
- }
- }
-
- @AutoValue
public abstract static class GenericsWithBuilder<T extends Number & Comparable<T>, U extends T> {
public abstract List<T> list();
public abstract U u();
@@ -1271,6 +1226,256 @@ public class AutoValueTest extends TestCase {
assertEquals(17, instance3.getAnInt());
}
+ @AutoValue
+ public abstract static class BuilderWithUnprefixedGetters<T extends Comparable<T>> {
+ public abstract ImmutableList<T> list();
+ @Nullable public abstract T t();
+ 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();
+ }
+ }
+
+ public void testBuilderWithUnprefixedGetter() {
+ ImmutableList<String> names = ImmutableList.of("fred", "jim");
+ int[] ints = {6, 28, 496, 8128, 33550336};
+ int noGetter = -1;
+
+ BuilderWithUnprefixedGetters.Builder<String> builder = BuilderWithUnprefixedGetters.builder();
+ assertNull(builder.t());
+ try {
+ builder.list();
+ fail("Attempt to retrieve unset list property should have failed");
+ } catch (IllegalStateException e) {
+ 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);
+ ints[0] = 0;
+ assertThat(builder.ints()[0]).isEqualTo(6);
+ 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();
+ @Nullable public abstract int[] 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();
+ }
+ }
+
+ public void testBuilderWithPrefixedGetter() {
+ ImmutableList<String> names = ImmutableList.of("fred", "jim");
+ String name = "sheila";
+ int noGetter = -1;
+
+ BuilderWithPrefixedGetters.Builder<String> builder = BuilderWithPrefixedGetters.builder();
+ assertThat(builder.getInts()).isNull();
+ try {
+ builder.getList();
+ fail("Attempt to retrieve unset list property should have failed");
+ } catch (IllegalStateException e) {
+ 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());
+ }
+
@Retention(RetentionPolicy.RUNTIME)
@interface GwtCompatible {
boolean funky() default false;
diff --git a/value/src/main/java/com/google/auto/value/AutoValue.java b/value/src/main/java/com/google/auto/value/AutoValue.java
index d05be395..5948c3bd 100644
--- a/value/src/main/java/com/google/auto/value/AutoValue.java
+++ b/value/src/main/java/com/google/auto/value/AutoValue.java
@@ -75,14 +75,4 @@ public @interface AutoValue {
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Builder {}
-
- /**
- * Specifies that the annotated method is a validation method. The method should be a non-private
- * no-argument method in an AutoValue class. It will be called by the {@code build()} method of
- * the {@link Builder @AutoValue.Builder} implementation, immediately after constructing the new
- * object. It can throw an exception if the new object fails validation checks.
- */
- @Retention(RetentionPolicy.SOURCE)
- @Target(ElementType.METHOD)
- public @interface Validate {}
}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java
index fec3c577..e932470a 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java
@@ -44,8 +44,7 @@ import javax.tools.Diagnostic;
public class AutoValueBuilderProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
- return ImmutableSet.of(
- AutoValue.Builder.class.getCanonicalName(), AutoValue.Validate.class.getCanonicalName());
+ return ImmutableSet.of(AutoValue.Builder.class.getCanonicalName());
}
@Override
@@ -71,19 +70,6 @@ public class AutoValueBuilderProcessor extends AbstractProcessor {
+ " @AutoValue class");
}
}
-
- Set<? extends Element> validateMethods =
- roundEnv.getElementsAnnotatedWith(AutoValue.Validate.class);
- if (!SuperficialValidation.validateElements(validateMethods)) {
- return false;
- }
- for (Element annotatedMethod : validateMethods) {
- if (isAnnotationPresent(annotatedMethod, AutoValue.Validate.class)) {
- validate(
- annotatedMethod,
- "@AutoValue.Validate can only be applied to a method inside an @AutoValue class");
- }
- }
return false;
}
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 b079b4f2..e8eaa311 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
@@ -287,6 +287,16 @@ public class AutoValueProcessor extends AbstractProcessor {
return "";
}
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof Property && ((Property) obj).method.equals(method);
+ }
+
+ @Override
+ public int hashCode() {
+ return method.hashCode();
+ }
}
private static boolean isJavaLangObject(TypeElement type) {
@@ -434,7 +444,7 @@ public class AutoValueProcessor extends AbstractProcessor {
}
// If we are running from Eclipse, undo the work of its compiler which sorts methods.
eclipseHack().reorderProperties(props);
- vars.props = props;
+ vars.props = ImmutableSet.copyOf(props);
vars.serialVersionUID = getSerialVersionUID(type);
vars.formalTypes = typeSimplifier.formalTypeParametersString(type);
vars.actualTypes = TypeSimplifier.actualTypeParametersString(type);
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 1c68845e..1f9b280e 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
@@ -15,13 +15,15 @@
*/
package com.google.auto.value.processor;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+
import org.apache.velocity.runtime.parser.node.SimpleNode;
import java.util.Collections;
-import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.SortedSet;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.util.Types;
@@ -33,8 +35,11 @@ import javax.lang.model.util.Types;
*/
@SuppressWarnings("unused") // the fields in this class are only read via reflection
class AutoValueTemplateVars extends TemplateVars {
- /** The properties defined by the parent class's abstract methods. */
- List<AutoValueProcessor.Property> props;
+ /**
+ * The properties defined by the parent class's abstract methods. The elements of this set are
+ * in the same order as the original abstract method declarations in the AutoValue class.
+ */
+ ImmutableSet<AutoValueProcessor.Property> props;
/** Whether to generate an equals(Object) method. */
Boolean equals;
@@ -47,7 +52,7 @@ class AutoValueTemplateVars extends TemplateVars {
Types types;
/** The fully-qualified names of the classes to be imported in the generated class. */
- SortedSet<String> imports;
+ ImmutableSortedSet<String> imports;
/**
* The spelling of the javax.annotation.Generated class: Generated or javax.annotation.Generated.
@@ -131,18 +136,33 @@ class AutoValueTemplateVars extends TemplateVars {
/**
* A map from property names (like foo) to the corresponding setter method names (foo or setFoo).
*/
- Map<String, String> builderSetterNames = Collections.emptyMap();
+ ImmutableMap<String, String> builderSetterNames = ImmutableMap.of();
/**
- * The names of any {@code toBuilder()} methods, that is methods that return the builder type.
+ * A map from property names to information about the associated property builder. A property
+ * called foo (defined by a method foo() or getFoo()) can have a property builder called
+ * fooBuilder(). The type of foo must be an immutable Guava type, like ImmutableSet, and
+ * fooBuilder() must return the corresponding builder, like ImmutableSet.Builder.
*/
- List<String> toBuilderMethods;
+ ImmutableMap<String, BuilderSpec.PropertyBuilder> builderPropertyBuilders =
+ ImmutableMap.of();
/**
- * The simple names of validation methods (marked {@code @AutoValue.Validate}) in the AutoValue
- * class. (Currently, this set is either empty or a singleton.)
+ * 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).
+ */
+ ImmutableSet<AutoValueProcessor.Property> builderRequiredProperties = ImmutableSet.of();
+
+ /**
+ * Properties that have getters in the builder.
+ */
+ ImmutableSet<String> propertiesWithBuilderGetters = ImmutableSet.of();
+
+ /**
+ * The names of any {@code toBuilder()} methods, that is methods that return the builder type.
*/
- Set<String> validators = Collections.emptySet();
+ ImmutableList<String> toBuilderMethods;
private static final SimpleNode TEMPLATE = parsedTemplateForResource("autovalue.vm");
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 105177e3..d52d2264 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
@@ -15,19 +15,26 @@
*/
package com.google.auto.value.processor;
+import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.base.Equivalence;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+
import java.beans.Introspector;
import java.util.Map;
import java.util.Set;
+
+import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
/**
* Classifies methods inside builder types, based on their names and parameter and return types.
@@ -38,25 +45,39 @@ class BuilderMethodClassifier {
private static final Equivalence<TypeMirror> TYPE_EQUIVALENCE = MoreTypes.equivalence();
private final ErrorReporter errorReporter;
+ private final Types typeUtils;
private final TypeElement autoValueClass;
private final TypeElement builderType;
private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName;
+ private final ImmutableMap<String, ExecutableElement> getterNameToGetter;
+
private final Set<ExecutableElement> buildMethods = Sets.newLinkedHashSet();
+ private final Set<String> propertiesWithBuilderGetters = Sets.newLinkedHashSet();
private final Map<String, ExecutableElement> propertyNameToPrefixedSetter =
Maps.newLinkedHashMap();
private final Map<String, ExecutableElement> propertyNameToUnprefixedSetter =
Maps.newLinkedHashMap();
+ private final Map<String, ExecutableElement> propertyNameToPropertyBuilder =
+ Maps.newLinkedHashMap();
private boolean settersPrefixed;
private BuilderMethodClassifier(
ErrorReporter errorReporter,
+ ProcessingEnvironment processingEnv,
TypeElement autoValueClass,
TypeElement builderType,
ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
this.errorReporter = errorReporter;
+ this.typeUtils = processingEnv.getTypeUtils();
this.autoValueClass = autoValueClass;
this.builderType = builderType;
this.getterToPropertyName = getterToPropertyName;
+ ImmutableMap.Builder<String, ExecutableElement> getterToPropertyNameBuilder =
+ ImmutableMap.builder();
+ for (ExecutableElement getter : getterToPropertyName.keySet()) {
+ getterToPropertyNameBuilder.put(getter.getSimpleName().toString(), getter);
+ }
+ this.getterNameToGetter = getterToPropertyNameBuilder.build();
}
/**
@@ -74,11 +95,12 @@ class BuilderMethodClassifier {
static Optional<BuilderMethodClassifier> classify(
Iterable<ExecutableElement> methods,
ErrorReporter errorReporter,
+ ProcessingEnvironment processingEnv,
TypeElement autoValueClass,
TypeElement builderType,
ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
BuilderMethodClassifier classifier = new BuilderMethodClassifier(
- errorReporter, autoValueClass, builderType, getterToPropertyName);
+ errorReporter, processingEnv, autoValueClass, builderType, getterToPropertyName);
if (classifier.classifyMethods(methods)) {
return Optional.of(classifier);
} else {
@@ -94,7 +116,22 @@ class BuilderMethodClassifier {
* {@code foo} or {@code setFoo}.
*/
Map<String, ExecutableElement> propertyNameToSetter() {
- return settersPrefixed ? propertyNameToPrefixedSetter : propertyNameToUnprefixedSetter;
+ return ImmutableMap.copyOf(
+ settersPrefixed ? propertyNameToPrefixedSetter : propertyNameToUnprefixedSetter);
+ }
+
+ Map<String, ExecutableElement> propertyNameToPropertyBuilder() {
+ return propertyNameToPropertyBuilder;
+ }
+
+ /**
+ * Returns the set of properties that have getters in the builder. If a property is defined by
+ * an abstract method in the {@code @AutoValue} class called {@code foo()} or {@code getFoo()}
+ * then the name of the property is {@code foo}, If the builder also has a method of the same name
+ * ({@code foo()} or {@code getFoo()}) then the set returned here will contain {@code foo}.
+ */
+ ImmutableSet<String> propertiesWithBuilderGetters() {
+ return ImmutableSet.copyOf(propertiesWithBuilderGetters);
}
/**
@@ -132,7 +169,14 @@ class BuilderMethodClassifier {
}
for (Map.Entry<ExecutableElement, String> getterEntry : getterToPropertyName.entrySet()) {
String property = getterEntry.getValue();
- if (!propertyNameToSetter.containsKey(property)) {
+ boolean hasSetter = propertyNameToSetter.containsKey(property);
+ boolean hasBuilder = propertyNameToPropertyBuilder.containsKey(property);
+ if (hasSetter && hasBuilder) {
+ String error =
+ String.format("Property %s cannot have both a setter and a builder", property);
+ errorReporter.reportError(error, builderType);
+ } else if (!hasSetter && !hasBuilder) {
+ // TODO(emcmanus): also mention the possible builder method if the property type allows one
String setterName = settersPrefixed ? prefixWithSet(property) : property;
String error = String.format("Expected a method with this signature: %s%s %s(%s)",
builderType,
@@ -171,19 +215,90 @@ class BuilderMethodClassifier {
* @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();
+ ExecutableElement getter = getterNameToGetter.get(methodName);
+ if (getter != null) {
+ return classifyGetter(method, getter);
+ }
+
+ if (methodName.endsWith("Builder")) {
+ String property = methodName.substring(0, methodName.length() - "Builder".length());
+ if (getterToPropertyName.containsValue(property)) {
+ return classifyPropertyBuilder(method, property);
+ }
+ }
+
if (TYPE_EQUIVALENCE.equivalent(returnType, autoValueClass.asType())) {
buildMethods.add(method);
return true;
}
- errorReporter.reportError(
- "Method without arguments should be a build method returning "
- + autoValueClass + typeParamsString(), method);
+ String error = String.format(
+ "Method without arguments should be a build method returning %1$s%2$s"
+ + " or a getter method with the same name and type as a getter method of %1$s",
+ autoValueClass, typeParamsString());
+ errorReporter.reportError(error, method);
return false;
}
+ 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;
+ }
+ propertiesWithBuilderGetters.add(getterToPropertyName.get(originalGetter));
+ return true;
+ }
+
+ // 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();
+
+ private boolean classifyPropertyBuilder(ExecutableElement method, String property) {
+ TypeMirror builderTypeMirror = method.getReturnType();
+ TypeElement builderTypeElement = MoreTypes.asTypeElement(builderTypeMirror);
+ String builderTypeString = builderTypeElement.getQualifiedName().toString();
+ boolean isGuavaBuilder = (builderTypeString.startsWith(COM_GOOGLE_COMMON_COLLECT_IMMUTABLE)
+ && builderTypeString.endsWith(".Builder"));
+ if (!isGuavaBuilder) {
+ errorReporter.reportError("Method looks like a property builder, but its return type "
+ + "is not a builder for an immutable type in com.google.common.collect", method);
+ return false;
+ }
+ // Given, e.g. ImmutableSet.Builder<String>, construct ImmutableSet<String> and check that
+ // it is indeed the type of the property.
+ DeclaredType builderTypeDeclared = MoreTypes.asDeclared(builderTypeMirror);
+ TypeMirror[] builderTypeArgs =
+ builderTypeDeclared.getTypeArguments().toArray(new TypeMirror[0]);
+ if (builderTypeArgs.length == 0) {
+ errorReporter.reportError("Property builder type cannot be raw (missing <...>)", method);
+ return false;
+ }
+ TypeElement enclosingTypeElement =
+ MoreElements.asType(builderTypeElement.getEnclosingElement());
+ TypeMirror expectedPropertyType =
+ typeUtils.getDeclaredType(enclosingTypeElement, builderTypeArgs);
+ TypeMirror actualPropertyType = getterToPropertyName.inverse().get(property).getReturnType();
+ if (!TYPE_EQUIVALENCE.equivalent(expectedPropertyType, actualPropertyType)) {
+ String error = String.format(
+ "Return type of property-builder method implies a property of type %s, but property "
+ + "%s has type %s",
+ expectedPropertyType, property, actualPropertyType);
+ errorReporter.reportError(error, method);
+ return false;
+ }
+ propertyNameToPropertyBuilder.put(property, method);
+ return true;
+ }
+
/**
* Classify a method given that it has one argument. Currently, a method with one argument can
* only be a setter, meaning that it must look like {@code foo(T)} or {@code setFoo(T)}, where
@@ -205,7 +320,9 @@ class BuilderMethodClassifier {
propertyNameToSetter = propertyNameToPrefixedSetter;
valueGetter = propertyNameToGetter.get(propertyName);
}
- if (valueGetter == null) {
+ if (valueGetter == null || propertyNameToSetter == null) {
+ // The second disjunct isn't needed but convinces control-flow checkers that
+ // propertyNameToSetter can't be null when we call put on it below.
errorReporter.reportError(
"Method does not correspond to a property of " + autoValueClass, method);
return false;
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 1cfb2975..be6aacfc 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
@@ -15,9 +15,12 @@
*/
package com.google.auto.value.processor;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.value.AutoValue;
+import com.google.auto.value.processor.AutoValueProcessor.Property;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableBiMap;
@@ -44,7 +47,6 @@ import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
-import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
@@ -54,7 +56,7 @@ import javax.lang.model.util.Types;
*/
class BuilderSpec {
private final TypeElement autoValueClass;
- private final Elements elementUtils;
+ private final ProcessingEnvironment processingEnv;
private final ErrorReporter errorReporter;
BuilderSpec(
@@ -62,7 +64,7 @@ class BuilderSpec {
ProcessingEnvironment processingEnv,
ErrorReporter errorReporter) {
this.autoValueClass = autoValueClass;
- this.elementUtils = processingEnv.getElementUtils();
+ this.processingEnv = processingEnv;
this.errorReporter = errorReporter;
}
@@ -91,36 +93,9 @@ class BuilderSpec {
}
}
- Optional<ExecutableElement> validateMethod = Optional.absent();
- for (ExecutableElement containedMethod :
- ElementFilter.methodsIn(autoValueClass.getEnclosedElements())) {
- if (MoreElements.isAnnotationPresent(containedMethod, AutoValue.Validate.class)) {
- if (containedMethod.getModifiers().contains(Modifier.STATIC)) {
- errorReporter.reportError(
- "@AutoValue.Validate cannot apply to a static method", containedMethod);
- } else if (!containedMethod.getParameters().isEmpty()) {
- errorReporter.reportError(
- "@AutoValue.Validate method must not have parameters", containedMethod);
- } else if (containedMethod.getReturnType().getKind() != TypeKind.VOID) {
- errorReporter.reportError(
- "Return type of @AutoValue.Validate method must be void", containedMethod);
- } else if (validateMethod.isPresent()) {
- errorReporter.reportError(
- "There can only be one @AutoValue.Validate method", containedMethod);
- } else {
- validateMethod = Optional.of(containedMethod);
- }
- }
- }
-
if (builderTypeElement.isPresent()) {
- return builderFrom(builderTypeElement.get(), validateMethod);
+ return builderFrom(builderTypeElement.get());
} else {
- if (validateMethod.isPresent()) {
- errorReporter.reportError(
- "@AutoValue.Validate is only meaningful if there is an @AutoValue.Builder",
- validateMethod.get());
- }
return Optional.absent();
}
}
@@ -130,13 +105,9 @@ class BuilderSpec {
*/
class Builder {
private final TypeElement builderTypeElement;
- private final Optional<ExecutableElement> validateMethod;
- Builder(
- TypeElement builderTypeElement,
- Optional<ExecutableElement> validateMethod) {
+ Builder(TypeElement builderTypeElement) {
this.builderTypeElement = builderTypeElement;
- this.validateMethod = validateMethod;
}
/**
@@ -198,7 +169,12 @@ class BuilderSpec {
ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
Iterable<ExecutableElement> builderMethods = abstractMethods(builderTypeElement);
Optional<BuilderMethodClassifier> optionalClassifier = BuilderMethodClassifier.classify(
- builderMethods, errorReporter, autoValueClass, builderTypeElement, getterToPropertyName);
+ builderMethods,
+ errorReporter,
+ processingEnv,
+ autoValueClass,
+ builderTypeElement,
+ getterToPropertyName);
if (!optionalClassifier.isPresent()) {
return;
}
@@ -222,17 +198,106 @@ class BuilderSpec {
vars.builderFormalTypes = typeSimplifier.formalTypeParametersString(builderTypeElement);
vars.builderActualTypes = TypeSimplifier.actualTypeParametersString(builderTypeElement);
vars.buildMethodName = buildMethod.getSimpleName().toString();
- if (validateMethod.isPresent()) {
- vars.validators = ImmutableSet.of(validateMethod.get().getSimpleName().toString());
- } else {
- vars.validators = ImmutableSet.of();
- }
+ vars.propertiesWithBuilderGetters = classifier.propertiesWithBuilderGetters();
+
ImmutableMap.Builder<String, String> setterNameBuilder = ImmutableMap.builder();
for (Map.Entry<String, ExecutableElement> entry :
classifier.propertyNameToSetter().entrySet()) {
setterNameBuilder.put(entry.getKey(), entry.getValue().getSimpleName().toString());
}
vars.builderSetterNames = setterNameBuilder.build();
+
+ vars.builderPropertyBuilders =
+ makeBuilderPropertyBuilderMap(classifier, typeSimplifier, getterToPropertyName);
+
+ Set<Property> required = Sets.newLinkedHashSet(vars.props);
+ for (Property property : vars.props) {
+ if (property.isNullable() || vars.builderPropertyBuilders.containsKey(property.getName())) {
+ required.remove(property);
+ }
+ }
+ vars.builderRequiredProperties = ImmutableSet.copyOf(required);
+ }
+
+ private ImmutableMap<String, PropertyBuilder> makeBuilderPropertyBuilderMap(
+ BuilderMethodClassifier classifier,
+ TypeSimplifier typeSimplifier,
+ ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
+ ImmutableMap.Builder<String, PropertyBuilder> map = ImmutableMap.builder();
+ for (Map.Entry<String, ExecutableElement> entry :
+ classifier.propertyNameToPropertyBuilder().entrySet()) {
+ String property = entry.getKey();
+ ExecutableElement autoValuePropertyMethod = getterToPropertyName.inverse().get(property);
+ ExecutableElement propertyBuilderMethod = entry.getValue();
+ PropertyBuilder propertyBuilder = new PropertyBuilder(
+ autoValuePropertyMethod, propertyBuilderMethod, typeSimplifier);
+ map.put(property, propertyBuilder);
+ }
+ return map.build();
+ }
+ }
+
+ /**
+ * Information about a property builder, referenced from the autovalue.vm template. A property
+ * called foo (defined by a method foo() or getFoo()) can have a property builder called
+ * fooBuilder(). The type of foo must be an immutable Guava type, like ImmutableSet, and
+ * fooBuilder() must return the corresponding builder, like ImmutableSet.Builder.
+ */
+ public class PropertyBuilder {
+ private final String builderType;
+ private final String initializer;
+ private final String copyAll;
+
+ PropertyBuilder(
+ ExecutableElement autoValuePropertyMethod,
+ ExecutableElement propertyBuilderMethod,
+ TypeSimplifier typeSimplifier) {
+ String immutableType = typeSimplifier.simplify(autoValuePropertyMethod.getReturnType());
+ int typeParamIndex = immutableType.indexOf('<');
+ checkState(typeParamIndex > 0, immutableType);
+ String rawImmutableType = immutableType.substring(0, typeParamIndex);
+ this.builderType =
+ rawImmutableType + ".Builder" + immutableType.substring(typeParamIndex);
+ this.initializer = rawImmutableType + ".builder()";
+ // TODO(emcmanus): clean up TypeSimplifier and remove this hack.
+ // We want it to simplify com.google.common.collect.ImmutableSet.Builder<E>
+ // the same way it would simplify com.google.common.collect.ImmutableSet<E>.
+ // The issue is that getEnclosingElement tends to do the wrong thing in Eclipse
+ // so we end up with ImmutableSet<E>.Builder<E>.
+ TypeElement builderTypeElement = MoreElements.asType(
+ processingEnv.getTypeUtils().asElement(propertyBuilderMethod.getReturnType()));
+ Set<String> methodNames = Sets.newHashSet();
+ for (ExecutableElement builderMethod :
+ ElementFilter.methodsIn(builderTypeElement.getEnclosedElements())) {
+ methodNames.add(builderMethod.getSimpleName().toString());
+ }
+ if (methodNames.contains("addAll")) {
+ this.copyAll = "addAll";
+ } else if (methodNames.contains("putAll")) {
+ this.copyAll = "putAll";
+ } else {
+ throw new AssertionError("Builder contains neither addAll nor putAll: " + methodNames);
+ }
+ }
+
+ /** The type of the builder, for example {@code ImmutableSet.Builder<String>}. */
+ public String getBuilderType() {
+ return builderType;
+ }
+
+ /** An initializer for the builder field, for example {@code ImmutableSet.builder()}. */
+ public String getInitializer() {
+ return initializer;
+ }
+
+ /**
+ * The method to copy another immutable collection into this one. It is {@code copyAll} for
+ * one-dimensional collections like {@code ImmutableList} and {@code ImmutableSet}, and it is
+ * {@code putAll} for two-dimensional collections like {@code ImmutableMap} and
+ * {@code ImmutableTable}.
+ */
+ public String getCopyAll() {
+ return copyAll;
}
}
@@ -241,8 +306,7 @@ class BuilderSpec {
* class or interface has abstract methods that could not be part of any builder, emits error
* messages and returns null.
*/
- private Optional<Builder> builderFrom(
- TypeElement builderTypeElement, Optional<ExecutableElement> validateMethod) {
+ private Optional<Builder> builderFrom(TypeElement builderTypeElement) {
// We require the builder to have the same type parameters as the @AutoValue class, meaning the
// same names and bounds. In principle the type parameters could have different names, but that
@@ -276,7 +340,7 @@ class BuilderSpec {
+ "type parameters of " + autoValueClass, builderTypeElement);
return Optional.absent();
}
- return Optional.of(new Builder(builderTypeElement, validateMethod));
+ return Optional.of(new Builder(builderTypeElement));
}
// Return a list of all abstract methods in the given TypeElement or inherited from ancestors.
@@ -300,7 +364,7 @@ class BuilderSpec {
for (ExecutableElement method : ElementFilter.methodsIn(typeElement.getEnclosedElements())) {
for (Iterator<ExecutableElement> it = abstractMethods.iterator(); it.hasNext(); ) {
ExecutableElement maybeOverridden = it.next();
- if (elementUtils.overrides(method, maybeOverridden, typeElement)) {
+ if (processingEnv.getElementUtils().overrides(method, maybeOverridden, typeElement)) {
it.remove();
}
}
diff --git a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
index 0615cc1d..2b6bb013 100644
--- a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
+++ b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
@@ -244,7 +244,7 @@ class GwtSerialization {
private static final Charset UTF8 = Charset.forName("UTF-8");
- private String computeClassHash(List<AutoValueProcessor.Property> props) {
+ private String computeClassHash(Iterable<AutoValueProcessor.Property> props) {
TypeSimplifier typeSimplifier = new TypeSimplifier(
processingEnv.getTypeUtils(), "", new TypeMirrorSet(), null);
CRC32 crc = new CRC32();
diff --git a/value/src/main/java/com/google/auto/value/processor/Reformatter.java b/value/src/main/java/com/google/auto/value/processor/Reformatter.java
index 4d2fc325..6f875845 100644
--- a/value/src/main/java/com/google/auto/value/processor/Reformatter.java
+++ b/value/src/main/java/com/google/auto/value/processor/Reformatter.java
@@ -78,7 +78,7 @@ class Reformatter {
private static String compressSpace(String s) {
// Remove extra spaces. An "extra" space is one that is not part of the indentation at the start
// of a line, and where the next character is also a space or a right paren or a semicolon
- // or a comma, or the preceding character is a left paren.
+ // or a dot or a comma, or the preceding character is a left paren.
// TODO(emcmanus): consider merging all three passes using this tokenization approach.
StringBuilder sb = new StringBuilder(s.length());
Tokenizer tokenizer = new Tokenizer(s);
@@ -95,7 +95,7 @@ class Reformatter {
// Since we ensure that the tokenized string ends with \n, and a whitespace token stops
// at \n, it is safe to look at end.
char nextC = s.charAt(end);
- if (",;)".indexOf(nextC) >= 0) {
+ if (".,;)".indexOf(nextC) >= 0) {
continue;
}
sb.append(' ');
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 1e0f02f1..b2b2c4b9 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
@@ -20,14 +20,13 @@ import static javax.lang.model.element.Modifier.PRIVATE;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSortedSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
@@ -103,14 +102,14 @@ final class TypeSimplifier {
* import java.util.Map.Entry and refer to the property as Entry. We could also import just
* java.util.Map in this case and refer to Map.Entry, but currently we never do that.
*/
- SortedSet<String> typesToImport() {
- SortedSet<String> typesToImport = new TreeSet<String>();
+ ImmutableSortedSet<String> typesToImport() {
+ ImmutableSortedSet.Builder<String> typesToImport = ImmutableSortedSet.naturalOrder();
for (Map.Entry<String, Spelling> entry : imports.entrySet()) {
if (entry.getValue().importIt) {
typesToImport.add(entry.getKey());
}
}
- return typesToImport;
+ return typesToImport.build();
}
/**
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 51e8d225..ba34854e 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
@@ -38,7 +38,7 @@ final class $subclass$formalTypes extends $origClass$actualTypes {
#if ($p.nullable) ${p.nullableAnnotation} #end $p.type $p #if ($foreach.hasNext) , #end
#end ) {
#foreach ($p in $props)
- #if (!$p.kind.primitive && !$p.nullable)
+ #if (!$p.kind.primitive && !$p.nullable && !$builderPropertyBuilders[$p.name])
if ($p == null) {
throw new NullPointerException("Null $p.name");
@@ -210,6 +210,11 @@ final class $subclass$formalTypes extends $origClass$actualTypes {
private $types.boxedClass($p.typeMirror).simpleName $p;
+ #elseif ($builderPropertyBuilders[$p.name])
+
+ private ${builderPropertyBuilders[$p.name].builderType} $p = ##
+ ${builderPropertyBuilders[$p.name].initializer};
+
#else
private $p.type $p;
@@ -225,61 +230,117 @@ final class $subclass$formalTypes extends $origClass$actualTypes {
#foreach ($p in $props)
+ #if ($builderPropertyBuilders[$p.name])
+
+ ${p}.${builderPropertyBuilders[$p.name].copyAll}(source.${p.getter}());
+
+ #else
+
$builderSetterNames[$p.name](source.${p.getter}());
## We use the setter methods rather than assigning the fields directly so we don't have
## to duplicate the logic for cloning arrays. Not cloning arrays would conceivably be
## a security problem.
+ #end
+
#end
}
#foreach ($p in $props)
+ ## Setter and/or property builder
+
+ #if ($builderSetterNames[$p.name])
+
@Override
public ${builderTypeName}${builderActualTypes} $builderSetterNames[$p.name]($p.type $p) {
- #if ($p.kind == "ARRAY")
- #if ($p.nullable)
+ #if ($builderPropertyBuilders[$p.name])
+
+ this.$p = ${builderPropertyBuilders[$p.name].initializer};
+ this.$p.${builderPropertyBuilders[$p.name].copyAll}($p);
+
+ #elseif ($p.kind == "ARRAY")
+ #if ($p.nullable)
this.$p = ($p == null) ? null : ${p}.clone();
- #else
+ #else
this.$p = ${p}.clone();
- #end
- #else
+ #end
+ #else
this.$p = $p;
- #end
+ #end
return this;
}
- #end
+ #end
-#set ($requiredCount = 0)
-#foreach ($p in $props)
- #if (!$p.nullable)
- #set ($requiredCount = $requiredCount + 1)
+ #if ($builderPropertyBuilders[$p.name])
+
+ @Override
+ public ${builderPropertyBuilders[$p.name].builderType} ${p.name}Builder() {
+ return $p;
+ }
+
+ #end
+
+ ## Getter
+
+ #if ($propertiesWithBuilderGetters.contains($p.name))
+
+ @Override
+ public $p.type ${p.getter}() {
+ #if ($builderRequiredProperties.contains($p))
+
+ if ($p == null) {
+ throw new IllegalStateException("Property \"$p.name\" has not been set");
+ }
+
+ #end
+
+ #if ($p.kind == "ARRAY")
+ #if ($p.nullable)
+
+ return ($p == null) ? null : ${p}.clone();
+
+ #else
+
+ return ${p}.clone();
+
+ #end
+ #elseif ($builderPropertyBuilders[$p.name])
+
+ return ${p}.build();
+
+ #else
+
+ return $p;
+
+ #end
+
+ }
+
+ #end
#end
-#end
@Override
public ${origClass}${actualTypes} ${buildMethodName}() {
- #if ($requiredCount > 0)
+ #if (!$builderRequiredProperties.empty)
String missing = "";
- #foreach ($p in $props)
- #if (!$p.nullable)
+ #foreach ($p in $builderRequiredProperties)
if ($p == null) {
missing += " $p.name";
}
- #end
#end
if (!missing.isEmpty()) {
@@ -287,19 +348,13 @@ final class $subclass$formalTypes extends $origClass$actualTypes {
}
#end
- ${origClass}${actualTypes} result = new ${subclass}${actualTypes}(
+ return new ${subclass}${actualTypes}(
#foreach ($p in $props)
- this.$p #if ($foreach.hasNext) , #end
+ this.$p ##
+ #if (${builderPropertyBuilders[$p.name]}) .build() #end
+ #if ($foreach.hasNext) , #end
#end );
-
- #foreach ($v in $validators)
-
- result.${v}();
-
- #end
-
- return result;
}
}
#end
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 d9122c48..40955873 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
@@ -496,6 +496,7 @@ public class CompilationTest extends TestCase {
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
+ "import com.google.common.collect.ImmutableList;",
"",
"import java.util.List;",
"import javax.annotation.Nullable;",
@@ -506,22 +507,21 @@ public class CompilationTest extends TestCase {
" public abstract byte[] aByteArray();",
" @Nullable public abstract int[] aNullableIntArray();",
" public abstract List<T> aList();",
+ " public abstract ImmutableList<T> anImmutableList();",
"",
" public abstract Builder<T> toBuilder();",
"",
- " @AutoValue.Validate",
- " void validate() {",
- " if (anInt() < 0) {",
- " throw new IllegalStateException(\"Negative integer\");",
- " }",
- " }",
- "",
" @AutoValue.Builder",
" public interface Builder<T extends Number> {",
" Builder<T> anInt(int x);",
" Builder<T> aByteArray(byte[] x);",
" Builder<T> aNullableIntArray(@Nullable int[] x);",
" Builder<T> aList(List<T> x);",
+ " ImmutableList.Builder<T> anImmutableListBuilder();",
+ "",
+ " List<T> aList();",
+ " ImmutableList<T> anImmutableList();",
+ "",
" Baz<T> build();",
" }",
"",
@@ -533,6 +533,7 @@ public class CompilationTest extends TestCase {
"foo.bar.AutoValue_Baz",
"package foo.bar;",
"",
+ "import com.google.common.collect.ImmutableList",
"import java.util.Arrays;",
"import java.util.List;",
"import javax.annotation.Generated;",
@@ -543,9 +544,14 @@ public class CompilationTest extends TestCase {
" private final byte[] aByteArray;",
" private final int[] aNullableIntArray;",
" private final List<T> aList;",
- "",
- " private AutoValue_Baz("
- + "int anInt, byte[] aByteArray, @javax.annotation.Nullable int[] aNullableIntArray, List<T> aList) {",
+ " private final ImmutableList<T> anImmutableList;",
+ "",
+ " private AutoValue_Baz(",
+ " int anInt,",
+ " byte[] aByteArray,",
+ " @javax.annotation.Nullable int[] aNullableIntArray,",
+ " List<T> aList,",
+ " ImmutableList<T> anImmutableList) {",
" this.anInt = anInt;",
" if (aByteArray == null) {",
" throw new NullPointerException(\"Null aByteArray\");",
@@ -556,6 +562,7 @@ public class CompilationTest extends TestCase {
" throw new NullPointerException(\"Null aList\");",
" }",
" this.aList = aList;",
+ " this.anImmutableList = anImmutableList;",
" }",
"",
" @Override public int anInt() {",
@@ -575,12 +582,17 @@ public class CompilationTest extends TestCase {
" return aList;",
" }",
"",
+ " @Override public ImmutableList<T> anImmutableList() {",
+ " return anImmutableList;",
+ " }",
+ "",
" @Override public String toString() {",
" return \"Baz{\"",
" + \"anInt=\" + anInt + \", \"",
" + \"aByteArray=\" + Arrays.toString(aByteArray) + \", \"",
" + \"aNullableIntArray=\" + Arrays.toString(aNullableIntArray) + \", \"",
- " + \"aList=\" + aList",
+ " + \"aList=\" + aList + \", \"",
+ " + \"anImmutableList=\" + anImmutableList",
" + \"}\";",
" }",
"",
@@ -597,7 +609,8 @@ public class CompilationTest extends TestCase {
" && (Arrays.equals(this.aNullableIntArray, "
+ "(that instanceof AutoValue_Baz) "
+ "? ((AutoValue_Baz) that).aNullableIntArray : that.aNullableIntArray()))",
- " && (this.aList.equals(that.aList()));",
+ " && (this.aList.equals(that.aList()))",
+ " && (this.anImmutableList.equals(that.anImmutableList()));",
" }",
" return false;",
" }",
@@ -612,6 +625,8 @@ public class CompilationTest extends TestCase {
" h ^= Arrays.hashCode(aNullableIntArray);",
" h *= 1000003;",
" h ^= aList.hashCode();",
+ " h *= 1000003;",
+ " h ^= anImmutableList.hashCode();",
" return h;",
" }",
"",
@@ -624,6 +639,7 @@ public class CompilationTest extends TestCase {
" private byte[] aByteArray;",
" private int[] aNullableIntArray;",
" private List<T> aList;",
+ " private ImmutableList.Builder<T> anImmutableList = ImmutableList.builder();",
"",
" Builder() {",
" }",
@@ -633,6 +649,7 @@ public class CompilationTest extends TestCase {
" aByteArray(source.aByteArray());",
" aNullableIntArray(source.aNullableIntArray());",
" aList(source.aList());",
+ " anImmutableList.addAll(source.anImmutableList());",
" }",
"",
" @Override",
@@ -661,6 +678,24 @@ public class CompilationTest extends TestCase {
" }",
"",
" @Override",
+ " public List<T> aList() {",
+ " if (aList == null) {",
+ " throw new IllegalStateException(\"Property \\\"aList\\\" has not been set\");",
+ " }",
+ " return aList;",
+ " }",
+ "",
+ " @Override",
+ " public ImmutableList.Builder<T> anImmutableListBuilder() {",
+ " return anImmutableList;",
+ " }",
+ "",
+ " @Override",
+ " public ImmutableList<T> anImmutableList() {",
+ " return anImmutableList.build();",
+ " }",
+ "",
+ " @Override",
" public Baz<T> build() {",
" String missing = \"\";",
" if (anInt == null) {",
@@ -675,10 +710,12 @@ public class CompilationTest extends TestCase {
" if (!missing.isEmpty()) {",
" throw new IllegalStateException(\"Missing required properties:\" + missing);",
" }",
- " Baz<T> result = new AutoValue_Baz<T>(",
- " this.anInt, this.aByteArray, this.aNullableIntArray, this.aList);",
- " result.validate();",
- " return result;",
+ " return new AutoValue_Baz<T>(",
+ " this.anInt,",
+ " this.aByteArray,",
+ " this.aNullableIntArray,",
+ " this.aList,",
+ " this.anImmutableList.build());",
" }",
" }",
"}");
@@ -952,7 +989,7 @@ public class CompilationTest extends TestCase {
.in(javaFileObject).onLine(12);
}
- public void testAutoValueBuilderAlienMethod() {
+ public void testAutoValueBuilderWrongTypeGetter() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -960,12 +997,44 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
+ "public abstract class Baz<T, U> {",
+ " abstract T blim();",
+ " abstract U blam();",
+ "",
+ " @AutoValue.Builder",
+ " public interface Builder<T, U> {",
+ " Builder<T, U> blim(T x);",
+ " Builder<T, U> blam(U x);",
+ " T blim();",
+ " T blam();",
+ " Baz<T, U> build();",
+ " }",
+ "}");
+ assertAbout(javaSource())
+ .that(javaFileObject)
+ .processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+ .failsToCompile()
+ .withErrorContaining(
+ "Method matches a property of foo.bar.Baz but has return type T instead of U")
+ .in(javaFileObject).onLine(15);
+ }
+
+ public void testAutoValueBuilderPropertyBuilderAndSetter() {
+ JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "import com.google.common.collect.ImmutableSet;",
+ "",
+ "@AutoValue",
"public abstract class Baz {",
- " abstract String blam();",
+ " abstract ImmutableSet<String> blim();",
"",
" @AutoValue.Builder",
" public interface Builder {",
- " Builder blam(String x, String y);",
+ " abstract ImmutableSet.Builder<String> blimBuilder();",
+ " abstract Builder setBlim(ImmutableSet<String> blim);",
" Baz build();",
" }",
"}");
@@ -973,11 +1042,11 @@ public class CompilationTest extends TestCase {
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("Builder methods must have 0 or 1 parameters")
+ .withErrorContaining("Property blim cannot have both a setter and a builder")
.in(javaFileObject).onLine(11);
}
- public void testAutoValueBuilderMissingBuildMethod() {
+ public void testAutoValueBuilderPropertyBuilderInvalidType() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -985,12 +1054,13 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
- "public abstract class Baz<T> {",
- " abstract T blam();",
+ "public abstract class Baz<T, U> {",
+ " abstract String blim();",
"",
" @AutoValue.Builder",
- " public interface Builder<T> {",
- " Builder<T> blam(T x);",
+ " public interface Builder<T, U> {",
+ " StringBuilder blimBuilder();",
+ " Baz<T, U> build();",
" }",
"}");
assertAbout(javaSource())
@@ -998,54 +1068,55 @@ public class CompilationTest extends TestCase {
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining(
- "Builder must have a single no-argument method returning foo.bar.Baz<T>")
- .in(javaFileObject).onLine(10);
+ "Method looks like a property builder, but its return type is not a builder for an "
+ + "immutable type in com.google.common.collect")
+ .in(javaFileObject).onLine(11);
}
- public void testAutoValueBuilderDuplicateBuildMethods() {
+ public void testAutoValueBuilderPropertyBuilderRawType() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
+ "import com.google.common.collect.ImmutableList;",
"",
"@AutoValue",
- "public abstract class Baz {",
- " abstract String blam();",
+ "public abstract class Baz<T, U> {",
+ " abstract ImmutableList blim();",
"",
" @AutoValue.Builder",
- " public interface Builder {",
- " Builder blam(String x);",
- " Baz build();",
- " Baz create();",
+ " public interface Builder<T, U> {",
+ " ImmutableList.Builder blimBuilder();",
+ " Baz<T, U> build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
- .in(javaFileObject).onLine(12)
- .and()
- .withErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
- .in(javaFileObject).onLine(13);
+ .withErrorContaining(
+ "Property builder type cannot be raw (missing <...>)")
+ .in(javaFileObject).onLine(12);
}
- public void testAutoValueBuilderWrongTypeBuildMethod() {
+ public void testAutoValueBuilderPropertyBuilderWrongCollectionType() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
+ "import com.google.common.collect.ImmutableList;",
+ "import com.google.common.collect.ImmutableSet;",
"",
"@AutoValue",
- "public abstract class Baz {",
- " abstract String blam();",
+ "public abstract class Baz<T, U> {",
+ " abstract ImmutableList<T> blim();",
"",
" @AutoValue.Builder",
- " public interface Builder {",
- " Builder blam(String x);",
- " String build();",
+ " public interface Builder<T, U> {",
+ " ImmutableSet.Builder<T> blimBuilder();",
+ " Baz<T, U> build();",
" }",
"}");
assertAbout(javaSource())
@@ -1053,37 +1124,42 @@ public class CompilationTest extends TestCase {
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining(
- "Method without arguments should be a build method returning foo.bar.Baz")
- .in(javaFileObject).onLine(12);
+ "Return type of property-builder method implies a property of type "
+ + "com.google.common.collect.ImmutableSet<T>, but property blim has type "
+ + "com.google.common.collect.ImmutableList<T>")
+ .in(javaFileObject).onLine(13);
}
- public void testAutoValueBuilderTypeParametersDontMatch1() {
+ public void testAutoValueBuilderPropertyBuilderWrongElementType() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
+ "import com.google.common.collect.ImmutableSet;",
"",
"@AutoValue",
- "public abstract class Baz<T> {",
- " abstract String blam();",
+ "public abstract class Baz<T, U> {",
+ " abstract ImmutableSet<T> blim();",
"",
" @AutoValue.Builder",
- " public interface Builder {",
- " Builder blam(String x);",
- " Baz build();",
+ " public interface Builder<T, U> {",
+ " ImmutableSet.Builder<U> blimBuilder();",
+ " Baz<T, U> build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("Type parameters of foo.bar.Baz.Builder must have same names and "
- + "bounds as type parameters of foo.bar.Baz")
- .in(javaFileObject).onLine(10);
+ .withErrorContaining(
+ "Return type of property-builder method implies a property of type "
+ + "com.google.common.collect.ImmutableSet<U>, but property blim has type "
+ + "com.google.common.collect.ImmutableSet<T>")
+ .in(javaFileObject).onLine(12);
}
- public void testAutoValueBuilderTypeParametersDontMatch2() {
+ public void testAutoValueBuilderAlienMethod0() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -1091,12 +1167,13 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
- "public abstract class Baz<T> {",
- " abstract T blam();",
+ "public abstract class Baz {",
+ " abstract String blam();",
"",
" @AutoValue.Builder",
- " public interface Builder<E> {",
- " Builder<E> blam(E x);",
+ " public interface Builder {",
+ " Builder blam(String x);",
+ " Builder whut();",
" Baz build();",
" }",
"}");
@@ -1104,12 +1181,13 @@ public class CompilationTest extends TestCase {
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("Type parameters of foo.bar.Baz.Builder must have same names and "
- + "bounds as type parameters of foo.bar.Baz")
- .in(javaFileObject).onLine(10);
+ .withErrorContaining(
+ "Method without arguments should be a build method returning foo.bar.Baz"
+ + " or a getter method with the same name and type as a getter method of foo.bar.Baz")
+ .in(javaFileObject).onLine(12);
}
- public void testAutoValueBuilderTypeParametersDontMatch3() {
+ public void testAutoValueBuilderAlienMethod1() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -1117,12 +1195,12 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
- "public abstract class Baz<T extends Number & Comparable<T>> {",
- " abstract T blam();",
+ "public abstract class Baz {",
+ " abstract String blam();",
"",
" @AutoValue.Builder",
- " public interface Builder<T extends Number> {",
- " Builder<T> blam(T x);",
+ " public interface Builder {",
+ " void whut(String x);",
" Baz build();",
" }",
"}");
@@ -1130,12 +1208,11 @@ public class CompilationTest extends TestCase {
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("Type parameters of foo.bar.Baz.Builder must have same names and "
- + "bounds as type parameters of foo.bar.Baz")
- .in(javaFileObject).onLine(10);
+ .withErrorContaining("Method does not correspond to a property of foo.bar.Baz")
+ .in(javaFileObject).onLine(11);
}
- public void testAutoValueBuilderToBuilderWrongTypeParameters() {
+ public void testAutoValueBuilderAlienMethod2() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -1143,27 +1220,24 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
- "abstract class Baz<K extends Comparable<K>, V> {",
- " abstract K key();",
- " abstract V value();",
- " abstract Builder<V, K> toBuilder1();",
+ "public abstract class Baz {",
+ " abstract String blam();",
"",
" @AutoValue.Builder",
- " interface Builder<K extends Comparable<K>, V> {",
- " Builder<K, V> key(K key);",
- " Builder<K, V> value(V value);",
- " Baz<K, V> build();",
+ " public interface Builder {",
+ " Builder blam(String x, String y);",
+ " Baz build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("Builder converter method should return foo.bar.Baz.Builder<K, V>")
- .in(javaFileObject).onLine(9);
+ .withErrorContaining("Builder methods must have 0 or 1 parameters")
+ .in(javaFileObject).onLine(11);
}
- public void testAutoValueBuilderToBuilderDuplicate() {
+ public void testAutoValueBuilderMissingBuildMethod() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -1171,55 +1245,53 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
- "abstract class Baz<K extends Comparable<K>, V> {",
- " abstract K key();",
- " abstract V value();",
- " abstract Builder<K, V> toBuilder1();",
- " abstract Builder<K, V> toBuilder2();",
+ "public abstract class Baz<T> {",
+ " abstract T blam();",
"",
" @AutoValue.Builder",
- " interface Builder<K extends Comparable<K>, V> {",
- " Builder<K, V> key(K key);",
- " Builder<K, V> value(V value);",
- " Baz<K, V> build();",
+ " public interface Builder<T> {",
+ " Builder<T> blam(T x);",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("There can be at most one builder converter method")
- .in(javaFileObject).onLine(9);
+ .withErrorContaining(
+ "Builder must have a single no-argument method returning foo.bar.Baz<T>")
+ .in(javaFileObject).onLine(10);
}
- public void testAutoValueValidateNotInAutoValue() {
+ public void testAutoValueBuilderDuplicateBuildMethods() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
"",
"import com.google.auto.value.AutoValue;",
"",
+ "@AutoValue",
"public abstract class Baz {",
" abstract String blam();",
"",
- " @AutoValue.Validate",
- " void validate() {}",
- "",
+ " @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
" Baz build();",
+ " Baz create();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining(
- "@AutoValue.Validate can only be applied to a method inside an @AutoValue class")
- .in(javaFileObject).onLine(9);
+ .withErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
+ .in(javaFileObject).onLine(12)
+ .and()
+ .withErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
+ .in(javaFileObject).onLine(13);
}
- public void testAutoValueValidateWithoutBuilder() {
+ public void testAutoValueBuilderWrongTypeBuildMethod() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -1230,12 +1302,10 @@ public class CompilationTest extends TestCase {
"public abstract class Baz {",
" abstract String blam();",
"",
- " @AutoValue.Validate",
- " void validate() {}",
- "",
+ " @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
- " Baz build();",
+ " String build();",
" }",
"}");
assertAbout(javaSource())
@@ -1243,11 +1313,11 @@ public class CompilationTest extends TestCase {
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
.withErrorContaining(
- "@AutoValue.Validate is only meaningful if there is an @AutoValue.Builder")
- .in(javaFileObject).onLine(10);
+ "Method without arguments should be a build method returning foo.bar.Baz")
+ .in(javaFileObject).onLine(12);
}
- public void testAutoValueBuilderValidateMethodStatic() {
+ public void testAutoValueBuilderTypeParametersDontMatch1() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -1255,12 +1325,9 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
- "public abstract class Baz {",
+ "public abstract class Baz<T> {",
" abstract String blam();",
"",
- " @AutoValue.Validate",
- " static void validate() {}",
- "",
" @AutoValue.Builder",
" public interface Builder {",
" Builder blam(String x);",
@@ -1271,11 +1338,12 @@ public class CompilationTest extends TestCase {
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("@AutoValue.Validate cannot apply to a static method")
+ .withErrorContaining("Type parameters of foo.bar.Baz.Builder must have same names and "
+ + "bounds as type parameters of foo.bar.Baz")
.in(javaFileObject).onLine(10);
}
- public void testAutoValueBuilderValidateMethodNotVoid() {
+ public void testAutoValueBuilderTypeParametersDontMatch2() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -1283,17 +1351,12 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
- "public abstract class Baz {",
- " abstract String blam();",
- "",
- " @AutoValue.Validate",
- " Baz validate() {",
- " return this;",
- " }",
+ "public abstract class Baz<T> {",
+ " abstract T blam();",
"",
" @AutoValue.Builder",
- " public interface Builder {",
- " Builder blam(String x);",
+ " public interface Builder<E> {",
+ " Builder<E> blam(E x);",
" Baz build();",
" }",
"}");
@@ -1301,11 +1364,12 @@ public class CompilationTest extends TestCase {
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("@AutoValue.Validate method must be void")
+ .withErrorContaining("Type parameters of foo.bar.Baz.Builder must have same names and "
+ + "bounds as type parameters of foo.bar.Baz")
.in(javaFileObject).onLine(10);
}
- public void testAutoValueBuilderValidateMethodWithParameters() {
+ public void testAutoValueBuilderTypeParametersDontMatch3() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -1313,15 +1377,12 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
- "public abstract class Baz {",
- " abstract String blam();",
- "",
- " @AutoValue.Validate",
- " void validate(boolean why) {}",
+ "public abstract class Baz<T extends Number & Comparable<T>> {",
+ " abstract T blam();",
"",
" @AutoValue.Builder",
- " public interface Builder {",
- " Builder blam(String x);",
+ " public interface Builder<T extends Number> {",
+ " Builder<T> blam(T x);",
" Baz build();",
" }",
"}");
@@ -1329,11 +1390,12 @@ public class CompilationTest extends TestCase {
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("@AutoValue.Validate method must not have parameters")
+ .withErrorContaining("Type parameters of foo.bar.Baz.Builder must have same names and "
+ + "bounds as type parameters of foo.bar.Baz")
.in(javaFileObject).onLine(10);
}
- public void testAutoValueBuilderValidateMethodDuplicate() {
+ public void testAutoValueBuilderToBuilderWrongTypeParameters() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"foo.bar.Baz",
"package foo.bar;",
@@ -1341,27 +1403,53 @@ public class CompilationTest extends TestCase {
"import com.google.auto.value.AutoValue;",
"",
"@AutoValue",
- "public abstract class Baz {",
- " abstract String blam();",
+ "abstract class Baz<K extends Comparable<K>, V> {",
+ " abstract K key();",
+ " abstract V value();",
+ " abstract Builder<V, K> toBuilder1();",
+ "",
+ " @AutoValue.Builder",
+ " interface Builder<K extends Comparable<K>, V> {",
+ " Builder<K, V> key(K key);",
+ " Builder<K, V> value(V value);",
+ " Baz<K, V> build();",
+ " }",
+ "}");
+ assertAbout(javaSource())
+ .that(javaFileObject)
+ .processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+ .failsToCompile()
+ .withErrorContaining("Builder converter method should return foo.bar.Baz.Builder<K, V>")
+ .in(javaFileObject).onLine(9);
+ }
+
+ public void testAutoValueBuilderToBuilderDuplicate() {
+ JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
"",
- " @AutoValue.Validate",
- " void validate() {}",
+ "import com.google.auto.value.AutoValue;",
"",
- " @AutoValue.Validate",
- " void validateSomeMore() {}",
+ "@AutoValue",
+ "abstract class Baz<K extends Comparable<K>, V> {",
+ " abstract K key();",
+ " abstract V value();",
+ " abstract Builder<K, V> toBuilder1();",
+ " abstract Builder<K, V> toBuilder2();",
"",
" @AutoValue.Builder",
- " public interface Builder {",
- " Builder blam(String x);",
- " Baz build();",
+ " interface Builder<K extends Comparable<K>, V> {",
+ " Builder<K, V> key(K key);",
+ " Builder<K, V> value(V value);",
+ " Baz<K, V> build();",
" }",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor())
.failsToCompile()
- .withErrorContaining("There can only be one @AutoValue.Validate method")
- .in(javaFileObject).onLine(13);
+ .withErrorContaining("There can be at most one builder converter method")
+ .in(javaFileObject).onLine(9);
}
public void testGetFooIsFoo() throws Exception {
diff --git a/value/src/test/java/com/google/auto/value/processor/GuavaCollectionBuildersTest.java b/value/src/test/java/com/google/auto/value/processor/GuavaCollectionBuildersTest.java
new file mode 100644
index 00000000..7bf77957
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/GuavaCollectionBuildersTest.java
@@ -0,0 +1,99 @@
+package com.google.auto.value.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.ClassPath;
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+
+/**
+ * Validates the assumptions AutoValue makes about Guava immutable collection builders. We expect
+ * for each public class {@code com.google.common.collect.ImmutableFoo} that:
+ * <ul>
+ * <li>it contains a public nested class {@code ImmutableFoo.Builder} with the same type parameters;
+ * <li>there is a public static method {@code ImmutableFoo.builder()} that returns
+ * {@code ImmutableFoo.Builder};
+ * <li>there is a method {@code ImmutableFoo.Builder.build()} that returns {@code ImmutableFoo};
+ * <li>and there is a method in {@code ImmutableFoo.Builder} called either {@code addAll}
+ * or {@code putAll} with a single parameter to which {@code ImmutableFoo} can be assigned.
+ * </ul>
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@RunWith(JUnit4.class)
+public class GuavaCollectionBuildersTest {
+ private static final ImmutableSet<String> NON_BUILDABLE_COLLECTIONS =
+ ImmutableSet.of("ImmutableCollection", "ImmutableMapBuilder");
+
+ @Rule public final Expect expect = Expect.create();
+
+ @Test
+ public void testImmutableBuilders() throws Exception {
+ ClassPath classPath = ClassPath.from(getClass().getClassLoader());
+ ImmutableSet<ClassPath.ClassInfo> classes = classPath.getAllClasses();
+ int checked = 0;
+ for (ClassPath.ClassInfo classInfo : classes) {
+ if (classInfo.getPackageName().equals("com.google.common.collect")
+ && classInfo.getSimpleName().startsWith("Immutable")
+ && !NON_BUILDABLE_COLLECTIONS.contains(classInfo.getSimpleName())) {
+ Class<?> c = Class.forName(classInfo.getName());
+ if (Modifier.isPublic(c.getModifiers())) {
+ checked++;
+ checkImmutableClass(c);
+ }
+ }
+ }
+ expect.that(checked).isGreaterThan(10);
+ }
+
+ private void checkImmutableClass(Class<?> c)
+ throws ClassNotFoundException, NoSuchMethodException {
+ if (!Modifier.isPublic(c.getModifiers())) {
+ return;
+ }
+
+ // We have a public static ImmutableFoo.builder()
+ Method builderMethod = c.getMethod("builder");
+ assertThat(Modifier.isStatic(builderMethod.getModifiers())).isTrue();
+
+ // Its return type is Builder with the same type parameters.
+ Type builderMethodReturn = builderMethod.getGenericReturnType();
+ expect.that(builderMethodReturn).isInstanceOf(ParameterizedType.class);
+ ParameterizedType builderMethodParameterizedReturn = (ParameterizedType) builderMethodReturn;
+ Class<?> builderClass = Class.forName(c.getName() + "$Builder");
+ expect.that(builderMethod.getReturnType()).isEqualTo(builderClass);
+ expect.that(Arrays.toString(builderMethodParameterizedReturn.getActualTypeArguments()))
+ .named(c.getName())
+ .isEqualTo(Arrays.toString(builderClass.getTypeParameters()));
+
+ // The Builder has a public build() method that returns ImmutableFoo.
+ Method buildMethod = builderClass.getMethod("build");
+ expect.that(buildMethod.getReturnType()).isEqualTo(c);
+
+ // The Builder has either an addAll or a putAll public method with a parameter that
+ // ImmutableFoo can be assigned to.
+ boolean found = false;
+ for (Method m : builderClass.getMethods()) {
+ if ((m.getName().equals("addAll") || m.getName().equals("putAll"))
+ && m.getParameterTypes().length == 1) {
+ Class<?> parameter = m.getParameterTypes()[0];
+ if (parameter.isAssignableFrom(c)) {
+ found = true;
+ break;
+ }
+ }
+ }
+ expect.that(found).named(builderClass.getName() + " has addAll or putAll").isTrue();
+ }
+}