package org.checkerframework.framework.util;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import java.util.HashMap;
import java.util.Map;
/**
* TreePathCacher is a TreeScanner that creates and caches a TreePath for a target Tree.
*
* <p>This class replicates some logic from TreePath.getPath but also adds caching to all
* intermediate TreePaths that are generated. The intermediate TreePaths are reused when other
* targets have overlapping paths.
*
* @author mcarthur
*/
public class TreePathCacher extends TreeScanner<TreePath, Tree> {
private final Map<Tree, TreePath> foundPaths = new HashMap<>();
/**
* The TreePath of the previous tree scanned. It is always set back to null after a scan has
* completed.
*/
private TreePath path;
/**
* @param target the tree to search for
* @return true if the tree is cached
*/
public boolean isCached(Tree target) {
return foundPaths.containsKey(target);
}
/**
* Return the TreePath for a Tree.
*
* <p>This method uses try/catch and the Result Error for control flow to stop the superclass
* from scanning other subtrees when target is found.
*
* @param root the compilation unit to search in
* @param target the target tree to look for
* @return the TreePath corresponding to target, or null if target is not found in the
* compilation root
*/
public TreePath getPath(CompilationUnitTree root, Tree target) {
if (foundPaths.containsKey(target)) {
return foundPaths.get(target);
}
TreePath path = new TreePath(root);
if (path.getLeaf() == target) {
return path;
}
try {
this.scan(path, target);
} catch (Result result) {
return result.path;
}
return null;
}
private static class Result extends Error {
private static final long serialVersionUID = 4948452207518392627L;
TreePath path;
Result(TreePath path) {
this.path = path;
}
}
public void clear() {
foundPaths.clear();
}
/** Scan a single node. The current path is updated for the duration of the scan. */
@Override
public TreePath scan(Tree tree, Tree target) {
TreePath prev = path;
if (tree != null && foundPaths.get(tree) == null) {
TreePath current = new TreePath(path, tree);
foundPaths.put(tree, current);
path = current;
} else {
this.path = foundPaths.get(tree);
}
if (tree == target) {
throw new Result(path);
}
try {
return super.scan(tree, target);
} finally {
this.path = prev;
}
}
}