package annotator.find;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.element.Modifier;
import javax.lang.model.type.TypeKind;
import annotations.io.ASTPath;
import annotator.Main;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.UnionTypeTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.tree.WildcardTree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree;
/**
* A criterion to determine if a node matches a path through the AST.
*/
public class ASTPathCriterion implements Criterion {
public static boolean debug = Main.debug;
/**
* The path through the AST to match.
*/
ASTPath astPath;
/**
* Constructs a new ASTPathCriterion to match the given AST path.
* <p>
* This assumes that the astPath is valid. Specifically, that all of its
* arguments have been previously validated.
*
* @param astPath
* the AST path to match
*/
public ASTPathCriterion(ASTPath astPath) {
this.astPath = astPath;
}
/** {@inheritDoc} */
@Override
public boolean isSatisfiedBy(TreePath path, Tree leaf) {
assert path == null || path.getLeaf() == leaf;
return isSatisfiedBy(path);
}
/** {@inheritDoc} */
@Override
public boolean isSatisfiedBy(TreePath path) {
if (path == null) {
return false;
}
// actualPath stores the path through the source code AST to this
// location (specified by the "path" parameter to this method). It is
// computed by traversing from this location up the source code AST
// until it reaches a method node (this gets only the part of the path
// within a method) or class node (this gets only the part of the path
// within a field).
List<Tree> actualPath = new ArrayList<Tree>();
Tree leaf = path.getLeaf();
Tree.Kind kind = leaf.getKind();
while (kind != Tree.Kind.METHOD && !ASTPath.isClassEquiv(kind)) {
actualPath.add(0, leaf);
path = path.getParentPath();
if (path == null) { break; }
leaf = path.getLeaf();
kind = leaf.getKind();
}
// If astPath starts with Method.* or Class.*, include the
// MethodTree or ClassTree on actualPath.
if (path != null && !astPath.isEmpty()) {
Tree.Kind entryKind = astPath.get(0).getTreeKind();
if (entryKind == Tree.Kind.METHOD
&& kind == Tree.Kind.METHOD
|| entryKind == Tree.Kind.CLASS
&& ASTPath.isClassEquiv(kind)) {
actualPath.add(0, leaf);
}
}
if (debug) {
System.out.println("ASTPathCriterion.isSatisfiedBy");
System.out.println(" " + astPath);
for (Tree t : actualPath) {
System.out.println(" " + t.getKind() + ": "
+ t.toString().replace('\n', ' '));
}
}
int astPathLen = astPath.size();
int actualPathLen = actualPath.size();
if (astPathLen == 0 || actualPathLen == 0) { return false; }
// if (actualPathLen != astPathLen + (isOnNewArrayType ? 0 : 1)) {
// return false;
// }
Tree next = null;
int i = 0;
while (true) {
ASTPath.ASTEntry astNode = astPath.get(i);
Tree actualNode = actualPath.get(i);
if (!kindsMatch(astNode.getTreeKind(), actualNode.getKind())) {
return isBoundableWildcard(actualPath, i);
}
if (debug) {
System.out.println("astNode: " + astNode);
System.out.println("actualNode: " + actualNode.getKind());
}
// Based on the child selector and (optional) argument in "astNode",
// "next" will get set to the next source node below "actualNode".
// Then "next" will be compared with the node following "astNode"
// in "actualPath". If it's not a match, this is not the correct
// location. If it is a match, keep going.
next = getNext(actualNode, astPath, i);
if (next == null) {
return checkNull(actualPath, i);
}
if (!(next instanceof JCTree)) {
// converted from array type, not in source AST...
if (actualPathLen == i+1) {
// need to extend actualPath with "artificial" node
actualPath.add(next);
++actualPathLen;
}
}
if (debug) {
System.out.println("next: " + next);
}
// if (++i >= astPathLen || i >= actualPathLen) { break; }
if (++i >= astPathLen) { break; }
if (i >= actualPathLen) {
return checkNull(actualPath, i-1);
}
if (!matchNext(next, actualPath.get(i))) {
if (debug) {
System.out.println("no next match");
}
return false;
}
}
if (i < actualPathLen && matchNext(next, actualPath.get(i))
|| i <= actualPathLen
&& next.getKind() == Tree.Kind.NEW_ARRAY) {
return true;
}
if (debug) {
System.out.println("no next match");
}
return false;
}
private boolean matchNext(Tree next, Tree node) {
boolean b1 = next instanceof JCTree;
boolean b2 = node instanceof JCTree;
if (b1 && !b2) {
next = Insertions.TypeTree.fromJCTree((JCTree) next);
} else if (b2 && !b1) {
node = Insertions.TypeTree.fromJCTree((JCTree) node);
}
try {
return next.accept(new SimpleTreeVisitor<Boolean, Tree>() {
@Override
public Boolean
defaultAction(Tree t1, Tree t2) {
return t1 == t2;
}
@Override
public Boolean
visitIdentifier(IdentifierTree v, Tree t) {
return v == t;
// IdentifierTree i2 = (IdentifierTree) t;
// return i1.getName().toString()
// .equals(i2.getName().toString());
}
@Override
public Boolean
visitAnnotatedType(AnnotatedTypeTree a1, Tree t) {
AnnotatedTypeTree a2 = (AnnotatedTypeTree) t;
return matchNext(a1.getUnderlyingType(),
a2.getUnderlyingType());
}
// @Override
// public Boolean
// visitArrayType(ArrayTypeTree b1, Tree t) {
// ArrayTypeTree b2 = (ArrayTypeTree) t;
// return matchNext(b1.getType(), b2.getType());
// }
@Override
public Boolean
visitMemberSelect(MemberSelectTree c1, Tree t) {
MemberSelectTree c2 = (MemberSelectTree) t;
return c1.getIdentifier().toString()
.equals(c2.getIdentifier().toString())
&& matchNext(c1.getExpression(),
c2.getExpression());
}
@Override
public Boolean
visitWildcard(WildcardTree d1, Tree t) {
return d1 == (WildcardTree) t;
// WildcardTree d2 = (WildcardTree) t;
// Tree bound2 = d2.getBound();
// Tree bound1 = d1.getBound();
// return bound1 == bound2 || matchNext(bound1, bound2);
}
@Override
public Boolean
visitParameterizedType(ParameterizedTypeTree e1, Tree t) {
ParameterizedTypeTree e2 = (ParameterizedTypeTree) t;
List<? extends Tree> l2 = e2.getTypeArguments();
List<? extends Tree> l1 = e1.getTypeArguments();
if (l1.size() == l2.size()) {
int i = 0;
for (Tree t1 : l1) {
Tree t2 = l2.get(i++);
if (!matchNext(t1, t2)) { return false; }
}
return matchNext(e1.getType(), e2.getType());
}
return false;
}
}, node);
} catch (RuntimeException ex) {
return false;
}
}
private Tree getNext(Tree actualNode, ASTPath astPath, int ix) {
try {
ASTPath.ASTEntry astNode = astPath.get(ix);
switch (actualNode.getKind()) {
case ANNOTATED_TYPE: {
AnnotatedTypeTree annotatedType =
(AnnotatedTypeTree) actualNode;
if (astNode.childSelectorIs(ASTPath.ANNOTATION)) {
int arg = astNode.getArgument();
List<? extends AnnotationTree> annos =
annotatedType.getAnnotations();
if (arg >= annos.size()) {
return null;
}
return annos.get(arg);
} else {
return annotatedType.getUnderlyingType();
}
}
case ARRAY_ACCESS: {
ArrayAccessTree arrayAccess = (ArrayAccessTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
return arrayAccess.getExpression();
} else {
return arrayAccess.getIndex();
}
}
case ARRAY_TYPE: {
ArrayTypeTree arrayType = (ArrayTypeTree) actualNode;
return arrayType.getType();
}
case ASSERT: {
AssertTree azzert = (AssertTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
return azzert.getCondition();
} else {
return azzert.getDetail();
}
}
case ASSIGNMENT: {
AssignmentTree assignment = (AssignmentTree) actualNode;
if (astNode.childSelectorIs(ASTPath.VARIABLE)) {
return assignment.getVariable();
} else {
return assignment.getExpression();
}
}
case BLOCK: {
BlockTree block = (BlockTree) actualNode;
int arg = astNode.getArgument();
List<? extends StatementTree> statements = block.getStatements();
if (arg >= block.getStatements().size()) {
return null;
}
return statements.get(arg);
}
case CASE: {
CaseTree caze = (CaseTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
return caze.getExpression();
} else {
int arg = astNode.getArgument();
List<? extends StatementTree> statements = caze.getStatements();
if (arg >= statements.size()) {
return null;
}
return statements.get(arg);
}
}
case CATCH: {
CatchTree cach = (CatchTree) actualNode;
if (astNode.childSelectorIs(ASTPath.PARAMETER)) {
return cach.getParameter();
} else {
return cach.getBlock();
}
}
case ANNOTATION:
case CLASS:
case ENUM:
case INTERFACE: {
ClassTree clazz = (ClassTree) actualNode;
int arg = astNode.getArgument();
if (astNode.childSelectorIs(ASTPath.TYPE_PARAMETER)) {
return clazz.getTypeParameters().get(arg);
} else if (astNode.childSelectorIs(ASTPath.INITIALIZER)) {
int i = 0;
for (Tree member : clazz.getMembers()) {
if (member.getKind() == Tree.Kind.BLOCK && arg == i++) {
return member;
}
}
return null;
} else if (astNode.childSelectorIs(ASTPath.BOUND)) {
return arg < 0 ? clazz.getExtendsClause()
: clazz.getImplementsClause().get(arg);
} else {
return null;
}
}
case CONDITIONAL_EXPRESSION: {
ConditionalExpressionTree conditionalExpression = (ConditionalExpressionTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
return conditionalExpression.getCondition();
} else if (astNode.childSelectorIs(ASTPath.TRUE_EXPRESSION)) {
return conditionalExpression.getTrueExpression();
} else {
return conditionalExpression.getFalseExpression();
}
}
case DO_WHILE_LOOP: {
DoWhileLoopTree doWhileLoop = (DoWhileLoopTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
return doWhileLoop.getCondition();
} else {
return doWhileLoop.getStatement();
}
}
case ENHANCED_FOR_LOOP: {
EnhancedForLoopTree enhancedForLoop = (EnhancedForLoopTree) actualNode;
if (astNode.childSelectorIs(ASTPath.VARIABLE)) {
return enhancedForLoop.getVariable();
} else if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
return enhancedForLoop.getExpression();
} else {
return enhancedForLoop.getStatement();
}
}
case EXPRESSION_STATEMENT: {
ExpressionStatementTree expressionStatement = (ExpressionStatementTree) actualNode;
return expressionStatement.getExpression();
}
case FOR_LOOP: {
ForLoopTree forLoop = (ForLoopTree) actualNode;
if (astNode.childSelectorIs(ASTPath.INITIALIZER)) {
int arg = astNode.getArgument();
List<? extends StatementTree> inits = forLoop.getInitializer();
if (arg >= inits.size()) {
return null;
}
return inits.get(arg);
} else if (astNode.childSelectorIs(ASTPath.CONDITION)) {
return forLoop.getCondition();
} else if (astNode.childSelectorIs(ASTPath.UPDATE)) {
int arg = astNode.getArgument();
List<? extends ExpressionStatementTree> updates = forLoop.getUpdate();
if (arg >= updates.size()) {
return null;
}
return updates.get(arg);
} else {
return forLoop.getStatement();
}
}
case IF: {
IfTree iff = (IfTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
return iff.getCondition();
} else if (astNode.childSelectorIs(ASTPath.THEN_STATEMENT)) {
return iff.getThenStatement();
} else {
return iff.getElseStatement();
}
}
case INSTANCE_OF: {
InstanceOfTree instanceOf = (InstanceOfTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
return instanceOf.getExpression();
} else {
return instanceOf.getType();
}
}
case LABELED_STATEMENT: {
LabeledStatementTree labeledStatement = (LabeledStatementTree) actualNode;
return labeledStatement.getStatement();
}
case LAMBDA_EXPRESSION: {
LambdaExpressionTree lambdaExpression = (LambdaExpressionTree) actualNode;
if (astNode.childSelectorIs(ASTPath.PARAMETER)) {
int arg = astNode.getArgument();
List<? extends VariableTree> params = lambdaExpression.getParameters();
if (arg >= params.size()) {
return null;
}
return params.get(arg);
} else {
return lambdaExpression.getBody();
}
}
case MEMBER_REFERENCE: {
MemberReferenceTree memberReference = (MemberReferenceTree) actualNode;
if (astNode.childSelectorIs(ASTPath.QUALIFIER_EXPRESSION)) {
return memberReference.getQualifierExpression();
} else {
int arg = astNode.getArgument();
List<? extends ExpressionTree> typeArgs = memberReference.getTypeArguments();
if (arg >= typeArgs.size()) {
return null;
}
return typeArgs.get(arg);
}
}
case MEMBER_SELECT: {
MemberSelectTree memberSelect = (MemberSelectTree) actualNode;
return memberSelect.getExpression();
}
case METHOD: {
MethodTree method = (MethodTree) actualNode;
if (astNode.childSelectorIs(ASTPath.TYPE)) {
return method.getReturnType();
} else if (astNode.childSelectorIs(ASTPath.PARAMETER)) {
int arg = astNode.getArgument();
List<? extends VariableTree> params =
method.getParameters();
return arg < 0 ? method.getReceiverParameter()
: arg < params.size() ? params.get(arg) : null;
} else if (astNode.childSelectorIs(ASTPath.TYPE_PARAMETER)) {
int arg = astNode.getArgument();
return method.getTypeParameters().get(arg);
} else { // BODY
return method.getBody();
}
}
case METHOD_INVOCATION: {
MethodInvocationTree methodInvocation = (MethodInvocationTree) actualNode;
if (astNode.childSelectorIs(ASTPath.TYPE_ARGUMENT)) {
int arg = astNode.getArgument();
List<? extends Tree> typeArgs = methodInvocation.getTypeArguments();
if (arg >= typeArgs.size()) {
return null;
}
return typeArgs.get(arg);
} else if (astNode.childSelectorIs(ASTPath.METHOD_SELECT)) {
return methodInvocation.getMethodSelect();
} else {
int arg = astNode.getArgument();
List<? extends ExpressionTree> args = methodInvocation.getArguments();
if (arg >= args.size()) {
return null;
}
return args.get(arg);
}
}
case NEW_ARRAY: {
NewArrayTree newArray = (NewArrayTree) actualNode;
if (astNode.childSelectorIs(ASTPath.TYPE)) {
Type type = ((JCTree.JCNewArray) newArray).type;
Tree typeTree = Insertions.TypeTree.fromType(type);
int arg = astNode.getArgument();
if (arg == 0 && astPath.size() == ix+1) {
return newArray;
// if (astPath.size() != ix+1) { return null; }
// return typeTree;
// return ((ArrayTypeTree) typeTree).getType();
// return newArray;
}
typeTree = ((NewArrayTree) typeTree).getType();
while (--arg > 0) {
if (typeTree.getKind() != Tree.Kind.ARRAY_TYPE) {
return null;
}
typeTree = ((ArrayTypeTree) typeTree).getType();
}
return typeTree;
} else if (astNode.childSelectorIs(ASTPath.DIMENSION)) {
int arg = astNode.getArgument();
List<? extends ExpressionTree> dims = newArray.getDimensions();
return arg < dims.size() ? dims.get(arg) : null;
} else {
int arg = astNode.getArgument();
List<? extends ExpressionTree> inits =
newArray.getInitializers();
return arg < inits.size() ? inits.get(arg) : null;
}
}
case NEW_CLASS: {
NewClassTree newClass = (NewClassTree) actualNode;
if (astNode.childSelectorIs(ASTPath.ENCLOSING_EXPRESSION)) {
return newClass.getEnclosingExpression();
} else if (astNode.childSelectorIs(ASTPath.TYPE_ARGUMENT)) {
int arg = astNode.getArgument();
List<? extends Tree> typeArgs = newClass.getTypeArguments();
if (arg >= typeArgs.size()) {
return null;
}
return typeArgs.get(arg);
} else if (astNode.childSelectorIs(ASTPath.IDENTIFIER)) {
return newClass.getIdentifier();
} else if (astNode.childSelectorIs(ASTPath.ARGUMENT)) {
int arg = astNode.getArgument();
List<? extends ExpressionTree> args = newClass.getArguments();
if (arg >= args.size()) {
return null;
}
return args.get(arg);
} else {
return newClass.getClassBody(); // For anonymous classes
}
}
case PARAMETERIZED_TYPE: {
ParameterizedTypeTree parameterizedType = (ParameterizedTypeTree) actualNode;
if (astNode.childSelectorIs(ASTPath.TYPE)) {
return parameterizedType.getType();
} else {
int arg = astNode.getArgument();
List<? extends Tree> typeArgs = parameterizedType.getTypeArguments();
if (arg >= typeArgs.size()) {
return null;
}
return typeArgs.get(arg);
}
}
case PARENTHESIZED: {
ParenthesizedTree parenthesized = (ParenthesizedTree) actualNode;
return parenthesized.getExpression();
}
case RETURN: {
ReturnTree returnn = (ReturnTree) actualNode;
return returnn.getExpression();
}
case SWITCH: {
SwitchTree zwitch = (SwitchTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
return zwitch.getExpression();
} else {
int arg = astNode.getArgument();
List<? extends CaseTree> cases = zwitch.getCases();
if (arg >= cases.size()) {
return null;
}
return cases.get(arg);
}
}
case SYNCHRONIZED: {
SynchronizedTree synchronizzed = (SynchronizedTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
return synchronizzed.getExpression();
} else {
return synchronizzed.getBlock();
}
}
case THROW: {
ThrowTree throww = (ThrowTree) actualNode;
return throww.getExpression();
}
case TRY: {
TryTree tryy = (TryTree) actualNode;
if (astNode.childSelectorIs(ASTPath.BLOCK)) {
return tryy.getBlock();
} else if (astNode.childSelectorIs(ASTPath.CATCH)) {
int arg = astNode.getArgument();
List<? extends CatchTree> catches = tryy.getCatches();
if (arg >= catches.size()) {
return null;
}
return catches.get(arg);
} else if (astNode.childSelectorIs(ASTPath.FINALLY_BLOCK)) {
return tryy.getFinallyBlock();
} else {
int arg = astNode.getArgument();
List<? extends Tree> resources = tryy.getResources();
if (arg >= resources.size()) {
return null;
}
return resources.get(arg);
}
}
case TYPE_CAST: {
TypeCastTree typeCast = (TypeCastTree) actualNode;
if (astNode.childSelectorIs(ASTPath.TYPE)) {
return typeCast.getType();
} else {
return typeCast.getExpression();
}
}
case TYPE_PARAMETER: {
TypeParameterTree typeParam = (TypeParameterTree) actualNode;
List<? extends Tree> bounds = typeParam.getBounds();
int arg = astNode.getArgument();
return bounds.get(arg);
}
case UNION_TYPE: {
UnionTypeTree unionType = (UnionTypeTree) actualNode;
int arg = astNode.getArgument();
List<? extends Tree> typeAlts = unionType.getTypeAlternatives();
if (arg >= typeAlts.size()) {
return null;
}
return typeAlts.get(arg);
}
case VARIABLE: {
// A VariableTree can have modifiers, but we only look at
// the initializer and type because modifiers can't be
// annotated. Any annotations on the LHS must be on the type.
VariableTree var = (VariableTree) actualNode;
if (astNode.childSelectorIs(ASTPath.INITIALIZER)) {
return var.getInitializer();
} else if (astNode.childSelectorIs(ASTPath.TYPE)) {
return var.getType();
} else {
return null;
}
}
case WHILE_LOOP: {
WhileLoopTree whileLoop = (WhileLoopTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
return whileLoop.getCondition();
} else {
return whileLoop.getStatement();
}
}
default: {
if (ASTPath.isBinaryOperator(actualNode.getKind())) {
BinaryTree binary = (BinaryTree) actualNode;
if (astNode.childSelectorIs(ASTPath.LEFT_OPERAND)) {
return binary.getLeftOperand();
} else {
return binary.getRightOperand();
}
} else if (ASTPath.isCompoundAssignment(actualNode.getKind())) {
CompoundAssignmentTree compoundAssignment =
(CompoundAssignmentTree) actualNode;
if (astNode.childSelectorIs(ASTPath.VARIABLE)) {
return compoundAssignment.getVariable();
} else {
return compoundAssignment.getExpression();
}
} else if (ASTPath.isUnaryOperator(actualNode.getKind())) {
UnaryTree unary = (UnaryTree) actualNode;
return unary.getExpression();
} else if (isWildcard(actualNode.getKind())) {
WildcardTree wildcard = (WildcardTree) actualNode;
return wildcard.getBound();
} else {
throw new IllegalArgumentException("Illegal kind: "
+ actualNode.getKind());
}
}
}
} catch (RuntimeException ex) { return null; }
}
private boolean checkNull(List<Tree> path, int ix) {
Tree node = path.get(path.size()-1);
int last = astPath.size() - 1;
ASTPath.ASTEntry entry = astPath.get(ix);
Tree.Kind kind = entry.getTreeKind();
switch (kind) {
// case ANNOTATION:
// case INTERFACE:
case CLASS: // "extends" clause?
return ASTPath.isClassEquiv(kind)
&& ix == last && entry.getArgument() == -1
&& entry.childSelectorIs(ASTPath.BOUND);
case TYPE_PARAMETER:
return node.getKind() == Tree.Kind.TYPE_PARAMETER
&& ix == last && entry.getArgument() == 0
&& entry.childSelectorIs(ASTPath.BOUND);
case METHOD: // nullary constructor? receiver?
if (node.getKind() != Tree.Kind.METHOD) { return false; }
MethodTree method = (MethodTree) node;
List<? extends VariableTree> params = method.getParameters();
if ("<init>".equals(method.getName().toString())) {
if (ix == last) { return true; }
ASTPath.ASTEntry next = astPath.get(++ix);
String selector = next.getChildSelector();
Tree typeTree =
ASTPath.TYPE_PARAMETER.equals(selector)
? method.getTypeParameters().get(next.getArgument())
: ASTPath.PARAMETER.equals(selector)
? params.get(next.getArgument()).getType()
: null;
return typeTree != null && checkTypePath(ix, typeTree);
} else if (entry.childSelectorIs(ASTPath.PARAMETER)
&& entry.getArgument() == -1) {
if (ix == last) { return true; }
VariableTree rcvrParam = method.getReceiverParameter();
if (rcvrParam == null) { // TODO
// ClassTree clazz = methodReceiverType(path);
// return checkReceiverType(ix,
// ((JCTree.JCClassDecl) clazz).type);
} else {
return checkTypePath(ix+1, rcvrParam.getType());
}
}
return false;
case NEW_ARRAY:
if (node.getKind() != Tree.Kind.NEW_ARRAY) { return false; }
NewArrayTree newArray = (NewArrayTree) node;
int arg = entry.getArgument();
if (entry.childSelectorIs(ASTPath.TYPE)) {
if (ix == last) { return true; }
// Tree t = newArray.getType();
// int depth = 1;
// while (t.getKind() == Tree.Kind.ARRAY_TYPE) {
// t = ((ArrayTypeTree) t).getType();
// ++depth;
// }
return arg == arrayDepth(newArray);
} else {
List<? extends ExpressionTree> typeTrees =
entry.childSelectorIs(ASTPath.DIMENSION)
? newArray.getDimensions()
: entry.childSelectorIs(ASTPath.INITIALIZER)
? newArray.getInitializers()
: null;
return typeTrees != null && arg < typeTrees.size()
&& checkTypePath(ix+1, typeTrees.get(arg));
}
case UNBOUNDED_WILDCARD:
return isBoundableWildcard(path, path.size()-1);
default: // TODO: casts?
return false;
}
}
private static int arrayDepth(Tree tree) {
if (tree.getKind() == Tree.Kind.NEW_ARRAY) {
NewArrayTree newArray = (NewArrayTree) tree;
Tree type = newArray.getType();
if (type != null) {
return type.accept(new SimpleTreeVisitor<Integer, Integer>() {
@Override
public Integer visitArrayType(ArrayTypeTree t, Integer i) {
return t.getType().accept(this, i+1);
}
@Override
public Integer defaultAction(Tree t, Integer i) {
return i;
}
}, 1);
}
int depth = newArray.getDimensions().size();
for (ExpressionTree elem : newArray.getInitializers()) {
Tree.Kind kind = elem.getKind();
if (kind == Tree.Kind.NEW_ARRAY
|| kind == Tree.Kind.ARRAY_TYPE) {
depth = Math.max(depth, arrayDepth(elem)+1);
}
}
return depth;
} else if (tree.getKind() == Tree.Kind.ANNOTATED_TYPE) {
return arrayDepth(((AnnotatedTypeTree) tree).getUnderlyingType());
} else if (tree.getKind() == Tree.Kind.ARRAY_TYPE) {
return 1 + arrayDepth(((ArrayTypeTree) tree).getType());
} else {
return 0;
}
}
private boolean checkReceiverType(int i, Type t) {
if (t == null) { return false; }
while (++i < astPath.size()) {
ASTPath.ASTEntry entry = astPath.get(i);
switch (entry.getTreeKind()) {
case ANNOTATED_TYPE:
break;
case ARRAY_TYPE:
if (t.getKind() != TypeKind.ARRAY) { return false; }
t = ((Type.ArrayType) t).getComponentType();
break;
case MEMBER_SELECT:
// TODO
break;
case PARAMETERIZED_TYPE:
if (entry.childSelectorIs(ASTPath.TYPE_PARAMETER)) {
if (!t.isParameterized()) { return false; }
List<Type> args = t.getTypeArguments();
int a = entry.getArgument();
if (a >= args.size()) { return false; }
t = args.get(a);
} // else TYPE -- stay?
break;
case TYPE_PARAMETER:
if (t.getKind() != TypeKind.WILDCARD) { return false; }
t = t.getLowerBound();
break;
case EXTENDS_WILDCARD:
if (t.getKind() != TypeKind.WILDCARD) { return false; }
t = ((Type.WildcardType) t).getExtendsBound();
break;
case SUPER_WILDCARD:
if (t.getKind() != TypeKind.WILDCARD) { return false; }
t = ((Type.WildcardType) t).getSuperBound();
break;
case UNBOUNDED_WILDCARD:
if (t.getKind() != TypeKind.WILDCARD) { return false; }
t = t.getLowerBound();
break;
default:
return false;
}
if (t == null) { return false; }
}
return true;
}
private static ClassTree methodReceiverType(TreePath path) {
Tree t = path.getLeaf();
if (t.getKind() != Tree.Kind.METHOD) { return null; }
JCTree.JCMethodDecl method = (JCTree.JCMethodDecl) t;
if ((method.mods.flags & Flags.STATIC) != 0) { return null; }
// Find the name of the class with type parameters to create the
// receiver. Walk up the tree and pick up class names to add to
// the receiver type. Since we're starting from the innermost
// class, the classes we get to at earlier iterations of the loop
// are inside of the classes we get to at later iterations.
TreePath parent = path.getParentPath();
Tree leaf = parent.getLeaf();
Tree.Kind kind = leaf.getKind();
// For an inner class constructor, the receiver comes from the
// superclass, so skip past the first type definition.
boolean skip = method.getReturnType() == null;
while (kind != Tree.Kind.COMPILATION_UNIT
&& kind != Tree.Kind.NEW_CLASS) {
if (kind == Tree.Kind.CLASS
|| kind == Tree.Kind.INTERFACE
|| kind == Tree.Kind.ENUM
|| kind == Tree.Kind.ANNOTATION_TYPE) {
JCTree.JCClassDecl clazz = (JCTree.JCClassDecl) leaf;
boolean isStatic = kind == Tree.Kind.INTERFACE
|| kind == Tree.Kind.ENUM
|| clazz.getModifiers().getFlags().contains(Modifier.STATIC);
skip &= !isStatic;
if (!skip || isStatic) { return clazz; }
skip = false;
}
parent = path.getParentPath();
leaf = parent.getLeaf();
kind = leaf.getKind();
}
throw new IllegalArgumentException("no receiver for non-inner constructor");
}
private boolean checkTypePath(int i, Tree typeTree) {
try {
loop: while (typeTree != null && i < astPath.size()) {
ASTPath.ASTEntry entry = astPath.get(i);
Tree.Kind kind = entry.getTreeKind();
switch (kind) {
case ANNOTATED_TYPE:
typeTree = ((AnnotatedTypeTree) typeTree)
.getUnderlyingType();
continue;
case ARRAY_TYPE:
typeTree = ((ArrayTypeTree) typeTree).getType();
break;
case MEMBER_SELECT:
typeTree = ((MemberSelectTree) typeTree).getExpression();
break;
case PARAMETERIZED_TYPE:
if (entry.childSelectorIs(ASTPath.TYPE_ARGUMENT)) {
int arg = entry.getArgument();
typeTree = ((ParameterizedTypeTree) typeTree)
.getTypeArguments().get(arg);
} else { // TYPE
typeTree = ((ParameterizedTypeTree) typeTree).getType();
}
break;
default:
if (isWildcard(kind)) {
return ++i == astPath.size(); // ???
}
break loop;
}
++i;
}
} catch (RuntimeException ex) {}
return false;
}
/**
* Determines if the given kinds match, false otherwise. Two kinds match if
* they're exactly the same or if the two kinds are both compound
* assignments, unary operators, binary operators or wildcards.
* <p>
* This is necessary because in the JAIF file these kinds are represented by
* their general types (i.e. BinaryOperator, CompoundOperator, etc.) rather
* than their kind (i.e. PLUS, MINUS, PLUS_ASSIGNMENT, XOR_ASSIGNMENT,
* etc.). Internally, a single kind is used to represent each general type
* (i.e. PLUS is used for BinaryOperator, PLUS_ASSIGNMENT is used for
* CompoundAssignment, etc.). Yet, the actual source nodes have the correct
* kind. So if an AST path entry has a PLUS kind, that really means it could
* be any BinaryOperator, resulting in PLUS matching any other
* BinaryOperator.
*
* @param kind1
* the first kind to match
* @param kind2
* the second kind to match
* @return {@code true} if the kinds match as described above, {@code false}
* otherwise.
*/
private boolean kindsMatch(Tree.Kind kind1, Tree.Kind kind2) {
return kind1 == kind2 ? true
: ASTPath.isClassEquiv(kind1)
? ASTPath.isClassEquiv(kind2)
: ASTPath.isCompoundAssignment(kind1)
? ASTPath.isCompoundAssignment(kind2)
: ASTPath.isUnaryOperator(kind1)
? ASTPath.isUnaryOperator(kind2)
: ASTPath.isBinaryOperator(kind1)
? ASTPath.isBinaryOperator(kind2)
: ASTPath.isWildcard(kind1)
? ASTPath.isWildcard(kind2)
: false;
}
/**
* Determines if the given kind is a binary operator.
*
* @param kind
* the kind to test
* @return true if the given kind is a binary operator
*/
public boolean isBinaryOperator(Tree.Kind kind) {
return kind == Tree.Kind.MULTIPLY || kind == Tree.Kind.DIVIDE
|| kind == Tree.Kind.REMAINDER || kind == Tree.Kind.PLUS
|| kind == Tree.Kind.MINUS || kind == Tree.Kind.LEFT_SHIFT
|| kind == Tree.Kind.RIGHT_SHIFT
|| kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT
|| kind == Tree.Kind.LESS_THAN
|| kind == Tree.Kind.GREATER_THAN
|| kind == Tree.Kind.LESS_THAN_EQUAL
|| kind == Tree.Kind.GREATER_THAN_EQUAL
|| kind == Tree.Kind.EQUAL_TO || kind == Tree.Kind.NOT_EQUAL_TO
|| kind == Tree.Kind.AND || kind == Tree.Kind.XOR
|| kind == Tree.Kind.OR || kind == Tree.Kind.CONDITIONAL_AND
|| kind == Tree.Kind.CONDITIONAL_OR;
}
public boolean isExpression(Tree.Kind kind) {
switch (kind) {
case ARRAY_ACCESS:
case ASSIGNMENT:
case CONDITIONAL_EXPRESSION:
case EXPRESSION_STATEMENT:
case MEMBER_SELECT:
case MEMBER_REFERENCE:
case IDENTIFIER:
case INSTANCE_OF:
case METHOD_INVOCATION:
case NEW_ARRAY:
case NEW_CLASS:
case LAMBDA_EXPRESSION:
case PARENTHESIZED:
case TYPE_CAST:
case POSTFIX_INCREMENT:
case POSTFIX_DECREMENT:
case PREFIX_INCREMENT:
case PREFIX_DECREMENT:
case UNARY_PLUS:
case UNARY_MINUS:
case BITWISE_COMPLEMENT:
case LOGICAL_COMPLEMENT:
case MULTIPLY:
case DIVIDE:
case REMAINDER:
case PLUS:
case MINUS:
case LEFT_SHIFT:
case RIGHT_SHIFT:
case UNSIGNED_RIGHT_SHIFT:
case LESS_THAN:
case GREATER_THAN:
case LESS_THAN_EQUAL:
case GREATER_THAN_EQUAL:
case EQUAL_TO:
case NOT_EQUAL_TO:
case AND:
case XOR:
case OR:
case CONDITIONAL_AND:
case CONDITIONAL_OR:
case MULTIPLY_ASSIGNMENT:
case DIVIDE_ASSIGNMENT:
case REMAINDER_ASSIGNMENT:
case PLUS_ASSIGNMENT:
case MINUS_ASSIGNMENT:
case LEFT_SHIFT_ASSIGNMENT:
case RIGHT_SHIFT_ASSIGNMENT:
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
case AND_ASSIGNMENT:
case XOR_ASSIGNMENT:
case OR_ASSIGNMENT:
case INT_LITERAL:
case LONG_LITERAL:
case FLOAT_LITERAL:
case DOUBLE_LITERAL:
case BOOLEAN_LITERAL:
case CHAR_LITERAL:
case STRING_LITERAL:
case NULL_LITERAL:
return true;
default:
return false;
}
}
/**
* Determines if the given kind is a wildcard.
*
* @param kind
* the kind to test
* @return true if the given kind is a wildcard
*/
private boolean isWildcard(Tree.Kind kind) {
return kind == Tree.Kind.UNBOUNDED_WILDCARD
|| kind == Tree.Kind.EXTENDS_WILDCARD
|| kind == Tree.Kind.SUPER_WILDCARD;
}
// 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<?>.
private boolean isBoundableWildcard(List<Tree> actualPath, int i) {
if (i <= 0) { return false; }
Tree actualNode = actualPath.get(i);
if (actualNode.getKind() == Tree.Kind.UNBOUNDED_WILDCARD) {
// isWildcard(actualNode.getKind())
// TODO: refactor GenericArrayLoc to use same code?
Tree ancestor = actualPath.get(i-1);
if (ancestor.getKind() == Tree.Kind.INSTANCE_OF) {
TreeFinder.warn.debug("WARNING: wildcard bounds not allowed "
+ "in 'instanceof' expression; skipping insertion%n");
return false;
} else if (i > 1 && ancestor.getKind() ==
Tree.Kind.PARAMETERIZED_TYPE) {
ancestor = actualPath.get(i-2);
if (ancestor.getKind() == Tree.Kind.ARRAY_TYPE) {
TreeFinder.warn.debug("WARNING: wildcard bounds not allowed "
+ "in 'instanceof' expression; skipping insertion%n");
return false;
}
}
return true;
}
return false;
}
/** {@inheritDoc} */
@Override
public Kind getKind() {
return Kind.AST_PATH;
}
/** {@inheritDoc} */
@Override
public String toString() {
return "ASTPathCriterion: " + astPath;
}
}