package org.checkerframework.framework.util.element;
import static com.sun.tools.javac.code.TargetType.CAST;
import static com.sun.tools.javac.code.TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT;
import static com.sun.tools.javac.code.TargetType.CONSTRUCTOR_REFERENCE;
import static com.sun.tools.javac.code.TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT;
import static com.sun.tools.javac.code.TargetType.EXCEPTION_PARAMETER;
import static com.sun.tools.javac.code.TargetType.INSTANCEOF;
import static com.sun.tools.javac.code.TargetType.LOCAL_VARIABLE;
import static com.sun.tools.javac.code.TargetType.METHOD_FORMAL_PARAMETER;
import static com.sun.tools.javac.code.TargetType.METHOD_INVOCATION_TYPE_ARGUMENT;
import static com.sun.tools.javac.code.TargetType.METHOD_RECEIVER;
import static com.sun.tools.javac.code.TargetType.METHOD_REFERENCE;
import static com.sun.tools.javac.code.TargetType.METHOD_REFERENCE_TYPE_ARGUMENT;
import static com.sun.tools.javac.code.TargetType.METHOD_RETURN;
import static com.sun.tools.javac.code.TargetType.METHOD_TYPE_PARAMETER;
import static com.sun.tools.javac.code.TargetType.METHOD_TYPE_PARAMETER_BOUND;
import static com.sun.tools.javac.code.TargetType.NEW;
import static com.sun.tools.javac.code.TargetType.RESOURCE_VARIABLE;
import static com.sun.tools.javac.code.TargetType.THROWS;
import static org.checkerframework.framework.util.element.ElementAnnotationUtil.addAnnotationsFromElement;
import static org.checkerframework.framework.util.element.ElementAnnotationUtil.annotateViaTypeAnnoPosition;
import static org.checkerframework.framework.util.element.ElementAnnotationUtil.applyAllElementAnnotations;
import static org.checkerframework.framework.util.element.ElementAnnotationUtil.partitionByTargetType;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Attribute.TypeCompound;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.TargetType;
import com.sun.tools.javac.code.TypeAnnotationPosition;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.lang.model.element.Element;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.util.PluginUtil;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.ErrorReporter;
/**
* Adds annotations from element to the return type, formal parameter types, type parameters, and
* throws clauses of the AnnotatedExecutableType type.
*/
public class MethodApplier extends TargetedElementAnnotationApplier {
public static void apply(
AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) {
new MethodApplier(type, element, typeFactory).extractAndApply();
}
public static boolean accepts(final AnnotatedTypeMirror typeMirror, final Element element) {
return element instanceof Symbol.MethodSymbol
&& typeMirror instanceof AnnotatedExecutableType;
}
private final AnnotatedTypeFactory typeFactory;
/** Method being annotated, this symbol contains all relevant annotations */
private final Symbol.MethodSymbol methodSymbol;
private final AnnotatedExecutableType methodType;
MethodApplier(AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) {
super(type, element);
this.typeFactory = typeFactory;
this.methodSymbol = (Symbol.MethodSymbol) element;
this.methodType = (AnnotatedExecutableType) type;
}
/**
* @return receiver, returns, and throws. See extract and apply as we also annotate type params.
*/
@Override
protected TargetType[] annotatedTargets() {
return new TargetType[] {METHOD_RECEIVER, METHOD_RETURN, THROWS};
}
/** @return all possible annotation positions for a method except those in annotatedTargets */
@Override
protected TargetType[] validTargets() {
return new TargetType[] {
LOCAL_VARIABLE,
RESOURCE_VARIABLE,
EXCEPTION_PARAMETER,
NEW,
CAST,
INSTANCEOF,
METHOD_INVOCATION_TYPE_ARGUMENT,
CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT,
METHOD_REFERENCE,
CONSTRUCTOR_REFERENCE,
METHOD_REFERENCE_TYPE_ARGUMENT,
CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT,
METHOD_TYPE_PARAMETER,
METHOD_TYPE_PARAMETER_BOUND,
METHOD_FORMAL_PARAMETER
};
}
/** @return the annotations on the method symbol (element) */
@Override
protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
return methodSymbol.getRawTypeAttributes();
}
@Override
protected boolean isAccepted() {
return MethodApplier.accepts(type, element);
}
/**
* Sets the method's element, annotates its return type, parameters, type parameters, and throws
* annotations.
*/
@Override
public void extractAndApply() {
methodType.setElement(methodSymbol); // Preserves previous behavior
// Add declaration annotations to the return type if
if (methodType.getReturnType() instanceof AnnotatedTypeVariable) {
applyTypeVarUseOnReturnType();
}
addAnnotationsFromElement(methodType.getReturnType(), methodSymbol.getAnnotationMirrors());
final List<AnnotatedTypeMirror> params = methodType.getParameterTypes();
for (int i = 0; i < params.size(); ++i) {
// Add declaration annotations to the parameter type
addAnnotationsFromElement(
params.get(i), methodSymbol.getParameters().get(i).getAnnotationMirrors());
}
// ensures that we check that there are only valid target types on this class, there are no "invalid" locations
super.extractAndApply();
applyAllElementAnnotations(
methodType.getParameterTypes(), methodSymbol.getParameters(), typeFactory);
applyAllElementAnnotations(
methodType.getTypeVariables(), methodSymbol.getTypeParameters(), typeFactory);
}
// NOTE that these are the only locations not handled elsewhere, otherwise we call apply
@Override
protected void handleTargeted(final List<Attribute.TypeCompound> targeted) {
final List<TypeCompound> unmatched = new ArrayList<>();
final Map<TargetType, List<TypeCompound>> targetTypeToAnno =
partitionByTargetType(targeted, unmatched, METHOD_RECEIVER, METHOD_RETURN, THROWS);
annotateViaTypeAnnoPosition(
methodType.getReceiverType(), targetTypeToAnno.get(METHOD_RECEIVER));
annotateViaTypeAnnoPosition(
methodType.getReturnType(), targetTypeToAnno.get(METHOD_RETURN));
applyThrowsAnnotations(targetTypeToAnno.get(THROWS));
if (unmatched.size() > 0) {
ErrorReporter.errorAbort(
"Unexpected annotations ( "
+ PluginUtil.join(",", unmatched)
+ " ) for"
+ "type ( "
+ type
+ " ) and element ( "
+ element
+ " ) ");
}
}
/** For each thrown type, collect all the annotations for that type and apply them */
private void applyThrowsAnnotations(final List<Attribute.TypeCompound> annos) {
final List<AnnotatedTypeMirror> thrown = methodType.getThrownTypes();
if (thrown.isEmpty()) {
return;
}
Map<AnnotatedTypeMirror, List<TypeCompound>> typeToAnnos = new LinkedHashMap<>();
for (final AnnotatedTypeMirror thrownType : thrown) {
typeToAnnos.put(thrownType, new ArrayList<TypeCompound>());
}
for (TypeCompound anno : annos) {
final TypeAnnotationPosition annoPos = anno.position;
if (annoPos.type_index >= 0 && annoPos.type_index < thrown.size()) {
final AnnotatedTypeMirror thrownType = thrown.get(annoPos.type_index);
typeToAnnos.get(thrownType).add(anno);
} else {
ErrorReporter.errorAbort(
"MethodApplier.applyThrowsAnnotation: "
+ "invalid throws index "
+ annoPos.type_index
+ " for annotation: "
+ anno
+ " for element: "
+ ElementUtils.getVerboseName(element));
}
}
for (final Entry<AnnotatedTypeMirror, List<TypeCompound>> typeToAnno :
typeToAnnos.entrySet()) {
annotateViaTypeAnnoPosition(typeToAnno.getKey(), typeToAnno.getValue());
}
}
/**
* If the return type is a use of a type variable first apply the bound annotations from the
* type variables declaration
*/
private void applyTypeVarUseOnReturnType() {
new TypeVarUseApplier(methodType.getReturnType(), methodSymbol, typeFactory)
.extractAndApply();
}
}