| 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 "[]"; |
| } |
| } |