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