/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.j2objc.ast;
import com.google.common.base.Predicate;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.TypeUtil;
import java.io.File;
import java.util.AbstractList;
import java.util.Iterator;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
/**
* Collection of utility methods for examining tree nodes.
*/
public class TreeUtil {
public static <T extends TreeNode> T remove(T node) {
if (node == null) {
return null;
}
node.remove();
return node;
}
public static <T extends TreeNode> List<T> copyList(List<T> originalList) {
List<T> newList = Lists.newArrayListWithCapacity(originalList.size());
copyList(originalList, newList);
return newList;
}
@SuppressWarnings("unchecked")
public static <T extends TreeNode> void copyList(List<T> fromList, List<T> toList) {
for (T elem : fromList) {
toList.add((T) elem.copy());
}
}
/**
* Moves nodes from one list to another, ensuring that they are not
* double-parented in the process.
*/
public static <T> void moveList(List<T> fromList, List<T> toList) {
for (Iterator<T> iter = fromList.iterator(); iter.hasNext(); ) {
T elem = iter.next();
iter.remove();
toList.add(elem);
}
}
private static final Predicate<Annotation> IS_RUNTIME_PREDICATE = new Predicate<Annotation>() {
@Override
public boolean apply(Annotation annotation) {
return ElementUtil.isRuntimeAnnotation(annotation.getAnnotationMirror());
}
};
public static Iterable<Annotation> getRuntimeAnnotations(Iterable<Annotation> annotations) {
return Iterables.filter(annotations, IS_RUNTIME_PREDICATE);
}
public static List<Annotation> getRuntimeAnnotationsList(Iterable<Annotation> annotations) {
return Lists.newArrayList(getRuntimeAnnotations(annotations));
}
public static boolean hasAnnotation(Class<?> annotationClass, List<Annotation> annotations) {
return getAnnotation(annotationClass, annotations) != null;
}
public static Annotation getAnnotation(Class<?> annotationClass, List<Annotation> annotations) {
for (Annotation annotation : annotations) {
TypeMirror annotationType = annotation.getAnnotationMirror().getAnnotationType();
if (TypeUtil.getQualifiedName(annotationType).equals(annotationClass.getName())) {
return annotation;
}
}
return null;
}
public static <T> T getNearestAncestorWithType(Class<T> type, TreeNode node) {
while (node != null) {
if (type.isInstance(node)) {
return type.cast(node);
}
node = node.getParent();
}
return null;
}
public static TreeNode getNearestAncestorWithTypeOneOf(List<Class<?>> types, TreeNode node) {
while (node != null) {
for (Class<?> c : types) {
if (c.isInstance(node)) {
return node;
}
}
node = node.getParent();
}
return null;
}
/**
* Returns the first descendant of the given node that is not a ParenthesizedExpression.
*/
public static Expression trimParentheses(Expression node) {
while (node instanceof ParenthesizedExpression) {
node = ((ParenthesizedExpression) node).getExpression();
}
return node;
}
public static MethodDeclaration getEnclosingMethod(TreeNode node) {
return getNearestAncestorWithType(MethodDeclaration.class, node);
}
private static final ImmutableList<Class<?>> EXECUTABLE_DECLARATION_TYPES =
ImmutableList.of(MethodDeclaration.class, FunctionDeclaration.class);
public static TypeMirror getOwningReturnType(TreeNode node) {
TreeNode enclosingNode = getNearestAncestorWithTypeOneOf(EXECUTABLE_DECLARATION_TYPES, node);
if (enclosingNode instanceof MethodDeclaration) {
return ((MethodDeclaration) enclosingNode).getExecutableElement().getReturnType();
} else if (enclosingNode instanceof FunctionDeclaration) {
return ((FunctionDeclaration) enclosingNode).getReturnType().getTypeMirror();
}
return null;
}
/**
* Returns the statement which is the parent of the specified node.
*/
public static Statement getOwningStatement(TreeNode node) {
return getNearestAncestorWithType(Statement.class, node);
}
/**
* Gets the CompilationUnit ancestor of this node.
*/
public static CompilationUnit getCompilationUnit(TreeNode node) {
return getNearestAncestorWithType(CompilationUnit.class, node);
}
public static Iterable<FieldDeclaration> getFieldDeclarations(AbstractTypeDeclaration node) {
return Iterables.filter(node.getBodyDeclarations(), FieldDeclaration.class);
}
public static List<FieldDeclaration> getFieldDeclarationsList(AbstractTypeDeclaration node) {
return Lists.newArrayList(getFieldDeclarations(node));
}
public static Iterable<VariableDeclarationFragment> getAllFields(AbstractTypeDeclaration node) {
return asFragments(getFieldDeclarations(node));
}
public static Iterable<VariableDeclarationFragment> asFragments(
final Iterable<FieldDeclaration> fieldDecls) {
return new Iterable<VariableDeclarationFragment>() {
@Override
public Iterator<VariableDeclarationFragment> iterator() {
final Iterator<FieldDeclaration> fieldIter = fieldDecls.iterator();
return new AbstractIterator<VariableDeclarationFragment>() {
private Iterator<VariableDeclarationFragment> fragIter;
@Override protected VariableDeclarationFragment computeNext() {
do {
if (fragIter != null && fragIter.hasNext()) {
return fragIter.next();
}
if (fieldIter.hasNext()) {
fragIter = fieldIter.next().getFragments().iterator();
}
} while (fieldIter.hasNext() || (fragIter != null && fragIter.hasNext()));
return endOfData();
}
};
}
};
}
public static Iterable<MethodDeclaration> getMethodDeclarations(AbstractTypeDeclaration node) {
return getMethodDeclarations(node.getBodyDeclarations());
}
private static Iterable<MethodDeclaration> getMethodDeclarations(List<BodyDeclaration> nodes) {
return Iterables.filter(nodes, MethodDeclaration.class);
}
public static List<MethodDeclaration> getMethodDeclarationsList(AbstractTypeDeclaration node) {
return Lists.newArrayList(getMethodDeclarations(node));
}
public static Iterable<FunctionDeclaration> getFunctionDeclarations(
AbstractTypeDeclaration node) {
return Iterables.filter(node.getBodyDeclarations(), FunctionDeclaration.class);
}
public static List<BodyDeclaration> asDeclarationSublist(BodyDeclaration node) {
List<BodyDeclaration> declarations =
((AbstractTypeDeclaration) node.getParent()).getBodyDeclarations();
int index = declarations.indexOf(node);
assert index != -1;
return declarations.subList(index, index + 1);
}
/**
* Gets the element that is declared by this node.
*/
public static Element getDeclaredElement(TreeNode node) {
if (node instanceof AbstractTypeDeclaration) {
return ((AbstractTypeDeclaration) node).getTypeElement();
} else if (node instanceof MethodDeclaration) {
return ((MethodDeclaration) node).getExecutableElement();
} else if (node instanceof VariableDeclaration) {
return ((VariableDeclaration) node).getVariableElement();
}
return null;
}
/**
* Gets a variable element for the given expression if the expression
* represents a variable. Returns null otherwise.
*/
public static VariableElement getVariableElement(Expression node) {
node = trimParentheses(node);
switch (node.getKind()) {
case FIELD_ACCESS:
return ((FieldAccess) node).getVariableElement();
case SUPER_FIELD_ACCESS:
return ((SuperFieldAccess) node).getVariableElement();
case QUALIFIED_NAME:
case SIMPLE_NAME:
return getVariableElement((Name) node);
default:
return null;
}
}
public static VariableElement getVariableElement(Name node) {
Element element = node.getElement();
return element != null && ElementUtil.isVariable(element) ? (VariableElement) element : null;
}
public static ExecutableElement getExecutableElement(Expression node) {
switch (node.getKind()) {
case CLASS_INSTANCE_CREATION:
return ((ClassInstanceCreation) node).getExecutableElement();
case METHOD_INVOCATION:
return ((MethodInvocation) node).getExecutableElement();
case SUPER_METHOD_INVOCATION:
return ((SuperMethodInvocation) node).getExecutableElement();
default:
return null;
}
}
public static AbstractTypeDeclaration getEnclosingType(TreeNode node) {
return getNearestAncestorWithType(AbstractTypeDeclaration.class, node);
}
public static TypeElement getEnclosingTypeElement(TreeNode node) {
return getEnclosingType(node).getTypeElement();
}
public static List<BodyDeclaration> getEnclosingTypeBodyDeclarations(TreeNode node) {
return getEnclosingType(node).getBodyDeclarations();
}
private static final ImmutableList<Class<?>> NODE_TYPES_WITH_ELEMENTS =
ImmutableList.of(
AbstractTypeDeclaration.class, MethodDeclaration.class, VariableDeclaration.class);
public static Element getEnclosingElement(TreeNode node) {
return getDeclaredElement(getNearestAncestorWithTypeOneOf(NODE_TYPES_WITH_ELEMENTS, node));
}
/**
* Gets the fully qualified name of the main type in this compilation unit.
*/
public static String getQualifiedMainTypeName(CompilationUnit unit) {
PackageDeclaration pkg = unit.getPackage();
if (pkg.isDefaultPackage()) {
return unit.getMainTypeName();
} else {
return pkg.getName().getFullyQualifiedName() + '.' + unit.getMainTypeName();
}
}
/**
* Gets the relative file path of the source java file for this compilation
* unit.
*/
public static String getSourceFileName(CompilationUnit unit) {
return getQualifiedMainTypeName(unit).replace('.', File.separatorChar) + ".java";
}
/**
* Returns the given statement as a list of statements that can be added to.
* If node is a Block, then returns it's statement list. If node is the direct
* child of a Block, returns the sublist containing node as the only element.
* Otherwise, creates a new Block node in the place of node and returns its
* list of statements.
*/
public static List<Statement> asStatementList(Statement node) {
if (node instanceof Block) {
return ((Block) node).getStatements();
}
TreeNode parent = node.getParent();
if (parent instanceof Block) {
List<Statement> stmts = ((Block) parent).getStatements();
for (int i = 0; i < stmts.size(); i++) {
if (stmts.get(i) == node) {
return stmts.subList(i, i + 1);
}
}
}
return new LonelyStatementList(node);
}
/**
* This list wraps a single statement, and inserts a block node in its place
* upon adding additional nodes.
*/
private static class LonelyStatementList extends AbstractList<Statement> {
private final Statement lonelyStatement;
private List<Statement> delegate = null;
public LonelyStatementList(Statement stmt) {
lonelyStatement = stmt;
}
private List<Statement> getDelegate() {
if (delegate == null) {
Block block = new Block();
lonelyStatement.replaceWith(block);
delegate = block.getStatements();
delegate.add(lonelyStatement);
}
return delegate;
}
@Override
public Statement get(int idx) {
if (delegate != null) {
return delegate.get(idx);
}
if (idx != 0) {
throw new IndexOutOfBoundsException();
}
return lonelyStatement;
}
@Override
public int size() {
if (delegate != null) {
return delegate.size();
}
return 1;
}
@Override
public void add(int idx, Statement stmt) {
getDelegate().add(idx, stmt);
}
}
public static void insertAfter(Statement node, Statement toInsert) {
asStatementList(node).add(toInsert);
}
public static void insertBefore(Statement node, Statement toInsert) {
asStatementList(node).add(0, toInsert);
}
public static Expression newLiteral(Object value, TypeUtil typeUtil) {
if (value instanceof Boolean) {
return new BooleanLiteral((Boolean) value, typeUtil);
} else if (value instanceof Character) {
return new CharacterLiteral((Character) value, typeUtil);
} else if (value instanceof Number) {
return new NumberLiteral((Number) value, typeUtil).setToken(value.toString());
} else if (value instanceof String) {
return new StringLiteral((String) value, typeUtil);
}
throw new AssertionError("unknown constant type: " + value.getClass().getName());
}
public static List<AnnotationTypeMemberDeclaration> getAnnotationMembers(
AbstractTypeDeclaration node) {
return Lists.newArrayList(
Iterables.filter(node.getBodyDeclarations(), AnnotationTypeMemberDeclaration.class));
}
}