diff options
Diffstat (limited to 'value/src/main/java/com')
-rw-r--r-- | value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java | 165 | ||||
-rw-r--r-- | value/src/main/java/com/google/auto/value/processor/BuilderSpec.java | 65 |
2 files changed, 105 insertions, 125 deletions
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 8169f8e4..ca20f0b8 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 @@ -19,6 +19,7 @@ import static com.google.common.collect.Sets.difference; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; +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; @@ -26,6 +27,7 @@ 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; @@ -33,10 +35,12 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import javax.annotation.processing.ProcessingEnvironment; 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; @@ -65,9 +69,9 @@ class BuilderMethodClassifier { private final Set<ExecutableElement> buildMethods = new LinkedHashSet<>(); private final Map<String, BuilderSpec.PropertyGetter> builderGetters = new LinkedHashMap<>(); private final Map<String, PropertyBuilder> propertyNameToPropertyBuilder = new LinkedHashMap<>(); - private final Multimap<String, ExecutableElement> propertyNameToPrefixedSetters = + private final Multimap<String, PropertySetter> propertyNameToPrefixedSetters = LinkedListMultimap.create(); - private final Multimap<String, ExecutableElement> propertyNameToUnprefixedSetters = + private final Multimap<String, PropertySetter> propertyNameToUnprefixedSetters = LinkedListMultimap.create(); private final EclipseHack eclipseHack; @@ -141,10 +145,10 @@ class BuilderMethodClassifier { * 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 is a method in the builder called {@code foo} or - * {@code setFoo}. + * where the key is {@code "foo"} and the value describes a method in the builder called + * {@code foo} or {@code setFoo}. */ - ImmutableMultimap<String, ExecutableElement> propertyNameToSetters() { + ImmutableMultimap<String, PropertySetter> propertyNameToSetters() { return ImmutableMultimap.copyOf( settersPrefixed ? propertyNameToPrefixedSetters : propertyNameToUnprefixedSetters); } @@ -182,7 +186,7 @@ class BuilderMethodClassifier { if (errorReporter.errorCount() > startErrorCount) { return false; } - Multimap<String, ExecutableElement> propertyNameToSetter; + Multimap<String, PropertySetter> propertyNameToSetter; if (propertyNameToPrefixedSetters.isEmpty()) { propertyNameToSetter = propertyNameToUnprefixedSetters; this.settersPrefixed = false; @@ -192,7 +196,7 @@ class BuilderMethodClassifier { } else { errorReporter.reportError( "If any setter methods use the setFoo convention then all must", - propertyNameToUnprefixedSetters.values().iterator().next()); + propertyNameToUnprefixedSetters.values().iterator().next().getSetter()); return false; } getterToPropertyName.forEach( @@ -254,16 +258,15 @@ class BuilderMethodClassifier { * {@code @AutoValue} class; or it can be a property builder, like {@code * ImmutableList.Builder<String> foosBuilder()} for the property defined by {@code * ImmutableList<String> foos()} or {@code getFoos()}. - * - * @return true if the method was successfully classified, false if an error has been reported. */ - private boolean classifyMethodNoArgs(ExecutableElement method) { + private void classifyMethodNoArgs(ExecutableElement method) { String methodName = method.getSimpleName().toString(); TypeMirror returnType = builderMethodReturnType(method); ExecutableElement getter = getterNameToGetter.get(methodName); if (getter != null) { - return classifyGetter(method, getter); + classifyGetter(method, getter); + return; } if (methodName.endsWith("Builder")) { @@ -276,29 +279,25 @@ class BuilderMethodClassifier { propertyBuilderClassifier.makePropertyBuilder(method, property); if (propertyBuilder.isPresent()) { propertyNameToPropertyBuilder.put(property, propertyBuilder.get()); - return true; - } else { - return false; } + return; } } if (TYPE_EQUIVALENCE.equivalent(returnType, autoValueClass.asType())) { buildMethods.add(method); - return true; + } else { + 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," + + " or fooBuilder() where foo() or getFoo() is a getter method of %1$s", + autoValueClass, typeParamsString()); + errorReporter.reportError(error, 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) { + private void classifyGetter(ExecutableElement builderGetter, ExecutableElement originalGetter) { String propertyName = getterToPropertyName.get(originalGetter); TypeMirror originalGetterType = getterToPropertyType.get(originalGetter); TypeMirror builderGetterType = builderMethodReturnType(builderGetter); @@ -307,7 +306,7 @@ class BuilderMethodClassifier { builderGetters.put( propertyName, new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, null)); - return true; + return; } Optionalish optional = Optionalish.createIfOptional(builderGetterType); if (optional != null) { @@ -324,7 +323,7 @@ class BuilderMethodClassifier { builderGetters.put( propertyName, new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, optional)); - return true; + return; } } String error = @@ -333,22 +332,19 @@ class BuilderMethodClassifier { + "or an Optional wrapping of %3$s", autoValueClass, builderGetterType, originalGetterType); errorReporter.reportError(error, builderGetter); - return false; } /** * Classifies 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 the * {@code AutoValue} class has a property called {@code foo} of type {@code T}. - * - * @return true if the method was successfully classified, false if an error has been reported. */ - private boolean classifyMethodOneArg(ExecutableElement method) { + private void classifyMethodOneArg(ExecutableElement method) { String methodName = method.getSimpleName().toString(); Map<String, ExecutableElement> propertyNameToGetter = getterToPropertyName.inverse(); String propertyName = null; ExecutableElement valueGetter = propertyNameToGetter.get(methodName); - Multimap<String, ExecutableElement> propertyNameToSetters = null; + Multimap<String, PropertySetter> propertyNameToSetters = null; if (valueGetter != null) { propertyNameToSetters = propertyNameToUnprefixedSetters; propertyName = methodName; @@ -373,18 +369,21 @@ class BuilderMethodClassifier { errorReporter.reportError( "Method does not correspond to a property of " + autoValueClass, method); checkForFailedJavaBean(method); - return false; + return; } - if (!checkSetterParameter(valueGetter, method)) { - return false; - } else if (!TYPE_EQUIVALENCE.equivalent( - builderMethodReturnType(method), builderType.asType())) { - errorReporter.reportError( - "Setter methods must return " + builderType + typeParamsString(), method); - return false; - } else { - propertyNameToSetters.put(propertyName, method); - return true; + Optional<Function<String, String>> function = getSetterFunction(valueGetter, 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( + "Setter methods must return " + builderType + typeParamsString(), method); + } } } @@ -408,50 +407,53 @@ class BuilderMethodClassifier { } /** - * Checks that the given setter method has a parameter type that is compatible with the return - * type of the given getter. Compatible means either that it is the same, or that it is a type - * that can be copied using a method like {@code ImmutableList.copyOf} or {@code Optional.of}. - * - * @return true if the types correspond, false if an error has been reported. + * 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 boolean checkSetterParameter(ExecutableElement valueGetter, ExecutableElement setter) { + private Optional<Function<String, String>> getSetterFunction( + ExecutableElement valueGetter, ExecutableElement setter) { TypeMirror targetType = getterToPropertyType.get(valueGetter); ExecutableType finalSetter = MoreTypes.asExecutable( typeUtils.asMemberOf(MoreTypes.asDeclared(builderType.asType()), setter)); TypeMirror parameterType = finalSetter.getParameterTypes().get(0); if (typeUtils.isSameType(parameterType, targetType)) { - return true; + return Optional.of(s -> s); } // Parameter type is not equal to property type, but might be convertible with copyOf. - ImmutableList<ExecutableElement> copyOfMethods = copyOfMethods(targetType); + ImmutableList<ExecutableElement> copyOfMethods = copyOfMethods(targetType, setter); if (!copyOfMethods.isEmpty()) { - return canMakeCopyUsing(copyOfMethods, valueGetter, setter); + return getConvertingSetterFunction(copyOfMethods, valueGetter, setter); } String error = String.format( "Parameter type %s of setter method should be %s to match getter %s.%s", parameterType, targetType, autoValueClass, valueGetter.getSimpleName()); errorReporter.reportError(error, setter); - return false; + return Optional.empty(); } /** - * Checks that the given setter method has a parameter type that can be copied to the return type - * of the given getter using one of the given {@code copyOf} methods. - * - * @return true if the copy can be made, false if an error has been reported. + * 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 boolean canMakeCopyUsing( + private Optional<Function<String, String>> getConvertingSetterFunction( ImmutableList<ExecutableElement> copyOfMethods, ExecutableElement valueGetter, ExecutableElement setter) { DeclaredType targetType = MoreTypes.asDeclared(getterToPropertyType.get(valueGetter)); TypeMirror parameterType = setter.getParameters().get(0).asType(); for (ExecutableElement copyOfMethod : copyOfMethods) { - if (canMakeCopyUsing(copyOfMethod, targetType, parameterType)) { - return true; + Optional<Function<String, String>> function = + getConvertingSetterFunction(copyOfMethod, targetType, parameterType); + if (function.isPresent()) { + return function; } } String targetTypeSimpleName = targetType.asElement().getSimpleName().toString(); @@ -466,15 +468,16 @@ class BuilderMethodClassifier { copyOfMethods.get(0).getSimpleName(), targetType); errorReporter.reportError(error, setter); - return false; + return Optional.empty(); } /** - * Returns true if {@code copyOfMethod} can be used to copy the {@code parameterType} to the - * {@code targetType}. For example, we might have a property of type {@code ImmutableSet<T>} and - * our setter has a parameter of type {@code Set<? extends T>}. Can we use {@code - * ImmutableSet<E> ImmutableSet.copyOf(Collection<? extends E>)} to set the property? What about - * {@code ImmutableSet<E> ImmutableSet.copyOf(E[])}? + * 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<T>} and our + * setter has a parameter of type {@code Set<? extends T>}. Can we use {@code ImmutableSet<E> + * ImmutableSet.copyOf(Collection<? extends E>)} to set the property? What about {@code + * ImmutableSet<E> ImmutableSet.copyOf(E[])}? * * <p>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<T>}. One subtle point is that the @@ -488,10 +491,10 @@ class BuilderMethodClassifier { * examples. * @param targetType the type of the property to be set, {@code ImmutableSet<T>} in the example. * @param parameterType the type of the setter parameter, {@code Set<? extends T>} in the example. - * @return true if the method can be used to make the right type of result from the given - * parameter type. + * @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 boolean canMakeCopyUsing( + private Optional<Function<String, String>> 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 @@ -504,8 +507,12 @@ class BuilderMethodClassifier { // are static. So even if our target type is ImmutableSet<String>, if we ask what the type of // copyOf is in ImmutableSet<String> it will still tell us <T> Optional<T> (T). // Instead, we do the variable substitutions ourselves. - return TypeVariables.canAssignStaticMethodResult( - copyOfMethod, parameterType, targetType, typeUtils); + if (TypeVariables.canAssignStaticMethodResult( + copyOfMethod, parameterType, targetType, typeUtils)) { + String method = TypeEncoder.encodeRaw(targetType) + "." + copyOfMethod.getSimpleName(); + return Optional.of(s -> method + "(" + s + ")"); + } + return Optional.empty(); } /** @@ -514,11 +521,23 @@ class BuilderMethodClassifier { * least one such method, but we will also accept other classes with an appropriate method, such * as {@link java.util.EnumSet}. */ - private ImmutableList<ExecutableElement> copyOfMethods(TypeMirror targetType) { + private ImmutableList<ExecutableElement> copyOfMethods( + TypeMirror targetType, ExecutableElement setter) { if (!targetType.getKind().equals(TypeKind.DECLARED)) { return ImmutableList.of(); } - String copyOf = Optionalish.isOptional(targetType) ? "of" : "copyOf"; + String copyOf; + Optionalish optionalish = Optionalish.createIfOptional(targetType); + if (optionalish == null) { + copyOf = "copyOf"; + } else { + VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters()); + boolean nullable = + AutoValueOrOneOfProcessor.nullableAnnotationFor( + parameterElement, parameterElement.asType()) + .isPresent(); + copyOf = nullable ? optionalish.ofNullable() : "of"; + } TypeElement targetTypeElement = MoreElements.asType(typeUtils.asElement(targetType)); ImmutableList.Builder<ExecutableElement> copyOfMethods = ImmutableList.builder(); for (ExecutableElement method : 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 a9c464a8..9cebcad1 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 @@ -25,13 +25,13 @@ import com.google.auto.common.MoreTypes; import com.google.auto.value.processor.AutoValueOrOneOfProcessor.Property; import com.google.common.collect.ImmutableBiMap; 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 java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -41,7 +41,6 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; 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; @@ -214,23 +213,7 @@ class BuilderSpec { vars.builderActualTypes = TypeSimplifier.actualTypeParametersString(builderTypeElement); vars.buildMethod = Optional.of(new SimpleMethod(buildMethod)); vars.builderGetters = classifier.builderGetters(); - - DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderTypeElement.asType()); - ImmutableMultimap.Builder<String, PropertySetter> setterBuilder = ImmutableMultimap.builder(); - classifier.propertyNameToSetters().forEach( - (property, setter) -> { - ExecutableElement getter = getterToPropertyName.inverse().get(property); - // Get the effective parameter type, which might need asMemberOf in cases where the method - // is inherited from a generic ancestor type and references type parameters. - ExecutableType setterMirror = MoreTypes.asExecutable( - processingEnv.getTypeUtils().asMemberOf(builderTypeMirror, setter)); - TypeMirror parameterType = Iterables.getOnlyElement(setterMirror.getParameterTypes()); - TypeMirror propertyType = getterToPropertyType.get(getter); - PropertySetter propertySetter = new PropertySetter( - setter, parameterType, propertyType, processingEnv.getTypeUtils()); - setterBuilder.put(property, propertySetter); - }); - vars.builderSetters = setterBuilder.build(); + vars.builderSetters = classifier.propertyNameToSetters(); vars.builderPropertyBuilders = ImmutableMap.copyOf(classifier.propertyNameToPropertyBuilder()); @@ -304,18 +287,18 @@ class BuilderSpec { * with a type that can be copied to {@code T} through {@code Optional.of}. */ public static class PropertySetter { + private final ExecutableElement setter; private final String access; private final String name; private final String parameterTypeString; private final boolean primitiveParameter; private final String nullableAnnotation; - private final String copyOf; + private final Function<String, String> copyFunction; PropertySetter( - ExecutableElement setter, - TypeMirror parameterType, - TypeMirror propertyType, - Types typeUtils) { + ExecutableElement setter, TypeMirror parameterType, Function<String, String> copyFunction) { + this.setter = setter; + this.copyFunction = copyFunction; this.access = SimpleMethod.access(setter); this.name = setter.getSimpleName().toString(); primitiveParameter = parameterType.getKind().isPrimitive(); @@ -324,8 +307,10 @@ class BuilderSpec { Optional<String> maybeNullable = AutoValueOrOneOfProcessor.nullableAnnotationFor(parameterElement, parameterType); this.nullableAnnotation = maybeNullable.orElse(""); - boolean nullable = maybeNullable.isPresent(); - this.copyOf = copyOfString(propertyType, parameterType, typeUtils, nullable); + } + + ExecutableElement getSetter() { + return setter; } private static String parameterTypeString(ExecutableElement setter, TypeMirror parameterType) { @@ -342,26 +327,6 @@ class BuilderSpec { } } - private String copyOfString( - TypeMirror propertyType, TypeMirror parameterType, Types typeUtils, boolean nullable) { - boolean sameType = typeUtils.isAssignable(parameterType, propertyType); - if (sameType) { - return null; - } - TypeMirror erasedPropertyType = typeUtils.erasure(propertyType); - String rawTarget = TypeEncoder.encodeRaw(erasedPropertyType); - String of; - Optionalish optionalish = Optionalish.createIfOptional(propertyType); - if (optionalish == null) { - of = "copyOf"; - } else if (nullable) { - of = optionalish.ofNullable(); - } else { - of = "of"; - } - return rawTarget + "." + of + "(%s)"; - } - public String getAccess() { return access; } @@ -383,14 +348,10 @@ class BuilderSpec { } public String copy(AutoValueProcessor.Property property) { - if (copyOf == null) { - return property.toString(); - } - - String copy = String.format(copyOf, property); + String copy = copyFunction.apply(property.toString()); // Add a null guard only in cases where we are using copyOf and the property is @Nullable. - if (property.isNullable()) { + if (property.isNullable() && !copy.equals(property.toString())) { copy = String.format("(%s == null ? null : %s)", property, copy); } |