All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.auto.value.processor.AutoBuilderProcessor Maven / Gradle / Ivy

/*
 * Copyright 2021 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.common.MoreElements.getPackage;
import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION;
import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static javax.lang.model.util.ElementFilter.constructorsIn;

import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.common.Visibility;
import com.google.auto.service.AutoService;
import com.google.auto.value.processor.MissingTypes.MissingTypeException;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;

/**
 * Javac annotation processor (compiler plugin) for builders; user code never references this class.
 *
 * @see AutoValue User's Guide
 * @author Éamonn McManus
 */
@AutoService(Processor.class)
@SupportedAnnotationTypes(AUTO_BUILDER_NAME)
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
public class AutoBuilderProcessor extends AutoValueishProcessor {
  private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable";

  public AutoBuilderProcessor() {
    super(AUTO_BUILDER_NAME);
  }

  @Override
  public Set getSupportedOptions() {
    return ImmutableSet.of(OMIT_IDENTIFIERS_OPTION, ALLOW_OPTION);
  }

  private TypeMirror javaLangVoid;

  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    javaLangVoid = elementUtils().getTypeElement("java.lang.Void").asType();
  }

  @Override
  void processType(TypeElement autoBuilderType) {
    if (!processingEnv.getOptions().containsKey(ALLOW_OPTION)) {
      errorReporter()
          .abortWithError(
              autoBuilderType,
              "Compile with -A%s to enable this UNSUPPORTED AND UNSTABLE prototype",
              ALLOW_OPTION);
    }
    if (autoBuilderType.getKind() != ElementKind.CLASS
        && autoBuilderType.getKind() != ElementKind.INTERFACE) {
      errorReporter()
          .abortWithError(
              autoBuilderType,
              "[AutoBuilderWrongType] @AutoBuilder only applies to classes and interfaces");
    }
    checkModifiersIfNested(autoBuilderType);
    TypeElement constructedType = findConstructedType(autoBuilderType);
    checkModifiersIfNested(constructedType); // TODO: error message is wrong
    ExecutableElement constructor = findConstructor(constructedType, autoBuilderType);
    BuilderSpec builderSpec = new BuilderSpec(constructedType, processingEnv, errorReporter());
    BuilderSpec.Builder builder = builderSpec.new Builder(autoBuilderType);
    ImmutableSet methods =
        abstractMethodsIn(
            getLocalAndInheritedMethods(autoBuilderType, typeUtils(), elementUtils()));
    Optional> classifier =
        BuilderMethodClassifierForAutoBuilder.classify(
            methods, errorReporter(), processingEnv, constructor, constructedType, autoBuilderType);
    if (!classifier.isPresent()) {
      // We've already output one or more error messages.
      return;
    }
    AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars();
    vars.props = propertySet(constructor);
    builder.defineVars(vars, classifier.get());
    vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
    String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_");
    vars.builderName = TypeSimplifier.simpleNameOf(generatedClassName);
    vars.builtClass = TypeEncoder.encodeRaw(constructedType.asType());
    vars.types = typeUtils();
    vars.toBuilderConstructor = false;
    defineSharedVarsForType(constructedType, ImmutableSet.of(), vars);
    String text = vars.toText();
    text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoBuilderType.asType());
    text = Reformatter.fixup(text);
    writeSourceFile(generatedClassName, text, autoBuilderType);
  }

  private ImmutableSet propertySet(ExecutableElement constructor) {
    return constructor.getParameters().stream().map(this::newProperty).collect(toImmutableSet());
  }

  private Property newProperty(VariableElement var) {
    String name = var.getSimpleName().toString();
    TypeMirror type = var.asType();
    return new Property(
        name,
        name,
        TypeEncoder.encode(type),
        type,
        Optional.empty());
  }

  private ExecutableElement findConstructor(
      TypeElement constructedType, TypeElement autoBuilderType) {
    // TODO(b/183005059): choose a constructor based on parameter names and types.
    // Currently we always choose the constructor with the most parameters, and there must be
    // exactly one of those.
    List constructors = visibleConstructors(constructedType, autoBuilderType);
    List maxConstructors = maxConstructors(constructors);
    if (maxConstructors.size() > 1) {
      errorReporter()
          .abortWithError(
              autoBuilderType,
              "[AutoBuilderNoMaxConstructor] @AutoBuilder constructed type %s must have one"
                  + " visible constructor with more parameters than any other, but there are %d"
                  + " constructors with %d parameters",
              constructedType,
              maxConstructors.size(),
              maxConstructors.get(0).getParameters().size());
    }
    return maxConstructors.get(0);
  }

  private ImmutableList visibleConstructors(
      TypeElement constructedType, TypeElement autoBuilderType) {
    ImmutableList constructors =
        constructorsIn(constructedType.getEnclosedElements()).stream()
            .filter(c -> visibleFrom(c, getPackage(autoBuilderType)))
            .collect(toImmutableList());
    if (constructors.isEmpty()) {
      errorReporter()
          .abortWithError(
              autoBuilderType,
              "[AutoBuilderNoConstructor] No visible constructors for %s",
              constructedType);
    }
    return constructors;
  }

  private ImmutableList maxConstructors(List constructors) {
    int maxParams = constructors.stream().mapToInt(c -> c.getParameters().size()).max().getAsInt();
    return constructors.stream()
        .filter(c -> c.getParameters().size() == maxParams)
        .collect(toImmutableList());
  }

  private boolean visibleFrom(Element element, PackageElement fromPackage) {
    Visibility visibility = Visibility.effectiveVisibilityOfElement(element);
    switch (visibility) {
      case PUBLIC:
        return true;
      case PROTECTED:
        // We care about whether the constructor is visible from the generated class. The generated
        // class is never going to be a subclass of the class containing the constructor, so
        // protected and default access are equivalent.
      case DEFAULT:
        return getPackage(element).equals(fromPackage);
      default:
        return false;
    }
  }

  private static final ElementKind ELEMENT_KIND_RECORD = elementKindRecord();

  private static ElementKind elementKindRecord() {
    try {
      Field record = ElementKind.class.getField("RECORD");
      return (ElementKind) record.get(null);
    } catch (ReflectiveOperationException e) {
      // OK: we must be on a JDK version that predates this.
      return null;
    }
  }

  private TypeElement findConstructedType(TypeElement autoBuilderType) {
    TypeElement ofClassValue = findOfClassValue(autoBuilderType);
    boolean isDefault = typeUtils().isSameType(ofClassValue.asType(), javaLangVoid);
    if (!isDefault) {
      return ofClassValue;
    }
    Element enclosing = autoBuilderType.getEnclosingElement();
    ElementKind enclosingKind = enclosing.getKind();
    if (enclosing.getKind() != ElementKind.CLASS && enclosingKind != ELEMENT_KIND_RECORD) {
      errorReporter()
          .abortWithError(
              autoBuilderType,
              "[AutoBuilderEnclosing] @AutoBuilder must specify ofClass=Something.class or it"
                  + " must be nested inside the class to be built; actually nested inside %s %s.",
              Ascii.toLowerCase(enclosingKind.name()),
              enclosing);
    }
    return MoreElements.asType(enclosing);
  }

  private TypeElement findOfClassValue(TypeElement autoBuilderType) {
    // The annotation is guaranteed to be present by the contract of Processor#process
    AnnotationMirror autoBuilderAnnotation =
        getAnnotationMirror(autoBuilderType, AUTO_BUILDER_NAME).get();
    AnnotationValue ofClassValue =
        AnnotationMirrors.getAnnotationValue(autoBuilderAnnotation, "ofClass");
    Object value = ofClassValue.getValue();
    if (value instanceof TypeMirror) {
      TypeMirror ofClassType = (TypeMirror) value;
      switch (ofClassType.getKind()) {
        case DECLARED:
          return MoreTypes.asTypeElement(ofClassType);
        case ERROR:
          throw new MissingTypeException(MoreTypes.asError(ofClassType));
        default:
          break;
      }
    }
    throw new MissingTypeException(null);
  }

  @Override
  Optional nullableAnnotationForMethod(ExecutableElement propertyMethod) {
    // TODO(b/183005059): implement
    return Optional.empty();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy