package annotator.find;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.lang.model.type.TypeKind;
import annotations.el.InnerTypeLocation;
import annotator.Main;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WildcardTree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
/**
* GenericArrayLocationCriterion represents the criterion specifying the location
* of an element in the generic/array hierarchy as specified by the
* JSR 308 proposal.
*/
public class GenericArrayLocationCriterion implements Criterion {
private static final boolean debug = false;
// the full location list
private final List<TypePathEntry> location;
// represents all but the last element of the location list
// TODO: this field is initialized, but never read!
// I removed it, see the version control history.
// private Criterion parentCriterion;
/**
* Creates a new GenericArrayLocationCriterion specifying that the element
* is an outer type, such as:
* <code>@A List<Integer></code>
* or
* <code>Integer @A []</code>
*/
public GenericArrayLocationCriterion() {
this(new ArrayList<TypePathEntry>());
}
/**
* Creates a new GenericArrayLocationCriterion representing the given
* location.
*
* @param innerTypeLoc the location of the element being represented
*/
public GenericArrayLocationCriterion(InnerTypeLocation innerTypeLoc) {
this(innerTypeLoc.location);
}
private GenericArrayLocationCriterion(List<TypePathEntry> location) {
this.location = location;
}
/** {@inheritDoc} */
@Override
public boolean isSatisfiedBy(TreePath path, Tree leaf) {
assert path == null || path.getLeaf() == leaf;
return isSatisfiedBy(path);
}
/**
* Determines if the given list holds only {@link TypePathEntry}s with the tag
* {@link TypePathEntryKind#ARRAY}.
*
* @param location the list to check
* @return {@code true} if the list only contains
* {@link TypePathEntryKind#ARRAY}, {@code false} otherwise.
*/
private boolean containsOnlyArray(List<TypePathEntry> location) {
for (TypePathEntry tpe : location) {
if (tpe.tag != TypePathEntryKind.ARRAY) {
return false;
}
}
return true;
}
/** {@inheritDoc} */
@Override
public boolean isSatisfiedBy(TreePath path) {
if (path == null || path.getParentPath() == null) {
if (debug) {
System.out.println("GenericArrayLocationCriterion.isSatisfiedBy() with null path gives false.");
}
return false;
}
if (debug) {
System.out.printf("GenericArrayLocationCriterion.isSatisfiedBy():%n leaf of path: %s%n searched location: %s%n",
path.getLeaf(), location);
}
TreePath pathRemaining = path;
Tree leaf = path.getLeaf();
// Don't allow annotations directly on these tree kinds if the child type is
// a MEMBER_SELECT. This way we'll continue to traverse deeper in the tree
// to find the correct MEMBER_SELECT.
Tree child = null;
if (leaf.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
child = ((ParameterizedTypeTree) leaf).getType();
} else if (leaf.getKind() == Tree.Kind.VARIABLE) {
child = ((VariableTree) leaf).getType();
} else if (leaf.getKind() == Tree.Kind.NEW_CLASS) {
child = ((NewClassTree) leaf).getIdentifier();
} else if (leaf.getKind() == Tree.Kind.NEW_ARRAY && !location.isEmpty()) {
child = ((NewArrayTree) leaf).getType();
}
if (child != null && child.getKind() == Tree.Kind.MEMBER_SELECT) {
JCExpression exp = ((JCFieldAccess) child).getExpression();
if (exp.type != null && exp.type.getKind() == TypeKind.PACKAGE
|| location.isEmpty()
|| (location.get(location.size()-1)).tag
!= TypePathEntryKind.INNER_TYPE) {
return false;
}
}
if (leaf.getKind() == Tree.Kind.MEMBER_SELECT) {
JCFieldAccess fieldAccess = (JCFieldAccess) leaf;
if (isStatic(fieldAccess)) {
// If this MEMBER_SELECT is for a static class...
if (location.isEmpty()) {
// ...and it does not go on a compound type, this is the right place.
return true;
} else if (isGenericOrArray(path.getParentPath().getLeaf())
&& isGenericOrArray(path.getParentPath().getParentPath().getLeaf())) {
// If the two parents above this are compound types, then skip
// the compound type directly above. For example, to get to Outer.Inner
// of Outer.Inner<K, V> we had to get through the PARAMETERIZED_TYPE
// node. But we didn't go deeper in the compound type in the way the
// type path defines, we just went deeper to get to the outer type. So
// skip this node later when checking to make sure that we're in the
// correct part of the compound type.
pathRemaining = path.getParentPath();
}
} else {
JCExpression exp = fieldAccess.getExpression();
if (exp.getKind() == Tree.Kind.MEMBER_SELECT && exp.type != null
&& exp.type.getKind() == TypeKind.PACKAGE) {
if (location.isEmpty()) {
return true;
} // else, keep going to make sure we're in the right part of the
// compound type
} else {
if (!location.isEmpty()
&& location.get(location.size()-1).tag
!= TypePathEntryKind.INNER_TYPE) {
return false;
}
}
}
}
if (location.isEmpty()) {
// no inner type location, want to annotate outermost type
// e.g., @Nullable List list;
// @Nullable List<String> list;
// String @Nullable [] array;
leaf = path.getLeaf();
Tree parent = path.getParentPath().getLeaf();
boolean result = ((leaf.getKind() == Tree.Kind.NEW_ARRAY)
|| (leaf.getKind() == Tree.Kind.NEW_CLASS)
|| (leaf.getKind() == Tree.Kind.ANNOTATED_TYPE
&& isSatisfiedBy(TreePath.getPath(path,
((AnnotatedTypeTree) leaf).getUnderlyingType())))
|| ((isGenericOrArray(leaf)
// or, it might be a raw type
|| (leaf.getKind() == Tree.Kind.IDENTIFIER) // IdentifierTree
|| (leaf.getKind() == Tree.Kind.METHOD) // MethodTree
|| (leaf.getKind() == Tree.Kind.TYPE_PARAMETER) // TypeParameterTree
// I don't know why a GenericArrayLocationCriterion
// is being created in this case, but it is.
|| (leaf.getKind() == Tree.Kind.PRIMITIVE_TYPE) // PrimitiveTypeTree
// TODO: do we need wildcards here?
// || leaf instanceof WildcardTree
)
&& ! isGenericOrArray(parent)));
if (debug) {
System.out.printf("GenericArrayLocationCriterion.isSatisfiedBy: locationInParent==null%n leaf=%s (%s)%n parent=%s (%s)%n => %s (%s %s)%n",
leaf, leaf.getClass(), parent, parent.getClass(), result, isGenericOrArray(leaf), ! isGenericOrArray(parent));
}
return result;
}
// If we've made it this far then we've determined that *if* this is the right
// place to insert the annotation this is the MEMBER_SELECT it should be
// inserted on. So remove the rest of the MEMBER_SELECTs to get down to the
// compound type and make sure the compound type location matches.
while (pathRemaining.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) {
pathRemaining = pathRemaining.getParentPath();
}
List<TypePathEntry> locationRemaining = new ArrayList<TypePathEntry>(location);
while (locationRemaining.size() != 0) {
// annotating an inner type
leaf = pathRemaining.getLeaf();
if ((leaf.getKind() == Tree.Kind.NEW_ARRAY)
&& containsOnlyArray(locationRemaining)) {
if (debug) {
System.out.println("Found a matching NEW_ARRAY");
}
return true;
}
TreePath parentPath = pathRemaining.getParentPath();
if (parentPath == null) {
if (debug) {
System.out.println("Parent path is null and therefore false.");
}
return false;
}
Tree parent = parentPath.getLeaf();
if (parent.getKind() == Tree.Kind.ANNOTATED_TYPE) {
// If the parent is an annotated type, we did not really go up a level.
// Therefore, skip up one more level.
parentPath = parentPath.getParentPath();
parent = parentPath.getLeaf();
}
if (debug) {
System.out.printf("locationRemaining: %s, leaf: %s parent: %s %s%n",
locationRemaining, Main.treeToString(leaf), Main.treeToString(parent), parent.getClass());
}
TypePathEntry loc = locationRemaining.get(locationRemaining.size()-1);
if (loc.tag == TypePathEntryKind.INNER_TYPE) {
if (leaf.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
leaf = parent;
parentPath = parentPath.getParentPath();
parent = parentPath.getLeaf();
}
if (leaf.getKind() != Tree.Kind.MEMBER_SELECT) { return false; }
JCFieldAccess fieldAccess = (JCFieldAccess) leaf;
if (isStatic(fieldAccess)) {
return false;
}
locationRemaining.remove(locationRemaining.size()-1);
leaf = fieldAccess.selected;
pathRemaining = parentPath;
// TreePath.getPath(pathRemaining.getCompilationUnit(), leaf);
} else if (loc.tag == TypePathEntryKind.WILDCARD
&& leaf.getKind() == Tree.Kind.UNBOUNDED_WILDCARD) {
// Check if the leaf is an unbounded wildcard instead of the parent, since unbounded
// wildcard has no members so it can't be the parent of anything.
if (locationRemaining.size() == 0) {
return false;
}
// The following check is necessary because Oracle has decided that
// x instanceof Class<? extends Object>
// will remain illegal even though it means the same thing as
// x instanceof Class<?>.
TreePath gpath = parentPath.getParentPath();
if (gpath != null) { // TODO: skip over existing annotations?
Tree gparent = gpath.getLeaf();
if (gparent.getKind() == Tree.Kind.INSTANCE_OF) {
TreeFinder.warn.debug("WARNING: wildcard bounds not allowed "
+ "in 'instanceof' expression; skipping insertion%n");
return false;
} else if (gparent.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
gpath = gpath.getParentPath();
if (gpath != null
&& gpath.getLeaf().getKind() == Tree.Kind.ARRAY_TYPE) {
TreeFinder.warn.debug("WARNING: wildcard bounds not allowed "
+ "in generic array type; skipping insertion%n");
return false;
}
}
}
locationRemaining.remove(locationRemaining.size() - 1);
} else if (parent.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
if (loc.tag != TypePathEntryKind.TYPE_ARGUMENT) {
return false;
}
// Find the outermost type in the AST; if it has parameters,
// pop the stack once for each inner type on the end of the type
// path and check the parameter.
Tree inner = ((ParameterizedTypeTree) parent).getType();
int i = locationRemaining.size() - 1; // last valid type path index
locationRemaining.remove(i--);
while (inner.getKind() == Tree.Kind.MEMBER_SELECT
&& !isStatic((JCFieldAccess) inner)) {
// fieldAccess.type != null && fieldAccess.type.getKind() == TypeKind.DECLARED
// && fieldAccess.type.tsym.isStatic()
// TODO: check whether MEMBER_SELECT indicates inner or qualifier?
if (i < 0) { break; }
if (locationRemaining.get(i).tag != TypePathEntryKind.INNER_TYPE) {
return false;
}
locationRemaining.remove(i--);
inner = ((MemberSelectTree) inner).getExpression();
if (inner.getKind() == Tree.Kind.ANNOTATED_TYPE) {
inner = ((AnnotatedTypeTree) inner).getUnderlyingType();
}
if (inner.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
inner = ((ParameterizedTypeTree) inner).getType();
}
}
if (i >= 0 && locationRemaining.get(i).tag ==
TypePathEntryKind.INNER_TYPE) {
return false;
}
// annotating List<@A Integer>
// System.out.printf("parent instanceof ParameterizedTypeTree: %s loc=%d%n",
// Main.treeToString(parent), loc);
List<? extends Tree> childTrees =
((ParameterizedTypeTree) parent).getTypeArguments();
boolean found = false;
if (childTrees.size() > loc.arg) {
Tree childi = childTrees.get(loc.arg);
if (childi.getKind() == Tree.Kind.ANNOTATED_TYPE) {
childi = ((AnnotatedTypeTree) childi).getUnderlyingType();
}
if (childi == leaf) {
for (TreePath outerPath = parentPath.getParentPath();
outerPath.getLeaf().getKind() == Tree.Kind.MEMBER_SELECT
&& !isStatic((JCFieldAccess) outerPath.getLeaf());
outerPath = outerPath.getParentPath()) {
outerPath = outerPath.getParentPath();
if (outerPath.getLeaf().getKind() == Tree.Kind.ANNOTATED_TYPE) {
outerPath = outerPath.getParentPath();
}
if (outerPath.getLeaf().getKind() != Tree.Kind.PARAMETERIZED_TYPE) {
break;
}
parentPath = outerPath;
}
pathRemaining = parentPath;
found = true;
}
}
if (!found) {
if (debug) {
System.out.printf("Generic failed for leaf: %s: nr children: %d loc: %s child: %s%n",
leaf, childTrees.size(), loc,
((childTrees.size() > loc.arg) ? childTrees.get(loc.arg) : null));
}
return false;
}
} else if (parent.getKind() == Tree.Kind.EXTENDS_WILDCARD
|| parent.getKind() == Tree.Kind.SUPER_WILDCARD) {
if (loc.tag != TypePathEntryKind.WILDCARD || locationRemaining.size() == 1) {
// If there's only one location left, this can't be a match since a wildcard
// needs to be in another kind of compound type.
return false;
}
locationRemaining.remove(locationRemaining.size() - 1);
// annotating List<? extends @A Integer>
// System.out.printf("parent instanceof extends WildcardTree: %s loc=%d%n",
// Main.treeToString(parent), loc);
WildcardTree wct = (WildcardTree) parent;
Tree boundTree = wct.getBound();
if (debug) {
String wildcardType;
if (parent.getKind() == Tree.Kind.EXTENDS_WILDCARD) {
wildcardType = "ExtendsWildcard";
} else {
wildcardType = "SuperWildcard";
}
System.out.printf("%s with bound %s gives %s%n", wildcardType, boundTree, boundTree.equals(leaf));
}
if (boundTree.equals(leaf)) {
if (locationRemaining.isEmpty()) {
return true;
} else {
pathRemaining = parentPath;
}
} else {
return false;
}
} else if (parent.getKind() == Tree.Kind.ARRAY_TYPE) {
if (loc.tag != TypePathEntryKind.ARRAY) {
return false;
}
locationRemaining.remove(locationRemaining.size() - 1);
// annotating Integer @A []
parentPath = TreeFinder.largestContainingArray(parentPath);
parent = parentPath.getLeaf();
// System.out.printf("parent instanceof ArrayTypeTree: %s loc=%d%n",
// parent, loc);
Tree elt = ((ArrayTypeTree) parent).getType();
while (locationRemaining.size() > 0
&& locationRemaining.get(locationRemaining.size() - 1).tag == TypePathEntryKind.ARRAY) {
if (elt.getKind() != Tree.Kind.ARRAY_TYPE) { // ArrayTypeTree
if (debug) {
System.out.printf("Element: %s is not an ArrayTypeTree and therefore false.\n", elt);
}
return false;
}
elt = ((ArrayTypeTree) elt).getType();
locationRemaining.remove(locationRemaining.size() - 1);
}
boolean b = elt.equals(leaf);
if (debug) {
System.out.printf("parent %s instanceof ArrayTypeTree: %b %s %s %s%n",
parent, elt.equals(leaf), elt, leaf, loc);
System.out.printf("b=%s elt=%s leaf=%s%n", b, elt, leaf);
}
// TODO: The parent criterion should be exact, not just "in".
// Otherwise the criterion [1] matches [5 4 3 2 1].
// This is a disadvantage of working from the inside out instead of the outside in.
if (b) {
pathRemaining = parentPath;
} else {
return false;
}
} else if (parent.getKind() == Tree.Kind.NEW_ARRAY) {
if (loc.tag != TypePathEntryKind.ARRAY) {
return false;
}
if (debug) {
System.out.println("Parent is a NEW_ARRAY and always gives true.");
}
return true;
} else {
if (debug) {
System.out.printf("unrecognized parent kind = %s%n", parent.getKind());
}
return false;
}
}
// no (remaining) inner type location, want to annotate outermost type
// e.g., @Nullable List list;
// @Nullable List<String> list;
TreePath parentPath = pathRemaining.getParentPath();
if (parentPath == null) {
if (debug) {
System.out.println("Parent path is null and therefore false.");
}
return false;
}
Tree parent = pathRemaining.getParentPath().getLeaf();
if (debug) {
leaf = pathRemaining.getLeaf();
System.out.printf("No (remaining) inner type location:%n leaf: %s %b%n parent: %s %b%n result: %s%n",
Main.treeToString(leaf), isGenericOrArray(leaf),
Main.treeToString(parent), isGenericOrArray(parent),
! isGenericOrArray(parent));
}
return ! isGenericOrArray(parent);
}
/**
* @param fieldAccess
* @return
*/
private boolean isStatic(JCFieldAccess fieldAccess) {
return fieldAccess.type != null
&& fieldAccess.type.getKind() == TypeKind.DECLARED
&& fieldAccess.type.tsym.isStatic();
}
private boolean isGenericOrArray(Tree t) {
return ((t.getKind() == Tree.Kind.PARAMETERIZED_TYPE)
|| (t.getKind() == Tree.Kind.ARRAY_TYPE)
|| (t.getKind() == Tree.Kind.EXTENDS_WILDCARD)
|| (t.getKind() == Tree.Kind.SUPER_WILDCARD)
|| (t.getKind() == Tree.Kind.ANNOTATED_TYPE &&
isGenericOrArray(((AnnotatedTypeTree)t).getUnderlyingType()))
// Monolithic: one node for entire "new". So, handle specially.
// || (t.getKind() == Tree.Kind.NEW_ARRAY)
);
}
@Override
public Kind getKind() {
return Criterion.Kind.GENERIC_ARRAY_LOCATION;
}
@Override
public String toString() {
return "GenericArrayLocationCriterion at " +
((location.isEmpty())
? "outermost type"
: ("( " + location.toString() + " )"));
}
/**
* Gets the type path location of this criterion.
*
* @return an unmodifiable list of {@link TypePathEntry}s
*/
public List<TypePathEntry> getLocation() {
return Collections.unmodifiableList(location);
}
}