package org.checkerframework.common.reflection;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ArrayType;
import com.sun.tools.javac.code.Type.UnionClassType;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.reflection.qual.ClassBound;
import org.checkerframework.common.reflection.qual.ClassVal;
import org.checkerframework.common.reflection.qual.ClassValBottom;
import org.checkerframework.common.reflection.qual.ForName;
import org.checkerframework.common.reflection.qual.GetClass;
import org.checkerframework.common.reflection.qual.UnknownClass;
import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
import org.checkerframework.common.value.ValueChecker;
import org.checkerframework.common.value.qual.StringVal;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
public class ClassValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
protected final AnnotationMirror CLASSVAL_TOP =
AnnotationUtils.fromClass(elements, UnknownClass.class);
public ClassValAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
if (this.getClass().equals(ClassValAnnotatedTypeFactory.class)) {
this.postInit();
}
}
@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
return new HashSet<Class<? extends Annotation>>(
Arrays.asList(
UnknownClass.class,
ClassVal.class,
ClassBound.class,
ClassValBottom.class));
}
private AnnotationMirror createClassVal(List<String> values) {
AnnotationBuilder builder =
new AnnotationBuilder(processingEnv, ClassVal.class.getCanonicalName());
builder.setValue("value", values);
return builder.build();
}
private AnnotationMirror createClassBound(List<String> values) {
AnnotationBuilder builder =
new AnnotationBuilder(processingEnv, ClassBound.class.getCanonicalName());
builder.setValue("value", values);
return builder.build();
}
/**
* Returns the list of classnames from {@code @ClassBound} or {@code @ClassVal} if anno is
* {@code @ClassBound} or {@code @ClassVal}, otherwise returns an empty list
*
* @param anno any AnnotationMirror
* @return list of classnames in anno
*/
public static List<String> getClassNamesFromAnnotation(AnnotationMirror anno) {
if (AnnotationUtils.areSameByClass(anno, ClassBound.class)
|| AnnotationUtils.areSameByClass(anno, ClassVal.class)) {
return AnnotationUtils.getElementValueArray(anno, "value", String.class, true);
}
return new ArrayList<>();
}
@Override
public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) {
return new ClassValQualifierHierarchy(factory);
}
/** The qualifier hierarchy for the ClassVal type system */
protected class ClassValQualifierHierarchy extends MultiGraphQualifierHierarchy {
public ClassValQualifierHierarchy(MultiGraphFactory f) {
super(f);
}
/*
* Determines the least upper bound of a1 and a2. If both are ClassVal
* annotations, then the least upper bound is the set of elements
* obtained by combining the values of both annotations.
*/
@Override
public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
if (!AnnotationUtils.areSameIgnoringValues(
getTopAnnotation(a1), getTopAnnotation(a2))) {
return null;
} else if (isSubtype(a1, a2)) {
return a2;
} else if (isSubtype(a2, a1)) {
return a1;
} else {
List<String> a1ClassNames = getClassNamesFromAnnotation(a1);
List<String> a2ClassNames = getClassNamesFromAnnotation(a2);
Set<String> lubClassNames = new TreeSet<String>();
lubClassNames.addAll(a1ClassNames);
lubClassNames.addAll(a2ClassNames);
// If either annotation is a ClassBound, the lub must also be a class bound.
if (AnnotationUtils.areSameByClass(a1, ClassBound.class)
|| AnnotationUtils.areSameByClass(a2, ClassBound.class)) {
return createClassBound(new ArrayList<>(lubClassNames));
} else {
return createClassVal(new ArrayList<>(lubClassNames));
}
}
}
@Override
public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
if (!AnnotationUtils.areSameIgnoringValues(
getTopAnnotation(a1), getTopAnnotation(a2))) {
return null;
} else if (isSubtype(a1, a2)) {
return a1;
} else if (isSubtype(a2, a1)) {
return a2;
} else {
List<String> a1ClassNames = getClassNamesFromAnnotation(a1);
List<String> a2ClassNames = getClassNamesFromAnnotation(a2);
Set<String> glbClassNames = new TreeSet<String>();
glbClassNames.addAll(a1ClassNames);
glbClassNames.retainAll(a2ClassNames);
// If either annotation is a ClassVal, the glb must also be a ClassVal.
// For example:
// GLB( @ClassVal(a,b), @ClassBound(a,c)) is @ClassVal(a)
// because @ClassBound(a) is not a subtype of @ClassVal(a,b)
if (AnnotationUtils.areSameByClass(a1, ClassVal.class)
|| AnnotationUtils.areSameByClass(a2, ClassVal.class)) {
return createClassVal(new ArrayList<>(glbClassNames));
} else {
return createClassBound(new ArrayList<>(glbClassNames));
}
}
}
/*
* Computes subtyping as per the subtyping in the qualifier hierarchy
* structure unless both annotations are ClassVal. In this case, rhs is
* a subtype of lhs iff lhs contains at least every element of rhs.
*/
@Override
public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
if (AnnotationUtils.areSame(subAnno, superAnno)
|| AnnotationUtils.areSameByClass(superAnno, UnknownClass.class)
|| AnnotationUtils.areSameByClass(subAnno, ClassValBottom.class)) {
return true;
}
if (AnnotationUtils.areSameByClass(subAnno, UnknownClass.class)
|| AnnotationUtils.areSameByClass(superAnno, ClassValBottom.class)) {
return false;
}
if (AnnotationUtils.areSameByClass(superAnno, ClassVal.class)
&& AnnotationUtils.areSameByClass(subAnno, ClassBound.class)) {
return false;
}
// if super: ClassVal && sub is ClassVal
// if super: ClassBound && (sub is ClassBound or ClassVal)
List<String> supValues = getClassNamesFromAnnotation(superAnno);
List<String> subValues = getClassNamesFromAnnotation(subAnno);
return supValues.containsAll(subValues);
}
}
@Override
protected TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(new ClassValTreeAnnotator(this), super.createTreeAnnotator());
}
/**
* Implements these type inference rules:
*
* <pre>
* C.class: @ClassVal(fully qualified name of C)
* Class.forName(name): @ClassVal("name")
* exp.getClass(): @ClassBound(fully qualified classname of exp)
* </pre>
*/
protected class ClassValTreeAnnotator extends TreeAnnotator {
protected ClassValTreeAnnotator(ClassValAnnotatedTypeFactory factory) {
super(factory);
}
@Override
public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) {
if (TreeUtils.isClassLiteral(tree)) {
// Create annotations for Class literals
// C.class: @ClassVal(fully qualified name of C)
ExpressionTree etree = tree.getExpression();
Type classType = (Type) InternalUtils.typeOf(etree);
String name = getClassNameFromType(classType);
if (name != null) {
AnnotationMirror newQual = createClassVal(Arrays.asList(name));
type.replaceAnnotation(newQual);
}
}
return null;
}
@Override
public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
if (isForNameMethodInovaction(tree)) {
// Class.forName(name): @ClassVal("name")
ExpressionTree arg = tree.getArguments().get(0);
List<String> classNames = getStringValues(arg);
if (classNames != null) {
AnnotationMirror newQual = createClassVal(classNames);
type.replaceAnnotation(newQual);
}
} else if (isGetClassMethodInovaction(tree)) {
// exp.getClass(): @ClassBound(fully qualified class name of exp)
Type clType;
if (TreeUtils.getReceiverTree(tree) != null) {
clType = (Type) InternalUtils.typeOf(TreeUtils.getReceiverTree(tree));
} else { // receiver is null, so it is implicitly "this"
ClassTree classTree = TreeUtils.enclosingClass(getPath(tree));
clType = (Type) InternalUtils.typeOf(classTree);
}
String className = getClassNameFromType(clType);
AnnotationMirror newQual = createClassBound(Arrays.asList(className));
type.replaceAnnotation(newQual);
}
return null;
}
private boolean isForNameMethodInovaction(MethodInvocationTree tree) {
return getDeclAnnotation(InternalUtils.symbol(tree), ForName.class) != null;
}
private boolean isGetClassMethodInovaction(MethodInvocationTree tree) {
return getDeclAnnotation(InternalUtils.symbol(tree), GetClass.class) != null;
}
private List<String> getStringValues(ExpressionTree arg) {
ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
AnnotationMirror annotation = valueATF.getAnnotationMirror(arg, StringVal.class);
if (annotation == null) {
return null;
}
return AnnotationUtils.getElementValueArray(annotation, "value", String.class, true);
}
/**
* Return String representation of class name. This will not return the correct name for
* anonymous classes.
*/
private String getClassNameFromType(Type classType) {
switch (classType.getKind()) {
case ARRAY:
String array = "";
while (classType.getKind() == TypeKind.ARRAY) {
classType = ((ArrayType) classType).getComponentType();
array += "[]";
}
return getClassNameFromType(classType) + array;
case DECLARED:
StringBuilder className =
new StringBuilder(
TypesUtils.getQualifiedName((DeclaredType) classType)
.toString());
if (classType.getEnclosingType() != null) {
while (classType.getEnclosingType().getKind() != TypeKind.NONE) {
classType = classType.getEnclosingType();
int last = className.lastIndexOf(".");
if (last > -1) {
className.replace(last, last + 1, "$");
}
}
}
return className.toString();
case INTERSECTION:
// This could be more precise
return "java.lang.Object";
case NULL:
return "java.lang.Object";
case UNION:
classType = ((UnionClassType) classType).getLub();
return getClassNameFromType(classType);
case TYPEVAR:
case WILDCARD:
classType = classType.getUpperBound();
return getClassNameFromType(classType);
case INT:
return int.class.getCanonicalName();
case LONG:
return long.class.getCanonicalName();
case SHORT:
return short.class.getCanonicalName();
case BYTE:
return byte.class.getCanonicalName();
case CHAR:
return char.class.getCanonicalName();
case DOUBLE:
return double.class.getCanonicalName();
case FLOAT:
return float.class.getCanonicalName();
case BOOLEAN:
return boolean.class.getCanonicalName();
default:
checker.errorAbort(
"ClassValAnnotatedTypeFactory.getClassname: did not expect "
+ classType.getKind());
return "java.lang.Object";
}
}
}
}