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

org.checkerframework.checker.calledmethods.builder.LombokSupport Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.44.0
Show newest version
package org.checkerframework.checker.calledmethods.builder;

import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.VariableTree;
import java.beans.Introspector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;

/**
 * Lombok support for the Called Methods Checker. This class adds CalledMethods annotations to the
 * code generated by Lombok.
 */
public class LombokSupport implements BuilderFrameworkSupport {

  /** The type factory. */
  private CalledMethodsAnnotatedTypeFactory atypeFactory;

  /**
   * Create a new LombokSupport.
   *
   * @param atypeFactory the typechecker's type factory
   */
  public LombokSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) {
    this.atypeFactory = atypeFactory;
  }

  // The list is copied from lombok.core.handlers.HandlerUtil. The list cannot be used from that
  // class directly because Lombok does not provide class files for its own implementation, to
  // prevent itself from being accidentally added to clients' compile classpaths. This design
  // decision means that it is impossible to depend directly on Lombok internals.
  /** The list of annotations that Lombok treats as non-null. */
  public static final List NONNULL_ANNOTATIONS =
      Collections.unmodifiableList(
          Arrays.asList(
              "android.annotation.NonNull",
              "android.support.annotation.NonNull",
              "com.sun.istack.internal.NotNull",
              "edu.umd.cs.findbugs.annotations.NonNull",
              "javax.annotation.Nonnull",
              // "javax.validation.constraints.NotNull", // The field might contain a
              // null value until it is persisted.
              "lombok.NonNull",
              "org.checkerframework.checker.nullness.qual.NonNull",
              "org.eclipse.jdt.annotation.NonNull",
              "org.eclipse.jgit.annotations.NonNull",
              "org.jetbrains.annotations.NotNull",
              "org.jmlspecs.annotation.NonNull",
              "org.netbeans.api.annotations.common.NonNull",
              "org.springframework.lang.NonNull"));

  /**
   * A map from elements that have a lombok.Builder.Default annotation to the simple property name
   * that should be treated as defaulted.
   *
   * 

This cache is kept because the usual method for checking that an element has been defaulted * (calling declarationFromElement and examining the resulting VariableTree) only works if a * corresponding Tree is available (for code that is only available as bytecode, no such Tree is * available and that method returns null). See the code in {@link * #getLombokRequiredProperties(Element)} that handles fields. */ private final Map defaultedElements = new HashMap<>(2); @Override public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) { TypeElement candidateGeneratedBuilderElement = (TypeElement) candidateBuildElement.getEnclosingElement(); if ((ElementUtils.hasAnnotation(candidateGeneratedBuilderElement, "lombok.Generated") || ElementUtils.hasAnnotation(candidateBuildElement, "lombok.Generated")) && candidateGeneratedBuilderElement.getSimpleName().toString().endsWith("Builder")) { return candidateBuildElement.getSimpleName().contentEquals("build"); } return false; } @Override public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) { ExecutableElement buildElement = builderBuildType.getElement(); TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); // The class with the @lombok.Builder annotation... Element annotatedWithBuilderElement = generatedBuilderElement.getEnclosingElement(); List requiredProperties = getLombokRequiredProperties(annotatedWithBuilderElement); AnnotationMirror newCalledMethodsAnno = atypeFactory.createAccumulatorAnnotation(requiredProperties); builderBuildType.getReceiverType().addAnnotation(newCalledMethodsAnno); } @Override public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) { return candidateToBuilderElement.getSimpleName().contentEquals("toBuilder") && (ElementUtils.hasAnnotation(candidateToBuilderElement, "lombok.Generated") || ElementUtils.hasAnnotation( candidateToBuilderElement.getEnclosingElement(), "lombok.Generated")); } @Override public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) { AnnotatedTypeMirror returnType = toBuilderType.getReturnType(); ExecutableElement buildElement = toBuilderType.getElement(); TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); handleToBuilderType(returnType, generatedBuilderElement); } /** * Add, to a type, a CalledMethods annotation that states that all required setters have been * called. The type can be the return type of toBuilder or of the corresponding generated "copy" * constructor. * * @param type type to update * @param classElement corresponding AutoValue class */ private void handleToBuilderType(AnnotatedTypeMirror type, Element classElement) { List requiredProperties = getLombokRequiredProperties(classElement); AnnotationMirror calledMethodsAnno = atypeFactory.createAccumulatorAnnotation(requiredProperties); type.replaceAnnotation(calledMethodsAnno); } /** * Computes the required properties of a @lombok.Builder class, i.e., the names of the fields * with @lombok.NonNull annotations. * * @param lombokClassElement the class with the @lombok.Builder annotation * @return a list of required property names */ private List getLombokRequiredProperties(final Element lombokClassElement) { List requiredPropertyNames = new ArrayList<>(); List defaultedPropertyNames = new ArrayList<>(); for (Element member : lombokClassElement.getEnclosedElements()) { if (member.getKind() == ElementKind.FIELD) { // Lombok never generates non-null fields with initializers in builders, unless the field is // annotated with @Default or @Singular, which are handled elsewhere. So, this code doesn't // need to consider whether the field has or does not have initializers. for (AnnotationMirror anm : atypeFactory.getElementUtils().getAllAnnotationMirrors(member)) { if (NONNULL_ANNOTATIONS.contains(AnnotationUtils.annotationName(anm))) { requiredPropertyNames.add(member.getSimpleName().toString()); } } } else if (member.getKind() == ElementKind.METHOD && ElementUtils.hasAnnotation(member, "lombok.Generated")) { String methodName = member.getSimpleName().toString(); // If a field foo has an @Builder.Default annotation, Lombok always generates a // method called $default$foo. if (methodName.startsWith("$default$")) { String propName = methodName.substring(9); // $default$ has 9 characters defaultedPropertyNames.add(propName); } } else if (member.getKind().isClass() && member.toString().endsWith("Builder")) { // Note that the test above will fail to catch builders generated by Lombok that have custom // names using the builderClassName attribute. TODO: find a way to handle such builders too. // If a field bar has an @Singular annotation, Lombok always generates a method called // clearBar in the builder class itself. Therefore, search the builder for such a method, // and extract the appropriate property name to treat as defaulted. for (Element builderMember : member.getEnclosedElements()) { if (builderMember.getKind() == ElementKind.METHOD && ElementUtils.hasAnnotation(builderMember, "lombok.Generated")) { String methodName = builderMember.getSimpleName().toString(); if (methodName.startsWith("clear")) { String propName = Introspector.decapitalize(methodName.substring(5)); // clear has 5 characters defaultedPropertyNames.add(propName); } } else if (builderMember.getKind() == ElementKind.FIELD) { VariableTree variableTree = (VariableTree) atypeFactory.declarationFromElement(builderMember); if (variableTree != null && variableTree.getInitializer() != null) { Name propName = variableTree.getName(); defaultedPropertyNames.add(propName.toString()); defaultedElements.put(builderMember, propName); } else if (defaultedElements.containsKey(builderMember)) { defaultedPropertyNames.add(defaultedElements.get(builderMember).toString()); } } } } } requiredPropertyNames.removeAll(defaultedPropertyNames); return requiredPropertyNames; } @Override public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) { return; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy