Java tutorial
/* * AutoRefactor - Eclipse plugin to automatically refactor Java code bases. * * Copyright (C) 2013-2016 Jean-Nol Rouvignac - initial API and implementation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program under LICENSE-GNUGPL. If not, see * <http://www.gnu.org/licenses/>. * * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution under LICENSE-ECLIPSE, and is * available at http://www.eclipse.org/legal/epl-v10.html */ package org.autorefactor.refactoring; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.autorefactor.util.IllegalArgumentException; import org.autorefactor.util.IllegalStateException; import org.autorefactor.util.NotImplementedException; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTMatcher; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.ArrayAccess; import org.eclipse.jdt.core.dom.ArrayCreation; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.ArrayType; import org.eclipse.jdt.core.dom.AssertStatement; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BlockComment; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.BreakStatement; import org.eclipse.jdt.core.dom.CastExpression; import org.eclipse.jdt.core.dom.CatchClause; import org.eclipse.jdt.core.dom.CharacterLiteral; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.Comment; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ConditionalExpression; import org.eclipse.jdt.core.dom.ConstructorInvocation; import org.eclipse.jdt.core.dom.ContinueStatement; import org.eclipse.jdt.core.dom.DoStatement; import org.eclipse.jdt.core.dom.EmptyStatement; import org.eclipse.jdt.core.dom.EnhancedForStatement; import org.eclipse.jdt.core.dom.EnumConstantDeclaration; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ForStatement; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IExtendedModifier; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.IfStatement; import org.eclipse.jdt.core.dom.ImportDeclaration; import org.eclipse.jdt.core.dom.InfixExpression; import org.eclipse.jdt.core.dom.Initializer; import org.eclipse.jdt.core.dom.InstanceofExpression; import org.eclipse.jdt.core.dom.Javadoc; import org.eclipse.jdt.core.dom.LabeledStatement; import org.eclipse.jdt.core.dom.LineComment; import org.eclipse.jdt.core.dom.MarkerAnnotation; import org.eclipse.jdt.core.dom.MemberRef; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.MethodRef; import org.eclipse.jdt.core.dom.MethodRefParameter; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.NullLiteral; import org.eclipse.jdt.core.dom.NumberLiteral; import org.eclipse.jdt.core.dom.PackageDeclaration; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.ParenthesizedExpression; import org.eclipse.jdt.core.dom.PostfixExpression; import org.eclipse.jdt.core.dom.PrefixExpression; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.PrimitiveType.Code; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.QualifiedType; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.core.dom.SuperFieldAccess; import org.eclipse.jdt.core.dom.SuperMethodInvocation; import org.eclipse.jdt.core.dom.SwitchCase; import org.eclipse.jdt.core.dom.SwitchStatement; import org.eclipse.jdt.core.dom.SynchronizedStatement; import org.eclipse.jdt.core.dom.TagElement; import org.eclipse.jdt.core.dom.TextElement; import org.eclipse.jdt.core.dom.ThisExpression; import org.eclipse.jdt.core.dom.ThrowStatement; import org.eclipse.jdt.core.dom.TryStatement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeDeclarationStatement; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.TypeParameter; import org.eclipse.jdt.core.dom.UnionType; import org.eclipse.jdt.core.dom.VariableDeclarationExpression; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.WhileStatement; import org.eclipse.jdt.core.dom.WildcardType; import static org.autorefactor.util.Utils.*; import static org.eclipse.jdt.core.dom.ASTNode.*; import static org.eclipse.jdt.core.dom.IBinding.*; /** Helper class for manipulating, converting, navigating and checking {@link ASTNode}s. */ public final class ASTHelper { /** Enum representing a primitive type. */ public static enum PrimitiveEnum { /** The {@code boolean} type. */ BOOLEAN("boolean"), /** The {@code byte} type. */ BYTE("byte"), /** The {@code char} type. */ CHAR("char"), /** The {@code short} type. */ SHORT("short"), /** The {@code int} type. */ INT("int"), /** The {@code long} type. */ LONG("long"), /** The {@code float} type. */ FLOAT("float"), /** The {@code double} type. */ DOUBLE("double"); private final String name; private PrimitiveEnum(String name) { this.name = name; } private static PrimitiveEnum valueOf2(String name) { for (PrimitiveEnum primEnum : values()) { if (primEnum.name.equals(name)) { return primEnum; } } return null; } @Override public String toString() { return name; } } /** Compares {@link ASTNode}s according to their start position. */ public static final class NodeStartPositionComparator implements Comparator<ASTNode> { @Override public int compare(ASTNode o1, ASTNode o2) { return o1.getStartPosition() - o2.getStartPosition(); } } /** * Boolean constant to use when returning from an {@link ASTVisitor} {{visit(*)}} method * to indicate that the visitor does not want to visit a node's subtree. This helps readability. */ public static final boolean DO_NOT_VISIT_SUBTREE = false; /** * Boolean constant to use when returning from an {@link ASTVisitor} {{visit(*)}} method * to indicate that the visitor wants to visit a node's subtree. This helps readability. */ public static final boolean VISIT_SUBTREE = true; private ASTHelper() { super(); } // AST nodes manipulation /** * Returns the same node after removing any parentheses around it. * * @param node the node around which parentheses must be removed * @return the same node after removing any parentheses around it. * If there are no parentheses around it then the exact same node is returned */ public static ASTNode removeParentheses(ASTNode node) { if (node instanceof Expression) { return removeParentheses((Expression) node); } return node; } /** * Returns the same expression after removing any parentheses around it. * * @param expr the expression around which parentheses must be removed * @return the same expression after removing any parentheses around it * If there are no parentheses around it then the exact same expression is returned */ public static Expression removeParentheses(Expression expr) { if (expr.getNodeType() == PARENTHESIZED_EXPRESSION) { return removeParentheses(((ParenthesizedExpression) expr).getExpression()); } return expr; } // AST nodes conversions /** * Returns the provided statement as a non null list of statements: * <ul> * <li>if the statement is null, then an empty list is returned</li> * <li>if the statement is a {@link Block}, then its children are returned</li> * <li>otherwise, the current node is returned wrapped in a list</li> * </ul> * * @param stmt the statement to analyze * @return the provided statement as a non null list of statements */ public static List<Statement> asList(Statement stmt) { if (stmt == null) { return Collections.emptyList(); } else if (stmt instanceof Block) { return statements((Block) stmt); } return Arrays.asList(stmt); } /** * Casts the provided statement to an object of the provided type if type matches. * * @param <T> the required statement type * @param stmt the statement to cast * @param stmtClazz the class representing the required statement type * @return the provided statement as an object of the provided type if type matches, null otherwise */ @SuppressWarnings("unchecked") public static <T extends Statement> T as(Statement stmt, Class<T> stmtClazz) { if (stmt != null) { final List<Statement> stmts = asList(stmt); if (stmts.size() == 1 && stmtClazz.isAssignableFrom(stmts.get(0).getClass())) { return (T) stmts.get(0); } } return null; } /** * Returns whether the provided statement has the provided type. * * @param stmt the statement to test * @param stmtClazz the type to test the statement against * @return {@code true} if the provided statement has the provided type, {@code false} otherwise */ public static boolean is(Statement stmt, Class<? extends Statement> stmtClazz) { return as(stmt, stmtClazz) != null; } /** * Casts the provided expression to an object of the provided type if type matches. * * @param <T> the required expression type * @param expr the expression to cast * @param exprClazz the class representing the required expression type * @return the provided expression as an object of the provided type if type matches, null otherwise */ @SuppressWarnings("unchecked") public static <T extends Expression> T as(Expression expr, Class<T> exprClazz) { if (expr != null) { if (exprClazz.isAssignableFrom(expr.getClass())) { return (T) expr; } else if (expr instanceof ParenthesizedExpression) { return as(((ParenthesizedExpression) expr).getExpression(), exprClazz); } } return null; } /** * Returns whether the provided expression has the provided type. * * @param expr the expression to test * @param exprClazz the type to test the expression against * @return {@code true} if the provided expression has the provided type, {@code false} otherwise */ public static boolean is(Expression expr, Class<? extends Expression> exprClazz) { return as(expr, exprClazz) != null; } /** * Returns whether the provided expression represents a {@link NullLiteral} ignoring parentheses. * * @param expr the expression to check * @return true if the provided expression represents a {@link NullLiteral} ignoring parentheses, false otherwise */ public static boolean isNullLiteral(Expression expr) { return is(expr, NullLiteral.class); } /** * If the provided expression collection only has one element, * then that unique expression is cast to an object of the provided type if type matches. * * @param <T> the required expression type * @param exprs the singleton expression to cast * @param exprClazz the class representing the required expression type * @return the provided singleton expression as an object of the provided type if type matches, null otherwise */ public static <T extends Expression> T as(Collection<? extends Expression> exprs, Class<T> exprClazz) { if (exprs != null && exprs.size() == 1) { return as(exprs.iterator().next(), exprClazz); } return null; } /** * Returns the {@link Expression} of a specified type out of the provided {@link Statement}. * Note the provided statement is first converted to an {@link ExpressionStatement} if possible. * * @param <T> the required expression type * @param stmt the statement * @param exprClazz the class representing the required expression type * @return the {@link Expression} of a specified type out of an {@link ExpressionStatement} */ public static <T extends Expression> T asExpression(Statement stmt, Class<T> exprClazz) { final ExpressionStatement es = as(stmt, ExpressionStatement.class); if (es != null) { return as(es.getExpression(), exprClazz); } return null; } /** * Returns whether the two provided expressions are cast compatible. * * @param expr1 the first expression * @param expr2 the second expression * @return {@code true} if the two provided expressions are cast compatible, {@code false} otherwise * @see ITypeBinding#isCastCompatible(ITypeBinding) */ public static boolean isCastCompatible(Expression expr1, Expression expr2) { final ITypeBinding tb1 = expr1.resolveTypeBinding(); final ITypeBinding tb2 = expr2.resolveTypeBinding(); return tb1 != null && tb2 != null && tb1.isCastCompatible(tb2); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see ClassInstanceCreation#arguments() */ @SuppressWarnings("unchecked") public static List<Expression> arguments(ClassInstanceCreation node) { return node.arguments(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see ConstructorInvocation#arguments() */ @SuppressWarnings("unchecked") public static List<Expression> arguments(ConstructorInvocation node) { return node.arguments(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see MethodInvocation#arguments() */ @SuppressWarnings("unchecked") public static List<Expression> arguments(MethodInvocation node) { return node.arguments(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see SuperConstructorInvocation#arguments() */ @SuppressWarnings("unchecked") public static List<Expression> arguments(SuperConstructorInvocation node) { return node.arguments(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see SuperMethodInvocation#arguments() */ @SuppressWarnings("unchecked") public static List<Expression> arguments(SuperMethodInvocation node) { return node.arguments(); } /** * Returns the first argument of the provided method invocation. * <p> * Equivalent to: * <pre> * ASTHelper.arguments(methodInvocation).get(0) * </pre> * * @param node the method invocation for which to get the first argument * @return the first argument if it exists * @throws IllegalArgumentException if this method invocation has no arguments */ public static Expression arg0(final MethodInvocation node) { final List<Expression> args = arguments(node); if (args.isEmpty()) { throw new IllegalArgumentException(node, "The arguments must not be empty for method " + node); } return args.get(0); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of body declarations * @see AnonymousClassDeclaration#bodyDeclarations() */ @SuppressWarnings("unchecked") public static List<BodyDeclaration> bodyDeclarations(AnonymousClassDeclaration node) { return node.bodyDeclarations(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of body declarations * @see TypeDeclaration#bodyDeclarations() */ @SuppressWarnings("unchecked") public static List<BodyDeclaration> bodyDeclarations(AbstractTypeDeclaration node) { return node.bodyDeclarations(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see TryStatement#catchClauses() */ @SuppressWarnings("unchecked") public static List<CatchClause> catchClauses(TryStatement node) { return node.catchClauses(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see ArrayInitializer#expressions() */ @SuppressWarnings("unchecked") public static List<Expression> expressions(ArrayInitializer node) { return node.expressions(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see InfixExpression#extendedOperands() */ @SuppressWarnings("unchecked") public static List<Expression> extendedOperands(InfixExpression node) { return node.extendedOperands(); } /** * Returns all the operands from the provided infix expressions. * * @param node the infix expression * @return a List of expressions */ public static List<Expression> allOperands(InfixExpression node) { final List<Expression> extOps = extendedOperands(node); final List<Expression> results = new ArrayList<Expression>(2 + extOps.size()); results.add(node.getLeftOperand()); results.add(node.getRightOperand()); results.addAll(extOps); return results; } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see ForStatement#initializers() */ @SuppressWarnings("unchecked") public static List<Expression> initializers(ForStatement node) { return node.initializers(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see ForStatement#updaters() */ @SuppressWarnings("unchecked") public static List<Expression> updaters(ForStatement node) { return node.updaters(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see FieldDeclaration#fragments() */ @SuppressWarnings("unchecked") public static List<VariableDeclarationFragment> fragments(FieldDeclaration node) { return node.fragments(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see VariableDeclarationExpression#fragments() */ @SuppressWarnings("unchecked") public static List<VariableDeclarationFragment> fragments(VariableDeclarationExpression node) { return node.fragments(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see VariableDeclarationStatement#fragments() */ @SuppressWarnings("unchecked") public static List<VariableDeclarationFragment> fragments(VariableDeclarationStatement node) { return node.fragments(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see CompilationUnit#getCommentList() */ @SuppressWarnings("unchecked") public static List<Comment> getCommentList(CompilationUnit node) { return node.getCommentList(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see CompilationUnit#imports() */ @SuppressWarnings("unchecked") public static List<ImportDeclaration> imports(CompilationUnit node) { return node.imports(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see BodyDeclaration#modifiers() */ @SuppressWarnings("unchecked") public static List<IExtendedModifier> modifiers(BodyDeclaration node) { return node.modifiers(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see BodyDeclaration#modifiers() */ @SuppressWarnings("unchecked") public static List<IExtendedModifier> modifiers(SingleVariableDeclaration node) { return node.modifiers(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see MethodDeclaration#parameters() */ @SuppressWarnings("unchecked") public static List<SingleVariableDeclaration> parameters(MethodDeclaration node) { return node.parameters(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of variable declaration expressions * @see TryStatement#resources() */ @SuppressWarnings("unchecked") public static List<VariableDeclarationExpression> resources(TryStatement node) { return node.resources(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see Block#statements() */ @SuppressWarnings("unchecked") public static List<Statement> statements(Block node) { return node.statements(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see SwitchStatement#statements() */ @SuppressWarnings("unchecked") public static List<Statement> statements(SwitchStatement node) { return node.statements(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see Javadoc#tags() */ @SuppressWarnings("unchecked") public static List<TagElement> tags(Javadoc node) { return node.tags(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see ParameterizedType#typeArguments() */ @SuppressWarnings("unchecked") public static List<Type> typeArguments(MethodInvocation node) { return node.typeArguments(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see ParameterizedType#typeArguments() */ @SuppressWarnings("unchecked") public static List<Type> typeArguments(ParameterizedType node) { return node.typeArguments(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of abstract type declarations * @see CompilationUnit#types() */ @SuppressWarnings("unchecked") public static List<AbstractTypeDeclaration> types(CompilationUnit node) { return node.types(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of abstract type declarations * @see UnionType#types() */ @SuppressWarnings("unchecked") public static List<Type> types(UnionType node) { return node.types(); } /** * Generecized version of the equivalent JDT method. * * @param node the node on which to call the equivalent JDT method * @return a List of expressions * @see NormalAnnotation#values() */ @SuppressWarnings("unchecked") public static List<MemberValuePair> values(NormalAnnotation node) { return node.values(); } /** * Returns the {@link Boolean} object value represented by the provided expression. * * @param expr the expression to analyze * @return the {@link Boolean} object value if the provided expression represents one, null otherwise */ public static Boolean getBooleanLiteral(Expression expr) { final BooleanLiteral bl = as(expr, BooleanLiteral.class); if (bl != null) { return bl.booleanValue(); } final QualifiedName qn = as(expr, QualifiedName.class); if (hasType(qn, "java.lang.Boolean")) { return getBooleanObject(qn); } return null; } /** * Returns the {@link Boolean} object constant value represented by the provided qualified name. * * @param qualifiedName * the qualified name that must represent a Boolean object constant * @return the {@link Boolean} object constant value represented by the provided qualified name, * or null if the qualified name does not represent a {@link Boolean} object constant value. */ public static Boolean getBooleanObject(final QualifiedName qualifiedName) { final String fqn = qualifiedName.getFullyQualifiedName(); if ("Boolean.TRUE".equals(fqn)) { return true; } else if ("Boolean.FALSE".equals(fqn)) { return false; } return null; } /** * Converts the provided type binding to a new {@link Type} object. * * @param ast the {@link AST} * @param typeBinding the type binding * @return a new {@link Type} object */ public static Type toType(final AST ast, final ITypeBinding typeBinding) { if (typeBinding == null) { return null; } else if (typeBinding.isArray()) { return ast.newArrayType(toType(ast, typeBinding.getComponentType())); } else if (typeBinding.isPrimitive()) { final Code primitiveTypeCode = PrimitiveType.toCode(typeBinding.getName()); if (primitiveTypeCode != null) { return ast.newPrimitiveType(primitiveTypeCode); } } else if (typeBinding.isClass() || typeBinding.isInterface()) { final Type result = toType(ast, typeBinding.getQualifiedName()); if (result == null) { throw new IllegalStateException(null, "Cannot create a new type from an ITypeBinding without qualified name: " + typeBinding); } return result; } throw new NotImplementedException(null, "Unknown type for typeBinding " + typeBinding.getQualifiedName() + ", isAnnotation()=" + typeBinding.isAnnotation() + ", isAnonymous()=" + typeBinding.isAnonymous() + ", isCapture()=" + typeBinding.isCapture() + ", isEnum()=" + typeBinding.isEnum() + ", isGenericType()=" + typeBinding.isGenericType() + ", isParameterizedType()=" + typeBinding.isParameterizedType() + ", isTypeVariable()=" + typeBinding.isTypeVariable() + ", isRawType()=" + typeBinding.isRawType() + ", isWildcardType()=" + typeBinding.isWildcardType()); } /** * Converts the provided qualified type name to a new {@link Type} object. * * @param ast the {@link AST} * @param qualifiedName the qualified type name * @return a new {@link Type} object */ public static Type toType(final AST ast, String qualifiedName) { final String[] names = qualifiedName.split("\\."); if (names.length == 0) { return null; } final SimpleType simpleType = ast.newSimpleType(ast.newSimpleName(names[0])); if (names.length == 1) { return simpleType; } Type result = simpleType; for (int i = 1; i < names.length; i++) { result = ast.newQualifiedType(result, ast.newSimpleName(names[i])); } return result; } // AST navigation /** * Returns the first ancestor of the provided node which has the required type. * * @param <T> the required ancestor's type * @param node the start node * @param ancestorClazz the required ancestor's type * @return the first ancestor of the provided node which has the required type */ @SuppressWarnings("unchecked") public static <T extends ASTNode> T getAncestor(ASTNode node, Class<T> ancestorClazz) { if (node == null || node.getParent() == null) { throw new IllegalStateException(node, "Could not find any ancestor for " + ancestorClazz + "and node " + node); } final ASTNode parent = node.getParent(); if (ancestorClazz.isAssignableFrom(parent.getClass())) { return (T) parent; } return getAncestor(parent, ancestorClazz); } /** * Returns the enclosing type of the provided node. * <p> * i.e. this returns the most immediate type declaration surrounding the provided node. * * @param node the start node * @return the enclosing type of the provided node, or {@code null} */ public static ASTNode getEnclosingType(ASTNode node) { final Class<?>[] ancestorClasses = { AbstractTypeDeclaration.class, AnonymousClassDeclaration.class }; final ASTNode ancestor = getFirstAncestorOrNull(node, ancestorClasses); if (ancestor == null) { throw new IllegalStateException(node, "Could not find any ancestor for " + Arrays.toString(ancestorClasses) + " and node type " + (node != null ? node.getClass().getSimpleName() : null) + " node.toString() " + node); } return ancestor; } /** * Returns the first ancestor of the provided node which has any of the required types. * * @param node the start node * @param ancestorClasses the required ancestor's types * @return the first ancestor of the provided node which has any of the required type, or {@code null} */ public static ASTNode getFirstAncestorOrNull(ASTNode node, Class<?>... ancestorClasses) { if (node == null || node.getParent() == null) { return null; } final ASTNode parent = node.getParent(); for (Class<?> ancestorClazz : ancestorClasses) { if (ancestorClazz.isAssignableFrom(parent.getClass())) { return parent; } } return getFirstAncestorOrNull(parent, ancestorClasses); } /** * Returns the previous body declaration in the same block if it exists. * * @param startNode the start node * @return the previous body declaration in the same block if it exists, null otherwise */ public static BodyDeclaration getPreviousSibling(BodyDeclaration startNode) { return getSibling(startNode, true); } /** * Returns the previous statement in the same block if it exists. * * @param startNode the start node * @return the previous statement in the same block if it exists, null otherwise */ public static Statement getPreviousSibling(Statement startNode) { return getSibling(startNode, true); } /** * Returns the previous statement in the source file if it exists. * * @param startNode the start node * @return the previous statement in the source file if it exists, null otherwise */ public static Statement getPreviousStatement(Statement startNode) { final Statement previousSibling = getPreviousSibling(startNode); if (previousSibling != null) { return previousSibling; } final ASTNode parent = startNode.getParent(); if (parent instanceof Statement) { return getPreviousStatement((Statement) parent); } return null; } /** * Returns the next body declaration in the same block if it exists. * * @param startNode the start node * @return the next body declaration in the same block if it exists, null otherwise */ public static BodyDeclaration getNextSibling(BodyDeclaration startNode) { return getSibling(startNode, false); } /** * Returns the next statement in the same block if it exists. * * @param startNode the start node * @return the next statement in the same block if it exists, null otherwise */ public static Statement getNextSibling(Statement startNode) { return getSibling(startNode, false); } /** * Returns the next statement in the source file if it exists. * * @param startNode the start node * @return the next statement in the source file if it exists, null otherwise */ public static Statement getNextStatement(Statement startNode) { final Statement nextSibling = getNextSibling(startNode); if (nextSibling != null) { return nextSibling; } final ASTNode parent = startNode.getParent(); if (parent instanceof Statement) { return getNextStatement((Statement) parent); } return null; } private static BodyDeclaration getSibling(BodyDeclaration node, boolean lookForPrevious) { final ASTNode parent = node.getParent(); if (parent instanceof AbstractTypeDeclaration) { final AbstractTypeDeclaration typeDecl = (AbstractTypeDeclaration) parent; return getSibling(node, typeDecl, lookForPrevious); } else if (parent instanceof AnonymousClassDeclaration) { final AnonymousClassDeclaration classDecl = (AnonymousClassDeclaration) parent; return getSibling(node, classDecl, lookForPrevious); } else if (parent instanceof CompilationUnit) { final CompilationUnit cu = (CompilationUnit) parent; final List<AbstractTypeDeclaration> types = types(cu); final int index = types.indexOf(node); if (index != -1 && index + 1 < types.size()) { return types.get(index + 1); } } return null; } private static BodyDeclaration getSibling(BodyDeclaration node, AbstractTypeDeclaration parent, boolean lookForPrevious) { return getSibling(node, bodyDeclarations(parent), lookForPrevious); } private static BodyDeclaration getSibling(BodyDeclaration node, AnonymousClassDeclaration parent, boolean lookForPrevious) { return getSibling(node, bodyDeclarations(parent), lookForPrevious); } private static BodyDeclaration getSibling(BodyDeclaration node, List<BodyDeclaration> bodyDeclarations, boolean lookForPrevious) { final TreeSet<BodyDeclaration> children = new TreeSet<BodyDeclaration>(new NodeStartPositionComparator()); children.addAll(bodyDeclarations); BodyDeclaration previous = null; boolean returnNext = false; for (BodyDeclaration child : children) { if (lookForPrevious) { if (child.equals(node)) { return previous; } } else { if (returnNext) { return child; } } previous = child; returnNext = child.equals(node); } return null; } private static Statement getSibling(Statement node, boolean isPrevious) { if (node.getParent() instanceof Block) { final List<Statement> stmts = asList((Statement) node.getParent()); final int indexOfNode = stmts.indexOf(node); final int siblingIndex = isPrevious ? indexOfNode - 1 : indexOfNode + 1; if (0 <= siblingIndex && siblingIndex < stmts.size()) { return stmts.get(siblingIndex); } } return null; } /** * Returns the {@link ITypeBinding} of the {@link VariableDeclarationFragment}. * * @param vdf the variable declaration fragment * @return the fragment's type binding, or null if none can be found */ public static ITypeBinding resolveTypeBinding(final VariableDeclarationFragment vdf) { if (vdf != null) { final IVariableBinding varBinding = vdf.resolveBinding(); if (varBinding != null) { return varBinding.getType(); } } return null; } // AST checks /** * Returns whether the provided operator is the same as the one of provided node. * * @param node the node for which to test the operator * @param operator the operator to test * @return true if the provided node has the provided operator, false otherwise. */ public static boolean hasOperator(Assignment node, Assignment.Operator operator) { return node != null && operator.equals(node.getOperator()); } /** * Returns whether the provided operator is the same as the one of provided node. * * @param node the node for which to test the operator * @param operator the operator to test * @return true if the provided node has the provided operator, false otherwise. */ public static boolean hasOperator(InfixExpression node, InfixExpression.Operator operator) { return node != null && operator.equals(node.getOperator()); } /** * Returns whether the provided operator is the same as the one of provided node. * * @param node the node for which to test the operator * @param operator the operator to test * @return true if the provided node has the provided operator, false otherwise. */ public static boolean hasOperator(PostfixExpression node, PostfixExpression.Operator operator) { return node != null && operator.equals(node.getOperator()); } /** * Returns whether the provided operator is the same as the one of provided node. * * @param node the node for which to test the operator * @param operator the operator to test * @return true if the provided node has the provided operator, false otherwise. */ public static boolean hasOperator(PrefixExpression node, PrefixExpression.Operator operator) { return node != null && operator.equals(node.getOperator()); } /** * Returns whether the provided expression evaluates to exactly one of the provided type. * * @param expr the expression to analyze * @param oneOfQualifiedTypeNames * the type binding qualified name must be equal to one of these qualified type names * @return true if the provided expression evaluates to exactly one of the provided type, false otherwise */ public static boolean hasType(Expression expr, String... oneOfQualifiedTypeNames) { return expr != null && hasType(expr.resolveTypeBinding(), oneOfQualifiedTypeNames); } /** * Returns whether the provided type binding is exactly one of the provided type. * * @param typeBinding the type binding to analyze * @param oneOfQualifiedTypeNames * the type binding qualified name must be equal to one of these qualified type names * @return {@code true} if the provided type binding is exactly one of the provided type, {@code false} otherwise */ public static boolean hasType(final ITypeBinding typeBinding, String... oneOfQualifiedTypeNames) { if (typeBinding != null) { final String qualifiedName = typeBinding.getErasure().getQualifiedName(); for (String qualifiedTypeName : oneOfQualifiedTypeNames) { if (qualifiedTypeName.equals(qualifiedName)) { return true; } } } return false; } /** * Returns whether the provided expressions evaluate to the same type. * * @param expr1 the first expression to analyze * @param expr2 the second expression to analyze * @return {@code true} if the provided expression evaluates to exactly one of the provided type, * {@code false} otherwise */ public static boolean haveSameType(Expression expr1, Expression expr2) { return expr1 != null && expr2 != null && equalNotNull(expr1.resolveTypeBinding(), expr2.resolveTypeBinding()); } /** * Returns whether the provided expression is an instance of the qualified type name. * * @param expr the expression to analyze * @param qualifiedTypeName the qualified type name * @return {@code true} if the provided expression is an instance of the qualified type name, * {@code false} otherwise */ public static boolean instanceOf(Expression expr, String qualifiedTypeName) { return expr != null && instanceOf(expr.resolveTypeBinding(), qualifiedTypeName); } /** * Returns whether the provided type binding is an instance of the qualified type name. * * @param typeBinding the type binding to analyze * @param qualifiedTypeName the qualified type name * @return true if the provided type binding is an instance of the qualified type name, false otherwise */ public static boolean instanceOf(ITypeBinding typeBinding, String qualifiedTypeName) { return findImplementedType(typeBinding, qualifiedTypeName) != null; } /** * Returns whether the provided expression represents an array. * * @param expr the expression to analyze * @return true the provided expression represents an array, false otherwise */ public static boolean isArray(Expression expr) { if (expr != null) { final ITypeBinding typeBinding = expr.resolveTypeBinding(); return typeBinding != null && typeBinding.isArray(); } return false; } /** * Returns whether the provided expression represents a constant value. * * @param expr the expression to analyze * @return true the provided expression represents a constant value, false otherwise */ public static boolean isConstant(final Expression expr) { return (expr != null && expr.resolveConstantExpressionValue() != null) || isEnumConstant(expr); } private static boolean isEnumConstant(final Expression expr) { // TODO JNR make it work for enums fields which are static final, but not null if (expr instanceof Name) { final IBinding binding = ((Name) expr).resolveBinding(); if (binding instanceof IVariableBinding) { return ((IVariableBinding) binding).isEnumConstant(); } } return false; } /** * Returns whether the provided binding represents a local variable. * * @param binding the binding to analyze * @return {@code true} if the provided binding represents a local variable, {@code false} otherwise */ public static boolean isLocalVariable(IBinding binding) { if (binding != null && binding.getKind() == VARIABLE) { final IVariableBinding bnd = (IVariableBinding) binding; return !bnd.isField() && !bnd.isEnumConstant(); } return false; } /** * Returns whether the provided binding and expression represent the same local variable. * * @param binding the binding to analyze * @param expr the expression to analyze * @return {@code true} if the provided binding and expression represent the same local variable, * {@code false} otherwise */ public static boolean isSameLocalVariable(IBinding binding, Expression expr) { return isLocalVariable(binding) && expr != null && expr.getNodeType() == SIMPLE_NAME // no need to use IVariableBinding.isEqualTo(IBinding) since we are looking for a *local* variable && binding.equals(((SimpleName) expr).resolveBinding()); } /** * Returns whether the provided expressions represent the same local variable. * * @param expr1 the first expression to analyze * @param expr2 the second expression to analyze * @return {@code true} if the provided expressions represent the same local variable, {@code false} otherwise */ public static boolean isSameLocalVariable(Expression expr1, Expression expr2) { return expr1 != null && expr1.getNodeType() == SIMPLE_NAME && isSameLocalVariable(((SimpleName) expr1).resolveBinding(), expr2); } /** * Returns whether the provided expression evaluates to a primitive type. * * @param expr the expression to analyze * @param primitiveTypeName the primitive type name * @return true if the provided expression evaluates to a primitive type, false otherwise */ public static boolean isPrimitive(Expression expr, String primitiveTypeName) { return expr != null && isPrimitive(expr.resolveTypeBinding(), primitiveTypeName); } /** * Returns whether the provided expression evaluates to a primitive type. * * @param expr the expression to analyze * @return true if the provided expression evaluates to a primitive type, false otherwise */ public static boolean isPrimitive(Expression expr) { return expr != null && isPrimitive(expr.resolveTypeBinding()); } /** * Returns whether the provided type binding represents the provided primitive type. * * @param typeBinding the type binding to analyze * @param primitiveTypeName the primitive type name * @return true if the provided type binding represents the provided primitive type, false otherwise */ public static boolean isPrimitive(ITypeBinding typeBinding, String primitiveTypeName) { return typeBinding != null && typeBinding.isPrimitive() && typeBinding.getQualifiedName().equals(primitiveTypeName); } /** * Returns whether the provided type binding represents a primitive type. * * @param typeBinding the type binding to analyze * @return true if the provided type binding represents a primitive type, false otherwise */ public static boolean isPrimitive(ITypeBinding typeBinding) { return typeBinding != null && typeBinding.isPrimitive() && Arrays.asList("boolean", "byte", "char", "short", "int", "long", "float", "double") .contains(typeBinding.getQualifiedName()); } /** * Returns an enum representing the primitive type of this type binding. * * @param typeBinding the type binding to analyze * @return an enum representing the primitive type of this type binding, * {@code null} if the type binding is null, or if it does not a primitive type. */ public static PrimitiveEnum getPrimitiveEnum(ITypeBinding typeBinding) { return typeBinding != null && typeBinding.isPrimitive() ? PrimitiveEnum.valueOf2(typeBinding.getQualifiedName()) : null; } /** * Returns the type binding for the provided qualified type name * if it can be found in the type hierarchy of the provided type binding. * * @param typeBinding the type binding to analyze * @param qualifiedTypeName the qualified type name to find * @return the type binding for the provided qualified type name * if it can be found in the type hierarchy of the provided type binding, * or {@code null} otherwise */ public static ITypeBinding findImplementedType(ITypeBinding typeBinding, String qualifiedTypeName) { if (typeBinding == null) { return null; } final ITypeBinding typeErasure = typeBinding.getErasure(); if (qualifiedTypeName.equals(typeBinding.getQualifiedName()) || qualifiedTypeName.equals(typeErasure.getQualifiedName())) { return typeBinding; } final Set<String> visitedClasses = new HashSet<String>(); visitedClasses.add(typeErasure.getQualifiedName()); return findImplementedType(typeBinding, qualifiedTypeName, visitedClasses); } private static ITypeBinding findImplementedType(ITypeBinding typeBinding, String qualifiedTypeName, Set<String> visitedInterfaces) { final ITypeBinding superclass = typeBinding.getSuperclass(); if (superclass != null) { final String superClassQualifiedName = superclass.getErasure().getQualifiedName(); if (qualifiedTypeName.equals(superClassQualifiedName)) { return superclass; } visitedInterfaces.add(superClassQualifiedName); final ITypeBinding implementedType = findImplementedType(superclass, qualifiedTypeName, visitedInterfaces); if (implementedType != null) { return implementedType; } } for (ITypeBinding itfBinding : typeBinding.getInterfaces()) { final String itfQualifiedName = itfBinding.getErasure().getQualifiedName(); if (qualifiedTypeName.equals(itfQualifiedName)) { return itfBinding; } visitedInterfaces.add(itfQualifiedName); final ITypeBinding implementedType = findImplementedType(itfBinding, qualifiedTypeName, visitedInterfaces); if (implementedType != null) { return implementedType; } } return null; } /** * Returns whether the provided node defines a loop. * * @param node the node * @return true if the provided node defines a loop, false otherwise */ public static boolean isLoop(ASTNode node) { return node instanceof DoStatement || node instanceof EnhancedForStatement || node instanceof ForStatement || node instanceof WhileStatement; } /** * Returns whether the provided node is breakable. * * @param node the node * @return true if the provided node is breakable, false otherwise */ public static boolean isBreakable(ASTNode node) { return isLoop(node) || node instanceof SwitchStatement; } /** * Returns whether the provided qualified name accesses a field with the provided signature. * * @param node the qualified name to compare * @param qualifiedTypeName the qualified name of the type declaring the field * @param fieldName the field name * @return true if the provided qualified name matches the provided field signature, false otherwise */ public static boolean isField(QualifiedName node, String qualifiedTypeName, String fieldName) { return instanceOf(node, qualifiedTypeName) && node.getName().getIdentifier().equals(fieldName); } /** * Returns whether the provided method has the provided method signature. * The method signature is compared against the erasure of the invoked method. * * @param methodBinding the method binding node to compare * @param typeQualifiedName the qualified name of the type declaring the method * @param methodName the method name * @param parameterTypesQualifiedNames the qualified names of the parameter types * @return true if the provided method invocation matches the provided method signature, false otherwise */ public static boolean isMethod(IMethodBinding methodBinding, String typeQualifiedName, String methodName, String... parameterTypesQualifiedNames) { // let's do the fast checks first if (methodBinding == null || !methodName.equals(methodBinding.getName()) || methodBinding.getParameterTypes().length != parameterTypesQualifiedNames.length) { return false; } // ok more heavy checks now final ITypeBinding declaringClazz = methodBinding.getDeclaringClass(); final ITypeBinding implementedType = findImplementedType(declaringClazz, typeQualifiedName); final boolean isInstanceOf = instanceOf(declaringClazz, typeQualifiedName); if (parameterTypesMatch(implementedType, isInstanceOf, methodBinding, parameterTypesQualifiedNames)) { return true; } // a lot more heavy checks // FIXME find a more efficient way to do this. It would be awesome // if an API to directly find the overriddenMethod IMethodBinding existed IMethodBinding overriddenMethod = findOverridenMethod(declaringClazz, typeQualifiedName, methodName, parameterTypesQualifiedNames); return overriddenMethod != null && methodBinding.overrides(overriddenMethod); } /** * Returns whether the provided method invocation invokes a method with the provided method signature. * The method signature is compared against the erasure of the invoked method. * * @param node the method invocation to compare * @param typeQualifiedName the qualified name of the type declaring the method * @param methodName the method name * @param parameterTypesQualifiedNames the qualified names of the parameter types * @return true if the provided method invocation matches the provided method signature, false otherwise */ public static boolean isMethod(MethodInvocation node, String typeQualifiedName, String methodName, String... parameterTypesQualifiedNames) { if (node == null) { return false; } final IMethodBinding methodBinding = node.resolveMethodBinding(); return isMethod(methodBinding, typeQualifiedName, methodName, parameterTypesQualifiedNames); } private static boolean parameterTypesMatch(ITypeBinding implementedType, boolean isInstanceOf, IMethodBinding methodBinding, String[] parameterTypesQualifiedNames) { if (implementedType != null) { final ITypeBinding erasure = implementedType.getErasure(); if (erasure.isGenericType() || erasure.isParameterizedType()) { return parameterizedTypesMatch(implementedType, erasure, methodBinding); } } return isInstanceOf && concreteTypesMatch(methodBinding.getParameterTypes(), parameterTypesQualifiedNames); } private static boolean concreteTypesMatch(ITypeBinding[] typeBindings, String... typesQualifiedNames) { if (typeBindings.length != typesQualifiedNames.length) { return false; } for (int i = 0; i < typesQualifiedNames.length; i++) { if (!typesQualifiedNames[i].equals(typeBindings[i].getQualifiedName())) { return false; } } return true; } private static boolean parameterizedTypesMatch(final ITypeBinding clazz, final ITypeBinding clazzErasure, IMethodBinding methodBinding) { if (clazz.isParameterizedType() && !clazz.equals(clazzErasure)) { final Map<ITypeBinding, ITypeBinding> genericToConcreteTypeParams = getGenericToConcreteTypeParamsMap( clazz, clazzErasure); for (IMethodBinding declaredMethod : clazzErasure.getDeclaredMethods()) { if (declaredMethod.getName().equals(methodBinding.getName()) && parameterizedTypesMatch2(genericToConcreteTypeParams, methodBinding, declaredMethod)) { return true; } } } return false; } private static Map<ITypeBinding, ITypeBinding> getGenericToConcreteTypeParamsMap(final ITypeBinding clazz, final ITypeBinding clazzErasure) { final ITypeBinding[] typeParams = clazz.getTypeArguments(); final ITypeBinding[] genericTypeParams = clazzErasure.getTypeParameters(); final Map<ITypeBinding, ITypeBinding> results = new HashMap<ITypeBinding, ITypeBinding>(); for (int i = 0; i < typeParams.length; i++) { results.put(genericTypeParams[i], typeParams[i]); } return results; } private static boolean parameterizedTypesMatch2(Map<ITypeBinding, ITypeBinding> genericToConcreteTypeParams, IMethodBinding parameterizedMethod, IMethodBinding genericMethod) { final ITypeBinding[] paramTypes = parameterizedMethod.getParameterTypes(); final ITypeBinding[] genericParamTypes = genericMethod.getParameterTypes(); if (paramTypes.length != genericParamTypes.length) { return false; } for (int i = 0; i < genericParamTypes.length; i++) { ITypeBinding genericParamType = genericParamTypes[i]; ITypeBinding concreteParamType = genericToConcreteTypeParams.get(genericParamType); if (concreteParamType == null) { concreteParamType = genericParamType; } final ITypeBinding erasure1 = paramTypes[i].getErasure(); final ITypeBinding erasure2 = concreteParamType.getErasure(); if (!erasure1.equals(erasure2)) { return false; } } return true; } /** * Returns a set made of all the method bindings which are overridden by the provided method binding. * * @param overridingMethod the overriding method binding * @return a set made of all the method bindings which are overridden by the provided method binding */ public static Set<IMethodBinding> getOverridenMethods(IMethodBinding overridingMethod) { final Set<IMethodBinding> results = new HashSet<IMethodBinding>(); findOverridenMethods(overridingMethod, results, overridingMethod.getDeclaringClass()); return results; } private static void findOverridenMethods(IMethodBinding overridingMethod, Set<IMethodBinding> results, ITypeBinding declaringClass) { final ITypeBinding superclass = declaringClass.getSuperclass(); if (superclass != null) { for (IMethodBinding methodFromSuperclass : superclass.getDeclaredMethods()) { if (overridingMethod.overrides(methodFromSuperclass)) { if (!results.add(methodFromSuperclass)) { // type has already been visited return; } } } findOverridenMethods(overridingMethod, results, superclass); } for (ITypeBinding itf : declaringClass.getInterfaces()) { for (IMethodBinding methodFromItf : itf.getDeclaredMethods()) { if (overridingMethod.overrides(methodFromItf)) { if (!results.add(methodFromItf)) { // type has already been visited return; } } } findOverridenMethods(overridingMethod, results, itf); } } private static IMethodBinding findOverridenMethod(ITypeBinding typeBinding, String typeQualifiedName, String methodName, String[] parameterTypesQualifiedNames) { // superclass ITypeBinding superclassBinding = typeBinding.getSuperclass(); if (superclassBinding != null) { superclassBinding = superclassBinding.getErasure(); if (typeQualifiedName.equals(superclassBinding.getErasure().getQualifiedName())) { // found the type return findOverridenMethod(methodName, parameterTypesQualifiedNames, superclassBinding.getDeclaredMethods()); } IMethodBinding overridenMethod = findOverridenMethod(superclassBinding, typeQualifiedName, methodName, parameterTypesQualifiedNames); if (overridenMethod != null) { return overridenMethod; } } // interfaces for (ITypeBinding itfBinding : typeBinding.getInterfaces()) { itfBinding = itfBinding.getErasure(); if (typeQualifiedName.equals(itfBinding.getQualifiedName())) { // found the type return findOverridenMethod(methodName, parameterTypesQualifiedNames, itfBinding.getDeclaredMethods()); } IMethodBinding overridenMethod = findOverridenMethod(itfBinding, typeQualifiedName, methodName, parameterTypesQualifiedNames); if (overridenMethod != null) { return overridenMethod; } } return null; } private static IMethodBinding findOverridenMethod(String methodName, String[] parameterTypesQualifiedNames, IMethodBinding[] declaredMethods) { for (IMethodBinding methodBinding : declaredMethods) { final IMethodBinding methodDecl = methodBinding.getMethodDeclaration(); if (methodBinding.getName().equals(methodName) && methodDecl != null && concreteTypesMatch(methodDecl.getParameterTypes(), parameterTypesQualifiedNames)) { return methodBinding; } } return null; } /** * Returns whether the two names are equal. * * @param name1 the first name to compare * @param name2 the second name to compare * @return true if the two names are equal, false otherwise. */ public static boolean isEqual(Name name1, Name name2) { if (name1 instanceof SimpleName && name2 instanceof SimpleName) { return isEqual((SimpleName) name1, (SimpleName) name2); } else if (name1 instanceof QualifiedName && name2 instanceof QualifiedName) { return isEqual((QualifiedName) name1, (QualifiedName) name2); } return false; } /** * Returns whether the two simple names are equal. * * @param name1 the first simple name to compare * @param name2 the second simple name to compare * @return true if the two simple names are equal, false otherwise. */ public static boolean isEqual(SimpleName name1, SimpleName name2) { return name1.getIdentifier().equals(name2.getIdentifier()); } /** * Returns whether the two qualified names are equal. * * @param name1 the first qualified name to compare * @param name2 the second qualified name to compare * @return true if the two qualified names are equal, false otherwise. */ public static boolean isEqual(QualifiedName name1, QualifiedName name2) { return isEqual(name1.getName(), name2.getName()) && isEqual(name1.getQualifier(), name2.getQualifier()); } private static boolean sameClass(ASTNode node1, ASTNode node2) { return node1 != null && node2 != null && node1.getClass().equals(node2.getClass()); } /** * Returns whether the two provided nodes structurally match. * * @param matcher the AST matcher * @param node1 the first node to compare * @param node2 the second node to compare * @return true if the two provided nodes structurally match, false otherwise */ public static boolean match(ASTMatcher matcher, ASTNode node1, ASTNode node2) { if (sameClass(node1, node2)) { // FIXME JNR implement all expressions // TODO JNR // can we match "this.ast" and the unqualified "ast" for example? // can we match "MyClass.CONSTANT" and the unqualified "CONSTANT" for example? // can we use IVariableBindings to compare them? switch (node1.getNodeType()) { case ASTNode.ANNOTATION_TYPE_DECLARATION: return matcher.match((AnnotationTypeDeclaration) node1, node2); case ASTNode.ANNOTATION_TYPE_MEMBER_DECLARATION: return matcher.match((AnnotationTypeMemberDeclaration) node1, node2); case ASTNode.ANONYMOUS_CLASS_DECLARATION: return matcher.match((AnonymousClassDeclaration) node1, node2); case ASTNode.ARRAY_ACCESS: return matcher.match((ArrayAccess) node1, node2); case ASTNode.ARRAY_CREATION: return matcher.match((ArrayCreation) node1, node2); case ASTNode.ARRAY_INITIALIZER: return matcher.match((ArrayInitializer) node1, node2); case ASTNode.ARRAY_TYPE: return matcher.match((ArrayType) node1, node2); case ASTNode.ASSERT_STATEMENT: return matcher.match((AssertStatement) node1, node2); case ASTNode.ASSIGNMENT: return matcher.match((Assignment) node1, node2); case ASTNode.BLOCK: return matcher.match((Block) node1, node2); case ASTNode.BLOCK_COMMENT: return matcher.match((BlockComment) node1, node2); case ASTNode.BOOLEAN_LITERAL: return matcher.match((BooleanLiteral) node1, node2); case ASTNode.BREAK_STATEMENT: return matcher.match((BreakStatement) node1, node2); case ASTNode.CAST_EXPRESSION: return matcher.match((CastExpression) node1, node2); case ASTNode.CATCH_CLAUSE: return matcher.match((CatchClause) node1, node2); case ASTNode.CHARACTER_LITERAL: return matcher.match((CharacterLiteral) node1, node2); case ASTNode.CLASS_INSTANCE_CREATION: return matcher.match((ClassInstanceCreation) node1, node2); case ASTNode.COMPILATION_UNIT: return matcher.match((CompilationUnit) node1, node2); case ASTNode.CONDITIONAL_EXPRESSION: return matcher.match((ConditionalExpression) node1, node2); case ASTNode.CONSTRUCTOR_INVOCATION: return matcher.match((ConstructorInvocation) node1, node2); case ASTNode.CONTINUE_STATEMENT: return matcher.match((ContinueStatement) node1, node2); case ASTNode.DO_STATEMENT: return matcher.match((DoStatement) node1, node2); case ASTNode.EMPTY_STATEMENT: return matcher.match((EmptyStatement) node1, node2); case ASTNode.ENHANCED_FOR_STATEMENT: return matcher.match((EnhancedForStatement) node1, node2); case ASTNode.ENUM_DECLARATION: return matcher.match((EnumDeclaration) node1, node2); case ASTNode.ENUM_CONSTANT_DECLARATION: return matcher.match((EnumConstantDeclaration) node1, node2); case ASTNode.EXPRESSION_STATEMENT: return matcher.match((ExpressionStatement) node1, node2); case ASTNode.FIELD_ACCESS: return matcher.match((FieldAccess) node1, node2); case ASTNode.FIELD_DECLARATION: return matcher.match((FieldDeclaration) node1, node2); case ASTNode.FOR_STATEMENT: return matcher.match((ForStatement) node1, node2); case ASTNode.IF_STATEMENT: return matcher.match((IfStatement) node1, node2); case ASTNode.IMPORT_DECLARATION: return matcher.match((ImportDeclaration) node1, node2); case ASTNode.INFIX_EXPRESSION: return matcher.match((InfixExpression) node1, node2); case ASTNode.INITIALIZER: return matcher.match((Initializer) node1, node2); case ASTNode.INSTANCEOF_EXPRESSION: return matcher.match((InstanceofExpression) node1, node2); case ASTNode.JAVADOC: return matcher.match((Javadoc) node1, node2); case ASTNode.LABELED_STATEMENT: return matcher.match((LabeledStatement) node1, node2); case ASTNode.LINE_COMMENT: return matcher.match((LineComment) node1, node2); case ASTNode.MARKER_ANNOTATION: return matcher.match((MarkerAnnotation) node1, node2); case ASTNode.MEMBER_REF: return matcher.match((MemberRef) node1, node2); case ASTNode.MEMBER_VALUE_PAIR: return matcher.match((MemberValuePair) node1, node2); case ASTNode.METHOD_DECLARATION: return matcher.match((MethodDeclaration) node1, node2); case ASTNode.METHOD_INVOCATION: return matcher.match((MethodInvocation) node1, node2); case ASTNode.METHOD_REF: return matcher.match((MethodRef) node1, node2); case ASTNode.METHOD_REF_PARAMETER: return matcher.match((MethodRefParameter) node1, node2); case ASTNode.MODIFIER: return matcher.match((Modifier) node1, node2); case ASTNode.NORMAL_ANNOTATION: return matcher.match((NormalAnnotation) node1, node2); case ASTNode.NULL_LITERAL: return matcher.match((NullLiteral) node1, node2); case ASTNode.NUMBER_LITERAL: return matcher.match((NumberLiteral) node1, node2); case ASTNode.PACKAGE_DECLARATION: return matcher.match((PackageDeclaration) node1, node2); case ASTNode.PARAMETERIZED_TYPE: return matcher.match((ParameterizedType) node1, node2); case ASTNode.PARENTHESIZED_EXPRESSION: return matcher.match((ParenthesizedExpression) node1, node2); case ASTNode.POSTFIX_EXPRESSION: return matcher.match((PostfixExpression) node1, node2); case ASTNode.PREFIX_EXPRESSION: return matcher.match((PrefixExpression) node1, node2); case ASTNode.PRIMITIVE_TYPE: return matcher.match((PrimitiveType) node1, node2); case ASTNode.QUALIFIED_NAME: return matcher.match((QualifiedName) node1, node2); case ASTNode.QUALIFIED_TYPE: return matcher.match((QualifiedType) node1, node2); case ASTNode.RETURN_STATEMENT: return matcher.match((ReturnStatement) node1, node2); case ASTNode.SIMPLE_NAME: return matcher.match((SimpleName) node1, node2); case ASTNode.SIMPLE_TYPE: return matcher.match((SimpleType) node1, node2); case ASTNode.SINGLE_MEMBER_ANNOTATION: return matcher.match((SingleMemberAnnotation) node1, node2); case ASTNode.SINGLE_VARIABLE_DECLARATION: return matcher.match((SingleVariableDeclaration) node1, node2); case ASTNode.STRING_LITERAL: return matcher.match((StringLiteral) node1, node2); case ASTNode.SUPER_CONSTRUCTOR_INVOCATION: return matcher.match((SuperConstructorInvocation) node1, node2); case ASTNode.SUPER_FIELD_ACCESS: return matcher.match((SuperFieldAccess) node1, node2); case ASTNode.SUPER_METHOD_INVOCATION: return matcher.match((SuperMethodInvocation) node1, node2); case ASTNode.SWITCH_CASE: return matcher.match((SwitchCase) node1, node2); case ASTNode.SWITCH_STATEMENT: return matcher.match((SwitchStatement) node1, node2); case ASTNode.SYNCHRONIZED_STATEMENT: return matcher.match((SynchronizedStatement) node1, node2); case ASTNode.TAG_ELEMENT: return matcher.match((TagElement) node1, node2); case ASTNode.TEXT_ELEMENT: return matcher.match((TextElement) node1, node2); case ASTNode.THIS_EXPRESSION: return matcher.match((ThisExpression) node1, node2); case ASTNode.THROW_STATEMENT: return matcher.match((ThrowStatement) node1, node2); case ASTNode.TRY_STATEMENT: return matcher.match((TryStatement) node1, node2); case ASTNode.TYPE_DECLARATION: return matcher.match((TypeDeclaration) node1, node2); case ASTNode.TYPE_DECLARATION_STATEMENT: return matcher.match((TypeDeclarationStatement) node1, node2); case ASTNode.TYPE_LITERAL: return matcher.match((TypeLiteral) node1, node2); case ASTNode.TYPE_PARAMETER: return matcher.match((TypeParameter) node1, node2); case ASTNode.UNION_TYPE: return matcher.match((UnionType) node1, node2); case ASTNode.VARIABLE_DECLARATION_EXPRESSION: return matcher.match((VariableDeclarationExpression) node1, node2); case ASTNode.VARIABLE_DECLARATION_FRAGMENT: return matcher.match((VariableDeclarationFragment) node1, node2); case ASTNode.VARIABLE_DECLARATION_STATEMENT: return matcher.match((VariableDeclarationStatement) node1, node2); case ASTNode.WHILE_STATEMENT: return matcher.match((WhileStatement) node1, node2); case ASTNode.WILDCARD_TYPE: return matcher.match((WildcardType) node1, node2); default: throw new NotImplementedException(node1); } } return false; } private static boolean areVariableBindingsEqual(ASTNode node1, ASTNode node2) { final IBinding b1 = varBinding(node1); final IBinding b2 = varBinding(node2); return b1 != null && b2 != null && b1.isEqualTo(b2); } private static IBinding varBinding(ASTNode node) { switch (node.getNodeType()) { case FIELD_ACCESS: return ((FieldAccess) node).resolveFieldBinding(); case QUALIFIED_NAME: return ((QualifiedName) node).resolveBinding(); case SIMPLE_NAME: return ((SimpleName) node).resolveBinding(); case SINGLE_VARIABLE_DECLARATION: return ((SingleVariableDeclaration) node).resolveBinding(); case VARIABLE_DECLARATION_FRAGMENT: return ((VariableDeclarationFragment) node).resolveBinding(); } return null; } /** * Returns whether the two provided names represent the same variable. * * @param name1 the first name to compare * @param name2 the second name to compare * @return true if the two provided names represent the same variable, false otherwise */ public static boolean isSameVariable(SimpleName name1, QualifiedName name2) { return false; } /** * Returns whether the two provided names represent the same variable. * * @param name1 the first name to compare * @param name2 the second name to compare * @return true if the two provided names represent the same variable, false otherwise */ public static boolean isSameVariable(SimpleName name1, SimpleName name2) { return areVariableBindingsEqual(name1, name1); } /** * Returns whether the two provided expressions represent the same variable. * * @param name1 the first expression to compare * @param field2 the second expression to compare * @return true if the two provided expressions represent the same variable, false otherwise */ public static boolean isSameVariable(SimpleName name1, FieldAccess field2) { return as(field2.getExpression(), ThisExpression.class) != null && areVariableBindingsEqual(field2, name1); } /** * Returns whether the two provided qualified names represent the same variable. * * @param name1 the first qualified name to compare * @param name2 the second qualified name to compare * @return true if the two provided qualified names represent the same variable, false otherwise */ public static boolean isSameVariable(QualifiedName name1, QualifiedName name2) { return areVariableBindingsEqual(name1, name2) && isSameVariable(name1.getQualifier(), name2.getQualifier()); } /** * Returns whether the two provided expressions represent the same variable. * * @param name1 the first expression to compare * @param field2 the second expression to compare * @return true if the two provided expressions represent the same variable, false otherwise */ public static boolean isSameVariable(QualifiedName name1, FieldAccess field2) { return areVariableBindingsEqual(name1, field2) && isSameVariable(field2.getExpression(), name1.getQualifier()); } /** * Returns whether the two provided field accesses represent the same variable. * * @param field1 the first field access to compare * @param field2 the second field access to compare * @return true if the two provided field accesses represent the same variable, false otherwise */ public static boolean isSameVariable(FieldAccess field1, FieldAccess field2) { return areVariableBindingsEqual(field1, field2) && isSameVariable(field1.getExpression(), field2.getExpression()); } /** * Returns whether the two provided nodes represent the same variable. * * @param node1 the first node to compare * @param node2 the second node to compare * @return true if the two provided nodes represent the same variable, false otherwise */ public static boolean isSameVariable(ASTNode node1, ASTNode node2) { if (node1 == null || node2 == null) { return false; } switch (node1.getNodeType()) { case THIS_EXPRESSION: return node2.getNodeType() == THIS_EXPRESSION; case SIMPLE_NAME: final SimpleName sn = (SimpleName) node1; switch (node2.getNodeType()) { case QUALIFIED_NAME: return isSameVariable(sn, (QualifiedName) node2); case FIELD_ACCESS: return isSameVariable(sn, (FieldAccess) node2); } break; case QUALIFIED_NAME: final QualifiedName qn = (QualifiedName) node1; switch (node2.getNodeType()) { case SIMPLE_NAME: return isSameVariable((SimpleName) node2, qn); case QUALIFIED_NAME: return isSameVariable(qn, (QualifiedName) node2); case FIELD_ACCESS: return isSameVariable(qn, (FieldAccess) node2); } break; case FIELD_ACCESS: final FieldAccess fa = (FieldAccess) node1; switch (node2.getNodeType()) { case SIMPLE_NAME: return isSameVariable((SimpleName) node2, fa); case QUALIFIED_NAME: return isSameVariable((QualifiedName) node2, fa); case FIELD_ACCESS: return isSameVariable(fa, (FieldAccess) node2); } } return areVariableBindingsEqual(node1, node2); } /** * Returns the last parent node whose class is part of the included classes list * or the provided node otherwise. * * @param node the node * @param includedClasses the classes to include when looking for the parent node * @return the last parent node of the provided classes, or the current node otherwise */ public static ASTNode getFirstParentOfType(ASTNode node, Class<?>... includedClasses) { final ASTNode parent = node.getParent(); if (instanceOf(parent, includedClasses)) { return getFirstParentOfType(parent, includedClasses); } return node; } private static boolean instanceOf(ASTNode node, Class<?>... clazzes) { if (node == null) { return false; } for (Class<?> clazz : clazzes) { if (clazz.isAssignableFrom(node.getClass())) { return true; } } return false; } /** * Returns the first parent node which has a different type that the provided ignored classes. * * @param node the node * @param ignoredClasses the classes to ignore when looking for the parent node * @return the parent node by ignoring the provided types */ public static ASTNode getParentIgnoring(ASTNode node, Class<?>... ignoredClasses) { final ASTNode parent = node.getParent(); if (parent == null) { return node; } if (instanceOf(parent, ignoredClasses)) { return getParentIgnoring(parent, ignoredClasses); } return parent; } /** * Returns the file name where the node comes from, or "FakeClass.java" if this is a fake node. * * @param node the node * @return the file name where the node comes from, or "FakeClass.java" if this is a fake node. */ public static String getFileName(ASTNode node) { if (node.getRoot() instanceof CompilationUnit) { CompilationUnit cu = (CompilationUnit) node.getRoot(); if (cu.getTypeRoot() != null) { // added for unit tests return cu.getTypeRoot().getElementName(); } return "FakeClass.java"; } return null; } /** * Returns a string suitable for identifying a location in the source. * * @param node the node from which to retrieve the source location * @return a string suitable for identifying a location in the source */ public static String getSourceLocation(ASTNode node) { final ASTNode root = node != null ? node.getRoot() : null; if (root instanceof CompilationUnit) { final CompilationUnit cu = (CompilationUnit) root; final int position = node.getStartPosition(); final int line = cu.getLineNumber(position); final int column = cu.getColumnNumber(position) + 1; if (cu.getTypeRoot() != null) { return cu.getTypeRoot().getElementName() + ":" + line + ":" + column; } // it was not created from a file return line + ":" + column; } return ""; } }