package checkers.util;
import java.util.*;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeKind;
import checkers.quals.*;
import checkers.types.*;
import checkers.types.AnnotatedTypeMirror.*;
import checkers.types.visitors.AnnotatedTypeScanner;
import com.sun.source.tree.*;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Symbol;
/**
* Determines the default qualifiers on a type.
* Default qualifiers are specified via the {@link DefaultQualifier} annotation.
*
* @see DefaultQualifier
*/
public class QualifierDefaults {
// TODO add visitor state to get the default annotations from the top down?
// TODO apply from package elements also
// TODO try to remove some dependencies (e.g. on factory)
private final AnnotatedTypeFactory factory;
private final AnnotationUtils annoFactory;
private AnnotationMirror absoluteDefaultAnno;
private Set<DefaultLocation> absoluteDefaultLocs;
private Map<String, String> qualifiedNameMap;
/**
* @param factory the factory for this checker
* @param annoFactory an annotation factory, used to get annotations by name
*/
public QualifierDefaults(AnnotatedTypeFactory factory, AnnotationUtils annoFactory) {
this.factory = factory;
this.annoFactory = annoFactory;
qualifiedNameMap = new HashMap<String, String>();
for (Name name : factory.getQualifierHierarchy().getTypeQualifiers()) {
if (name == null)
continue;
String qualified = name.toString();
String unqualified = qualified.substring(qualified.lastIndexOf('.') + 1);
qualifiedNameMap.put(qualified, qualified);
qualifiedNameMap.put(unqualified, qualified);
}
}
/**
* Sets the default annotation. A programmer may override this by
* writing the @DefaultQualifier annotation.
*/
public void setAbsoluteDefaults(AnnotationMirror absoluteDefaultAnno, Set<DefaultLocation> locations) {
this.absoluteDefaultAnno = absoluteDefaultAnno;
this.absoluteDefaultLocs = new HashSet<DefaultLocation>(locations);
}
public void annotateTypeElement(TypeElement elt, AnnotatedTypeMirror type) {
applyDefaults(elt, type);
}
/**
* Applies default annotations to a type given an {@link Element}.
*
* @param elt the element from which the type was obtained
* @param type the type to annotate
*/
public void annotate(Element elt, AnnotatedTypeMirror type) {
applyDefaults(elt, type);
}
/**
* Applies default annotations to a type given a {@link Tree}.
*
* @param tree the tree from which the type was obtained
* @param type the type to annotate
*/
public void annotate(Tree tree, AnnotatedTypeMirror type) {
applyDefaults(tree, type);
}
/**
* Determines the nearest enclosing element for a tree by climbing the tree
* toward the root and obtaining the element for the first declaration
* (variable, method, or class) that encloses the tree.
*
* @param tree the tree
* @return the nearest enclosing element for a tree
*/
private Element nearestEnclosing(Tree tree) {
TreePath path = factory.getPath(tree);
if (path == null) return InternalUtils.symbol(tree);
for (Tree t : path) {
switch (t.getKind()) {
case VARIABLE:
return TreeUtils.elementFromDeclaration((VariableTree)t);
case METHOD:
return TreeUtils.elementFromDeclaration((MethodTree)t);
case CLASS:
return TreeUtils.elementFromDeclaration((ClassTree)t);
default: // Do nothing.
}
}
return null;
}
/**
* Applies default annotations to a type.
* A {@link Tree} that determines the appropriate scope for defaults.
* <p>
*
* For instance, if the tree is associated with a declaration (e.g., it's
* the use of a field, or a method invocation), defaults in the scope of the
* <i>declaration</i> are used; if the tree is not associated with a
* declaration (e.g., a typecast), defaults in the scope of the tree are
* used.
*
* @param tree the tree associated with the type
* @param type the type to which defaults will be applied
*
* @see #applyDefaults(Element, AnnotatedTypeMirror)
*/
private void applyDefaults(Tree tree, AnnotatedTypeMirror type) {
// The location to take defaults from.
Element elt = null;
switch (tree.getKind()) {
case MEMBER_SELECT:
elt = TreeUtils.elementFromUse((MemberSelectTree)tree);
break;
case IDENTIFIER:
elt = TreeUtils.elementFromUse((IdentifierTree)tree);
break;
case METHOD_INVOCATION:
elt = TreeUtils.elementFromUse((MethodInvocationTree)tree);
break;
// TODO cases for array access, etc. -- every expression tree
// (The above probably means that we should use defaults in the
// scope of the declaration of the array. Is that right? -MDE)
default:
// If no associated symbol was found, use the tree's (lexical)
// scope.
elt = nearestEnclosing(tree);
}
if (elt != null)
applyDefaults(elt, type);
}
private Map<Element, List<DefaultQualifier>> qualifierCache =
new IdentityHashMap<Element, List<DefaultQualifier>>();
private List<DefaultQualifier> defaultsAt(final Element elt) {
if (elt == null)
return Collections.emptyList();
if (qualifierCache.containsKey(elt))
return qualifierCache.get(elt);
List<DefaultQualifier> qualifiers = new ArrayList<DefaultQualifier>();
DefaultQualifier d = elt.getAnnotation(DefaultQualifier.class);
if (d != null)
qualifiers.add(d);
DefaultQualifiers ds = elt.getAnnotation(DefaultQualifiers.class);
if (ds != null)
qualifiers.addAll(Arrays.asList(ds.value()));
Element parent;
if (elt.getKind() == ElementKind.PACKAGE)
parent = ((Symbol)elt).owner;
else
parent = elt.getEnclosingElement();
List<DefaultQualifier> parentDefaults = defaultsAt(parent);
if (qualifiers.isEmpty())
qualifiers = parentDefaults;
else
qualifiers.addAll(parentDefaults);
qualifierCache.put(elt, qualifiers);
return qualifiers;
}
/**
* Applies default annotations to a type.
* The defaults are taken from an {@link Element} by using the
* {@link DefaultQualifier} annotation present on the element
* or any of its enclosing elements.
*
* @param annotationScope the element representing the nearest enclosing
* default annotation scope for the type
* @param type the type to which defaults will be applied
*/
private void applyDefaults(final Element annotationScope, final AnnotatedTypeMirror type) {
List<DefaultQualifier> defaults = defaultsAt(annotationScope);
for (DefaultQualifier dq : defaults)
applyDefault(annotationScope, dq, type);
if (this.absoluteDefaultAnno != null)
new DefaultApplier(annotationScope, this.absoluteDefaultLocs, type).scan(type, absoluteDefaultAnno);
}
private void applyDefault(Element annotationScope, DefaultQualifier d, AnnotatedTypeMirror type) {
String name = d.value();
if (qualifiedNameMap.containsKey(name))
name = qualifiedNameMap.get(name);
AnnotationMirror anno = annoFactory.fromName(name);
if (anno == null)
return;
new DefaultApplier(annotationScope, d.locations(), type).scan(type, anno);
}
private static class DefaultApplier
extends AnnotatedTypeScanner<Void, AnnotationMirror> {
private final Element elt;
private final Collection<DefaultLocation> locations;
private final AnnotatedTypeMirror type;
public DefaultApplier(Element elt, DefaultLocation[] locations, AnnotatedTypeMirror type) {
this(elt, Arrays.asList(locations), type);
}
public DefaultApplier(Element elt, Collection<DefaultLocation> locations, AnnotatedTypeMirror type) {
this.elt = elt;
this.locations = locations; /* no need to copy locations */
this.type = type;
}
@Override
public Void scan(AnnotatedTypeMirror t,
AnnotationMirror p) {
if (t == null || t.getKind() == TypeKind.NONE)
return null;
// Skip type variables, but continue to scan their bounds.
if (t.getKind() == TypeKind.WILDCARD
|| t.getKind() == TypeKind.TYPEVAR)
return super.scan(t, p);
// Skip annotating this type if:
// - the default is "all except (the raw types of) locals"
// - we are applying defaults to a local
// - and the type is a raw type
if (elt.getKind() == ElementKind.LOCAL_VARIABLE
&& locations.contains(DefaultLocation.ALL_EXCEPT_LOCALS)
&& t == type)
return super.scan(t, p);
if (locations.contains(DefaultLocation.UPPER_BOUNDS)
&& locations.size() == 1
&& !this.isTypeVarExtends) {
return super.scan(t, p);
}
// Add the default annotation, but only if no other
// annotation is present.
if (!t.isAnnotated())
t.addAnnotation(p);
return super.scan(t, p);
}
// Skip method receivers.
@Override
public Void visitExecutable(AnnotatedExecutableType t,
AnnotationMirror p) {
return super.visitExecutable(t, p);
// scan(t.getReturnType(), p);
// scanAndReduce(t.getParameterTypes(), p, null);
// scanAndReduce(t.getThrownTypes(), p, null);
// scanAndReduce(t.getTypeVariables(), p, null);
// return null;
}
private boolean isTypeVarExtends = false;
@Override
public Void visitTypeVariable(AnnotatedTypeVariable type, AnnotationMirror p) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
Void r = scan(type.getLowerBound(), p);
visitedNodes.put(type, r);
boolean prevIsTypeVarExtends = isTypeVarExtends;
isTypeVarExtends = true;
try {
r = scanAndReduce(type.getUpperBound(), p, r);
} finally {
isTypeVarExtends = prevIsTypeVarExtends;
}
visitedNodes.put(type, r);
return r;
}
@Override
public Void visitWildcard(AnnotatedWildcardType type, AnnotationMirror p) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
Void r;
boolean prevIsTypeVarExtends = isTypeVarExtends;
isTypeVarExtends = true;
try {
r = scan(type.getExtendsBound(), p);
} finally {
isTypeVarExtends = prevIsTypeVarExtends;
}
visitedNodes.put(type, r);
r = scanAndReduce(type.getSuperBound(), p, r);
visitedNodes.put(type, r);
return r;
}
}
}