com.google.auto.value.processor.BuilderMethodClassifier Maven / Gradle / Ivy
Show all versions of auto-value Show documentation
/*
* Copyright 2015 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.auto.value.processor;
import static com.google.auto.value.processor.AutoValueishProcessor.nullableAnnotationFor;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.value.processor.BuilderSpec.Copier;
import com.google.auto.value.processor.BuilderSpec.PropertySetter;
import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
import com.google.common.base.Equivalence;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
* Classifies methods inside builder types, based on their names and parameter and return types.
*
* @param the kind of {@link Element} that the corresponding properties are defined by. This is
* {@link ExecutableElement} for AutoValue, where properties are defined by abstract methods,
* and {@link VariableElement} for AutoBuilder, where they are defined by constructor or method
* parameters.
* @author Éamonn McManus
*/
abstract class BuilderMethodClassifier {
private static final Equivalence TYPE_EQUIVALENCE = MoreTypes.equivalence();
private final ErrorReporter errorReporter;
private final Types typeUtils;
private final Elements elementUtils;
private final TypeMirror builtType;
private final TypeElement builderType;
/**
* Property types, rewritten to refer to type variables in the builder. For example, suppose you
* have {@code @AutoValue abstract class Foo} with a getter {@code abstract T bar()} and a
* builder {@code @AutoValue.Builder interface Builder} with a setter {@code abstract
* Builder setBar(T t)}. Then the {@code T} of {@code Foo} and the {@code T} of {@code
* Foo.Builder} are two separate variables. Originally {@code bar()} returned the {@code T} of
* {@code Foo}, but in this map we have rewritten it to be the {@code T} of {@code
* Foo.Builder}.
*
* Importantly, this rewrite loses type annotations, so when those are important we must
* be careful to look at the original type as reported by the {@link #originalPropertyType}
* method.
*/
private final ImmutableMap rewrittenPropertyTypes;
private final Set buildMethods = new LinkedHashSet<>();
private final Map builderGetters = new LinkedHashMap<>();
private final Map propertyNameToPropertyBuilder = new LinkedHashMap<>();
private final Multimap propertyNameToPrefixedSetters =
LinkedListMultimap.create();
private final Multimap propertyNameToUnprefixedSetters =
LinkedListMultimap.create();
private final EclipseHack eclipseHack;
private boolean settersPrefixed;
BuilderMethodClassifier(
ErrorReporter errorReporter,
ProcessingEnvironment processingEnv,
TypeMirror builtType,
TypeElement builderType,
ImmutableMap rewrittenPropertyTypes) {
this.errorReporter = errorReporter;
this.typeUtils = processingEnv.getTypeUtils();
this.elementUtils = processingEnv.getElementUtils();
this.builtType = builtType;
this.builderType = builderType;
this.rewrittenPropertyTypes = rewrittenPropertyTypes;
this.eclipseHack = new EclipseHack(processingEnv);
}
/**
* Returns a multimap from the name of a property to the methods that set it. If the property is
* defined by an abstract method in the {@code @AutoValue} class called {@code foo()} or {@code
* getFoo()} then the name of the property is {@code foo} and there will be an entry in the map
* where the key is {@code "foo"} and the value describes a method in the builder called {@code
* foo} or {@code setFoo}.
*/
ImmutableMultimap propertyNameToSetters() {
return ImmutableMultimap.copyOf(
settersPrefixed ? propertyNameToPrefixedSetters : propertyNameToUnprefixedSetters);
}
Map propertyNameToPropertyBuilder() {
return propertyNameToPropertyBuilder;
}
/**
* Returns the set of properties that have getters in the builder. If a property is defined by an
* abstract method in the {@code @AutoValue} class called {@code foo()} or {@code getFoo()} then
* the name of the property is {@code foo}, If the builder also has a method of the same name
* ({@code foo()} or {@code getFoo()}) then the set returned here will contain {@code foo}.
*/
ImmutableMap builderGetters() {
return ImmutableMap.copyOf(builderGetters);
}
/**
* Returns the methods that were identified as {@code build()} methods. These are methods that
* have no parameters and return the {@code @AutoValue} type, conventionally called {@code
* build()}.
*/
Set buildMethods() {
return ImmutableSet.copyOf(buildMethods);
}
/** Classifies the given methods and sets the state of this object based on what is found. */
boolean classifyMethods(Iterable methods, boolean autoValueHasToBuilder) {
int startErrorCount = errorReporter.errorCount();
for (ExecutableElement method : methods) {
classifyMethod(method);
}
if (errorReporter.errorCount() > startErrorCount) {
return false;
}
Multimap propertyNameToSetter;
if (propertyNameToPrefixedSetters.isEmpty()) {
propertyNameToSetter = propertyNameToUnprefixedSetters;
this.settersPrefixed = false;
} else if (propertyNameToUnprefixedSetters.isEmpty()) {
propertyNameToSetter = propertyNameToPrefixedSetters;
this.settersPrefixed = true;
} else {
errorReporter.reportError(
propertyNameToUnprefixedSetters.values().iterator().next().getSetter(),
"[%sSetNotSet] If any setter methods use the setFoo convention then all must",
autoWhat());
return false;
}
for (String property : rewrittenPropertyTypes.keySet()) {
TypeMirror propertyType = rewrittenPropertyTypes.get(property);
boolean hasSetter = propertyNameToSetter.containsKey(property);
PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property);
boolean hasBuilder = propertyBuilder != null;
if (hasBuilder) {
// If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must
// be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a
// toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is
// possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll
// method that accepts a Bar argument.
boolean canMakeBarBuilder =
(propertyBuilder.getBuiltToBuilder() != null || propertyBuilder.getCopyAll() != null);
boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter);
if (needToMakeBarBuilder && !canMakeBarBuilder) {
errorReporter.reportError(
propertyBuilder.getPropertyBuilderMethod(),
"[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no"
+ " way to make that type from %2$s: %2$s does not have a non-static"
+ " toBuilder() method that returns %1$s, and %1$s does not have a method"
+ " addAll or putAll that accepts an argument of type %2$s",
propertyBuilder.getBuilderTypeMirror(),
propertyType);
}
} else if (!hasSetter) {
// We have neither barBuilder() nor setBar(Bar), so we should complain.
String setterName = settersPrefixed ? prefixWithSet(property) : property;
errorReporter.reportError(
builderType,
"[%sBuilderMissingMethod] Expected a method with this signature: %s"
+ " %s(%s), or a %sBuilder() method",
autoWhat(),
builderType.asType(),
setterName,
propertyType,
property);
}
}
return errorReporter.errorCount() == startErrorCount;
}
/** Classifies a method and update the state of this object based on what is found. */
private void classifyMethod(ExecutableElement method) {
switch (method.getParameters().size()) {
case 0:
classifyMethodNoArgs(method);
break;
case 1:
classifyMethodOneArg(method);
break;
default:
errorReporter.reportError(
method, "[%sBuilderArgs] Builder methods must have 0 or 1 parameters", autoWhat());
}
}
/**
* Classifies a method given that it has no arguments. Currently a method with no arguments can be
* a {@code build()} method, meaning that its return type must be the {@code @AutoValue} class; it
* can be a getter, with the same signature as one of the property getters in the
* {@code @AutoValue} class; or it can be a property builder, like {@code
* ImmutableList.Builder foosBuilder()} for the property defined by {@code
* ImmutableList foos()} or {@code getFoos()}.
*/
private void classifyMethodNoArgs(ExecutableElement method) {
Optional getterProperty = propertyForBuilderGetter(method);
if (getterProperty.isPresent()) {
classifyGetter(method, getterProperty.get());
return;
}
String methodName = method.getSimpleName().toString();
TypeMirror returnType = builderMethodReturnType(method);
if (methodName.endsWith("Builder")) {
String property = methodName.substring(0, methodName.length() - "Builder".length());
if (rewrittenPropertyTypes.containsKey(property)) {
PropertyBuilderClassifier propertyBuilderClassifier =
new PropertyBuilderClassifier(
errorReporter,
typeUtils,
elementUtils,
this,
this::propertyIsNullable,
rewrittenPropertyTypes,
eclipseHack);
Optional propertyBuilder =
propertyBuilderClassifier.makePropertyBuilder(method, property);
if (propertyBuilder.isPresent()) {
propertyNameToPropertyBuilder.put(property, propertyBuilder.get());
}
return;
}
}
if (TYPE_EQUIVALENCE.equivalent(returnType, builtType)) {
buildMethods.add(method);
} else {
errorReporter.reportError(
method,
"[%1$sBuilderNoArg] Method without arguments should be a build method returning"
+ " %2$s, or a getter method with the same name and type as %3$s,"
+ " or fooBuilder() where %4$s is %3$s",
// "where foo() or getFoo() is a method in..." or "where foo is a parameter of..."
autoWhat(),
builtType,
getterMustMatch(),
fooBuilderMustMatch());
}
}
private void classifyGetter(ExecutableElement builderGetter, String propertyName) {
TypeMirror originalGetterType = rewrittenPropertyTypes.get(propertyName);
TypeMirror builderGetterType = builderMethodReturnType(builderGetter);
String builderGetterTypeString = TypeEncoder.encodeWithAnnotations(builderGetterType);
if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) {
builderGetters.put(
propertyName,
new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, null));
return;
}
Optionalish optional = Optionalish.createIfOptional(builderGetterType);
if (optional != null) {
TypeMirror containedType = optional.getContainedType(typeUtils);
// If the original method is int getFoo() then we allow Optional here.
// boxedOriginalType is Integer, and containedType is also Integer.
// We don't need any special code for OptionalInt because containedType will be int then.
TypeMirror boxedOriginalType =
originalGetterType.getKind().isPrimitive()
? typeUtils.boxedClass(MoreTypes.asPrimitiveType(originalGetterType)).asType()
: null;
if (TYPE_EQUIVALENCE.equivalent(containedType, originalGetterType)
|| TYPE_EQUIVALENCE.equivalent(containedType, boxedOriginalType)) {
builderGetters.put(
propertyName,
new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, optional));
return;
}
}
errorReporter.reportError(
builderGetter,
"[AutoValueBuilderReturnType] Method matches a property of %1$s but has return type %2$s"
+ " instead of %3$s or an Optional wrapping of %3$s",
builtType,
builderGetterType,
originalGetterType);
}
/**
* Classifies a method given that it has one argument. A method with one argument can be:
*
*
* - a setter, meaning that it looks like {@code foo(T)} or {@code setFoo(T)}, where the
* {@code AutoValue} class has a property called {@code foo} of type {@code T};
*
- a property builder with one argument, meaning it looks like {@code
* ImmutableSortedSet.Builder
foosBuilder(Comparator)}, where the {@code AutoValue}
* class has a property called {@code foos} with a type whose builder can be made with an
* argument of the given type.
*
*/
private void classifyMethodOneArg(ExecutableElement method) {
if (classifyPropertyBuilderOneArg(method)) {
return;
}
String methodName = method.getSimpleName().toString();
ImmutableMap propertyElements = propertyElements();
String propertyName = null;
E propertyElement = propertyElements.get(methodName);
Multimap propertyNameToSetters = null;
if (propertyElement != null) {
propertyNameToSetters = propertyNameToUnprefixedSetters;
propertyName = methodName;
} else if (methodName.startsWith("set") && methodName.length() > 3) {
propertyNameToSetters = propertyNameToPrefixedSetters;
propertyName = PropertyNames.decapitalizeLikeJavaBeans(methodName.substring(3));
propertyElement = propertyElements.get(propertyName);
if (propertyElement == null) {
// If our property is defined by a getter called getOAuth() then it is called "OAuth"
// because of JavaBeans rules. Therefore we want JavaBeans rules to be used for the setter
// too, so that you can write setOAuth(x). Meanwhile if the property is defined by a getter
// called oAuth() then it is called "oAuth", but you would still expect to be able to set it
// using setOAuth(x). Hence the second try using a decapitalize method without the quirky
// two-leading-capitals rule.
propertyName = PropertyNames.decapitalizeNormally(methodName.substring(3));
propertyElement = propertyElements.get(propertyName);
}
} else {
// We might also have an unprefixed setter, so the getter is called OAuth() or getOAuth() and
// the setter is called oAuth(x), where again JavaBeans rules imply that it should be called
// OAuth(x). Iterating over the properties here is a bit clunky but this case should be
// unusual.
propertyNameToSetters = propertyNameToUnprefixedSetters;
for (Map.Entry entry : propertyElements.entrySet()) {
if (methodName.equals(PropertyNames.decapitalizeNormally(entry.getKey()))) {
propertyName = entry.getKey();
propertyElement = entry.getValue();
break;
}
}
}
if (propertyElement == null || propertyNameToSetters == null) {
// The second disjunct isn't needed but convinces control-flow checkers that
// propertyNameToSetters can't be null when we call put on it below.
errorReporter.reportError(
method,
"[%sBuilderWhatProp] Method %s does not correspond to %s",
autoWhat(),
methodName,
getterMustMatch());
checkForFailedJavaBean(method);
return;
}
Optional function = getSetterFunction(propertyElement, method);
if (function.isPresent()) {
DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
ExecutableType methodMirror =
MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, method));
if (TYPE_EQUIVALENCE.equivalent(methodMirror.getReturnType(), builderType.asType())) {
TypeMirror parameterType = Iterables.getOnlyElement(methodMirror.getParameterTypes());
propertyNameToSetters.put(
propertyName, new PropertySetter(method, parameterType, function.get()));
} else {
errorReporter.reportError(
method,
"[%sBuilderRet] Setter methods must return %s",
autoWhat(),
builderType.asType());
}
}
}
/**
* Classifies a method given that it has one argument and is a property builder with a parameter,
* like {@code ImmutableSortedSet.Builder foosBuilder(Comparator)}.
*
* @param method A method to classify
* @return true if method has been classified successfully
*/
private boolean classifyPropertyBuilderOneArg(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
if (!methodName.endsWith("Builder")) {
return false;
}
String property = methodName.substring(0, methodName.length() - "Builder".length());
if (!rewrittenPropertyTypes.containsKey(property)) {
return false;
}
PropertyBuilderClassifier propertyBuilderClassifier =
new PropertyBuilderClassifier(
errorReporter,
typeUtils,
elementUtils,
this,
this::propertyIsNullable,
rewrittenPropertyTypes,
eclipseHack);
Optional maybePropertyBuilder =
propertyBuilderClassifier.makePropertyBuilder(method, property);
maybePropertyBuilder.ifPresent(
propertyBuilder -> propertyNameToPropertyBuilder.put(property, propertyBuilder));
return maybePropertyBuilder.isPresent();
}
/**
* Returns an {@code Optional} describing how to convert a value from the setter's parameter type
* to the getter's return type, or {@code Optional.empty()} if the conversion isn't possible. An
* error will have been reported in the latter case. We can convert if they are already the same
* type, when the returned function will be the identity; or if the setter type can be copied
* using a method like {@code ImmutableList.copyOf} or {@code Optional.of}, when the returned
* function will be something like {@code s -> "Optional.of(" + s + ")"}.
*/
private Optional getSetterFunction(E propertyElement, ExecutableElement setter) {
VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
boolean nullableParameter =
nullableAnnotationFor(parameterElement, parameterElement.asType()).isPresent();
String property = propertyElements().inverse().get(propertyElement);
TypeMirror targetType = rewrittenPropertyTypes.get(property);
ExecutableType finalSetter =
MoreTypes.asExecutable(
typeUtils.asMemberOf(MoreTypes.asDeclared(builderType.asType()), setter));
TypeMirror parameterType = finalSetter.getParameterTypes().get(0);
// Two types are assignable to each other if they are the same type, or if one is primitive and
// the other is the corresponding boxed type. There might be other cases where this is true, but
// we're likely to want to accept those too.
if (typeUtils.isAssignable(parameterType, targetType)
&& typeUtils.isAssignable(targetType, parameterType)) {
if (nullableParameter) {
boolean nullableProperty =
nullableAnnotationFor(propertyElement, originalPropertyType(propertyElement))
.isPresent();
if (!nullableProperty) {
errorReporter.reportError(
setter,
"[%sNullNotNull] Parameter of setter method is @Nullable but %s is not",
autoWhat(),
propertyString(propertyElement));
return Optional.empty();
}
}
return Optional.of(Copier.IDENTITY);
}
// Parameter type is not equal to property type, but might be convertible with copyOf.
ImmutableList copyOfMethods = copyOfMethods(targetType, nullableParameter);
if (!copyOfMethods.isEmpty()) {
return getConvertingSetterFunction(copyOfMethods, propertyElement, setter, parameterType);
}
errorReporter.reportError(
setter,
"[%sGetVsSet] Parameter type %s of setter method should be %s to match %s",
autoWhat(),
parameterType,
targetType,
propertyString(propertyElement));
return Optional.empty();
}
/**
* Returns an {@code Optional} describing how to convert a value from the setter's parameter type
* to the getter's return type using one of the given methods, or {@code Optional.empty()} if the
* conversion isn't possible. An error will have been reported in the latter case.
*/
private Optional getConvertingSetterFunction(
ImmutableList copyOfMethods,
E propertyElement,
ExecutableElement setter,
TypeMirror parameterType) {
String property = propertyElements().inverse().get(propertyElement);
DeclaredType targetType = MoreTypes.asDeclared(rewrittenPropertyTypes.get(property));
for (ExecutableElement copyOfMethod : copyOfMethods) {
Optional function =
getConvertingSetterFunction(copyOfMethod, targetType, parameterType);
if (function.isPresent()) {
return function;
}
}
String targetTypeSimpleName = targetType.asElement().getSimpleName().toString();
errorReporter.reportError(
setter,
"[%sGetVsSetOrConvert] Parameter type %s of setter method should be %s to match %s, or it"
+ " should be a type that can be passed to %s.%s to produce %s",
autoWhat(),
parameterType,
targetType,
propertyString(propertyElement),
targetTypeSimpleName,
copyOfMethods.get(0).getSimpleName(),
targetType);
return Optional.empty();
}
/**
* Returns an {@code Optional} containing a function to use {@code copyOfMethod} to copy the
* {@code parameterType} to the {@code targetType}, or {@code Optional.empty()} if the method
* can't be used. For example, we might have a property of type {@code ImmutableSet} and our
* setter has a parameter of type {@code Set extends T>}. Can we use {@code ImmutableSet
* ImmutableSet.copyOf(Collection extends E>)} to set the property? What about {@code
* ImmutableSet ImmutableSet.copyOf(E[])}?
*
* The example here is deliberately complicated, in that it has a type parameter of its own,
* presumably because the {@code @AutoValue} class is {@code Foo}. One subtle point is that the
* builder will then be {@code Builder} where this {@code T} is a different type
* variable. However, we've used {@link TypeVariables} to ensure that the {@code T} in {@code
* ImmutableSet} is actually the one from {@code Builder} instead of the original one from
* {@code Foo}.}
*
* @param copyOfMethod the candidate method to do the copy, {@code
* ImmutableSet.copyOf(Collection extends E>)} or {@code ImmutableSet.copyOf(E[])} in the
* examples.
* @param targetType the type of the property to be set, {@code ImmutableSet} in the example.
* @param parameterType the type of the setter parameter, {@code Set extends T>} in the example.
* @return a function that maps a string parameter to a method call using that parameter. For
* example it might map {@code foo} to {@code ImmutableList.copyOf(foo)}.
*/
private Optional getConvertingSetterFunction(
ExecutableElement copyOfMethod, DeclaredType targetType, TypeMirror parameterType) {
// We have a parameter type, for example Set extends T>, and we want to know if it can be
// passed to the given copyOf method, which might for example be one of these methods from
// ImmutableSet:
// public static ImmutableSet copyOf(Collection extends E> elements)
// public static ImmutableSet copyOf(E[] elements)
// Additionally, if it can indeed be passed to the method, we want to know whether the result
// (here ImmutableSet extends T>) is compatible with the property to be set.
// We can't use Types.asMemberOf to do the substitution for us, because the methods in question
// are static. So even if our target type is ImmutableSet, if we ask what the type of
// copyOf is in ImmutableSet it will still tell us Optional (T).
// Instead, we do the variable substitutions ourselves.
if (TypeVariables.canAssignStaticMethodResult(
copyOfMethod, parameterType, targetType, typeUtils)) {
String method = TypeEncoder.encodeRaw(targetType) + "." + copyOfMethod.getSimpleName();
Function callMethod = s -> method + "(" + s + ")";
// This is a big old hack. We guess that the method can accept a null parameter if it has
// "Nullable" in the name, which java.util.Optional.ofNullable and
// com.google.common.base.Optional.fromNullable do.
Copier copier =
method.contains("Nullable")
? Copier.acceptingNull(callMethod)
: Copier.notAcceptingNull(callMethod);
return Optional.of(copier);
}
return Optional.empty();
}
/**
* Returns {@code copyOf} methods from the given type. These are static methods with a single
* parameter, called {@code copyOf} or {@code copyOfSorted} for Guava collection types, and called
* {@code of} or {@code ofNullable} for {@code Optional}. All of Guava's concrete immutable
* collection types have at least one such method, but we will also accept other classes with an
* appropriate {@code copyOf} method, such as {@link java.util.EnumSet}.
*/
private ImmutableList copyOfMethods(
TypeMirror targetType, boolean nullableParameter) {
if (!targetType.getKind().equals(TypeKind.DECLARED)) {
return ImmutableList.of();
}
ImmutableSet copyOfNames;
Optionalish optionalish = Optionalish.createIfOptional(targetType);
if (optionalish == null) {
copyOfNames = ImmutableSet.of("copyOfSorted", "copyOf");
} else {
copyOfNames = ImmutableSet.of(nullableParameter ? optionalish.ofNullable() : "of");
}
TypeElement targetTypeElement = MoreElements.asType(typeUtils.asElement(targetType));
ImmutableList.Builder copyOfMethods = ImmutableList.builder();
for (String copyOfName : copyOfNames) {
for (ExecutableElement method :
ElementFilter.methodsIn(targetTypeElement.getEnclosedElements())) {
if (method.getSimpleName().contentEquals(copyOfName)
&& method.getParameters().size() == 1
&& method.getModifiers().contains(Modifier.STATIC)) {
copyOfMethods.add(method);
}
}
}
return copyOfMethods.build();
}
/**
* Returns the return type of the given method from the builder. This should be the final type of
* the method when any bound type variables are substituted. Consider this example:
*
* {@code
* abstract static class ParentBuilder {
* B setFoo(String s);
* }
* abstract static class ChildBuilder extends ParentBuilder {
* ...
* }
* }
*
* If the builder is {@code ChildBuilder} then the return type of {@code setFoo} is also {@code
* ChildBuilder}, and not {@code B} as its {@code getReturnType()} method would claim.
*
* If the caller is in a version of Eclipse with this bug then the {@code
* asMemberOf} call will fail if the method is inherited from an interface. We work around that
* for methods in the {@code @AutoValue} class using {@link EclipseHack#methodReturnTypes} but we
* don't try to do so here because it should be much less likely. You might need to change {@code
* ParentBuilder} from an interface to an abstract class to make it work, but you'll often need to
* do that anyway.
*/
TypeMirror builderMethodReturnType(ExecutableElement builderMethod) {
DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
TypeMirror methodMirror;
try {
methodMirror = typeUtils.asMemberOf(builderTypeMirror, builderMethod);
} catch (IllegalArgumentException e) {
// Presumably we've hit the Eclipse bug cited.
return builderMethod.getReturnType();
}
return MoreTypes.asExecutable(methodMirror).getReturnType();
}
private static String prefixWithSet(String propertyName) {
// This is not internationalizationally correct, but it corresponds to what
// Introspector.decapitalize does.
return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
}
/**
* True if the given property is nullable, either because its type has a {@code @Nullable} type
* annotation, or because its getter method has a {@code @Nullable} method annotation.
*/
private boolean propertyIsNullable(String property) {
E propertyElement = propertyElements().get(property);
return Stream.of(propertyElement, originalPropertyType(propertyElement))
.flatMap(ac -> ac.getAnnotationMirrors().stream())
.map(a -> a.getAnnotationType().asElement().getSimpleName())
.anyMatch(n -> n.contentEquals("Nullable"));
}
/**
* Returns a map from property names to the corresponding source program elements. For AutoValue,
* these elements are the abstract getter methods in the {@code @AutoValue} class. For
* AutoBuilder, they are the parameters of the constructor or method that the generated builder
* will call.
*/
abstract ImmutableBiMap propertyElements();
/**
* Returns the property type as it appears on the original source program element. This can be
* different from the type stored in {@link #rewrittenPropertyTypes} since that one will refer to
* type variables in the builder rather than in the original class. Also, {@link
* #rewrittenPropertyTypes} will not have type annotations even if they were present on the
* original element, so {@code originalPropertyType} is the right thing to use for those.
*/
abstract TypeMirror originalPropertyType(E propertyElement);
/**
* A string identifying the given property element, which is a method for AutoValue or a parameter
* for AutoBuilder.
*/
abstract String propertyString(E propertyElement);
/**
* Returns the name of the property that the given no-arg builder method queries, if
* any. For example, if your {@code @AutoValue} class has a method {@code abstract String
* getBar()} then an abstract method in its builder with the same signature will query the {@code
* bar} property.
*/
abstract Optional propertyForBuilderGetter(ExecutableElement method);
/**
* Checks for failed JavaBean usage when a method that looks like a setter doesn't actually match
* anything, and emits a compiler Note if detected. A frequent source of problems is where the
* JavaBeans conventions have been followed for most but not all getters. Then AutoValue considers
* that they haven't been followed at all, so you might have a property called getFoo where you
* thought it was called just foo, and you might not understand why your setter called setFoo is
* rejected (it would have to be called setGetFoo).
*
* This is not relevant for AutoBuilder, which uses parameter names rather than getters. The
* parameter names are unambiguously the same as the property names.
*/
abstract void checkForFailedJavaBean(ExecutableElement rejectedSetter);
/**
* A string describing what sort of Auto this is, {@code "AutoValue"} or {@code "AutoBuilder"}.
*/
abstract String autoWhat();
/**
* A string describing what a builder getter must match: a property method for AutoValue, a
* parameter for AutoBuilder.
*/
abstract String getterMustMatch();
/**
* A string describing what a property builder for property {@code foo} must match, {@code foo()
* or getFoo()} for AutoValue, {@code foo} for AutoBuilder.
*/
abstract String fooBuilderMustMatch();
}