| package org.checkerframework.common.returnsreceiver; |
| |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import org.checkerframework.checker.signature.qual.CanonicalName; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; |
| import org.checkerframework.javacutil.AnnotationUtils; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.TypesUtils; |
| |
| /** |
| * A utility class to support fluent API generators so the checker can add {@code @This} annotations |
| * on method return types when a generator has been used. To check whether a method is created by |
| * any of the generators and returns {@code this}, simply call the {@link FluentAPIGenerator#check} |
| * on the annotated type of the method signature. |
| */ |
| public class FluentAPIGenerator { |
| |
| /** |
| * Check if a method was generated by a known fluent API generator and returns its receiver. |
| * |
| * @param t the annotated type of the method signature |
| * @return {@code true} if the method was created by a generator and returns {@code this} |
| */ |
| public static boolean check(AnnotatedExecutableType t) { |
| for (FluentAPIGenerators fluentAPIGenerator : FluentAPIGenerators.values()) { |
| if (fluentAPIGenerator.returnsThis(t)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Enum of supported fluent API generators. For such generators, the checker can automatically |
| * add @This annotations on method return types in the generated code. |
| */ |
| private enum FluentAPIGenerators { |
| /** |
| * The <a |
| * href="https://github.com/google/auto/blob/master/value/userguide/builders.md">AutoValue</a> |
| * framework. |
| */ |
| AUTO_VALUE { |
| |
| /** |
| * The qualified name of the AutoValue Builder annotation. This needs to be constructed |
| * dynamically due to a side effect of the shadow plugin. See {@link |
| * FluentAPIGenerators#AUTO_VALUE#getAutoValueBuilderCanonicalName()} for more information. |
| */ |
| private final String AUTO_VALUE_BUILDER = getAutoValueBuilderCanonicalName(); |
| |
| @Override |
| public boolean returnsThis(AnnotatedExecutableType t) { |
| ExecutableElement element = t.getElement(); |
| Element enclosingElement = element.getEnclosingElement(); |
| boolean inAutoValueBuilder = |
| AnnotationUtils.containsSameByName( |
| enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER); |
| |
| if (!inAutoValueBuilder) { |
| // see if superclass is an AutoValue Builder, to handle generated code |
| TypeMirror superclass = ((TypeElement) enclosingElement).getSuperclass(); |
| // if enclosingElement is an interface, the superclass has TypeKind NONE |
| if (superclass.getKind() != TypeKind.NONE) { |
| // update enclosingElement to be for the superclass for this case |
| enclosingElement = TypesUtils.getTypeElement(superclass); |
| inAutoValueBuilder = |
| AnnotationUtils.containsSameByName( |
| enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER); |
| } |
| } |
| |
| if (inAutoValueBuilder) { |
| AnnotatedTypeMirror returnType = t.getReturnType(); |
| if (returnType == null) { |
| throw new BugInCF("Return type cannot be null: " + t); |
| } |
| return enclosingElement.equals(TypesUtils.getTypeElement(returnType.getUnderlyingType())); |
| } |
| return false; |
| } |
| |
| /** |
| * Get the qualified name of the AutoValue Builder annotation. This method constructs the |
| * String dynamically, to ensure it does not get rewritten due to relocation of the {@code |
| * "com.google"} package during the build process. |
| * |
| * @return {@code "com.google.auto.value.AutoValue.Builder"} |
| */ |
| private @CanonicalName String getAutoValueBuilderCanonicalName() { |
| String com = "com"; |
| @SuppressWarnings("signature:assignment") // string concatenation |
| @CanonicalName String result = com + "." + "google.auto.value.AutoValue.Builder"; |
| return result; |
| } |
| }, |
| /** <a href="https://projectlombok.org/features/Builder">Project Lombok</a>. */ |
| LOMBOK { |
| @Override |
| public boolean returnsThis(AnnotatedExecutableType t) { |
| ExecutableElement element = t.getElement(); |
| Element enclosingElement = element.getEnclosingElement(); |
| boolean inLombokBuilder = |
| (AnnotationUtils.containsSameByName( |
| enclosingElement.getAnnotationMirrors(), "lombok.Generated") |
| || AnnotationUtils.containsSameByName( |
| element.getAnnotationMirrors(), "lombok.Generated")) |
| && enclosingElement.getSimpleName().toString().endsWith("Builder"); |
| |
| if (inLombokBuilder) { |
| AnnotatedTypeMirror returnType = t.getReturnType(); |
| if (returnType == null) { |
| throw new BugInCF("Return type cannot be null: " + t); |
| } |
| return enclosingElement.equals(TypesUtils.getTypeElement(returnType.getUnderlyingType())); |
| } |
| return false; |
| } |
| }; |
| |
| /** |
| * Returns {@code true} if the method was created by this generator and returns {@code this}. |
| * |
| * @param t the annotated type of the method signature |
| * @return {@code true} if the method was created by this generator and returns {@code this} |
| */ |
| protected abstract boolean returnsThis(AnnotatedExecutableType t); |
| } |
| } |