/* * Copyright 2014 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.common.MoreElements.getLocalAndInheritedMethods; import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.hasAnnotationMirror; import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.nullableAnnotationFor; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME; import static com.google.common.collect.Sets.immutableEnumSet; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static javax.lang.model.util.ElementFilter.methodsIn; import static javax.lang.model.util.ElementFilter.typesIn; import com.google.auto.common.MoreTypes; import com.google.auto.value.extension.AutoValueExtension; import com.google.auto.value.processor.AutoValueOrOneOfProcessor.Property; import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import java.util.LinkedHashSet; import java.util.List; 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.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; 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.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; /** * Support for AutoValue builders. * * @author Éamonn McManus */ class BuilderSpec { private final TypeElement autoValueClass; private final ProcessingEnvironment processingEnv; private final ErrorReporter errorReporter; BuilderSpec( TypeElement autoValueClass, ProcessingEnvironment processingEnv, ErrorReporter errorReporter) { this.autoValueClass = autoValueClass; this.processingEnv = processingEnv; this.errorReporter = errorReporter; } private static final ImmutableSet<ElementKind> CLASS_OR_INTERFACE = immutableEnumSet(ElementKind.CLASS, ElementKind.INTERFACE); /** * Determines if the {@code @AutoValue} class for this instance has a correct nested * {@code @AutoValue.Builder} class or interface and return a representation of it in an {@code * Optional} if so. */ Optional<Builder> getBuilder() { Optional<TypeElement> builderTypeElement = Optional.empty(); for (TypeElement containedClass : typesIn(autoValueClass.getEnclosedElements())) { if (hasAnnotationMirror(containedClass, AUTO_VALUE_BUILDER_NAME)) { if (!CLASS_OR_INTERFACE.contains(containedClass.getKind())) { errorReporter.reportError( containedClass, "@AutoValue.Builder can only apply to a class or an interface"); } else if (!containedClass.getModifiers().contains(Modifier.STATIC)) { errorReporter.reportError( containedClass, "@AutoValue.Builder cannot be applied to a non-static class"); } else if (builderTypeElement.isPresent()) { errorReporter.reportError( containedClass, "%s already has a Builder: %s", autoValueClass, builderTypeElement.get()); } else { builderTypeElement = Optional.of(containedClass); } } } if (builderTypeElement.isPresent()) { return builderFrom(builderTypeElement.get()); } else { return Optional.empty(); } } /** Representation of an {@code AutoValue.Builder} class or interface. */ class Builder implements AutoValueExtension.BuilderContext { private final TypeElement builderTypeElement; private ImmutableSet<ExecutableElement> toBuilderMethods; private ExecutableElement buildMethod; private BuilderMethodClassifier classifier; Builder(TypeElement builderTypeElement) { this.builderTypeElement = builderTypeElement; } @Override public TypeElement builderType() { return builderTypeElement; } @Override public Set<ExecutableElement> builderMethods() { return methodsIn(autoValueClass.getEnclosedElements()).stream() .filter( m -> m.getParameters().isEmpty() && m.getModifiers().contains(Modifier.STATIC) && !m.getModifiers().contains(Modifier.PRIVATE) && erasedTypeIs(m.getReturnType(), builderTypeElement)) .collect(toSet()); } @Override public Optional<ExecutableElement> buildMethod() { return methodsIn(builderTypeElement.getEnclosedElements()).stream() .filter( m -> m.getSimpleName().contentEquals("build") && !m.getModifiers().contains(Modifier.PRIVATE) && !m.getModifiers().contains(Modifier.STATIC) && m.getParameters().isEmpty() && erasedTypeIs(m.getReturnType(), autoValueClass)) .findFirst(); } @Override public ExecutableElement autoBuildMethod() { return buildMethod; } @Override public Map<String, Set<ExecutableElement>> setters() { return Maps.transformValues( classifier.propertyNameToSetters().asMap(), propertySetters -> propertySetters.stream().map(PropertySetter::getSetter).collect(toSet())); } @Override public Map<String, ExecutableElement> propertyBuilders() { return Maps.transformValues( classifier.propertyNameToPropertyBuilder(), PropertyBuilder::getPropertyBuilderMethod); } private boolean erasedTypeIs(TypeMirror type, TypeElement baseType) { return type.getKind().equals(TypeKind.DECLARED) && MoreTypes.asDeclared(type).asElement().equals(baseType); } @Override public Set<ExecutableElement> toBuilderMethods() { return toBuilderMethods; } /** * Finds any methods in the set that return the builder type. If the builder has type parameters * {@code <A, B>}, then the return type of the method must be {@code Builder<A, B>} with the * same parameter names. We enforce elsewhere that the names and bounds of the builder * parameters must be the same as those of the @AutoValue class. Here's a correct example: * * <pre> * {@code @AutoValue abstract class Foo<A extends Number, B> { * abstract int someProperty(); * * abstract Builder<A, B> toBuilder(); * * interface Builder<A extends Number, B> {...} * }} * </pre> * * <p>We currently impose that there cannot be more than one such method. */ ImmutableSet<ExecutableElement> toBuilderMethods( Types typeUtils, Set<ExecutableElement> abstractMethods) { List<String> builderTypeParamNames = builderTypeElement.getTypeParameters().stream() .map(e -> e.getSimpleName().toString()) .collect(toList()); ImmutableSet.Builder<ExecutableElement> methods = ImmutableSet.builder(); for (ExecutableElement method : abstractMethods) { if (builderTypeElement.equals(typeUtils.asElement(method.getReturnType()))) { methods.add(method); DeclaredType returnType = MoreTypes.asDeclared(method.getReturnType()); List<String> typeArguments = returnType.getTypeArguments().stream() .filter(t -> t.getKind().equals(TypeKind.TYPEVAR)) .map(t -> typeUtils.asElement(t).getSimpleName().toString()) .collect(toList()); if (!builderTypeParamNames.equals(typeArguments)) { errorReporter.reportError( method, "Builder converter method should return %s%s", builderTypeElement, TypeSimplifier.actualTypeParametersString(builderTypeElement)); } } } ImmutableSet<ExecutableElement> builderMethods = methods.build(); if (builderMethods.size() > 1) { errorReporter.reportError( builderMethods.iterator().next(), "There can be at most one builder converter method"); } this.toBuilderMethods = builderMethods; return builderMethods; } void defineVars( AutoValueTemplateVars vars, ImmutableBiMap<ExecutableElement, String> getterToPropertyName) { Iterable<ExecutableElement> builderMethods = abstractMethods(builderTypeElement); boolean autoValueHasToBuilder = !toBuilderMethods.isEmpty(); ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType = TypeVariables.rewriteReturnTypes( processingEnv.getElementUtils(), processingEnv.getTypeUtils(), getterToPropertyName.keySet(), autoValueClass, builderTypeElement); Optional<BuilderMethodClassifier> optionalClassifier = BuilderMethodClassifier.classify( builderMethods, errorReporter, processingEnv, autoValueClass, builderTypeElement, getterToPropertyName, getterToPropertyType, autoValueHasToBuilder); if (!optionalClassifier.isPresent()) { return; } for (ExecutableElement method : methodsIn(builderTypeElement.getEnclosedElements())) { if (method.getSimpleName().contentEquals("builder") && method.getModifiers().contains(Modifier.STATIC) && method.getAnnotationMirrors().isEmpty()) { // For now we ignore methods with annotations, because for example we do want to allow // Jackson's @JsonCreator. errorReporter.reportWarning( method, "Static builder() method should be in the containing class"); } } this.classifier = optionalClassifier.get(); Set<ExecutableElement> buildMethods = classifier.buildMethods(); if (buildMethods.size() != 1) { Set<? extends Element> errorElements = buildMethods.isEmpty() ? ImmutableSet.of(builderTypeElement) : buildMethods; for (Element buildMethod : errorElements) { errorReporter.reportError( buildMethod, "Builder must have a single no-argument method returning %s%s", autoValueClass, typeParamsString()); } return; } this.buildMethod = Iterables.getOnlyElement(buildMethods); vars.builderIsInterface = builderTypeElement.getKind() == ElementKind.INTERFACE; vars.builderTypeName = TypeSimplifier.classNameOf(builderTypeElement); vars.builderFormalTypes = TypeEncoder.formalTypeParametersString(builderTypeElement); vars.builderActualTypes = TypeSimplifier.actualTypeParametersString(builderTypeElement); vars.buildMethod = Optional.of(new SimpleMethod(buildMethod)); vars.builderGetters = classifier.builderGetters(); vars.builderSetters = classifier.propertyNameToSetters(); vars.builderPropertyBuilders = ImmutableMap.copyOf(classifier.propertyNameToPropertyBuilder()); Set<Property> required = new LinkedHashSet<>(vars.props); for (Property property : vars.props) { if (property.isNullable() || property.getOptional() != null || vars.builderPropertyBuilders.containsKey(property.getName())) { required.remove(property); } } vars.builderRequiredProperties = ImmutableSet.copyOf(required); } } /** * Information about a builder property getter, referenced from the autovalue.vm template. A * property called foo (defined by a method {@code T foo()} or {@code T getFoo()}) can have a * getter method in the builder with the same name ({@code foo()} or {@code getFoo()}) and a * return type of either {@code T} or {@code Optional<T>}. The {@code Optional<T>} form can be * used to tell whether the property has been set. Here, {@code Optional<T>} can be either {@code * java.util.Optional} or {@code com.google.common.base.Optional}. If {@code T} is {@code int}, * {@code long}, or {@code double}, then instead of {@code Optional<T>} we can have {@code * OptionalInt} etc. If {@code T} is a primitive type (including these ones but also the other * five) then {@code Optional<T>} can be the corresponding boxed type. */ public static class PropertyGetter { private final String access; private final String type; private final Optionalish optional; /** * Makes a new {@code PropertyGetter} instance. * * @param method the source method which this getter is implementing. * @param type the type that the getter returns. This is written to take imports into account, * so it might be {@code List<String>} for example. It is either identical to the type of * the corresponding getter in the {@code @AutoValue} class, or it is an optional wrapper, * like {@code Optional<List<String>>}. * @param optional a representation of the {@code Optional} type that the getter returns, if * this is an optional getter, or null otherwise. An optional getter is one that returns * {@code Optional<T>} rather than {@code T}, as explained above. */ PropertyGetter(ExecutableElement method, String type, Optionalish optional) { this.access = SimpleMethod.access(method); this.type = type; this.optional = optional; } public String getAccess() { return access; } public String getType() { return type; } public Optionalish getOptional() { return optional; } } /** * Information about a property setter, referenced from the autovalue.vm template. A property * called foo (defined by a method {@code T foo()} or {@code T getFoo()}) can have a setter method * {@code foo(T)} or {@code setFoo(T)} that returns the builder type. Additionally, it can have a * setter with a type that can be copied to {@code T} through a {@code copyOf} method; for example * a property {@code foo} of type {@code ImmutableSet<String>} can be set with a method {@code * setFoo(Collection<String> foos)}. And, if {@code T} is {@code Optional}, it can have a setter * with a type that can be copied to {@code T} through {@code Optional.of}. */ public 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 Function<String, String> copyFunction; PropertySetter( 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(); this.parameterTypeString = parameterTypeString(setter, parameterType); VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters()); Optional<String> maybeNullable = nullableAnnotationFor(parameterElement, parameterType); this.nullableAnnotation = maybeNullable.orElse(""); } ExecutableElement getSetter() { return setter; } private static String parameterTypeString(ExecutableElement setter, TypeMirror parameterType) { if (setter.isVarArgs()) { TypeMirror componentType = MoreTypes.asArray(parameterType).getComponentType(); // This is a bit ugly. It's OK to annotate just the component type, because if it is // say `@Nullable String` then we will end up with `@Nullable String...`. Unlike the // normal array case, we can't have the situation where the array itself is annotated; // you can write `String @Nullable []` to mean that, but you can't write // `String @Nullable ...`. return TypeEncoder.encodeWithAnnotations(componentType) + "..."; } else { return TypeEncoder.encodeWithAnnotations(parameterType); } } public String getAccess() { return access; } public String getName() { return name; } public String getParameterType() { return parameterTypeString; } public boolean getPrimitiveParameter() { return primitiveParameter; } public String getNullableAnnotation() { return nullableAnnotation; } public String copy(AutoValueProcessor.Property 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() && !copy.equals(property.toString())) { copy = String.format("(%s == null ? null : %s)", property, copy); } return copy; } } /** * Returns a representation of the given {@code @AutoValue.Builder} class or interface. If the * class or interface has abstract methods that could not be part of any builder, emits error * messages and returns Optional.empty(). */ 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 // would be confusing, and our code would reject it anyway because it wouldn't consider that // the return type of Foo<U> build() was really the same as the declaration of Foo<T>. This // check produces a better error message in that case and similar ones. if (!sameTypeParameters(autoValueClass, builderTypeElement)) { errorReporter.reportError( builderTypeElement, "Type parameters of %s must have same names and bounds as type parameters of %s", builderTypeElement, autoValueClass); return Optional.empty(); } return Optional.of(new Builder(builderTypeElement)); } private static boolean sameTypeParameters(TypeElement a, TypeElement b) { int nTypeParameters = a.getTypeParameters().size(); if (nTypeParameters != b.getTypeParameters().size()) { return false; } for (int i = 0; i < nTypeParameters; i++) { TypeParameterElement aParam = a.getTypeParameters().get(i); TypeParameterElement bParam = b.getTypeParameters().get(i); if (!aParam.getSimpleName().equals(bParam.getSimpleName())) { return false; } Set<TypeMirror> autoValueBounds = new TypeMirrorSet(aParam.getBounds()); Set<TypeMirror> builderBounds = new TypeMirrorSet(bParam.getBounds()); if (!autoValueBounds.equals(builderBounds)) { return false; } } return true; } /** * Returns a set of all abstract methods in the given TypeElement or inherited from ancestors. If * any of the abstract methods has a return type or parameter type that is not currently defined * then this method will throw an exception that will cause us to defer processing of the current * class until a later annotation-processing round. */ private ImmutableSet<ExecutableElement> abstractMethods(TypeElement typeElement) { Set<ExecutableElement> methods = getLocalAndInheritedMethods( typeElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils()); ImmutableSet.Builder<ExecutableElement> abstractMethods = ImmutableSet.builder(); for (ExecutableElement method : methods) { if (method.getModifiers().contains(Modifier.ABSTRACT)) { MissingTypes.deferIfMissingTypesIn(method); abstractMethods.add(method); } } return abstractMethods.build(); } private String typeParamsString() { return TypeSimplifier.actualTypeParametersString(autoValueClass); } }