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

org.checkerframework.framework.ajava.TypeAnnotationMover 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.framework.ajava;

import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.framework.stub.AnnotationFileParser;

/**
 * Moves annotations in a JavaParser AST from declaration position onto the types they correspond
 * to.
 *
 * 

When parsing a method or field such as {@code @Tainted String myField}, JavaParser puts all * annotations on the declaration. * *

For each non-declaration annotation on a method or field declaration, this class moves it to * the type position. A non-declaration annotation is one with a {@code TYPE_USE} target but no * declaration target. */ public class TypeAnnotationMover extends VoidVisitorAdapter { /** * Annotations imported by the file, stored as a mapping from names to the TypeElements for the * annotations. Contains entries for the simple and fully qualified names of each annotation. */ private Map allAnnotations; /** Element utilities. */ private Elements elements; /** * Constructs a {@code TypeAnnotationMover}. * * @param allAnnotations the annotations imported by the file, as a mapping from annotation name * to TypeElement. There should be two entries for each annotation: the annotation's simple * name and its fully-qualified name both mapped to its TypeElement. * @param elements the Element utilities */ public TypeAnnotationMover(Map allAnnotations, Elements elements) { this.allAnnotations = new HashMap<>(allAnnotations); this.elements = elements; } @Override public void visit(FieldDeclaration node, Void p) { // Use the type of the first declared variable in the field declaration. Type type = node.getVariable(0).getType(); if (!type.isClassOrInterfaceType()) { return; } if (isMultiPartName(type)) { return; } List annosToMove = getAnnotationsToMove(node, ElementType.FIELD); if (annosToMove.isEmpty()) { return; } node.getAnnotations().removeAll(annosToMove); annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); } @Override public void visit(MethodDeclaration node, Void p) { Type type = node.getType(); if (!type.isClassOrInterfaceType()) { return; } if (isMultiPartName(type)) { return; } List annosToMove = getAnnotationsToMove(node, ElementType.METHOD); if (annosToMove.isEmpty()) { return; } node.getAnnotations().removeAll(annosToMove); annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); } /** * Given a declaration, returns a List of annotations currently in declaration position that can't * possibly be declaration annotations for that type of declaration. * * @param node JavaParser node for declaration * @param declarationType the type of declaration {@code node} represents; always FIELD or METHOD * @return a list of annotations in declaration position that should be on the declaration's type */ private List getAnnotationsToMove( NodeWithAnnotations node, ElementType declarationType) { List annosToMove = new ArrayList<>(); for (AnnotationExpr annotation : node.getAnnotations()) { if (!isPossiblyDeclarationAnnotation(annotation, declarationType)) { annosToMove.add(annotation); } } return annosToMove; } /** * Returns the TypeElement for an annotation, or null if it cannot be found. * * @param annotation a JavaParser annotation * @return the TypeElement for {@code annotation}, or null if it cannot be found */ private @Nullable TypeElement getAnnotationDeclaration(AnnotationExpr annotation) { @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 @FullyQualifiedName String annoNameFq = annotation.getNameAsString(); TypeElement annoTypeElt = allAnnotations.get(annoNameFq); if (annoTypeElt == null) { annoTypeElt = elements.getTypeElement(annoNameFq); if (annoTypeElt == null) { // Not a supported annotation. return null; } AnnotationFileParser.putAllNew( allAnnotations, AnnotationFileParser.createNameToAnnotationMap(Collections.singletonList(annoTypeElt))); } return annoTypeElt; } /** * Returns if {@code annotation} could be a declaration annotation for {@code declarationType}. * This would be the case if the annotation isn't recognized at all, or if it has no * {@code @Target} meta-annotation, or if it has {@code declarationType} as one of its targets. * * @param annotation a JavaParser annotation expression * @param declarationType the declaration type to check if {@code annotation} might be a * declaration annotation for * @return true unless {@code annotation} definitely cannot be a declaration annotation for {@code * declarationType} */ private boolean isPossiblyDeclarationAnnotation( AnnotationExpr annotation, ElementType declarationType) { TypeElement annotationType = getAnnotationDeclaration(annotation); if (annotationType == null) { return true; } return isDeclarationAnnotation(annotationType, declarationType); } /** * Returns whether the annotation represented by {@code annotationDeclaration} might be a * declaration annotation for {@code declarationType}. This holds if the TypeElement has no * {@code @Target} meta-annotation, or if {@code declarationType} is a target of the annotation. * * @param annotationDeclaration declaration for an annotation * @param declarationType the declaration type to check if the annotation might be a declaration * annotation for * @return true if {@code annotationDeclaration} contains {@code declarationType} as a target or * doesn't contain {@code ElementType.TYPE_USE} as a target */ private boolean isDeclarationAnnotation( TypeElement annotationDeclaration, ElementType declarationType) { Target target = annotationDeclaration.getAnnotation(Target.class); if (target == null) { return true; } boolean hasTypeUse = false; for (ElementType elementType : target.value()) { if (elementType == declarationType) { return true; } if (elementType == ElementType.TYPE_USE) { hasTypeUse = true; } } if (!hasTypeUse) { throw new Error( String.format( "Annotation %s cannot be used on declaration with type %s", annotationDeclaration.getQualifiedName(), declarationType)); } return false; } /** * Returns whether {@code type} has a name containing multiple parts separated by dots, e.g. * "java.lang.String" or "Outer.Inner". * *

Annotations should not be moved onto a Type for which this method returns true. A type like * {@code @Anno java.lang.String} is illegal since the annotation should go directly next to the * rightmost part of the fully qualified name, like {@code java.lang. @Anno String}. So if a file * contains a declaration like {@code @Anno java.lang.String myField}, the annotation must belong * to the declaration and not the type. * *

If a declaration contains an inner class type like {@code @Anno Outer.Inner myField}, it may * be the case that {@code @Anno} belongs to the type {@code Outer}, not the declaration, and * should be moved, but it's impossible to distinguish this from the above case using only the * JavaParser AST for a file. To be safe, the annotation still shouldn't be moved, but this may * lead to suboptimal formatting placing {@code @Anno} on its own line. * * @param type a JavaParser type node * @return true if {@code type} has a multi-part name */ private boolean isMultiPartName(Type type) { return type.isClassOrInterfaceType() && type.asClassOrInterfaceType().getScope().isPresent(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy