package org.checkerframework.framework.util.element;
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.contains;
import static org.checkerframework.framework.util.element.ElementAnnotationUtil.getTypeAtLocation;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.TargetType;
import com.sun.tools.javac.code.TypeAnnotationPosition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeParameterElement;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.ElementAnnotationApplier;
import org.checkerframework.javacutil.ErrorReporter;
/** Apply annotations to the use of a type parameter declaration */
public class TypeVarUseApplier {
public static void apply(
final AnnotatedTypeMirror type,
final Element element,
final AnnotatedTypeFactory typeFactory) {
new TypeVarUseApplier(type, element, typeFactory).extractAndApply();
}
private static ElementKind[] acceptedKinds = {
ElementKind.PARAMETER,
ElementKind.FIELD,
ElementKind.LOCAL_VARIABLE,
ElementKind.RESOURCE_VARIABLE,
ElementKind.METHOD
};
/**
* @return true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type
* variable component, and the element is not a TYPE_PARAMETER
*/
public static boolean accepts(AnnotatedTypeMirror type, Element element) {
return (type instanceof AnnotatedTypeVariable || isGenericArrayType(type))
&& contains(element.getKind(), acceptedKinds);
}
private static boolean isGenericArrayType(AnnotatedTypeMirror type) {
return type instanceof AnnotatedArrayType
&& getNestedComponentType(type) instanceof AnnotatedTypeVariable;
}
private static AnnotatedTypeMirror getNestedComponentType(AnnotatedTypeMirror type) {
AnnotatedTypeMirror componentType = type;
while (componentType instanceof AnnotatedArrayType) {
componentType = ((AnnotatedArrayType) componentType).getComponentType();
}
return componentType;
}
// In order to avoid sprinkling code for type parameter uses all over the various locations
// uses can show up we also handle generic array types. T [] myTArr;
private final AnnotatedArrayType arrayType;
private final AnnotatedTypeVariable typeVariable;
private final TypeParameterElement declarationElem;
private final Element useElem;
private AnnotatedTypeFactory typeFactory;
TypeVarUseApplier(
final AnnotatedTypeMirror type,
final Element element,
final AnnotatedTypeFactory typeFactory) {
if (!accepts(type, element)) {
ErrorReporter.errorAbort(
"TypeParamUseApplier does not accept type/element combination ("
+ " type ( "
+ type
+ " ) element ( "
+ element
+ " ) ");
}
if (isGenericArrayType(type)) {
this.arrayType = (AnnotatedArrayType) type;
this.typeVariable = (AnnotatedTypeVariable) getNestedComponentType(type);
this.declarationElem =
(TypeParameterElement) typeVariable.getUnderlyingType().asElement();
this.useElem = element;
this.typeFactory = typeFactory;
} else {
this.arrayType = null;
this.typeVariable = (AnnotatedTypeVariable) type;
this.declarationElem =
(TypeParameterElement) typeVariable.getUnderlyingType().asElement();
this.useElem = element;
this.typeFactory = typeFactory;
}
}
/**
* Applies the bound annotations from the declaration of the type parameter and then applies the
* explicit annotations written on the type variable
*/
public void extractAndApply() {
addAnnotationsFromElement(typeVariable, useElem.getAnnotationMirrors());
// apply declaration annotations
ElementAnnotationApplier.apply(typeVariable, declarationElem, typeFactory);
final List<Attribute.TypeCompound> annotations = getAnnotations(useElem, declarationElem);
final List<Attribute.TypeCompound> typeVarAnnotations;
if (arrayType != null) {
// if the outer-most type is an array type then we want to ensure the outer annotations
// are not applied as the type variables primary annotation
typeVarAnnotations = removeComponentAnnotations(arrayType, annotations);
annotateViaTypeAnnoPosition(arrayType, annotations);
} else {
typeVarAnnotations = annotations;
}
for (final Attribute.TypeCompound annotation : typeVarAnnotations) {
typeVariable.removeAnnotationInHierarchy(annotation);
typeVariable.addAnnotation(annotation);
final List<? extends AnnotatedTypeMirror> upperBounds;
if (typeVariable.getUpperBound() instanceof AnnotatedIntersectionType) {
upperBounds = typeVariable.getUpperBound().directSuperTypes();
} else {
upperBounds = Arrays.asList(typeVariable.getUpperBound());
}
//TODO: Should we just make primary annotations on annotated intersection types apply to all of
//TODO: them? Que dealio? What should we do?
for (final AnnotatedTypeMirror bound : upperBounds) {
bound.removeAnnotationInHierarchy(annotation);
bound.addAnnotation(annotation);
}
}
}
private List<Attribute.TypeCompound> removeComponentAnnotations(
final AnnotatedArrayType arrayType, final List<Attribute.TypeCompound> annotations) {
final List<Attribute.TypeCompound> componentAnnotations =
new ArrayList<Attribute.TypeCompound>();
if (arrayType != null) {
for (int i = 0; i < annotations.size(); ) {
final Attribute.TypeCompound anno = annotations.get(i);
if (isBaseComponent(arrayType, anno)) {
componentAnnotations.add(anno);
annotations.remove(anno);
} else {
i++;
}
}
}
return componentAnnotations;
}
private boolean isBaseComponent(
final AnnotatedArrayType arrayType, final Attribute.TypeCompound anno) {
return getTypeAtLocation(arrayType, anno.getPosition().location)
.getClass()
.equals(AnnotatedTypeVariable.class);
}
/**
* Depending on what element type the annotations are stored on, the relevant annotations might
* be stored with different annotation positions. getAnnotations finds the correct annotations
* by annotation position and element kind and returns them
*/
private static List<Attribute.TypeCompound> getAnnotations(
final Element useElem, final Element declarationElem) {
final List<Attribute.TypeCompound> annotations;
switch (useElem.getKind()) {
case METHOD:
annotations = getReturnAnnos(useElem);
break;
case PARAMETER:
annotations = getParameterAnnos(useElem);
break;
case FIELD:
case LOCAL_VARIABLE:
case RESOURCE_VARIABLE:
annotations = getVariableAnnos(useElem);
break;
default:
ErrorReporter.errorAbort(
"TypeVarUseApplier::extractAndApply : "
+ "Unhandled element kind "
+ useElem.getKind()
+ "useElem ( "
+ useElem
+ " ) "
+ "declarationElem ( "
+ declarationElem
+ " ) ");
annotations = null; // dead code
}
return annotations;
}
/** @return annotations on an element that apply to variable declarations */
private static List<Attribute.TypeCompound> getVariableAnnos(final Element variableElem) {
final VarSymbol varSymbol = (VarSymbol) variableElem;
final List<Attribute.TypeCompound> annotations = new ArrayList<Attribute.TypeCompound>();
for (Attribute.TypeCompound anno : varSymbol.getRawTypeAttributes()) {
TypeAnnotationPosition pos = anno.position;
switch (pos.type) {
case FIELD:
case LOCAL_VARIABLE:
case RESOURCE_VARIABLE:
case EXCEPTION_PARAMETER:
annotations.add(anno);
break;
default:
}
}
return annotations;
}
/**
* Currently, the metadata for storing annotations (i.e. the Attribute.TypeCompounds) is null
* for binary-only parameters and type parameters. However, it is present on the method. So in
* order to ensure that we correctly retrieve the annotations we need to index from the method
* and retrieve the annotations from its metadata.
*
* @return a list of annotations that were found on METHOD_FORMAL_PARAMETERS that match the
* parameter index of the input element in the parent methods formal parameter list
*/
private static List<Attribute.TypeCompound> getParameterAnnos(final Element paramElem) {
final Element enclosingElement = paramElem.getEnclosingElement();
if (!(enclosingElement instanceof ExecutableElement)) {
ErrorReporter.errorAbort(
"Bad element passed to TypeFromElement.getTypeParameterAnnotationAttributes: "
+ "element: "
+ paramElem
+ " not found in enclosing executable: "
+ enclosingElement);
}
final MethodSymbol enclosingMethod = (MethodSymbol) paramElem.getEnclosingElement();
final List<Attribute.TypeCompound> annotations = enclosingMethod.getRawTypeAttributes();
final int paramIndex = enclosingMethod.getParameters().indexOf(paramElem);
final List<Attribute.TypeCompound> result = new ArrayList<Attribute.TypeCompound>();
for (final Attribute.TypeCompound typeAnno : annotations) {
if (typeAnno.position.type == TargetType.METHOD_FORMAL_PARAMETER) {
if (typeAnno.position.parameter_index == paramIndex) {
result.add(typeAnno);
}
}
}
return result;
}
/** @return the annotations on the return type of the input ExecutableElement */
private static List<Attribute.TypeCompound> getReturnAnnos(final Element methodElem) {
if (!(methodElem instanceof ExecutableElement)) {
ErrorReporter.errorAbort(
"Bad element passed to TypeVarUseApplier.getReturnAnnos:" + methodElem);
}
final MethodSymbol enclosingMethod = (MethodSymbol) methodElem;
final List<Attribute.TypeCompound> annotations = enclosingMethod.getRawTypeAttributes();
final List<Attribute.TypeCompound> result = new ArrayList<Attribute.TypeCompound>();
for (final Attribute.TypeCompound typeAnno : annotations) {
if (typeAnno.position.type == TargetType.METHOD_RETURN) {
result.add(typeAnno);
}
}
return result;
}
}