package sk.stuba.fiit.perconik.core.java.dom;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.base.Predicate;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import sk.stuba.fiit.perconik.core.java.dom.compatibility.TreeCompatibility;
import sk.stuba.fiit.perconik.eclipse.jdt.core.dom.NodeType;
import sk.stuba.fiit.perconik.eclipse.jdt.core.dom.TreeApiLevel;
import static com.google.common.collect.Lists.newLinkedList;
// TODO add more helpers from org.eclipse.jdt.internal.corext.dom.ASTNodes
public final class Nodes {
private Nodes() {}
public static AST newTree() {
return newTree(TreeApiLevel.latest());
}
public static AST newTree(final TreeApiLevel level) {
return TreeCompatibility.getTreeFactory(level).newTree();
}
public static <N extends ASTNode> N newNode(final AST tree, final Class<N> type) {
return TreeCompatibility.getNodeFactory().newNode(tree, type);
}
public static ASTNode create(final ASTParser parser, final byte source[], final Charset charset) {
return create(parser, new String(source, charset));
}
public static ASTNode create(final ASTParser parser, final char[] source) {
parser.setSource(source);
return parser.createAST(null);
}
public static ASTNode create(final ASTParser parser, final CharSequence source) {
return create(parser, source.toString().toCharArray());
}
public static ASTNode create(final ASTParser parser, final ICompilationUnit source) {
return create(parser, source, false);
}
public static ASTNode create(final ASTParser parser, final ICompilationUnit source, final boolean resolveBindings) {
parser.setSource(source);
parser.setResolveBindings(resolveBindings);
return parser.createAST(null);
}
// TODO consider
// public static ASTNode create(final ASTParser parser, final IClassFile source)
// {
// return create(parser, source.toString().toCharArray());
// }
public static <N extends ASTNode> N copyOf(final N node) {
if (node == null) {
return null;
}
return copyOf(node, TreeApiLevel.valueOf(node.getAST().apiLevel()));
}
public static <N extends ASTNode> N copyOf(final N node, final TreeApiLevel level) {
return copyOf(node, newTree(level));
}
public static <N extends ASTNode> N copyOf(final N node, final AST tree) {
@SuppressWarnings("unchecked")
N result = (N) ASTNode.copySubtree(tree, node);
return result;
}
public static ASTNode root(@Nullable final ASTNode node) {
return node != null ? node.getRoot() : null;
}
public static ASTNode parent(@Nullable final ASTNode node) {
return node != null ? node.getParent() : null;
}
public static LinkedList<ASTNode> children(@Nullable final ASTNode node) {
final LinkedList<ASTNode> children = newLinkedList();
final ASTVisitor visitor = new ASTVisitor(true) {
@Override
public boolean preVisit2(final ASTNode child) {
if (isChild(node, child)) {
children.add(child);
return false;
}
return true;
}
};
if (node != null) {
node.accept(visitor);
}
return children;
}
public static LinkedList<ASTNode> ancestors(@Nullable ASTNode node) {
final LinkedList<ASTNode> ancestors = newLinkedList();
while (node != null) {
ancestors.add(node = node.getParent());
}
return ancestors;
}
public static LinkedList<ASTNode> descendants(@Nullable final ASTNode node) {
final LinkedList<ASTNode> descendants = newLinkedList();
if (node == null) {
return descendants;
}
final ASTVisitor visitor = new ASTVisitor(true) {
@Override
public void preVisit(final ASTNode descendant) {
descendants.add(descendant);
}
};
node.accept(visitor);
return descendants;
}
public static LinkedList<ASTNode> downToRoot(@Nullable ASTNode node) {
LinkedList<ASTNode> branch = newLinkedList();
if (node != null) {
do {
branch.add(node);
} while ((node = node.getParent()) != null);
}
return branch;
}
public static LinkedList<ASTNode> upToLeaves(@Nullable final ASTNode node) {
LinkedList<ASTNode> branch = descendants(node);
if (node != null) {
branch.addFirst(node);
}
return branch;
}
public static ASTNode firstDownToRoot(@Nullable ASTNode node, final Predicate<ASTNode> predicate) {
if (node != null) {
do {
if (predicate.apply(node)) {
return node;
}
} while ((node = node.getParent()) != null);
}
return null;
}
public static ASTNode firstUpToLeaves(@Nullable final ASTNode node, final Predicate<ASTNode> predicate) {
for (ASTNode other: upToLeaves(node)) {
if (predicate.apply(other)) {
return node;
}
}
return null;
}
public static ASTNode firstAncestor(@Nullable ASTNode node, final Predicate<ASTNode> predicate) {
while (node != null) {
if (predicate.apply(node = node.getParent())) {
return node;
}
}
return null;
}
public static ASTNode firstDescendant(@Nullable final ASTNode node, final Predicate<ASTNode> predicate) {
final MutableReference<ASTNode> descendant = new MutableReference<>();
final ASTVisitor visitor = new ASTVisitor(true) {
@Override
public boolean preVisit2(final ASTNode other) {
if (predicate.apply(other)) {
descendant.value = other;
}
return descendant.value == null;
}
};
if (node != null) {
node.accept(visitor);
}
return descendant.value;
}
public static Map<String, Object> genericProperties(final ASTNode node) {
return node.properties();
}
public static List<StructuralPropertyDescriptor> structuralProperties(final ASTNode node) {
return node.structuralPropertiesForType();
}
public static String source(final ASTNode node, final NodeRangeType range) {
ASTNode root = node.getRoot();
if (root instanceof CompilationUnit) {
CompilationUnit unit = (CompilationUnit) root;
ITypeRoot type = unit.getTypeRoot();
try {
if (type != null && type.getBuffer() != null) {
int offset = range.getOffset(unit, node);
int length = range.getLength(unit, node);
return type.getBuffer().getText(offset, length);
}
} catch (JavaModelException e) {
return null;
}
}
return null;
}
public static boolean hasSource(final ASTNode node) {
return node.getStartPosition() != -1;
}
public static boolean isRoot(@Nullable final ASTNode node) {
return node != null && node == node.getRoot();
}
public static boolean isParent(@Nullable final ASTNode node, @Nullable final ASTNode parent) {
return node != null && parent == node.getParent();
}
public static boolean isChild(@Nullable final ASTNode node, @Nullable final ASTNode child) {
return child != null && child.getParent() == node;
}
public static boolean isAncestor(@Nullable ASTNode node, @Nullable final ASTNode ancestor) {
while (node != null) {
if (ancestor == (node = node.getParent())) {
return true;
}
}
return false;
}
public static boolean isDescendant(@Nullable final ASTNode node, @Nullable final ASTNode descendant) {
if (node == null) {
return false;
}
final MutableBoolean visit = new MutableBoolean(true);
final ASTVisitor visitor = new ASTVisitor(true) {
@Override
public boolean preVisit2(final ASTNode other) {
if (other == descendant) {
visit.value = false;
}
return visit.value;
}
};
node.accept(visitor);
return !visit.value;
}
public static boolean isProblematic(final ASTNode node) {
int flags = node.getFlags();
return (flags & ASTNode.RECOVERED) != 0 || (flags & ASTNode.MALFORMED) != 0;
}
public static boolean isProblematicTree(final ASTNode node) {
if (isProblematic(node)) {
return true;
}
for (ASTNode descendant: descendants(node)) {
if (isProblematic(descendant)) {
return true;
}
}
return false;
}
static class MutableReference<T> {
T value;
MutableReference() {}
MutableReference(final T value) {
this.value = value;
}
}
static class MutableBoolean {
boolean value;
MutableBoolean(final boolean value) {
this.value = value;
}
}
public static Class<? extends ASTNode> toClass(@Nullable final ASTNode node) {
return node != null ? node.getClass() : null;
}
public static NodeType toType(@Nullable final ASTNode node) {
return node != null ? NodeType.valueOf(node) : null;
}
public static String toTypeString(@Nullable final ASTNode node) {
return node != null ? NodeType.valueOf(node).getName() : null;
}
}