| 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); |
| } |
| } |