| package org.checkerframework.framework.util; |
| |
| import com.sun.source.tree.BlockTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.IfTree; |
| import com.sun.source.tree.ParenthesizedTree; |
| import com.sun.source.tree.StatementTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.UnaryTree; |
| import com.sun.source.util.SimpleTreeVisitor; |
| import com.sun.source.util.TreePath; |
| import java.util.ArrayDeque; |
| import java.util.Arrays; |
| import org.checkerframework.javacutil.TreePathUtil; |
| import org.checkerframework.javacutil.TreeUtils; |
| |
| /** |
| * Utilities for determining tree-based heuristics. |
| * |
| * <p>For an example, see {@code org.checkerframework.checker.interning.InterningVisitor}. |
| */ |
| public class Heuristics { |
| |
| /** |
| * Determines whether a tree has a particular set of direct parents, ignoring blocks and |
| * parentheses. |
| * |
| * <p>For example, to test whether an expression (specified by {@code path}) is immediately |
| * contained by an if statement which is immediately contained in a method, one would invoke: |
| * |
| * <pre> |
| * matchParents(path, Kind.IF, Kind.METHOD) |
| * </pre> |
| * |
| * @param path the path to match |
| * @param kinds the tree kinds to match against, in ascending order starting from the desired kind |
| * of the parent |
| * @return true if the tree path matches the desired kinds, skipping blocks and parentheses, for |
| * as many kinds as specified |
| */ |
| public static boolean matchParents(TreePath path, Tree.Kind... kinds) { |
| |
| TreePath parentPath = path.getParentPath(); |
| boolean result = true; |
| |
| ArrayDeque<Tree.Kind> queue = new ArrayDeque<>(Arrays.asList(kinds)); |
| |
| Tree tree; |
| while ((tree = parentPath.getLeaf()) != null) { |
| |
| if (queue.isEmpty()) { |
| break; |
| } |
| |
| if (tree.getKind() == Tree.Kind.BLOCK || tree.getKind() == Tree.Kind.PARENTHESIZED) { |
| parentPath = parentPath.getParentPath(); |
| continue; |
| } |
| |
| result &= queue.removeFirst() == parentPath.getLeaf().getKind(); |
| parentPath = parentPath.getParentPath(); |
| } |
| |
| return result; |
| } |
| |
| /** A base class for tree-matching algorithms. Skips parentheses by default. */ |
| public static class Matcher extends SimpleTreeVisitor<Boolean, Void> { |
| |
| @Override |
| protected Boolean defaultAction(Tree node, Void p) { |
| return false; |
| } |
| |
| @Override |
| public Boolean visitParenthesized(ParenthesizedTree node, Void p) { |
| return visit(node.getExpression(), p); |
| } |
| |
| /** |
| * Returns true if the given path matches this Matcher. |
| * |
| * @param path the path to test |
| * @return true if the given path matches this Matcher |
| */ |
| public boolean match(TreePath path) { |
| return visit(path.getLeaf(), null); |
| } |
| } |
| |
| public static class PreceededBy extends Matcher { |
| private final Matcher matcher; |
| |
| public PreceededBy(Matcher matcher) { |
| this.matcher = matcher; |
| } |
| |
| @SuppressWarnings("interning:not.interned") |
| @Override |
| public boolean match(TreePath path) { |
| StatementTree stmt = TreePathUtil.enclosingOfClass(path, StatementTree.class); |
| if (stmt == null) { |
| return false; |
| } |
| TreePath p = path; |
| while (p.getLeaf() != stmt) { |
| p = p.getParentPath(); |
| } |
| assert p.getLeaf() == stmt; |
| |
| while (p != null && p.getLeaf() instanceof StatementTree) { |
| if (p.getParentPath().getLeaf() instanceof BlockTree) { |
| BlockTree block = (BlockTree) p.getParentPath().getLeaf(); |
| for (StatementTree st : block.getStatements()) { |
| if (st == p.getLeaf()) { |
| break; |
| } |
| |
| if (matcher.match(new TreePath(p, st))) { |
| return true; |
| } |
| } |
| } |
| p = p.getParentPath(); |
| } |
| |
| return false; |
| } |
| } |
| |
| /** |
| * {@code match()} returns true if called on a path, any element of which matches the given |
| * matcher (supplied at object initialization). That matcher is usually one that matches only the |
| * leaf of a path, ignoring all other parts of it. |
| */ |
| public static class Within extends Matcher { |
| /** The matcher that {@code Within.match} will try, on every parent of the path it is given. */ |
| private final Matcher matcher; |
| |
| /** |
| * Create a new Within matcher. |
| * |
| * @param matcher the matcher that {@code Within.match} will try, on every parent of the path it |
| * is given |
| */ |
| public Within(Matcher matcher) { |
| this.matcher = matcher; |
| } |
| |
| @Override |
| public boolean match(TreePath path) { |
| TreePath p = path; |
| while (p != null) { |
| if (matcher.match(p)) { |
| return true; |
| } |
| p = p.getParentPath(); |
| } |
| |
| return false; |
| } |
| } |
| |
| /** |
| * {@code match()} returns true if called on a path whose leaf is within the "then" clause of an |
| * if whose conditon matches the matcher (supplied at object initialization). Also returns true if |
| * the leaf is within the "else" of a negated condition that matches the supplied matcher. |
| */ |
| public static class WithinTrueBranch extends Matcher { |
| private final Matcher matcher; |
| /** @param conditionMatcher for the condition */ |
| public WithinTrueBranch(Matcher conditionMatcher) { |
| this.matcher = conditionMatcher; |
| } |
| |
| @SuppressWarnings("interning:not.interned") |
| @Override |
| public boolean match(TreePath path) { |
| TreePath prev = path, p = path.getParentPath(); |
| while (p != null) { |
| if (p.getLeaf().getKind() == Tree.Kind.IF) { |
| IfTree ifTree = (IfTree) p.getLeaf(); |
| ExpressionTree cond = TreeUtils.withoutParens(ifTree.getCondition()); |
| if (ifTree.getThenStatement() == prev.getLeaf() && matcher.match(new TreePath(p, cond))) { |
| return true; |
| } |
| if (cond.getKind() == Tree.Kind.LOGICAL_COMPLEMENT |
| && matcher.match(new TreePath(p, ((UnaryTree) cond).getExpression()))) { |
| return true; |
| } |
| } |
| prev = p; |
| p = p.getParentPath(); |
| } |
| |
| return false; |
| } |
| } |
| |
| /** |
| * {@code match()} returns true if called on a path whose leaf has the given kind (supplied at |
| * object initialization). |
| */ |
| public static class OfKind extends Matcher { |
| private final Tree.Kind kind; |
| private final Matcher matcher; |
| |
| public OfKind(Tree.Kind kind, Matcher matcher) { |
| this.kind = kind; |
| this.matcher = matcher; |
| } |
| |
| @Override |
| public boolean match(TreePath path) { |
| if (path.getLeaf().getKind() == kind) { |
| return matcher.match(path); |
| } |
| return false; |
| } |
| } |
| |
| /** {@code match()} returns true if any of the given matchers returns true. */ |
| public static class OrMatcher extends Matcher { |
| private final Matcher[] matchers; |
| |
| public OrMatcher(Matcher... matchers) { |
| this.matchers = matchers; |
| } |
| |
| @Override |
| public boolean match(TreePath path) { |
| for (Matcher matcher : matchers) { |
| if (matcher.match(path)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| } |