blob: ec88dd2b4c776b6d280e3c85e3e3de0ff74ae926 [file] [log] [blame]
package annotations.io;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import plume.ArraysMDE;
import annotations.util.PersistentStack;
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.CompilationUnitTree;
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.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.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.util.TreePath;
/**
* A path through the AST.
*/
public class ASTPath extends ConsStack<ASTPath.ASTEntry>
implements Comparable<ASTPath>, Iterable<ASTPath.ASTEntry> {
private static final ASTPath EMPTY = new ASTPath();
private static final String[] typeSelectors =
{ "bound", "identifier", "type", "typeAlternative", "typeArgument",
"typeParameter", "underlyingType" };
// Constants for the various child selectors.
public static final String ANNOTATION = "annotation";
public static final String ARGUMENT = "argument";
public static final String BLOCK = "block";
public static final String BODY = "body";
public static final String BOUND = "bound";
public static final String CASE = "case";
public static final String CATCH = "catch";
public static final String CLASS_BODY = "classBody";
public static final String CONDITION = "condition";
public static final String DETAIL = "detail";
public static final String DIMENSION = "dimension";
public static final String ELSE_STATEMENT = "elseStatement";
public static final String ENCLOSING_EXPRESSION = "enclosingExpression";
public static final String EXPRESSION = "expression";
public static final String FALSE_EXPRESSION = "falseExpression";
public static final String FINALLY_BLOCK = "finallyBlock";
public static final String IDENTIFIER = "identifier";
public static final String INDEX = "index";
public static final String INITIALIZER = "initializer";
public static final String LEFT_OPERAND = "leftOperand";
public static final String METHOD_SELECT = "methodSelect";
public static final String MODIFIERS = "modifiers";
public static final String PARAMETER = "parameter";
public static final String QUALIFIER_EXPRESSION = "qualifierExpression";
public static final String RESOURCE = "resource";
public static final String RIGHT_OPERAND = "rightOperand";
public static final String STATEMENT = "statement";
public static final String THEN_STATEMENT = "thenStatement";
public static final String THROWS = "throws";
public static final String TRUE_EXPRESSION = "trueExpression";
public static final String TYPE = "type";
public static final String TYPE_ALTERNATIVE = "typeAlternative";
public static final String TYPE_ARGUMENT = "typeArgument";
public static final String TYPE_PARAMETER = "typeParameter";
public static final String UNDERLYING_TYPE = "underlyingType";
public static final String UPDATE = "update";
public static final String VARIABLE = "variable";
/**
* A single entry in an AST path.
*/
public static class ASTEntry implements Comparable<ASTEntry> {
private Tree.Kind treeKind;
private String childSelector;
private Integer argument;
/**
* Constructs a new AST entry. For example, in the entry:
* <pre>
* {@code
* Block.statement 3
* }</pre>
* the tree kind is "Block", the child selector is "statement", and the
* argument is "3".
*
* @param treeKind the kind of this AST entry
* @param childSelector the child selector to this AST entry
* @param argument the argument
*/
public ASTEntry(Tree.Kind treeKind, String childSelector, Integer argument) {
this.treeKind = treeKind;
this.childSelector = childSelector;
this.argument = argument;
}
/**
* Constructs a new AST entry, without an argument.
*
* See {@link #ASTEntry(Tree.Kind, String, Integer)} for an example of the parameters.
*
* @param treeKind the kind of this AST entry
* @param childSelector the child selector to this AST entry
*/
public ASTEntry(Tree.Kind treeKind, String childSelector) {
this(treeKind, childSelector, null);
}
/**
* Gets the tree node equivalent kind of this AST entry. For example, in
* <pre>
* {@code
* Block.statement 3
* }</pre>
* "Block" is the tree kind.
* @return the tree kind
*/
public Tree.Kind getTreeKind() {
return treeKind;
}
/**
* Gets the child selector of this AST entry. For example, in
* <pre>
* {@code
* Block.statement 3
* }</pre>
* "statement" is the child selector.
* @return the child selector
*/
public String getChildSelector() {
return childSelector;
}
/**
* Determines if the given string is equal to this AST path entry's
* child selector.
*
* @param s the string to compare to
* @return {@code true} if the string matches the child selector,
* {@code false} otherwise.
*/
public boolean childSelectorIs(String s) {
return childSelector.equals(s);
}
/**
* Gets the argument of this AST entry. For example, in
* <pre>
* {@code
* Block.statement 3
* }</pre>
* "3" is the argument.
* @return the argument
* @throws IllegalStateException if this AST entry does not have an argument
*/
public int getArgument() {
if (argument >= (negativeAllowed() ? -1 : 0)) {
return argument;
}
throw new IllegalStateException("Value not set.");
}
/**
* Checks that this Entry has an argument.
*
* @return if this entry has an argument
*/
public boolean hasArgument() {
return argument == null ? false
: argument >= 0 ? true
: negativeAllowed();
}
// argument < 0 valid for two cases
private boolean negativeAllowed() {
switch (treeKind) {
case CLASS:
return childSelectorIs(ASTPath.BOUND);
case METHOD:
return childSelectorIs(ASTPath.PARAMETER);
default:
return false;
}
}
@Override
public int compareTo(ASTEntry o) {
if (o == null) {
return 1;
} else if (o.childSelector == null) {
if (childSelector != null) { return 1; }
} else if (childSelector == null) {
return -1;
}
int c = treeKind.compareTo(o.treeKind);
if (c != 0) { return c; }
c = childSelector.compareTo(o.childSelector);
if (c != 0) { return c; }
return o.argument == null
? argument == null ? 0 : 1
: argument == null ? -1 : argument.compareTo(o.argument);
}
@Override
public boolean equals(Object o) {
return o instanceof ASTEntry && compareTo((ASTEntry) o) == 0;
}
@Override
public int hashCode() {
int base = treeKind.hashCode() ^ childSelector.hashCode();
int shift = argument == null ? 0 : 2 + argument;
return Integer.rotateRight(base, shift);
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
switch (treeKind) {
case CLASS:
case ENUM:
case INTERFACE:
b.append("Class");
break;
case AND:
case CONDITIONAL_AND:
case CONDITIONAL_OR:
case DIVIDE:
case EQUAL_TO:
case GREATER_THAN:
case GREATER_THAN_EQUAL:
case LEFT_SHIFT:
case LESS_THAN:
case LESS_THAN_EQUAL:
case MINUS:
case MULTIPLY:
case NOT_EQUAL_TO:
case OR:
case PLUS:
case REMAINDER:
case RIGHT_SHIFT:
case XOR:
b.append("Binary");
break;
case LOGICAL_COMPLEMENT:
case POSTFIX_DECREMENT:
case POSTFIX_INCREMENT:
case PREFIX_DECREMENT:
case PREFIX_INCREMENT:
case UNARY_MINUS:
case UNARY_PLUS:
case UNSIGNED_RIGHT_SHIFT:
b.append("Unary");
break;
case AND_ASSIGNMENT:
case DIVIDE_ASSIGNMENT:
case LEFT_SHIFT_ASSIGNMENT:
case MINUS_ASSIGNMENT:
case MULTIPLY_ASSIGNMENT:
case OR_ASSIGNMENT:
case PLUS_ASSIGNMENT:
case REMAINDER_ASSIGNMENT:
case RIGHT_SHIFT_ASSIGNMENT:
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
case XOR_ASSIGNMENT:
b.append("CompoundAssignment");
break;
case EXTENDS_WILDCARD:
case SUPER_WILDCARD:
case UNBOUNDED_WILDCARD:
b.append("Wildcard");
break;
case ANNOTATION:
case TYPE_ANNOTATION:
b.append("Annotation");
break;
default:
String s = treeKind.toString();
int n = s.length();
boolean cap = true; // capitalize next character
for (int i = 0; i < n; i++) {
char c = s.charAt(i);
if (c == '_') {
cap = true;
} else {
b.append(cap ? Character.toUpperCase(c)
: Character.toLowerCase(c));
cap = false;
}
}
}
b.append(".").append(childSelector);
if (argument != null) { b.append(" ").append(argument); }
return b.toString();
}
}
private static Comparator<ASTPath> comparator = new Comparator<ASTPath>() {
@Override
public int compare(ASTPath p1, ASTPath p2) {
return p1 == null ? (p2 == null ? 0 : -1) : p1.compareTo(p2);
}
};
ASTPath() {}
public static ASTPath empty() { return EMPTY; }
public static Comparator<ASTPath> getComparator() {
return comparator;
}
// TODO: replace w/ skip list?
@Override
public Iterator<ASTEntry> iterator() {
PersistentStack<ASTEntry> s = this;
int n = size();
ASTEntry[] a = new ASTEntry[n];
while (--n >= 0) {
a[n] = s.peek();
s = s.pop();
}
return Arrays.asList(a).iterator();
}
public ASTPath extendNewArray(int depth) {
return extend(new ASTEntry(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, depth));
}
public ASTPath newArrayLevel(int depth) {
return add(new ASTEntry(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, depth));
}
public ASTPath add(ASTEntry entry) {
ASTPath path = EMPTY;
for (ASTEntry e : this) { path = path.extend(e); }
return path.extend(entry);
}
public ASTPath extend(ASTEntry entry) {
return (ASTPath) push(entry);
}
public ASTPath getParentPath() {
return (ASTPath) pop();
}
public ASTEntry get(int index) {
PersistentStack<ASTEntry> s = this;
int n = size();
if (index >= n) {
throw new NoSuchElementException(Integer.toString(index));
}
if (index < 0) {
index += n;
if (index < 0) {
throw new IllegalArgumentException("negative index " + index);
}
}
while (--n > index) { s = s.pop(); }
return s.peek();
}
@Override
public int hashCode() {
// hacky fix: remove {Method,Class}.body for comparison
PersistentStack<ASTEntry> s = canonical(this);
int hash = 0;
while (!s.isEmpty()) {
hash = Integer.rotateRight(hash ^ s.peek().hashCode(), 1);
s = s.pop();
}
return hash;
}
@Override
public boolean equals(Object o) {
return o instanceof ASTPath && equals((ASTPath) o);
}
public boolean equals(ASTPath astPath) {
return compareTo(astPath) == 0;
}
@Override
public int compareTo(ASTPath o) {
// hacky fix: remove {Method,Class}.body for comparison
PersistentStack<ASTEntry> s0 = canonical(this);
PersistentStack<ASTEntry> s1 = canonical(o);
Deque<ASTEntry> d0 = new LinkedList<ASTEntry>();
Deque<ASTEntry> d1 = new LinkedList<ASTEntry>();
int c = 0;
while (!s0.isEmpty()) {
d0.push(s0.peek());
s0 = s0.pop();
}
while (!s1.isEmpty()) {
d1.push(s1.peek());
s1 = s1.pop();
}
int n0 = d0.size();
int n1 = d1.size();
c = Integer.compare(n0, n1);
if (c == 0) {
Iterator<ASTEntry> i0 = d0.iterator();
Iterator<ASTEntry> i1 = d1.iterator();
while (i0.hasNext()) {
c = i0.next().compareTo(i1.next());
if (c != 0) { return c; }
}
}
return c;
}
private static ASTPath canonical(ASTPath astPath) {
// TODO
return astPath;
}
@Override
public String toString() {
if (isEmpty()) { return ""; }
Iterator<ASTEntry> iter = iterator();
StringBuilder sb = new StringBuilder().append(iter.next());
while (iter.hasNext()) {
sb = sb.append(", ").append(iter.next());
}
return sb.toString();
}
/**
* Create a new {@code ASTPath} from a formatted string description.
*
* @param s formatted string as in JAIF {@code insert-\{cast,annotation\}}
* @return the corresponding {@code ASTPath}
* @throws ParseException
*/
public static ASTPath parse(final String s) throws ParseException {
return new Parser(s).parseASTPath();
}
/**
* Determine whether this {@code ASTPath} matches a given {@code TreePath}.
*/
public boolean matches(TreePath treePath) {
CompilationUnitTree cut = treePath.getCompilationUnit();
Tree leaf = treePath.getLeaf();
ASTPath astPath = ASTIndex.indexOf(cut).get(leaf).astPath; // FIXME
return this.equals(astPath);
}
static class Parser {
// adapted from annotations.io.IndexFileParser
// TODO: refactor IndexFileParser to use this class
StreamTokenizer st;
Parser(String s) {
st = new StreamTokenizer(new StringReader(s));
}
private void getTok() {
try {
st.nextToken();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private boolean gotType(int t) {
return st.ttype == t;
}
private int intVal() throws ParseException {
if (gotType(StreamTokenizer.TT_NUMBER)) {
int n = (int) st.nval;
if (n == st.nval) {
return n;
}
}
throw new ParseException("expected integer, got " + st);
}
private String strVal() throws ParseException {
if (gotType(StreamTokenizer.TT_WORD)) {
return st.sval;
}
throw new ParseException("expected string, got " + st);
}
/**
* Parses an AST path.
* @return the AST path
*/
ASTPath parseASTPath() throws ParseException {
ASTPath astPath = new ASTPath().extend(parseASTEntry());
while (gotType(',')) {
getTok();
astPath = astPath.extend(parseASTEntry());
}
return astPath;
}
/**
* Parses and returns the next AST entry.
* @return a new AST entry
* @throws ParseException if the next entry type is invalid
*/
ASTEntry parseASTEntry() throws ParseException {
String s = strVal();
if (s.equals("AnnotatedType")) {
return newASTEntry(Tree.Kind.ANNOTATED_TYPE,
new String[] {ASTPath.ANNOTATION, ASTPath.UNDERLYING_TYPE},
new String[] {ASTPath.ANNOTATION});
} else if (s.equals("ArrayAccess")) {
return newASTEntry(Tree.Kind.ARRAY_ACCESS,
new String[] {ASTPath.EXPRESSION, ASTPath.INDEX});
} else if (s.equals("ArrayType")) {
return newASTEntry(Tree.Kind.ARRAY_TYPE,
new String[] {ASTPath.TYPE});
} else if (s.equals("Assert")) {
return newASTEntry(Tree.Kind.ASSERT,
new String[] {ASTPath.CONDITION, ASTPath.DETAIL});
} else if (s.equals("Assignment")) {
return newASTEntry(Tree.Kind.ASSIGNMENT,
new String[] {ASTPath.VARIABLE, ASTPath.EXPRESSION});
} else if (s.equals("Binary")) {
// Always use Tree.Kind.PLUS for Binary
return newASTEntry(Tree.Kind.PLUS,
new String[] {ASTPath.LEFT_OPERAND, ASTPath.RIGHT_OPERAND});
} else if (s.equals("Block")) {
return newASTEntry(Tree.Kind.BLOCK,
new String[] {ASTPath.STATEMENT},
new String[] {ASTPath.STATEMENT});
} else if (s.equals("Case")) {
return newASTEntry(Tree.Kind.CASE,
new String[] {ASTPath.EXPRESSION, ASTPath.STATEMENT},
new String[] {ASTPath.STATEMENT});
} else if (s.equals("Catch")) {
return newASTEntry(Tree.Kind.CATCH,
new String[] {ASTPath.PARAMETER, ASTPath.BLOCK});
} else if (s.equals("Class")) {
return newASTEntry(Tree.Kind.CLASS,
new String[] {ASTPath.BOUND, ASTPath.INITIALIZER,
ASTPath.TYPE_PARAMETER},
new String[] {ASTPath.BOUND, ASTPath.INITIALIZER,
ASTPath.TYPE_PARAMETER});
} else if (s.equals("CompoundAssignment")) {
// Always use Tree.Kind.PLUS_ASSIGNMENT for CompoundAssignment
return newASTEntry(Tree.Kind.PLUS_ASSIGNMENT,
new String[] {ASTPath.VARIABLE, ASTPath.EXPRESSION});
} else if (s.equals("ConditionalExpression")) {
return newASTEntry(Tree.Kind.CONDITIONAL_EXPRESSION,
new String[] {ASTPath.CONDITION,
ASTPath.TRUE_EXPRESSION,
ASTPath.FALSE_EXPRESSION});
} else if (s.equals("DoWhileLoop")) {
return newASTEntry(Tree.Kind.DO_WHILE_LOOP,
new String[] {ASTPath.CONDITION, ASTPath.STATEMENT});
} else if (s.equals("EnhancedForLoop")) {
return newASTEntry(Tree.Kind.ENHANCED_FOR_LOOP,
new String[] {ASTPath.VARIABLE,
ASTPath.EXPRESSION,
ASTPath.STATEMENT});
} else if (s.equals("ExpressionStatement")) {
return newASTEntry(Tree.Kind.EXPRESSION_STATEMENT,
new String[] {ASTPath.EXPRESSION});
} else if (s.equals("ForLoop")) {
return newASTEntry(Tree.Kind.FOR_LOOP,
new String[] {ASTPath.INITIALIZER, ASTPath.CONDITION,
ASTPath.UPDATE, ASTPath.STATEMENT},
new String[] {ASTPath.INITIALIZER, ASTPath.UPDATE});
} else if (s.equals("If")) {
return newASTEntry(Tree.Kind.IF,
new String[] {ASTPath.CONDITION,
ASTPath.THEN_STATEMENT, ASTPath.ELSE_STATEMENT});
} else if (s.equals("InstanceOf")) {
return newASTEntry(Tree.Kind.INSTANCE_OF,
new String[] {ASTPath.EXPRESSION, ASTPath.TYPE});
} else if (s.equals("LabeledStatement")) {
return newASTEntry(Tree.Kind.LABELED_STATEMENT,
new String[] {ASTPath.STATEMENT});
} else if (s.equals("LambdaExpression")) {
return newASTEntry(Tree.Kind.LAMBDA_EXPRESSION,
new String[] {ASTPath.PARAMETER, ASTPath.BODY},
new String[] {ASTPath.PARAMETER});
} else if (s.equals("MemberReference")) {
return newASTEntry(Tree.Kind.MEMBER_REFERENCE,
new String[] {ASTPath.QUALIFIER_EXPRESSION, ASTPath.TYPE_ARGUMENT},
new String[] {ASTPath.TYPE_ARGUMENT});
} else if (s.equals("MemberSelect")) {
return newASTEntry(Tree.Kind.MEMBER_SELECT,
new String[] {ASTPath.EXPRESSION});
} else if (s.equals("Method")) {
return newASTEntry(Tree.Kind.METHOD,
new String[] {ASTPath.BODY, ASTPath.PARAMETER,
ASTPath.TYPE, ASTPath.TYPE_PARAMETER},
new String[] {ASTPath.PARAMETER, ASTPath.TYPE_PARAMETER});
} else if (s.equals("MethodInvocation")) {
return newASTEntry(Tree.Kind.METHOD_INVOCATION,
new String[] {ASTPath.TYPE_ARGUMENT,
ASTPath.METHOD_SELECT, ASTPath.ARGUMENT},
new String[] {ASTPath.TYPE_ARGUMENT, ASTPath.ARGUMENT});
} else if (s.equals("NewArray")) {
return newASTEntry(Tree.Kind.NEW_ARRAY,
new String[] {ASTPath.TYPE, ASTPath.DIMENSION,
ASTPath.INITIALIZER},
new String[] {ASTPath.TYPE, ASTPath.DIMENSION,
ASTPath.INITIALIZER});
} else if (s.equals("NewClass")) {
return newASTEntry(Tree.Kind.NEW_CLASS,
new String[] {ASTPath.ENCLOSING_EXPRESSION,
ASTPath.TYPE_ARGUMENT, ASTPath.IDENTIFIER,
ASTPath.ARGUMENT, ASTPath.CLASS_BODY},
new String[] {ASTPath.TYPE_ARGUMENT, ASTPath.ARGUMENT});
} else if (s.equals("ParameterizedType")) {
return newASTEntry(Tree.Kind.PARAMETERIZED_TYPE,
new String[] {ASTPath.TYPE, ASTPath.TYPE_ARGUMENT},
new String[] {ASTPath.TYPE_ARGUMENT});
} else if (s.equals("Parenthesized")) {
return newASTEntry(Tree.Kind.PARENTHESIZED,
new String[] {ASTPath.EXPRESSION});
} else if (s.equals("Return")) {
return newASTEntry(Tree.Kind.RETURN,
new String[] {ASTPath.EXPRESSION});
} else if (s.equals("Switch")) {
return newASTEntry(Tree.Kind.SWITCH,
new String[] {ASTPath.EXPRESSION, ASTPath.CASE},
new String[] {ASTPath.CASE});
} else if (s.equals("Synchronized")) {
return newASTEntry(Tree.Kind.SYNCHRONIZED,
new String[] {ASTPath.EXPRESSION, ASTPath.BLOCK});
} else if (s.equals("Throw")) {
return newASTEntry(Tree.Kind.THROW,
new String[] {ASTPath.EXPRESSION});
} else if (s.equals("Try")) {
return newASTEntry(Tree.Kind.TRY,
new String[] {ASTPath.BLOCK, ASTPath.CATCH, ASTPath.FINALLY_BLOCK},
new String[] {ASTPath.CATCH});
} else if (s.equals("TypeCast")) {
return newASTEntry(Tree.Kind.TYPE_CAST,
new String[] {ASTPath.TYPE, ASTPath.EXPRESSION});
} else if (s.equals("Unary")) {
// Always use Tree.Kind.UNARY_PLUS for Unary
return newASTEntry(Tree.Kind.UNARY_PLUS,
new String[] {ASTPath.EXPRESSION});
} else if (s.equals("UnionType")) {
return newASTEntry(Tree.Kind.UNION_TYPE,
new String[] {ASTPath.TYPE_ALTERNATIVE},
new String[] {ASTPath.TYPE_ALTERNATIVE});
} else if (s.equals("Variable")) {
return newASTEntry(Tree.Kind.VARIABLE,
new String[] {ASTPath.TYPE, ASTPath.INITIALIZER});
} else if (s.equals("WhileLoop")) {
return newASTEntry(Tree.Kind.WHILE_LOOP,
new String[] {ASTPath.CONDITION, ASTPath.STATEMENT});
} else if (s.equals("Wildcard")) {
// Always use Tree.Kind.UNBOUNDED_WILDCARD for Wildcard
return newASTEntry(Tree.Kind.UNBOUNDED_WILDCARD,
new String[] {ASTPath.BOUND});
}
throw new ParseException("Invalid AST path type: " + s);
}
/**
* Parses and constructs a new AST entry, where none of the child selections require
* arguments. For example, the call:
*
* <pre>
* {@code newASTEntry(Tree.Kind.WHILE_LOOP, new String[] {"condition", "statement"});</pre>
*
* constructs a while loop AST entry, where the valid child selectors are "condition" or
* "statement".
*
* @param kind the kind of this AST entry
* @param legalChildSelectors a list of the legal child selectors for this AST entry
* @return a new {@link ASTEntry}
* @throws ParseException if an illegal argument is found
*/
private ASTEntry newASTEntry(Tree.Kind kind, String[] legalChildSelectors)
throws ParseException {
return newASTEntry(kind, legalChildSelectors, null);
}
/**
* Parses and constructs a new AST entry. For example, the call:
*
* <pre>
* {@code newASTEntry(Tree.Kind.CASE, new String[] {"expression", "statement"}, new String[] {"statement"});
* </pre>
*
* constructs a case AST entry, where the valid child selectors are
* "expression" or "statement" and the "statement" child selector requires
* an argument.
*
* @param kind the kind of this AST entry
* @param legalChildSelectors a list of the legal child selectors for this AST entry
* @param argumentChildSelectors a list of the child selectors that also require an argument.
* Entries here should also be in the legalChildSelectors list.
* @return a new {@link ASTEntry}
* @throws ParseException if an illegal argument is found
*/
private ASTEntry newASTEntry(Tree.Kind kind, String[] legalChildSelectors,
String[] argumentChildSelectors) throws ParseException {
if (gotType('.')) {
getTok();
} else {
throw new ParseException("expected '.', got " + st);
}
String s = strVal();
for (String arg : legalChildSelectors) {
if (s.equals(arg)) {
if (argumentChildSelectors != null
&& ArraysMDE.indexOf(argumentChildSelectors, arg) >= 0) {
getTok();
return new ASTEntry(kind, arg, intVal());
} else {
return new ASTEntry(kind, arg);
}
}
}
throw new ParseException("Invalid argument for " + kind
+ " (legal arguments - " + Arrays.toString(legalChildSelectors)
+ "): " + s);
}
}
static class Matcher {
// adapted from IndexFileParser.parseASTPath et al.
// TODO: refactor switch statement into TreeVisitor?
public static final DebugWriter dbug = new DebugWriter();
private ASTPath astPath;
Matcher(ASTPath astPath) {
this.astPath = astPath;
}
private boolean nonDecl(TreePath path) {
switch (path.getLeaf().getKind()) {
case CLASS:
case METHOD:
return false;
case VARIABLE:
TreePath parentPath = path.getParentPath();
return parentPath != null
&& parentPath.getLeaf().getKind() != Tree.Kind.CLASS;
default:
return true;
}
}
public boolean matches(TreePath path) {
return matches(path, -1);
}
public boolean matches(TreePath path, int depth) {
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>();
while (path != null && nonDecl(path)) {
actualPath.add(0, path.getLeaf());
path = path.getParentPath();
}
if (dbug.isEnabled()) {
dbug.debug("AST [%s]%n", astPath);
for (Tree t : actualPath) {
dbug.debug(" %s: %s%n", t.getKind(),
t.toString().replace('\n', ' '));
}
}
if (astPath.isEmpty() || actualPath.isEmpty()
|| actualPath.size() != astPath.size() + 1) {
return false;
}
for (int i = 0; i < astPath.size() && i < actualPath.size(); i++) {
ASTPath.ASTEntry astNode = astPath.get(i);
Tree actualNode = actualPath.get(i);
// 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.
Tree next = null;
dbug.debug("astNode: %s%n", astNode);
dbug.debug("actualNode: %s%n", actualNode.getKind());
if (!kindsMatch(astNode.getTreeKind(), actualNode.getKind())) {
return false;
}
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 false;
}
next = annos.get(arg);
} else {
next = annotatedType.getUnderlyingType();
}
break;
}
case ARRAY_ACCESS: {
ArrayAccessTree arrayAccess = (ArrayAccessTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
next = arrayAccess.getExpression();
} else {
next = arrayAccess.getIndex();
}
break;
}
case ARRAY_TYPE: {
ArrayTypeTree arrayType = (ArrayTypeTree) actualNode;
next = arrayType.getType();
break;
}
case ASSERT: {
AssertTree azzert = (AssertTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
next = azzert.getCondition();
} else {
next = azzert.getDetail();
}
break;
}
case ASSIGNMENT: {
AssignmentTree assignment = (AssignmentTree) actualNode;
if (astNode.childSelectorIs(ASTPath.VARIABLE)) {
next = assignment.getVariable();
} else {
next = assignment.getExpression();
}
break;
}
case BLOCK: {
BlockTree block = (BlockTree) actualNode;
int arg = astNode.getArgument();
List<? extends StatementTree> statements = block.getStatements();
if (arg >= block.getStatements().size()) {
return false;
}
next = statements.get(arg);
break;
}
case CASE: {
CaseTree caze = (CaseTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
next = caze.getExpression();
} else {
int arg = astNode.getArgument();
List<? extends StatementTree> statements = caze.getStatements();
if (arg >= statements.size()) {
return false;
}
next = statements.get(arg);
}
break;
}
case CATCH: {
CatchTree cach = (CatchTree) actualNode;
if (astNode.childSelectorIs(ASTPath.PARAMETER)) {
next = cach.getParameter();
} else {
next = cach.getBlock();
}
break;
}
case CLASS: {
ClassTree clazz = (ClassTree) actualNode;
int arg = astNode.getArgument();
if (astNode.childSelectorIs(ASTPath.BOUND)) {
next = arg == -1 ? clazz.getExtendsClause()
: clazz.getImplementsClause().get(arg);
} else {
next = clazz.getTypeParameters().get(arg);
}
break;
}
case CONDITIONAL_EXPRESSION: {
ConditionalExpressionTree conditionalExpression =
(ConditionalExpressionTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
next = conditionalExpression.getCondition();
} else if (astNode.childSelectorIs(ASTPath.TRUE_EXPRESSION)) {
next = conditionalExpression.getTrueExpression();
} else {
next = conditionalExpression.getFalseExpression();
}
break;
}
case DO_WHILE_LOOP: {
DoWhileLoopTree doWhileLoop =(DoWhileLoopTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
next = doWhileLoop.getCondition();
} else {
next = doWhileLoop.getStatement();
}
break;
}
case ENHANCED_FOR_LOOP: {
EnhancedForLoopTree enhancedForLoop = (EnhancedForLoopTree) actualNode;
if (astNode.childSelectorIs(ASTPath.VARIABLE)) {
next = enhancedForLoop.getVariable();
} else if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
next = enhancedForLoop.getExpression();
} else {
next = enhancedForLoop.getStatement();
}
break;
}
case EXPRESSION_STATEMENT: {
ExpressionStatementTree expressionStatement =
(ExpressionStatementTree) actualNode;
next = expressionStatement.getExpression();
break;
}
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 false;
}
next = inits.get(arg);
} else if (astNode.childSelectorIs(ASTPath.CONDITION)) {
next = forLoop.getCondition();
} else if (astNode.childSelectorIs(ASTPath.UPDATE)) {
int arg = astNode.getArgument();
List<? extends ExpressionStatementTree> updates = forLoop.getUpdate();
if (arg >= updates.size()) {
return false;
}
next = updates.get(arg);
} else {
next = forLoop.getStatement();
}
break;
}
case IF: {
IfTree iff = (IfTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
next = iff.getCondition();
} else if (astNode.childSelectorIs(ASTPath.THEN_STATEMENT)) {
next = iff.getThenStatement();
} else {
next = iff.getElseStatement();
}
break;
}
case INSTANCE_OF: {
InstanceOfTree instanceOf = (InstanceOfTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
next = instanceOf.getExpression();
} else {
next = instanceOf.getType();
}
break;
}
case LABELED_STATEMENT: {
LabeledStatementTree labeledStatement =
(LabeledStatementTree) actualNode;
next = labeledStatement.getStatement();
break;
}
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 false;
}
next = params.get(arg);
} else {
next = lambdaExpression.getBody();
}
break;
}
case MEMBER_REFERENCE: {
MemberReferenceTree memberReference = (MemberReferenceTree) actualNode;
if (astNode.childSelectorIs(ASTPath.QUALIFIER_EXPRESSION)) {
next = memberReference.getQualifierExpression();
} else {
int arg = astNode.getArgument();
List<? extends ExpressionTree> typeArgs =
memberReference.getTypeArguments();
if (arg >= typeArgs.size()) {
return false;
}
next = typeArgs.get(arg);
}
break;
}
case MEMBER_SELECT: {
MemberSelectTree memberSelect = (MemberSelectTree) actualNode;
next = memberSelect.getExpression();
break;
}
case METHOD: {
MethodTree method = (MethodTree) actualNode;
int arg = astNode.getArgument();
if (astNode.childSelectorIs(ASTPath.TYPE)) {
next = method.getReturnType();
} else if (astNode.childSelectorIs(ASTPath.PARAMETER)) {
next = arg == -1 ? method.getReceiverParameter()
: method.getParameters().get(arg);
} else if (astNode.childSelectorIs(ASTPath.TYPE_PARAMETER)) {
next = method.getTypeParameters().get(arg);
} else if (astNode.childSelectorIs(ASTPath.BODY)) {
next = method.getBody();
} else { // THROWS?
return false;
}
break;
}
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 false;
}
next = typeArgs.get(arg);
} else if (astNode.childSelectorIs(ASTPath.METHOD_SELECT)) {
next = methodInvocation.getMethodSelect();
} else {
int arg = astNode.getArgument();
List<? extends ExpressionTree> args = methodInvocation.getArguments();
if (arg >= args.size()) {
return false;
}
next = args.get(arg);
}
break;
}
case NEW_ARRAY: {
NewArrayTree newArray = (NewArrayTree) actualNode;
if (astNode.childSelectorIs(ASTPath.TYPE)) {
int arg = astNode.getArgument();
if (arg < 0) {
next = newArray.getType();
} else {
return arg == depth;
}
} else if (astNode.childSelectorIs(ASTPath.DIMENSION)) {
int arg = astNode.getArgument();
List<? extends ExpressionTree> dims = newArray.getDimensions();
if (arg >= dims.size()) {
return false;
}
next = dims.get(arg);
} else {
int arg = astNode.getArgument();
List<? extends ExpressionTree> inits = newArray.getInitializers();
if (arg >= inits.size()) {
return false;
}
next = inits.get(arg);
}
break;
}
case NEW_CLASS: {
NewClassTree newClass = (NewClassTree) actualNode;
if (astNode.childSelectorIs(ASTPath.ENCLOSING_EXPRESSION)) {
next = newClass.getEnclosingExpression();
} else if (astNode.childSelectorIs(ASTPath.TYPE_ARGUMENT)) {
int arg = astNode.getArgument();
List<? extends Tree> typeArgs = newClass.getTypeArguments();
if (arg >= typeArgs.size()) {
return false;
}
next = typeArgs.get(arg);
} else if (astNode.childSelectorIs(ASTPath.IDENTIFIER)) {
next = newClass.getIdentifier();
} else if (astNode.childSelectorIs(ASTPath.ARGUMENT)) {
int arg = astNode.getArgument();
List<? extends ExpressionTree> args = newClass.getArguments();
if (arg >= args.size()) {
return false;
}
next = args.get(arg);
} else {
next = newClass.getClassBody();
}
break;
}
case PARAMETERIZED_TYPE: {
ParameterizedTypeTree parameterizedType =
(ParameterizedTypeTree) actualNode;
if (astNode.childSelectorIs(ASTPath.TYPE)) {
next = parameterizedType.getType();
} else {
int arg = astNode.getArgument();
List<? extends Tree> typeArgs = parameterizedType.getTypeArguments();
if (arg >= typeArgs.size()) {
return false;
}
next = typeArgs.get(arg);
}
break;
}
case PARENTHESIZED: {
ParenthesizedTree parenthesized = (ParenthesizedTree) actualNode;
next = parenthesized.getExpression();
break;
}
case RETURN: {
ReturnTree returnn = (ReturnTree) actualNode;
next = returnn.getExpression();
break;
}
case SWITCH: {
SwitchTree zwitch = (SwitchTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
next = zwitch.getExpression();
} else {
int arg = astNode.getArgument();
List<? extends CaseTree> cases = zwitch.getCases();
if (arg >= cases.size()) {
return false;
}
next = cases.get(arg);
}
break;
}
case SYNCHRONIZED: {
SynchronizedTree synchronizzed = (SynchronizedTree) actualNode;
if (astNode.childSelectorIs(ASTPath.EXPRESSION)) {
next = synchronizzed.getExpression();
} else {
next = synchronizzed.getBlock();
}
break;
}
case THROW: {
ThrowTree throww = (ThrowTree) actualNode;
next = throww.getExpression();
break;
}
case TRY: {
TryTree tryy = (TryTree) actualNode;
if (astNode.childSelectorIs(ASTPath.BLOCK)) {
next = tryy.getBlock();
} else if (astNode.childSelectorIs(ASTPath.CATCH)) {
int arg = astNode.getArgument();
List<? extends CatchTree> catches = tryy.getCatches();
if (arg >= catches.size()) {
return false;
}
next = catches.get(arg);
} else if (astNode.childSelectorIs(ASTPath.FINALLY_BLOCK)) {
next = tryy.getFinallyBlock();
} else {
int arg = astNode.getArgument();
List<? extends Tree> resources = tryy.getResources();
if (arg >= resources.size()) {
return false;
}
next = resources.get(arg);
}
break;
}
case TYPE_CAST: {
TypeCastTree typeCast = (TypeCastTree) actualNode;
if (astNode.childSelectorIs(ASTPath.TYPE)) {
next = typeCast.getType();
} else {
next = typeCast.getExpression();
}
break;
}
case UNION_TYPE: {
UnionTypeTree unionType = (UnionTypeTree) actualNode;
int arg = astNode.getArgument();
List<? extends Tree> typeAlts = unionType.getTypeAlternatives();
if (arg >= typeAlts.size()) {
return false;
}
next = typeAlts.get(arg);
break;
}
case VARIABLE: {
VariableTree var = (VariableTree) actualNode;
if (astNode.childSelectorIs(ASTPath.INITIALIZER)) {
next = var.getInitializer();
} else {
next = var.getType();
}
break;
}
case WHILE_LOOP: {
WhileLoopTree whileLoop = (WhileLoopTree) actualNode;
if (astNode.childSelectorIs(ASTPath.CONDITION)) {
next = whileLoop.getCondition();
} else {
next = whileLoop.getStatement();
}
break;
}
default: {
if (isBinaryOperator(actualNode.getKind())) {
BinaryTree binary = (BinaryTree) actualNode;
if (astNode.childSelectorIs(ASTPath.LEFT_OPERAND)) {
next = binary.getLeftOperand();
} else {
next = binary.getRightOperand();
}
} else if (isCompoundAssignment(actualNode.getKind())) {
CompoundAssignmentTree compoundAssignment =
(CompoundAssignmentTree) actualNode;
if (astNode.childSelectorIs(ASTPath.VARIABLE)) {
next = compoundAssignment.getVariable();
} else {
next = compoundAssignment.getExpression();
}
} else if (isUnaryOperator(actualNode.getKind())) {
UnaryTree unary = (UnaryTree) actualNode;
next = unary.getExpression();
} else if (isWildcard(actualNode.getKind())) {
WildcardTree wildcard = (WildcardTree) actualNode;
// 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<?>.
if (i > 0) { // TODO: refactor GenericArrayLoc to use same code?
Tree ancestor = actualPath.get(i-1);
if (ancestor.getKind() == Tree.Kind.INSTANCE_OF) {
System.err.println("WARNING: wildcard bounds not allowed"
+ " in 'instanceof' expression; skipping insertion");
return false;
} else if (i > 1 && ancestor.getKind() ==
Tree.Kind.PARAMETERIZED_TYPE) {
ancestor = actualPath.get(i-2);
if (ancestor.getKind() == Tree.Kind.ARRAY_TYPE) {
System.err.println("WARNING: wildcard bounds not allowed"
+ " in generic array type; skipping insertion");
return false;
}
}
}
next = wildcard.getBound();
} else {
throw new IllegalArgumentException("Illegal kind: "
+ actualNode.getKind());
}
break;
}
}
dbug.debug("next: %s%n", next);
if (next != actualPath.get(i + 1)) {
dbug.debug("no next match%n");
return false;
}
}
return true;
}
/**
* 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 static boolean kindsMatch(Tree.Kind kind1, Tree.Kind kind2) {
return kind1 == kind2
|| (isCompoundAssignment(kind1) && isCompoundAssignment(kind2))
|| (isUnaryOperator(kind1) && isUnaryOperator(kind2))
|| (isBinaryOperator(kind1) && isBinaryOperator(kind2))
|| (isWildcard(kind1) && isWildcard(kind2));
}
/**
* Determines if the given kind is a compound assignment.
*
* @param kind
* the kind to test
* @return true if the given kind is a compound assignment
*/
private static boolean isCompoundAssignment(Tree.Kind kind) {
return kind == Tree.Kind.PLUS_ASSIGNMENT
|| kind == Tree.Kind.MINUS_ASSIGNMENT
|| kind == Tree.Kind.MULTIPLY_ASSIGNMENT
|| kind == Tree.Kind.DIVIDE_ASSIGNMENT
|| kind == Tree.Kind.OR_ASSIGNMENT
|| kind == Tree.Kind.AND_ASSIGNMENT
|| kind == Tree.Kind.REMAINDER_ASSIGNMENT
|| kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT
|| kind == Tree.Kind.RIGHT_SHIFT
|| kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT
|| kind == Tree.Kind.XOR_ASSIGNMENT;
}
/**
* Determines if the given kind is a unary operator.
*
* @param kind
* the kind to test
* @return true if the given kind is a unary operator
*/
private static boolean isUnaryOperator(Tree.Kind kind) {
return kind == Tree.Kind.POSTFIX_INCREMENT
|| kind == Tree.Kind.POSTFIX_DECREMENT
|| kind == Tree.Kind.PREFIX_INCREMENT
|| kind == Tree.Kind.PREFIX_DECREMENT
|| kind == Tree.Kind.UNARY_PLUS
|| kind == Tree.Kind.UNARY_MINUS
|| kind == Tree.Kind.BITWISE_COMPLEMENT
|| kind == Tree.Kind.LOGICAL_COMPLEMENT;
}
/**
* 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
*/
private static 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;
}
/**
* Determines if the given kind is a wildcard.
*
* @param kind
* the kind to test
* @return true if the given kind is a wildcard
*/
private static boolean isWildcard(Tree.Kind kind) {
return kind == Tree.Kind.UNBOUNDED_WILDCARD
|| kind == Tree.Kind.EXTENDS_WILDCARD
|| kind == Tree.Kind.SUPER_WILDCARD;
}
}
public static boolean isTypeSelector(String selector) {
return Arrays.<String>binarySearch(typeSelectors,
selector, Collator.getInstance()) >= 0;
}
public static boolean isClassEquiv(Tree.Kind kind) {
switch (kind) {
case CLASS:
case INTERFACE:
case ENUM:
case ANNOTATION_TYPE:
return true;
default:
return false;
}
}
/**
* Determines if the given kind is a compound assignment.
*
* @param kind
* the kind to test
* @return true if the given kind is a compound assignment
*/
public static boolean isCompoundAssignment(Tree.Kind kind) {
return kind == Tree.Kind.PLUS_ASSIGNMENT
|| kind == Tree.Kind.MINUS_ASSIGNMENT
|| kind == Tree.Kind.MULTIPLY_ASSIGNMENT
|| kind == Tree.Kind.DIVIDE_ASSIGNMENT
|| kind == Tree.Kind.OR_ASSIGNMENT
|| kind == Tree.Kind.AND_ASSIGNMENT
|| kind == Tree.Kind.REMAINDER_ASSIGNMENT
|| kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT
|| kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT
|| kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT
|| kind == Tree.Kind.XOR_ASSIGNMENT;
}
/**
* Determines if the given kind is a unary operator.
*
* @param kind
* the kind to test
* @return true if the given kind is a unary operator
*/
public static boolean isUnaryOperator(Tree.Kind kind) {
return kind == Tree.Kind.POSTFIX_INCREMENT
|| kind == Tree.Kind.POSTFIX_DECREMENT
|| kind == Tree.Kind.PREFIX_INCREMENT
|| kind == Tree.Kind.PREFIX_DECREMENT
|| kind == Tree.Kind.UNARY_PLUS
|| kind == Tree.Kind.UNARY_MINUS
|| kind == Tree.Kind.BITWISE_COMPLEMENT
|| kind == Tree.Kind.LOGICAL_COMPLEMENT;
}
/**
* 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 static 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 static boolean isLiteral(Tree.Kind kind) {
switch (kind) {
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;
}
}
public static 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:
return true;
default:
return isUnaryOperator(kind) || isBinaryOperator(kind)
|| isCompoundAssignment(kind) || isLiteral(kind);
}
}
public static boolean isTypeKind(Tree.Kind kind) {
switch (kind) {
case ANNOTATED_TYPE:
case ARRAY_TYPE:
case IDENTIFIER:
case INTERSECTION_TYPE:
// case MEMBER_SELECT:
case PARAMETERIZED_TYPE:
case PRIMITIVE_TYPE:
case UNION_TYPE:
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
*/
public static boolean isWildcard(Tree.Kind kind) {
return kind == Tree.Kind.UNBOUNDED_WILDCARD
|| kind == Tree.Kind.EXTENDS_WILDCARD
|| kind == Tree.Kind.SUPER_WILDCARD;
}
/**
* Determines if the given kind is a declaration.
*
* @param kind
* the kind to test
* @return true if the given kind is a declaration
*/
public static boolean isDeclaration(Tree.Kind kind) {
return kind == Tree.Kind.ANNOTATION
|| kind == Tree.Kind.CLASS
|| kind == Tree.Kind.ENUM
|| kind == Tree.Kind.INTERFACE
|| kind == Tree.Kind.METHOD
|| kind == Tree.Kind.VARIABLE;
}
/**
* Determines whether an {@code ASTPath} can identify nodes of the
* given kind.
*
* @param kind
* the kind to test
* @return true if the given kind can be identified by an {@code ASTPath}.
*/
public static boolean isHandled(Tree.Kind kind) {
switch (kind) {
case BREAK:
case COMPILATION_UNIT:
case CONTINUE:
case IMPORT:
case MODIFIERS:
return false;
default:
return !isDeclaration(kind);
}
} // TODO: need "isType"?
}
/**
*
* @author dbro
*
* @param <E> type of stack elements
*/
class ConsStack<E> implements PersistentStack<E> {
private int size;
private E elem;
private PersistentStack<E> rest;
public ConsStack() {
size = 0;
elem = null;
rest = null;
}
private static <T, S extends ConsStack<T>> S extend(T el, S s0) {
try {
@SuppressWarnings("unchecked")
S s1 = (S) s0.getClass().newInstance();
ConsStack<T> cs = (ConsStack<T>) s1;
cs.size = 1 + s0.size();
cs.elem = el;
cs.rest = s0;
return s1;
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isEmpty() { return size == 0; }
@Override
public E peek() {
if (size > 0) { return elem; }
throw new IllegalStateException("peek() on empty stack");
}
@Override
public PersistentStack<E> pop() {
if (size > 0) { return rest; }
throw new IllegalStateException("pop() on empty stack");
}
@Override
public PersistentStack<E> push(E elem) {
return extend(elem, this);
}
@Override
public int size() { return size; }
@Override
public String toString() {
if (size > 0) {
StringBuilder sb = new StringBuilder("]").insert(0, peek());
for (PersistentStack<E> stack = pop(); !stack.isEmpty();
stack = stack.pop()) {
sb = sb.insert(0, ", ").insert(0, stack.peek());
}
return sb.insert(0, "[").toString();
}
return "[]";
}
}