blob: 27f9485d1e0529bed478a077373374d1dacb3469 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Dmitry Stalnov (dstalnov@fusionone.com) - contributed fix for
* bug "inline method - doesn't handle implicit cast" (see
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=24941).
* Dmitry Stalnov (dstalnov@fusionone.com) - contributed fix for
* bug Encapsulate field can fail when two variables in one variable declaration (see
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=51540).
* Stephan Herrmann - Configuration for
* Bug 463360 - [override method][null] generating method override should not create redundant null annotations
* Fabrice TIERCELIN - Methods to identify a signature
* Pierre-Yves B. (pyvesdev@gmail.com) - contributed fix for
* Bug 434747 - [inline] Inlining a local variable leads to ambiguity with overloaded methods
*******************************************************************************/
package org.eclipse.jdt.internal.corext.dom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
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.Assignment;
import org.eclipse.jdt.core.dom.Block;
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.ChildListPropertyDescriptor;
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.DoStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
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.InfixExpression;
import org.eclipse.jdt.core.dom.LabeledStatement;
import org.eclipse.jdt.core.dom.LambdaExpression;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.Message;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.MethodReference;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NameQualifiedType;
import org.eclipse.jdt.core.dom.NodeFinder;
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.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.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.SwitchStatement;
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.UnionType;
import org.eclipse.jdt.core.dom.VariableDeclaration;
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.rewrite.ASTRewrite;
import org.eclipse.jdt.core.formatter.IndentManipulation;
import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
import org.eclipse.jdt.internal.core.manipulation.util.Strings;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TType;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TypeEnvironment;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
/**
* JDT-UI-internal helper methods that deal with {@link ASTNode}s:
* <ul>
* <li>additional operations on {@link ASTNode}s and subtypes</li>
* <li>finding related nodes in an AST</li>
* <li>some methods that deal with bindings (new such methods should go into {@link Bindings})</li>
* </ul>
*/
// @see JDTUIHelperClasses
public class ASTNodes {
/**
* This constant should be merged with org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.UNTOUCH_COMMENT
*/
public static final String UNTOUCH_COMMENT= "untouchComment"; //$NON-NLS-1$
public static final int NODE_ONLY= 0;
public static final int INCLUDE_FIRST_PARENT= 1;
public static final int INCLUDE_ALL_PARENTS= 2;
public static final int WARNING= 1 << 0;
public static final int ERROR= 1 << 1;
public static final int INFO= 1 << 2;
public static final int PROBLEMS= WARNING | ERROR | INFO;
public static final int EXCESSIVE_OPERAND_NUMBER= 5;
private static final Message[] EMPTY_MESSAGES= new Message[0];
private static final IProblem[] EMPTY_PROBLEMS= new IProblem[0];
private static final int CLEAR_VISIBILITY= ~(Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE);
/** Enum representing the possible side effect of an expression. */
public enum ExprActivity {
/** Does nothing. */
PASSIVE_WITHOUT_FALLING_THROUGH(0),
/** Does nothing but may fall through. */
PASSIVE(1),
/** May modify something. */
CAN_BE_ACTIVE(2),
/** Modify something. */
ACTIVE(3);
private final int asInteger;
ExprActivity(int asInteger) {
this.asInteger= asInteger;
}
}
private static final class ExprActivityVisitor extends InterruptibleVisitor {
private ExprActivity activityLevel= ExprActivity.PASSIVE_WITHOUT_FALLING_THROUGH;
public ExprActivity getActivityLevel() {
return activityLevel;
}
@Override
public boolean visit(CastExpression node) {
setActivityLevel(ExprActivity.PASSIVE);
return true;
}
@Override
public boolean visit(ArrayAccess node) {
setActivityLevel(ExprActivity.PASSIVE);
return true;
}
@Override
public boolean visit(FieldAccess node) {
setActivityLevel(ExprActivity.PASSIVE);
return true;
}
@Override
public boolean visit(QualifiedName node) {
if (node.getQualifier() == null
|| node.getQualifier().resolveBinding() == null
|| node.getQualifier().resolveBinding().getKind() != IBinding.PACKAGE
&& node.getQualifier().resolveBinding().getKind() != IBinding.TYPE) {
setActivityLevel(ExprActivity.PASSIVE);
}
return true;
}
@Override
public boolean visit(Assignment node) {
setActivityLevel(ExprActivity.ACTIVE);
return interruptVisit();
}
@Override
public boolean visit(PrefixExpression node) {
if (hasOperator(node, PrefixExpression.Operator.INCREMENT, PrefixExpression.Operator.DECREMENT)) {
setActivityLevel(ExprActivity.ACTIVE);
return interruptVisit();
} else if (hasType(node.getOperand(), Object.class.getCanonicalName())) {
setActivityLevel(ExprActivity.PASSIVE);
}
return true;
}
@Override
public boolean visit(PostfixExpression node) {
setActivityLevel(ExprActivity.ACTIVE);
return interruptVisit();
}
@Override
public boolean visit(InfixExpression node) {
if (hasOperator(node, InfixExpression.Operator.DIVIDE)) {
setActivityLevel(ExprActivity.PASSIVE);
} else {
for (Expression operand : allOperands(node)) {
if (hasType(operand, Object.class.getCanonicalName())) {
setActivityLevel(ExprActivity.PASSIVE);
break;
}
}
}
if (hasOperator(node, InfixExpression.Operator.PLUS) && hasType(node, String.class.getCanonicalName())
&& (mayCallActiveToString(node.getLeftOperand())
|| mayCallActiveToString(node.getRightOperand())
|| mayCallActiveToString(node.extendedOperands()))) {
setActivityLevel(ExprActivity.CAN_BE_ACTIVE);
}
return true;
}
private boolean mayCallActiveToString(List<Expression> extendedOperands) {
if (extendedOperands != null) {
for (Expression expression : extendedOperands) {
if (mayCallActiveToString(expression)) {
return true;
}
}
}
return false;
}
private boolean mayCallActiveToString(Expression expression) {
return !hasType(expression,
String.class.getCanonicalName(),
boolean.class.getSimpleName(),
int.class.getSimpleName(),
long.class.getSimpleName(),
double.class.getSimpleName(),
float.class.getSimpleName(),
short.class.getSimpleName(),
char.class.getSimpleName(),
Boolean.class.getCanonicalName(),
Integer.class.getCanonicalName(),
Long.class.getCanonicalName(),
Double.class.getCanonicalName(),
Float.class.getCanonicalName(),
Short.class.getCanonicalName(),
Character.class.getCanonicalName())
&& !(expression instanceof PrefixExpression)
&& !(expression instanceof InfixExpression)
&& !(expression instanceof PostfixExpression);
}
@Override
public boolean visit(SuperMethodInvocation node) {
setActivityLevel(ExprActivity.CAN_BE_ACTIVE);
return true;
}
@Override
public boolean visit(MethodInvocation node) {
setActivityLevel(ExprActivity.CAN_BE_ACTIVE);
return true;
}
@Override
public boolean visit(ClassInstanceCreation node) {
setActivityLevel(ExprActivity.CAN_BE_ACTIVE);
return true;
}
@Override
public boolean visit(ThrowStatement node) {
setActivityLevel(ExprActivity.CAN_BE_ACTIVE);
return true;
}
private void setActivityLevel(final ExprActivity newActivityLevel) {
if (activityLevel.asInteger < newActivityLevel.asInteger) {
activityLevel= newActivityLevel;
}
}
}
private ASTNodes() {
// no instance;
}
public static String asString(ASTNode node) {
ASTFlattener flattener= new ASTFlattener();
node.accept(flattener);
return flattener.getResult();
}
public static String asFormattedString(ASTNode node, int indent, String lineDelim, Map<String, String> options) {
String unformatted= asString(node);
TextEdit edit= CodeFormatterUtil.format2(node, unformatted, indent, lineDelim, options);
if (edit != null) {
Document document= new Document(unformatted);
try {
edit.apply(document, TextEdit.NONE);
} catch (BadLocationException e) {
// bug in the formatter
JavaManipulationPlugin.log(e);
}
return document.get();
}
return unformatted; // unknown node
}
/**
* Returns the source of the given node from the location where it was parsed.
* @param node the node to get the source from
* @param extendedRange if set, the extended ranges of the nodes should ne used
* @param removeIndent if set, the indentation is removed.
* @return return the source for the given node or null if accessing the source failed.
*/
public static String getNodeSource(ASTNode node, boolean extendedRange, boolean removeIndent) {
ASTNode root= node.getRoot();
if (root instanceof CompilationUnit) {
CompilationUnit astRoot= (CompilationUnit) root;
ITypeRoot typeRoot= astRoot.getTypeRoot();
try {
if (typeRoot != null && typeRoot.getBuffer() != null) {
IBuffer buffer= typeRoot.getBuffer();
int offset= extendedRange ? astRoot.getExtendedStartPosition(node) : node.getStartPosition();
int length= extendedRange ? astRoot.getExtendedLength(node) : node.getLength();
String str= buffer.getText(offset, length);
if (removeIndent) {
IJavaProject project= typeRoot.getJavaProject();
int indent= getIndentUsed(buffer, node.getStartPosition(), project);
str= Strings.changeIndent(str, indent, project, "", typeRoot.findRecommendedLineSeparator()); //$NON-NLS-1$
}
return str;
}
} catch (JavaModelException e) {
// ignore
}
}
return null;
}
private static int getIndentUsed(IBuffer buffer, int offset, IJavaProject project) {
int i= offset;
// find beginning of line
while (i > 0 && !IndentManipulation.isLineDelimiterChar(buffer.getChar(i - 1))) {
i--;
}
return Strings.computeIndentUnits(buffer.getText(i, offset - i), project);
}
/**
* Returns the list that contains the given ASTNode. If the node
* isn't part of any list, <code>null</code> is returned.
*
* @param node the node in question
* @return the list that contains the node or <code>null</code>
*/
public static List<? extends ASTNode> getContainingList(ASTNode node) {
StructuralPropertyDescriptor locationInParent= node.getLocationInParent();
if (locationInParent != null && locationInParent.isChildListProperty()) {
return getChildListProperty(node.getParent(), (ChildListPropertyDescriptor) locationInParent);
}
return null;
}
/**
* Variant of {@link ASTNode#getStructuralProperty(StructuralPropertyDescriptor)} that avoids
* unchecked casts in the caller.
* <p>
* To improve type-safety, callers can add the expected element type as explicit type argument, e.g.:
* <p>
* {@code ASTNodes.<BodyDeclaration>getChildListProperty(typeDecl, bodyDeclarationsProperty)}
*
* @param node the node
* @param propertyDescriptor the child list property to get
* @return the child list
* @exception RuntimeException if this node does not have the given property
*/
@SuppressWarnings("unchecked")
public static <T extends ASTNode> List<T> getChildListProperty(ASTNode node, ChildListPropertyDescriptor propertyDescriptor) {
return (List<T>) node.getStructuralProperty(propertyDescriptor);
}
/**
* Returns a list of the direct children of a node. The siblings are ordered by start offset.
* @param node the node to get the children for
* @return the children
*/
public static List<ASTNode> getChildren(ASTNode node) {
ChildrenCollector visitor= new ChildrenCollector();
node.accept(visitor);
return visitor.result;
}
private static class ChildrenCollector extends GenericVisitor {
public List<ASTNode> result;
public ChildrenCollector() {
super(true);
result= null;
}
@Override
protected boolean visitNode(ASTNode node) {
if (result == null) { // first visitNode: on the node's parent: do nothing, return true
result= new ArrayList<>();
return true;
}
result.add(node);
return false;
}
}
/**
* Returns true if this is an existing node, i.e. it was created as part of
* a parsing process of a source code file. Returns false if this is a newly
* created node which has not yet been given a source position.
*
* @param node the node to be tested.
* @return true if this is an existing node, false if not.
*/
public static boolean isExistingNode(ASTNode node) {
return node.getStartPosition() != -1;
}
/**
* Returns the element type. This is a convenience method that returns its
* argument if it is a simple type and the element type if the parameter is an array type.
* @param type The type to get the element type from.
* @return The element type of the type or the type itself.
*/
public static Type getElementType(Type type) {
if (! type.isArrayType())
return type;
return ((ArrayType)type).getElementType();
}
public static ASTNode findDeclaration(IBinding binding, ASTNode root) {
root= root.getRoot();
if (root instanceof CompilationUnit) {
return ((CompilationUnit)root).findDeclaringNode(binding);
}
return null;
}
public static VariableDeclaration findVariableDeclaration(IVariableBinding binding, ASTNode root) {
if (binding.isField())
return null;
ASTNode result= findDeclaration(binding, root);
if (result instanceof VariableDeclaration)
return (VariableDeclaration)result;
return null;
}
/**
* Returns the type node for the given declaration.
*
* @param declaration the declaration
* @return the type node or <code>null</code> if the given declaration represents a type
* inferred parameter in lambda expression
*/
public static Type getType(VariableDeclaration declaration) {
if (declaration instanceof SingleVariableDeclaration) {
return ((SingleVariableDeclaration)declaration).getType();
} else if (declaration instanceof VariableDeclarationFragment) {
ASTNode parent= ((VariableDeclarationFragment)declaration).getParent();
if (parent instanceof VariableDeclarationExpression)
return ((VariableDeclarationExpression)parent).getType();
else if (parent instanceof VariableDeclarationStatement)
return ((VariableDeclarationStatement)parent).getType();
else if (parent instanceof FieldDeclaration)
return ((FieldDeclaration)parent).getType();
else if (parent instanceof LambdaExpression)
return null;
}
Assert.isTrue(false, "Unknown VariableDeclaration"); //$NON-NLS-1$
return null;
}
public static int getDimensions(VariableDeclaration declaration) {
int dim= declaration.getExtraDimensions();
if (declaration instanceof VariableDeclarationFragment && declaration.getParent() instanceof LambdaExpression) {
LambdaExpression lambda= (LambdaExpression) declaration.getParent();
IMethodBinding methodBinding= lambda.resolveMethodBinding();
if (methodBinding != null) {
ITypeBinding[] parameterTypes= methodBinding.getParameterTypes();
int index= lambda.parameters().indexOf(declaration);
ITypeBinding typeBinding= parameterTypes[index];
return typeBinding.getDimensions();
}
} else {
Type type= getType(declaration);
if (type instanceof ArrayType) {
dim+= ((ArrayType) type).getDimensions();
}
}
return dim;
}
public static List<IExtendedModifier> getModifiers(VariableDeclaration declaration) {
Assert.isNotNull(declaration);
if (declaration instanceof SingleVariableDeclaration) {
return ((SingleVariableDeclaration)declaration).modifiers();
} else if (declaration instanceof VariableDeclarationFragment) {
ASTNode parent= declaration.getParent();
if (parent instanceof VariableDeclarationExpression)
return ((VariableDeclarationExpression)parent).modifiers();
else if (parent instanceof VariableDeclarationStatement)
return ((VariableDeclarationStatement)parent).modifiers();
}
return new ArrayList<>(0);
}
public static boolean isSingleDeclaration(VariableDeclaration declaration) {
Assert.isNotNull(declaration);
if (declaration instanceof SingleVariableDeclaration) {
return true;
} else if (declaration instanceof VariableDeclarationFragment) {
ASTNode parent= declaration.getParent();
if (parent instanceof VariableDeclarationExpression)
return ((VariableDeclarationExpression)parent).fragments().size() == 1;
else if (parent instanceof VariableDeclarationStatement)
return ((VariableDeclarationStatement)parent).fragments().size() == 1;
}
return false;
}
/**
* Returns whether the provided expression is an instance of the qualified type
* name.
*
* @param expression 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(final Expression expression, final String qualifiedTypeName) {
return expression != null && instanceOf(expression.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(final ITypeBinding typeBinding, final String qualifiedTypeName) {
return findImplementedType(typeBinding, qualifiedTypeName) != null;
}
/**
* Returns whether the provided expression represents an array.
*
* @param expression the expression to analyze
* @return true the provided expression represents an array, false otherwise
*/
public static boolean isArray(final Expression expression) {
if (expression != null) {
ITypeBinding typeBinding= expression.resolveTypeBinding();
return typeBinding != null && typeBinding.isArray();
}
return false;
}
private static boolean isEnumConstant(final Expression expression) {
if (expression instanceof Name) {
IBinding binding= ((Name) expression).resolveBinding();
if (binding instanceof IVariableBinding) {
return ((IVariableBinding) binding).isEnumConstant();
}
}
return false;
}
/**
* Returns whether the provided expression is hard-coded as a literal in the
* byte code ignoring parentheses.
*
* @param expression the expression to check
* @return true if the provided expression is hard-coded as a literal in the
* byte code ignoring parentheses, false otherwise
*/
public static boolean isHardCoded(final Expression expression) {
if (expression == null) {
return false;
}
switch (expression.getNodeType()) {
case ASTNode.BOOLEAN_LITERAL:
case ASTNode.CHARACTER_LITERAL:
case ASTNode.NUMBER_LITERAL:
case ASTNode.STRING_LITERAL:
case ASTNode.NULL_LITERAL:
return true;
case ASTNode.INFIX_EXPRESSION:
for (Expression operand : allOperands((InfixExpression) expression)) {
if (!isHardCoded(operand)) {
return false;
}
}
return true;
case ASTNode.PREFIX_EXPRESSION:
PrefixExpression prefixExpression= (PrefixExpression) expression;
return isHardCoded(prefixExpression.getOperand());
case ASTNode.POSTFIX_EXPRESSION:
PostfixExpression postfixExpression= (PostfixExpression) expression;
return isHardCoded(postfixExpression.getOperand());
case ASTNode.CAST_EXPRESSION:
return isHardCoded(((CastExpression) expression).getExpression());
case ASTNode.PARENTHESIZED_EXPRESSION:
return isHardCoded(((ParenthesizedExpression) expression).getExpression());
default:
return expression.resolveConstantExpressionValue() != null || isEnumConstant(expression);
}
}
/**
* Integer literal.
*
* @param input The input
* @return Integer literal.
*/
public static Long getIntegerLiteral(final Expression input) {
if (input == null) {
return null;
}
Object number= input.resolveConstantExpressionValue();
if (number instanceof Short) {
return Long.valueOf(((Short) number).intValue());
}
if (number instanceof Integer) {
return Long.valueOf(((Integer) number).intValue());
}
if (number instanceof Long) {
return (Long) number;
}
InfixExpression operation= as(input, InfixExpression.class);
if (operation != null
&& hasOperator(operation,
// All numerical operators
InfixExpression.Operator.AND,
InfixExpression.Operator.DIVIDE,
InfixExpression.Operator.LEFT_SHIFT,
InfixExpression.Operator.MINUS,
InfixExpression.Operator.OR,
InfixExpression.Operator.PLUS,
InfixExpression.Operator.REMAINDER,
InfixExpression.Operator.RIGHT_SHIFT_SIGNED,
InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED,
InfixExpression.Operator.TIMES,
InfixExpression.Operator.XOR)) {
List<Expression> operands= allOperands(operation);
Long leftValue= getIntegerLiteral(operands.remove(0));
if (leftValue == null) {
return null;
}
long result= leftValue;
for (Expression operand : operands) {
Long newObject= getIntegerLiteral(operand);
if (newObject == null) {
return null;
}
long newValue= newObject;
if (hasOperator(operation, InfixExpression.Operator.PLUS)) {
result= result + newValue;
} else if (hasOperator(operation, InfixExpression.Operator.MINUS)) {
result= result - newValue;
} else if (hasOperator(operation, InfixExpression.Operator.TIMES)) {
result= result * newValue;
} else if (hasOperator(operation, InfixExpression.Operator.AND)) {
result= result & newValue;
} else if (hasOperator(operation, InfixExpression.Operator.OR)) {
result= result | newValue;
} else if (hasOperator(operation, InfixExpression.Operator.XOR)) {
result= result ^ newValue;
} else if (hasOperator(operation, InfixExpression.Operator.LEFT_SHIFT)) {
result= result << newValue;
} else if (hasOperator(operation, InfixExpression.Operator.REMAINDER)) {
result= result % newValue;
} else if (hasOperator(operation, InfixExpression.Operator.RIGHT_SHIFT_SIGNED)) {
result= result >> newValue;
} else if (hasOperator(operation, InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED)) {
result= result >>> newValue;
} else if (hasOperator(operation, InfixExpression.Operator.DIVIDE) && result % newValue == 0) {
result= result / newValue;
} else {
return null;
}
}
return result;
}
PrefixExpression negativeContant= as(input, PrefixExpression.class);
if (negativeContant != null && hasOperator(negativeContant, PrefixExpression.Operator.MINUS)) {
Long value= getIntegerLiteral(negativeContant.getOperand());
if (value != null) {
return -value.longValue();
}
}
return null;
}
public static boolean isLiteral(Expression expression) {
int type= expression.getNodeType();
return type == ASTNode.BOOLEAN_LITERAL || type == ASTNode.CHARACTER_LITERAL || type == ASTNode.NULL_LITERAL ||
type == ASTNode.NUMBER_LITERAL || type == ASTNode.STRING_LITERAL || type == ASTNode.TYPE_LITERAL || type == ASTNode.TEXT_BLOCK;
}
public static boolean isLabel(SimpleName name) {
int parentType= name.getParent().getNodeType();
return parentType == ASTNode.LABELED_STATEMENT ||
(parentType == ASTNode.BREAK_STATEMENT && name.getLocationInParent() == BreakStatement.LABEL_PROPERTY) ||
parentType != ASTNode.CONTINUE_STATEMENT;
}
/**
* Return true if the node changes nothing and throws no exceptions.
*
* @param node The node to visit.
*
* @return True if the node changes nothing and throws no exceptions.
*/
public static boolean isPassiveWithoutFallingThrough(final ASTNode node) {
final ExprActivityVisitor visitor= new ExprActivityVisitor();
visitor.traverseNodeInterruptibly(node);
return ExprActivity.PASSIVE_WITHOUT_FALLING_THROUGH.equals(visitor.getActivityLevel());
}
public static boolean isStatic(BodyDeclaration declaration) {
return Modifier.isStatic(declaration.getModifiers());
}
/**
* Return true if the node changes nothing.
*
* @param node The node to visit.
*
* @return True if the node changes nothing.
*/
public static boolean isPassive(final ASTNode node) {
ExprActivityVisitor visitor= new ExprActivityVisitor();
visitor.traverseNodeInterruptibly(node);
return ExprActivity.PASSIVE_WITHOUT_FALLING_THROUGH.equals(visitor.getActivityLevel())
|| ExprActivity.PASSIVE.equals(visitor.getActivityLevel());
}
/**
* True if the method is static, false if it is not or null if it is unknown.
*
* @param method The method
* @return True if the method is static, false if it is not or null if it is unknown.
*/
public static Boolean isStatic(final MethodInvocation method) {
Expression calledType= method.getExpression();
if (method.resolveMethodBinding() != null) {
return (method.resolveMethodBinding().getModifiers() & Modifier.STATIC) != 0;
}
if ((calledType instanceof Name)
&& ((Name) calledType).resolveBinding() != null
&& ((Name) calledType).resolveBinding().getKind() == IBinding.TYPE) {
return Boolean.TRUE;
}
return null;
}
/**
* Return true if it always ends with a jump statement.
*
* @param statement the statement
* @return true if the statement falls through.
*/
public static boolean fallsThrough(final Statement statement) {
List<Statement> statements= asList(statement);
if (statements.isEmpty()) {
return false;
}
Statement lastStatement= statements.get(statements.size() - 1);
switch (lastStatement.getNodeType()) {
case ASTNode.RETURN_STATEMENT:
case ASTNode.THROW_STATEMENT:
case ASTNode.BREAK_STATEMENT:
case ASTNode.CONTINUE_STATEMENT:
return true;
case ASTNode.BLOCK:
Block block= (Block) lastStatement;
return fallsThrough(block);
case ASTNode.IF_STATEMENT:
IfStatement ifStatement= (IfStatement) lastStatement;
Statement thenStatement= ifStatement.getThenStatement();
Statement elseStatement= ifStatement.getElseStatement();
return fallsThrough(thenStatement) && fallsThrough(elseStatement);
case ASTNode.TRY_STATEMENT:
TryStatement tryStatement= (TryStatement) lastStatement;
if (!fallsThrough(tryStatement.getBody())
|| (tryStatement.getFinally() != null && fallsThrough(tryStatement.getFinally()))) {
return false;
}
if (tryStatement.catchClauses() != null) {
for (Object catchClause : tryStatement.catchClauses()) {
if (!fallsThrough(((CatchClause) catchClause).getBody())) {
return false;
}
}
}
return true;
default:
return false;
}
}
public static List<BodyDeclaration> getBodyDeclarations(ASTNode node) {
if (node instanceof AbstractTypeDeclaration) {
return ((AbstractTypeDeclaration)node).bodyDeclarations();
} else if (node instanceof AnonymousClassDeclaration) {
return ((AnonymousClassDeclaration)node).bodyDeclarations();
}
// should not happen.
Assert.isTrue(false);
return null;
}
/**
* Returns the structural property descriptor for the "bodyDeclarations" property
* of this node (element type: {@link BodyDeclaration}).
*
* @param node the node, either an {@link AbstractTypeDeclaration} or an {@link AnonymousClassDeclaration}
* @return the property descriptor
*/
public static ChildListPropertyDescriptor getBodyDeclarationsProperty(ASTNode node) {
if (node instanceof AbstractTypeDeclaration) {
return ((AbstractTypeDeclaration)node).getBodyDeclarationsProperty();
} else if (node instanceof AnonymousClassDeclaration) {
return AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY;
}
// should not happen.
Assert.isTrue(false);
return null;
}
/**
* Returns the simple name of the type, followed by array dimensions.
* Skips qualifiers, type arguments, and type annotations.
* <p>
* Does <b>not</b> work for WildcardTypes, etc.!
*
* @param type a type that has a simple name
* @return the simple name, followed by array dimensions
* @see #getSimpleNameIdentifier(Name)
* @since 3.10
*/
public static String getTypeName(Type type) {
final StringBuffer buffer= new StringBuffer();
ASTVisitor visitor= new ASTVisitor() {
@Override
public boolean visit(PrimitiveType node) {
buffer.append(node.getPrimitiveTypeCode().toString());
return false;
}
@Override
public boolean visit(SimpleType node) {
buffer.append(getSimpleNameIdentifier(node.getName()));
return false;
}
@Override
public boolean visit(QualifiedType node) {
buffer.append(node.getName().getIdentifier());
return false;
}
@Override
public boolean visit(NameQualifiedType node) {
buffer.append(node.getName().getIdentifier());
return false;
}
@Override
public boolean visit(ParameterizedType node) {
node.getType().accept(this);
return false;
}
@Override
public void endVisit(ArrayType node) {
for (int i= 0; i < node.dimensions().size(); i++) {
buffer.append("[]"); //$NON-NLS-1$
}
}
};
type.accept(visitor);
return buffer.toString();
}
/**
* Returns the (potentially qualified) name of a type, followed by array dimensions.
* Skips type arguments and type annotations.
*
* @param type a type that has a name
* @return the name, followed by array dimensions
* @since 3.10
*/
public static String getQualifiedTypeName(Type type) {
final StringBuffer buffer= new StringBuffer();
ASTVisitor visitor= new ASTVisitor() {
@Override
public boolean visit(SimpleType node) {
buffer.append(node.getName().getFullyQualifiedName());
return false;
}
@Override
public boolean visit(QualifiedType node) {
node.getQualifier().accept(this);
buffer.append('.');
buffer.append(node.getName().getIdentifier());
return false;
}
@Override
public boolean visit(NameQualifiedType node) {
buffer.append(node.getQualifier().getFullyQualifiedName());
buffer.append('.');
buffer.append(node.getName().getIdentifier());
return false;
}
@Override
public boolean visit(ParameterizedType node) {
node.getType().accept(this);
return false;
}
@Override
public void endVisit(ArrayType node) {
for (int i= 0; i < node.dimensions().size(); i++) {
buffer.append("[]"); //$NON-NLS-1$
}
}
};
type.accept(visitor);
return buffer.toString();
}
/**
* Returns the {@link Boolean} object value represented by the provided
* expression.
*
* @param node the expression to analyze
* @return the {@link Boolean} object value if the provided expression
* represents one, null otherwise
*/
public static Boolean getBooleanLiteral(final ASTNode node) {
if (!(node instanceof Expression)) {
return null;
}
Expression expression= (Expression) node;
final BooleanLiteral bl= as(expression, BooleanLiteral.class);
if (bl != null) {
return bl.booleanValue();
}
final QualifiedName qn= as(expression, QualifiedName.class);
if (hasType(qn, Boolean.class.getCanonicalName())) {
return getBooleanObject(qn);
}
return null;
}
/**
* Returns whether the provided statement has the provided type.
*
* @param statement the statement to test
* @param stmtClass 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(final Statement statement, final Class<? extends Statement> stmtClass) {
return as(statement, stmtClass) != null;
}
/**
* Returns whether the provided expression has the provided type.
*
* @param expression the expression to test
* @param exprClass 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(final Expression expression, final Class<? extends Expression> exprClass) {
return as(expression, exprClass) != null;
}
/**
* Casts the provided statement to an object of the provided type if type
* matches.
*
* @param <T> the required statement type
* @param statement the statement to cast
* @param stmtClass 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(final Statement statement, final Class<T> stmtClass) {
if (statement == null) {
return null;
}
List<Statement> statements= asList(statement);
if (statements.size() == 1) {
Statement oneStatement= statements.get(0);
if (stmtClass.isAssignableFrom(oneStatement.getClass())) {
return (T) oneStatement;
}
}
return null;
}
/**
* Casts the provided expression to an object of the provided type if type matches.
*
* @param <T> the required expression type
* @param expression the expression to cast
* @param exprClass 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 expression, Class<T> exprClass) {
if (expression != null) {
if (exprClass.isAssignableFrom(expression.getClass())) {
return (T) expression;
} else if (expression instanceof ParenthesizedExpression) {
expression= ASTNodes.getUnparenthesedExpression(expression);
return as(expression, exprClass);
}
}
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 statement the statement
* @param exprClass 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(final Statement statement, final Class<T> exprClass) {
ExpressionStatement es= as(statement, ExpressionStatement.class);
if (es != null) {
return as(es.getExpression(), exprClass);
}
return null;
}
/**
* 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 statement the statement to analyze
* @return the provided statement as a non null list of statements
*/
public static List<Statement> asList(Statement statement) {
if (statement == null) {
return Collections.emptyList();
}
if (statement instanceof Block) {
return ((Block) statement).statements();
}
return Arrays.asList(statement);
}
/**
* Return the items of an infix expression in the order it is specified. It reverses the operator if needed.
*
* @param <F> the required expression type
* @param <S> the required expression type
* @param node the supposed infix expression
* @param firstClass the class representing the required expression type
* @param secondClass the class representing the required expression type
* @return the items of an infix expression in the order it is specified. It reverses the operator if needed.
*/
public static <F extends Expression, S extends Expression> OrderedInfixExpression<F, S> orderedInfix(final Expression node, final Class<F> firstClass, final Class<S> secondClass) {
InfixExpression expression= as(node, InfixExpression.class);
if (expression == null || expression.hasExtendedOperands()) {
return null;
}
if (firstClass != null && firstClass.equals(secondClass)) {
F first= as(expression.getLeftOperand(), firstClass);
S second= as(expression.getRightOperand(), secondClass);
if (first != null && second != null) {
return new OrderedInfixExpression<>(first, expression.getOperator(), second);
}
} else {
F leftFirst= as(expression.getLeftOperand(), firstClass);
S rightSecond= as(expression.getRightOperand(), secondClass);
if (leftFirst != null && rightSecond != null) {
return new OrderedInfixExpression<>(leftFirst, expression.getOperator(), rightSecond);
}
InfixExpression.Operator mirroredOperator= mirrorOperator(expression);
if (mirroredOperator != null) {
F rightFirst= as(expression.getRightOperand(), firstClass);
S leftSecond= as(expression.getLeftOperand(), secondClass);
if (rightFirst != null && leftSecond != null) {
return new OrderedInfixExpression<>(rightFirst, mirroredOperator, leftSecond);
}
}
}
return null;
}
private static InfixExpression.Operator mirrorOperator(final InfixExpression expression) {
if (Arrays.asList(
InfixExpression.Operator.AND,
InfixExpression.Operator.CONDITIONAL_AND,
InfixExpression.Operator.CONDITIONAL_OR,
InfixExpression.Operator.EQUALS,
InfixExpression.Operator.NOT_EQUALS,
InfixExpression.Operator.OR,
InfixExpression.Operator.PLUS,
InfixExpression.Operator.TIMES,
InfixExpression.Operator.XOR).contains(expression.getOperator())) {
return expression.getOperator();
} else if (InfixExpression.Operator.GREATER.equals(expression.getOperator())) {
return InfixExpression.Operator.LESS;
} else if (InfixExpression.Operator.GREATER_EQUALS.equals(expression.getOperator())) {
return InfixExpression.Operator.LESS_EQUALS;
} else if (InfixExpression.Operator.LESS.equals(expression.getOperator())) {
return InfixExpression.Operator.GREATER;
} else if (InfixExpression.Operator.LESS_EQUALS.equals(expression.getOperator())) {
return InfixExpression.Operator.GREATER_EQUALS;
}
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(final Expression expr1, final Expression expr2) {
ITypeBinding tb1= expr1.resolveTypeBinding();
ITypeBinding tb2= expr2.resolveTypeBinding();
return tb1 != null && tb2 != null && tb1.isCastCompatible(tb2);
}
/**
* 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) {
List<Expression> extOps= node.extendedOperands();
List<Expression> operands= new ArrayList<>(2 + extOps.size());
operands.add(node.getLeftOperand());
operands.add(node.getRightOperand());
operands.addAll(extOps);
List<Expression> optimizedOperands= new ArrayList<>();
for (Expression expression : operands) {
if (expression instanceof InfixExpression && hasOperator((InfixExpression) expression, node.getOperator())) {
optimizedOperands.addAll(allOperands((InfixExpression) expression));
} else {
optimizedOperands.add(expression);
}
}
return optimizedOperands;
}
/**
* Returns the number of logical operands in the expression.
*
* @param node The expression
* @return the number of logical operands in the expression
*/
public static int getNbOperands(final Expression node) {
InfixExpression infixExpression= as(node, InfixExpression.class);
if (infixExpression == null
|| !hasOperator(infixExpression, InfixExpression.Operator.CONDITIONAL_AND, InfixExpression.Operator.CONDITIONAL_OR)
&& (!hasOperator(infixExpression, InfixExpression.Operator.AND, InfixExpression.Operator.OR, InfixExpression.Operator.XOR)
|| !hasType(infixExpression.getLeftOperand(), boolean.class.getCanonicalName(), Boolean.class.getCanonicalName()))) {
return 1;
}
int nbOperands= 0;
for (Expression operand : allOperands(infixExpression)) {
nbOperands+= getNbOperands(operand);
}
return nbOperands;
}
/**
* 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)) { //$NON-NLS-1$
return Boolean.TRUE;
} else if ("Boolean.FALSE".equals(fqn)) { //$NON-NLS-1$
return Boolean.FALSE;
}
return null;
}
/**
* 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 expectedOperator the first operator to test
* @param additionalExpectedOperators the other operators to test too
* @return true if the provided node has the provided operator, false otherwise.
*/
public static boolean hasOperator(Assignment node, Assignment.Operator expectedOperator, Assignment.Operator... additionalExpectedOperators) {
return node != null && isOperatorInList(node.getOperator(), expectedOperator, additionalExpectedOperators);
}
/**
* 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 expectedOperator the first operator to test
* @param additionalExpectedOperators the other operators to test too
* @return true if the provided node has the provided operator, false otherwise.
*/
public static boolean hasOperator(InfixExpression node, InfixExpression.Operator expectedOperator, InfixExpression.Operator... additionalExpectedOperators) {
return node != null && isOperatorInList(node.getOperator(), expectedOperator, additionalExpectedOperators);
}
/**
* 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 expectedOperator the first operator to test
* @param additionalExpectedOperators the other operators to test too
* @return true if the provided node has the provided operator, false otherwise.
*/
public static boolean hasOperator(PrefixExpression node, PrefixExpression.Operator expectedOperator, PrefixExpression.Operator... additionalExpectedOperators) {
return node != null && isOperatorInList(node.getOperator(), expectedOperator, additionalExpectedOperators);
}
/**
* 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 expectedOperator the first operator to test
* @param additionalExpectedOperators the other operators to test too
* @return true if the provided node has the provided operator, false otherwise.
*/
public static boolean hasOperator(PostfixExpression node, PostfixExpression.Operator expectedOperator, PostfixExpression.Operator... additionalExpectedOperators) {
return node != null && isOperatorInList(node.getOperator(), expectedOperator, additionalExpectedOperators);
}
private static <O> boolean isOperatorInList(O actualOperator, O expectedOperator, O[] additionalExpectedOperators) {
return actualOperator != null && (actualOperator.equals(expectedOperator) || Arrays.asList(additionalExpectedOperators).contains(actualOperator));
}
/**
* Returns whether the provided expression evaluates to exactly one of the provided type.
*
* @param expression 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 expression, String... oneOfQualifiedTypeNames) {
return expression != null && hasType(expression.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 the opposite infix operator. For boolean operators, the operands should be negated
* too.
*
* @param operator the infix operator
* @return the opposite infix operator
*/
public static InfixExpression.Operator negatedInfixOperator(InfixExpression.Operator operator) {
if (InfixExpression.Operator.LESS.equals(operator))
return InfixExpression.Operator.GREATER_EQUALS;
if (InfixExpression.Operator.LESS_EQUALS.equals(operator))
return InfixExpression.Operator.GREATER;
if (InfixExpression.Operator.GREATER.equals(operator))
return InfixExpression.Operator.LESS_EQUALS;
if (InfixExpression.Operator.GREATER_EQUALS.equals(operator))
return InfixExpression.Operator.LESS;
if (InfixExpression.Operator.EQUALS.equals(operator))
return InfixExpression.Operator.NOT_EQUALS;
if (InfixExpression.Operator.NOT_EQUALS.equals(operator))
return InfixExpression.Operator.EQUALS;
if (InfixExpression.Operator.CONDITIONAL_AND.equals(operator))
return InfixExpression.Operator.CONDITIONAL_OR;
if (InfixExpression.Operator.CONDITIONAL_OR.equals(operator))
return InfixExpression.Operator.CONDITIONAL_AND;
return null;
}
public static InfixExpression.Operator convertToInfixOperator(Assignment.Operator operator) {
if (operator.equals(Assignment.Operator.PLUS_ASSIGN))
return InfixExpression.Operator.PLUS;
if (operator.equals(Assignment.Operator.MINUS_ASSIGN))
return InfixExpression.Operator.MINUS;
if (operator.equals(Assignment.Operator.TIMES_ASSIGN))
return InfixExpression.Operator.TIMES;
if (operator.equals(Assignment.Operator.DIVIDE_ASSIGN))
return InfixExpression.Operator.DIVIDE;
if (operator.equals(Assignment.Operator.BIT_AND_ASSIGN))
return InfixExpression.Operator.AND;
if (operator.equals(Assignment.Operator.BIT_OR_ASSIGN))
return InfixExpression.Operator.OR;
if (operator.equals(Assignment.Operator.BIT_XOR_ASSIGN))
return InfixExpression.Operator.XOR;
if (operator.equals(Assignment.Operator.REMAINDER_ASSIGN))
return InfixExpression.Operator.REMAINDER;
if (operator.equals(Assignment.Operator.LEFT_SHIFT_ASSIGN))
return InfixExpression.Operator.LEFT_SHIFT;
if (operator.equals(Assignment.Operator.RIGHT_SHIFT_SIGNED_ASSIGN))
return InfixExpression.Operator.RIGHT_SHIFT_SIGNED;
if (operator.equals(Assignment.Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN))
return InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED;
Assert.isTrue(false, "Cannot convert assignment operator"); //$NON-NLS-1$
return null;
}
/**
* Returns true if a node at a given location is a body of a control statement. Such body nodes are
* interesting as when replacing them, it has to be evaluates if a Block is needed instead.
* E.g. <code> if (x) do(); -> if (x) { do1(); do2() } </code>
*
* @param locationInParent Location of the body node
* @return Returns true if the location is a body node location of a control statement.
*/
public static boolean isControlStatementBody(StructuralPropertyDescriptor locationInParent) {
return locationInParent == IfStatement.THEN_STATEMENT_PROPERTY
|| locationInParent == IfStatement.ELSE_STATEMENT_PROPERTY
|| locationInParent == ForStatement.BODY_PROPERTY
|| locationInParent == EnhancedForStatement.BODY_PROPERTY
|| locationInParent == WhileStatement.BODY_PROPERTY
|| locationInParent == DoStatement.BODY_PROPERTY;
}
/**
* Returns the type to which an inlined variable initializer should be cast, or
* <code>null</code> if no cast is necessary.
*
* @param initializer the initializer expression of the variable to inline
* @param reference the reference to the variable (which is to be inlined)
* @return a type binding to which the initializer should be cast, or <code>null</code> iff no cast is necessary
* @since 3.6
*/
public static ITypeBinding getExplicitCast(Expression initializer, Expression reference) {
ITypeBinding initializerType= initializer.resolveTypeBinding();
ITypeBinding referenceType= reference.resolveTypeBinding();
if (initializerType == null || referenceType == null)
return null;
if (initializerType.isPrimitive() && referenceType.isPrimitive() && ! referenceType.isEqualTo(initializerType)) {
return referenceType;
} else if (initializerType.isPrimitive() && ! referenceType.isPrimitive()) { // initializer is autoboxed
ITypeBinding unboxedReferenceType= Bindings.getUnboxedTypeBinding(referenceType, reference.getAST());
if (!unboxedReferenceType.isEqualTo(initializerType))
return unboxedReferenceType;
else if (needsExplicitBoxing(reference))
return referenceType;
} else if (! initializerType.isPrimitive() && referenceType.isPrimitive()) { // initializer is autounboxed
ITypeBinding unboxedInitializerType= Bindings.getUnboxedTypeBinding(initializerType, reference.getAST());
if (!unboxedInitializerType.isEqualTo(referenceType))
return referenceType;
} else if (initializerType.isRawType() && referenceType.isParameterizedType()) {
return referenceType; // don't lose the unchecked conversion
} else if (initializer instanceof LambdaExpression || initializer instanceof MethodReference) {
if (isTargetAmbiguous(reference, isExplicitlyTypedLambda(initializer))) {
return referenceType;
} else {
ITypeBinding targetType= getTargetType(reference);
if (targetType == null || targetType != referenceType) {
return referenceType;
}
}
} else if (! TypeRules.canAssign(initializerType, referenceType)) {
if (!Bindings.containsTypeVariables(referenceType))
return referenceType;
} else if (!initializerType.isEqualTo(referenceType)) {
if (isTargetAmbiguous(reference, initializerType)) {
return referenceType;
}
}
return null;
}
/**
* Checks whether overloaded methods can result in an ambiguous method call or a semantic change when the
* <code>expression</code> argument is replaced with a poly expression form of the functional
* interface instance.
*
* @param expression the method argument, which is a functional interface instance
* @param expressionIsExplicitlyTyped <code>true</code> iff the intended replacement for <code>expression</code>
* is an explicitly typed lambda expression (JLS8 15.27.1)
* @return <code>true</code> if overloaded methods can result in an ambiguous method call or a semantic change,
* <code>false</code> otherwise
*
* @since 3.10
*/
public static boolean isTargetAmbiguous(Expression expression, boolean expressionIsExplicitlyTyped) {
ParentSummary targetSummary= getParentSummary(expression);
if (targetSummary == null) {
return false;
}
if (targetSummary.methodBinding != null) {
ITypeBinding invocationTargetType= getInvocationType(expression.getParent(), targetSummary.methodBinding, targetSummary.invocationQualifier);
if (invocationTargetType != null) {
TypeBindingVisitor visitor= new FunctionalInterfaceAmbiguousMethodAnalyzer(invocationTargetType, targetSummary.methodBinding, targetSummary.argumentIndex,
targetSummary.argumentCount, expressionIsExplicitlyTyped);
return !visitor.visit(invocationTargetType)
|| !Bindings.visitHierarchy(invocationTargetType, visitor);
}
}
return true;
}
/**
* Checks whether overloaded methods can result in an ambiguous method call or a semantic change
* when the <code>expression</code> argument is inlined.
*
* @param expression the method argument, which is a functional interface instance
* @param initializerType the initializer type of the variable to inline
* @return <code>true</code> if overloaded methods can result in an ambiguous method call or a
* semantic change, <code>false</code> otherwise
*
* @since 3.19
*/
public static boolean isTargetAmbiguous(Expression expression, ITypeBinding initializerType) {
ParentSummary parentSummary= getParentSummary(expression);
if (parentSummary == null) {
return false;
}
IMethodBinding methodBinding= parentSummary.methodBinding;
if (methodBinding != null) {
ITypeBinding[] parameterTypes= methodBinding.getParameterTypes();
int argumentIndex= parentSummary.argumentIndex;
if (methodBinding.isVarargs() && argumentIndex >= parameterTypes.length - 1) {
argumentIndex= parameterTypes.length - 1;
initializerType= initializerType.createArrayType(1);
}
parameterTypes[argumentIndex]= initializerType;
ITypeBinding invocationType= getInvocationType(expression.getParent(), methodBinding, parentSummary.invocationQualifier);
if (invocationType != null) {
TypeEnvironment typeEnvironment= new TypeEnvironment();
TypeBindingVisitor visitor= new AmbiguousMethodAnalyzer(typeEnvironment, methodBinding, typeEnvironment.create(parameterTypes));
if (!visitor.visit(invocationType)) {
return true;
} else if (invocationType.isInterface()) {
return !Bindings.visitInterfaces(invocationType, visitor);
} else if (Modifier.isAbstract(invocationType.getModifiers())) {
return !Bindings.visitHierarchy(invocationType, visitor);
} else {
// it is not needed to visit interfaces if receiver is a concrete class
return !Bindings.visitSuperclasses(invocationType, visitor);
}
}
}
return true;
}
private static ParentSummary getParentSummary(Expression expression) {
StructuralPropertyDescriptor locationInParent= expression.getLocationInParent();
while (locationInParent == ParenthesizedExpression.EXPRESSION_PROPERTY
|| locationInParent == ConditionalExpression.THEN_EXPRESSION_PROPERTY
|| locationInParent == ConditionalExpression.ELSE_EXPRESSION_PROPERTY) {
expression= (Expression) expression.getParent();
locationInParent= expression.getLocationInParent();
}
ASTNode parent= expression.getParent();
IMethodBinding methodBinding;
int argumentIndex;
int argumentCount;
Expression invocationQualifier= null;
if (locationInParent == MethodInvocation.ARGUMENTS_PROPERTY) {
MethodInvocation methodInvocation= (MethodInvocation) parent;
methodBinding= methodInvocation.resolveMethodBinding();
argumentIndex= methodInvocation.arguments().indexOf(expression);
argumentCount= methodInvocation.arguments().size();
invocationQualifier= methodInvocation.getExpression();
} else if (locationInParent == SuperMethodInvocation.ARGUMENTS_PROPERTY) {
SuperMethodInvocation superMethodInvocation= (SuperMethodInvocation) parent;
methodBinding= superMethodInvocation.resolveMethodBinding();
argumentIndex= superMethodInvocation.arguments().indexOf(expression);
argumentCount= superMethodInvocation.arguments().size();
invocationQualifier= superMethodInvocation.getQualifier();
} else if (locationInParent == ConstructorInvocation.ARGUMENTS_PROPERTY) {
ConstructorInvocation constructorInvocation= (ConstructorInvocation) parent;
methodBinding= constructorInvocation.resolveConstructorBinding();
argumentIndex= constructorInvocation.arguments().indexOf(expression);
argumentCount= constructorInvocation.arguments().size();
} else if (locationInParent == SuperConstructorInvocation.ARGUMENTS_PROPERTY) {
SuperConstructorInvocation superConstructorInvocation= (SuperConstructorInvocation) parent;
methodBinding= superConstructorInvocation.resolveConstructorBinding();
argumentIndex= superConstructorInvocation.arguments().indexOf(expression);
argumentCount= superConstructorInvocation.arguments().size();
} else if (locationInParent == ClassInstanceCreation.ARGUMENTS_PROPERTY) {
ClassInstanceCreation creation= (ClassInstanceCreation) parent;
methodBinding= creation.resolveConstructorBinding();
argumentIndex= creation.arguments().indexOf(expression);
argumentCount= creation.arguments().size();
} else if (locationInParent == EnumConstantDeclaration.ARGUMENTS_PROPERTY) {
EnumConstantDeclaration enumConstantDecl= (EnumConstantDeclaration) parent;
methodBinding= enumConstantDecl.resolveConstructorBinding();
argumentIndex= enumConstantDecl.arguments().indexOf(expression);
argumentCount= enumConstantDecl.arguments().size();
} else {
return null;
}
return new ParentSummary(methodBinding, argumentIndex, argumentCount, invocationQualifier);
}
private static class ParentSummary {
private final IMethodBinding methodBinding;
private final int argumentIndex;
private final int argumentCount;
private final Expression invocationQualifier;
ParentSummary(IMethodBinding methodBinding, int argumentIndex, int argumentCount, Expression invocationQualifier) {
this.methodBinding= methodBinding;
this.argumentIndex= argumentIndex;
this.argumentCount= argumentCount;
this.invocationQualifier= invocationQualifier;
}
}
/**
* Returns the binding of the type which declares the method being invoked.
*
* @param invocationNode the method invocation node
* @param methodBinding binding of the method being invoked
* @param invocationQualifier the qualifier used for method invocation, or <code>null</code> if
* none
* @return the binding of the type which declares the method being invoked, or <code>null</code>
* if the type cannot be resolved
*/
public static ITypeBinding getInvocationType(ASTNode invocationNode, IMethodBinding methodBinding, Expression invocationQualifier) {
ITypeBinding invocationType;
if (invocationNode instanceof MethodInvocation || invocationNode instanceof SuperMethodInvocation) {
if (invocationQualifier != null) {
invocationType= invocationQualifier.resolveTypeBinding();
if (invocationType != null && invocationNode instanceof SuperMethodInvocation) {
invocationType= invocationType.getSuperclass();
}
} else {
ITypeBinding enclosingType= getEnclosingType(invocationNode);
if (enclosingType != null && invocationNode instanceof SuperMethodInvocation) {
enclosingType= enclosingType.getSuperclass();
}
if (enclosingType != null) {
IMethodBinding methodInHierarchy= Bindings.findMethodInHierarchy(enclosingType, methodBinding.getName(), methodBinding.getParameterTypes());
if (methodInHierarchy != null) {
invocationType= enclosingType;
} else {
invocationType= methodBinding.getDeclaringClass();
}
} else {
// not expected
invocationType= methodBinding.getDeclaringClass();
}
}
} else {
invocationType= methodBinding.getDeclaringClass();
}
return invocationType;
}
private static class AmbiguousMethodAnalyzer implements TypeBindingVisitor {
private TypeEnvironment fTypeEnvironment;
private TType[] fTypes;
private IMethodBinding fOriginal;
public AmbiguousMethodAnalyzer(TypeEnvironment typeEnvironment, IMethodBinding original, TType[] types) {
fTypeEnvironment= typeEnvironment;
fOriginal= original;
fTypes= types;
}
@Override
public boolean visit(ITypeBinding node) {
IMethodBinding[] methods= node.getDeclaredMethods();
for (IMethodBinding candidate : methods) {
if (candidate == fOriginal) {
continue;
}
if (fOriginal.getName().equals(candidate.getName())) {
if (canImplicitlyCall(candidate)) {
return false;
}
}
}
return true;
}
/**
* Returns <code>true</code> if the method can be called without explicit casts; otherwise
* <code>false</code>.
*
* @param candidate the method to test
* @return <code>true</code> if the method can be called without explicit casts
*/
private boolean canImplicitlyCall(IMethodBinding candidate) {
ITypeBinding[] parameters= candidate.getParameterTypes();
if (parameters.length != fTypes.length) {
return false;
}
for (int i= 0; i < parameters.length; i++) {
if (!fTypes[i].canAssignTo(fTypeEnvironment.create(parameters[i]))) {
return false;
}
}
return true;
}
}
private static class FunctionalInterfaceAmbiguousMethodAnalyzer implements TypeBindingVisitor {
private ITypeBinding fDeclaringType;
private IMethodBinding fOriginalMethod;
private int fArgIndex;
private int fArgumentCount;
private boolean fExpressionIsExplicitlyTyped;
/**
* @param declaringType the type binding declaring the <code>originalMethod</code>
* @param originalMethod the method declaration binding corresponding to the method call
* @param argumentIndex the index of the functional interface instance argument in the
* method call
* @param argumentCount the number of arguments in the method call
* @param expressionIsExplicitlyTyped <code>true</code> iff the intended replacement for <code>expression</code>
* is an explicitly typed lambda expression (JLS8 15.27.1)
*/
public FunctionalInterfaceAmbiguousMethodAnalyzer(ITypeBinding declaringType, IMethodBinding originalMethod, int argumentIndex, int argumentCount, boolean expressionIsExplicitlyTyped) {
fDeclaringType= declaringType;
fOriginalMethod= originalMethod;
fArgIndex= argumentIndex;
fArgumentCount= argumentCount;
fExpressionIsExplicitlyTyped= expressionIsExplicitlyTyped;
}
@Override
public boolean visit(ITypeBinding type) {
for (IMethodBinding candidate : type.getDeclaredMethods()) {
if (candidate.getMethodDeclaration() == fOriginalMethod.getMethodDeclaration()) {
continue;
}
ITypeBinding candidateDeclaringType= candidate.getDeclaringClass();
if (fDeclaringType != candidateDeclaringType) {
int modifiers= candidate.getModifiers();
if (candidateDeclaringType.isInterface() && Modifier.isStatic(modifiers)) {
continue;
}
if (Modifier.isPrivate(modifiers)) {
continue;
}
}
if (fOriginalMethod.getName().equals(candidate.getName()) && !fOriginalMethod.overrides(candidate)) {
ITypeBinding[] originalParameterTypes= fOriginalMethod.getParameterTypes();
ITypeBinding[] candidateParameterTypes= candidate.getParameterTypes();
boolean couldBeAmbiguous;
if (originalParameterTypes.length == candidateParameterTypes.length) {
couldBeAmbiguous= true;
} else if (fOriginalMethod.isVarargs() || candidate.isVarargs() ) {
int candidateMinArgumentCount= candidateParameterTypes.length;
if (candidate.isVarargs())
candidateMinArgumentCount--;
couldBeAmbiguous= fArgumentCount >= candidateMinArgumentCount;
} else {
couldBeAmbiguous= false;
}
if (couldBeAmbiguous) {
ITypeBinding parameterType= ASTResolving.getParameterTypeBinding(candidate, fArgIndex);
if (parameterType != null && parameterType.getFunctionalInterfaceMethod() != null) {
if (!fExpressionIsExplicitlyTyped) {
/* According to JLS8 15.12.2.2, implicitly typed lambda expressions are not "pertinent to applicability"
* and hence potentially applicable methods are always "applicable by strict invocation",
* regardless of whether argument expressions are compatible with the method's parameter types or not.
* If there are multiple such methods, 15.12.2.5 results in an ambiguous method invocation.
*/
return false;
}
/* Explicitly typed lambda expressions are pertinent to applicability, and hence
* compatibility with the corresponding method parameter type is checked. And since this check
* separates functional interface methods by their void-compatibility state, functional interfaces
* with a different void compatibility are not applicable any more and hence can't cause
* an ambiguous method invocation.
*/
ITypeBinding origParamType= ASTResolving.getParameterTypeBinding(fOriginalMethod, fArgIndex);
boolean originalIsVoidCompatible= Bindings.isVoidType(origParamType.getFunctionalInterfaceMethod().getReturnType());
boolean candidateIsVoidCompatible= Bindings.isVoidType(parameterType.getFunctionalInterfaceMethod().getReturnType());
if (originalIsVoidCompatible == candidateIsVoidCompatible) {
return false;
}
}
}
}
}
return true;
}
}
/**
* Derives the target type defined at the location of the given expression if the target context
* supports poly expressions.
*
* @param expression the expression at whose location the target type is required
* @return the type binding of the target type defined at the location of the given expression
* if the target context supports poly expressions, or <code>null</code> if the target
* type could not be derived
*
* @since 3.10
*/
public static ITypeBinding getTargetType(Expression expression) {
ASTNode parent= expression.getParent();
StructuralPropertyDescriptor locationInParent= expression.getLocationInParent();
if (locationInParent == VariableDeclarationFragment.INITIALIZER_PROPERTY || locationInParent == SingleVariableDeclaration.INITIALIZER_PROPERTY) {
return ((VariableDeclaration) parent).getName().resolveTypeBinding();
} else if (locationInParent == Assignment.RIGHT_HAND_SIDE_PROPERTY) {
return ((Assignment) parent).getLeftHandSide().resolveTypeBinding();
} else if (locationInParent == ReturnStatement.EXPRESSION_PROPERTY) {
return getTargetTypeForReturnStmt((ReturnStatement) parent);
} else if (locationInParent == ArrayInitializer.EXPRESSIONS_PROPERTY) {
return getTargetTypeForArrayInitializer((ArrayInitializer) parent);
} else if (locationInParent == ArrayAccess.INDEX_PROPERTY) {
return parent.getAST().resolveWellKnownType(int.class.getSimpleName());
} else if (locationInParent == ConditionalExpression.EXPRESSION_PROPERTY
|| locationInParent == IfStatement.EXPRESSION_PROPERTY
|| locationInParent == WhileStatement.EXPRESSION_PROPERTY
|| locationInParent == DoStatement.EXPRESSION_PROPERTY) {
return parent.getAST().resolveWellKnownType(boolean.class.getSimpleName());
} else if (locationInParent == SwitchStatement.EXPRESSION_PROPERTY) {
final ITypeBinding discriminentType= expression.resolveTypeBinding();
if (discriminentType.isPrimitive() || discriminentType.isEnum()
|| discriminentType.getQualifiedName().equals(String.class.getCanonicalName())) {
return discriminentType;
} else {
return Bindings.getUnboxedTypeBinding(discriminentType, parent.getAST());
}
} else if (locationInParent == MethodInvocation.ARGUMENTS_PROPERTY) {
MethodInvocation methodInvocation= (MethodInvocation) parent;
IMethodBinding methodBinding= methodInvocation.resolveMethodBinding();
if (methodBinding != null) {
return getParameterTypeBinding(expression, methodInvocation.arguments(), methodBinding);
}
} else if (locationInParent == SuperMethodInvocation.ARGUMENTS_PROPERTY) {
SuperMethodInvocation superMethodInvocation= (SuperMethodInvocation) parent;
IMethodBinding superMethodBinding= superMethodInvocation.resolveMethodBinding();
if (superMethodBinding != null) {
return getParameterTypeBinding(expression, superMethodInvocation.arguments(), superMethodBinding);
}
} else if (locationInParent == ConstructorInvocation.ARGUMENTS_PROPERTY) {
ConstructorInvocation constructorInvocation= (ConstructorInvocation) parent;
IMethodBinding constructorBinding= constructorInvocation.resolveConstructorBinding();
if (constructorBinding != null) {
return getParameterTypeBinding(expression, constructorInvocation.arguments(), constructorBinding);
}
} else if (locationInParent == SuperConstructorInvocation.ARGUMENTS_PROPERTY) {
SuperConstructorInvocation superConstructorInvocation= (SuperConstructorInvocation) parent;
IMethodBinding superConstructorBinding= superConstructorInvocation.resolveConstructorBinding();
if (superConstructorBinding != null) {
return getParameterTypeBinding(expression, superConstructorInvocation.arguments(), superConstructorBinding);
}
} else if (locationInParent == ClassInstanceCreation.ARGUMENTS_PROPERTY) {
ClassInstanceCreation creation= (ClassInstanceCreation) parent;
IMethodBinding creationBinding= creation.resolveConstructorBinding();
if (creationBinding != null) {
return getParameterTypeBinding(expression, creation.arguments(), creationBinding);
}
} else if (locationInParent == EnumConstantDeclaration.ARGUMENTS_PROPERTY) {
EnumConstantDeclaration enumConstantDecl= (EnumConstantDeclaration) parent;
IMethodBinding enumConstructorBinding= enumConstantDecl.resolveConstructorBinding();
if (enumConstructorBinding != null) {
return getParameterTypeBinding(expression, enumConstantDecl.arguments(), enumConstructorBinding);
}
} else if (locationInParent == LambdaExpression.BODY_PROPERTY) {
IMethodBinding methodBinding= ((LambdaExpression) parent).resolveMethodBinding();
if (methodBinding != null) {
return methodBinding.getReturnType();
}
} else if (locationInParent == ConditionalExpression.THEN_EXPRESSION_PROPERTY || locationInParent == ConditionalExpression.ELSE_EXPRESSION_PROPERTY) {
return getTargetType((ConditionalExpression) parent);
} else if (locationInParent == CastExpression.EXPRESSION_PROPERTY) {
return ((CastExpression) parent).getType().resolveBinding();
} else if (locationInParent == ParenthesizedExpression.EXPRESSION_PROPERTY) {
return getTargetType((ParenthesizedExpression) parent);
}
return null;
}
private static ITypeBinding getParameterTypeBinding(Expression expression, List<Expression> arguments, IMethodBinding methodBinding) {
int index= arguments.indexOf(expression);
return ASTResolving.getParameterTypeBinding(methodBinding, index);
}
private static ITypeBinding getTargetTypeForArrayInitializer(ArrayInitializer arrayInitializer) {
ASTNode initializerParent= arrayInitializer.getParent();
while (initializerParent instanceof ArrayInitializer) {
initializerParent= initializerParent.getParent();
}
if (initializerParent instanceof ArrayCreation) {
return ((ArrayCreation) initializerParent).getType().getElementType().resolveBinding();
} else if (initializerParent instanceof VariableDeclaration) {
ITypeBinding typeBinding= ((VariableDeclaration) initializerParent).getName().resolveTypeBinding();
if (typeBinding != null) {
return typeBinding.getElementType();
}
}
return null;
}
private static ITypeBinding getTargetTypeForReturnStmt(ReturnStatement returnStmt) {
LambdaExpression enclosingLambdaExpr= ASTResolving.findEnclosingLambdaExpression(returnStmt);
if (enclosingLambdaExpr != null) {
IMethodBinding methodBinding= enclosingLambdaExpr.resolveMethodBinding();
return methodBinding == null ? null : methodBinding.getReturnType();
}
MethodDeclaration enclosingMethodDecl= ASTResolving.findParentMethodDeclaration(returnStmt);
if (enclosingMethodDecl != null) {
IMethodBinding methodBinding= enclosingMethodDecl.resolveBinding();
return methodBinding == null ? null : methodBinding.getReturnType();
}
return null;
}
/**
* Returns the statement at the same level as its siblings.
*
* @param node the start node
* @return the statement at the same level
*/
private static Statement statementAtLevel(final Statement node) {
ASTNode parent= node.getParent();
if (parent instanceof LabeledStatement) {
return statementAtLevel((LabeledStatement) parent);
}
return node;
}
/**
* Returns true if a sibling may exist.
*
* @param node the start node
* @return true if a sibling may exist
*/
public static boolean canHaveSiblings(final Statement node) {
ASTNode statementAtLevel= statementAtLevel(node);
ASTNode parent= statementAtLevel.getParent();
return parent instanceof Block
|| parent instanceof SwitchStatement && statementAtLevel.getLocationInParent() == SwitchStatement.STATEMENTS_PROPERTY;
}
/**
* 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(final Statement startNode) {
List<Statement> siblings= getSiblings(startNode, false);
if (siblings.isEmpty()) {
return null;
}
return siblings.get(siblings.size() - 1);
}
private static List<Statement> getSiblings(final Statement startNode, final boolean isForward) {
Statement statementAtLevel= statementAtLevel(startNode);
if (canHaveSiblings(statementAtLevel)) {
List<Statement> statements;
if (statementAtLevel.getParent() instanceof SwitchStatement) {
statements= ((SwitchStatement) statementAtLevel.getParent()).statements();
} else {
statements= asList((Statement) statementAtLevel.getParent());
}
int indexOfNode= statements.indexOf(statementAtLevel);
int siblingIndex= indexOfNode + (isForward ? 1 : -1);
if (0 <= siblingIndex && siblingIndex < statements.size()) {
if (isForward) {
return statements.subList(siblingIndex, statements.size());
}
return statements.subList(0, siblingIndex + 1);
}
}
return Collections.emptyList();
}
/**
* 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(final Statement startNode) {
Statement nextSibling= getNextSibling(startNode);
if (nextSibling != null) {
return nextSibling;
}
ASTNode parent= startNode.getParent();
if (parent instanceof Statement) {
return getNextStatement((Statement) parent);
}
return null;
}
/**
* 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(final Statement startNode) {
List<Statement> siblings= getSiblings(startNode, true);
if (siblings.isEmpty()) {
return null;
}
return siblings.get(0);
}
/**
* Returns the next statements in the same block if it exists.
*
* @param startNode the start node
* @return the next statements in the same block if it exists, empty list
* otherwise
*/
public static List<Statement> getNextSiblings(final Statement startNode) {
return getSiblings(startNode, true);
}
/**
* Returns whether an expression at the given location needs explicit boxing.
*
* @param expression the expression
* @return <code>true</code> iff an expression at the given location needs explicit boxing
* @since 3.6
*/
private static boolean needsExplicitBoxing(Expression expression) {
StructuralPropertyDescriptor locationInParent= expression.getLocationInParent();
if (locationInParent == ParenthesizedExpression.EXPRESSION_PROPERTY)
return needsExplicitBoxing((ParenthesizedExpression) expression.getParent());
if (locationInParent == ClassInstanceCreation.EXPRESSION_PROPERTY
|| locationInParent == FieldAccess.EXPRESSION_PROPERTY
|| locationInParent == MethodInvocation.EXPRESSION_PROPERTY)
return true;
return false;
}
/**
* Checks whether the given expression is a lambda expression with explicitly typed parameters.
*
* @param expression the expression to check
* @return <code>true</code> if the expression is a lambda expression with explicitly typed
* parameters or no parameters, <code>false</code> otherwise
*/
public static boolean isExplicitlyTypedLambda(Expression expression) {
if (!(expression instanceof LambdaExpression))
return false;
LambdaExpression lambda= (LambdaExpression) expression;
List<VariableDeclaration> parameters= lambda.parameters();
if (parameters.isEmpty())
return true;
return parameters.get(0) instanceof SingleVariableDeclaration;
}
/**
* Returns the first ancestor of the provided node which has any of the required types.
*
* @param node the start node
* @param ancestorClass the required ancestor's type
* @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}
*/
@SafeVarargs
public static ASTNode getFirstAncestorOrNull(final ASTNode node, final Class<? extends ASTNode> ancestorClass, final Class<? extends ASTNode>... ancestorClasses) {
if (node == null || node.getParent() == null) {
return null;
}
ASTNode parent= node.getParent();
if (ancestorClass.isAssignableFrom(parent.getClass())) {
return parent;
}
for (Class<? extends ASTNode> oneClass : ancestorClasses) {
if (oneClass.isAssignableFrom(parent.getClass())) {
return parent;
}
}
return getFirstAncestorOrNull(parent, ancestorClass, ancestorClasses);
}
/**
* Returns the closest ancestor of <code>node</code> that is an instance of <code>parentClass</code>, or <code>null</code> if none.
* <p>
* <b>Warning:</b> This method does not stop at any boundaries like parentheses, statements, body declarations, etc.
* The resulting node may be in a totally different scope than the given node.
* Consider using one of the {@link ASTResolving}<code>.find(..)</code> methods instead.
* </p>
* @param node the node
* @param parentClass the class of the sought ancestor node
* @return the closest ancestor of <code>node</code> that is an instance of <code>parentClass</code>, or <code>null</code> if none
*/
public static <T extends ASTNode> T getParent(ASTNode node, Class<T> parentClass) {
do {
node= node.getParent();
} while (node != null && !parentClass.isInstance(node));
return parentClass.cast(node);
}
/**
* Returns the closest ancestor of <code>node</code> whose type is <code>nodeType</code>, or <code>null</code> if none.
* <p>
* <b>Warning:</b> This method does not stop at any boundaries like parentheses, statements, body declarations, etc.
* The resulting node may be in a totally different scope than the given node.
* Consider using one of the {@link ASTResolving}<code>.find(..)</code> methods instead.
* </p>
* @param node the node
* @param nodeType the node type constant from {@link ASTNode}
* @return the closest ancestor of <code>node</code> whose type is <code>nodeType</code>, or <code>null</code> if none
*/
public static ASTNode getParent(ASTNode node, int nodeType) {
do {
node= node.getParent();
} while (node != null && node.getNodeType() != nodeType);
return node;
}
public static ASTNode findParent(ASTNode node, StructuralPropertyDescriptor[][] pathes) {
for (StructuralPropertyDescriptor[] path : pathes) {
ASTNode current= node;
int d= path.length - 1;
for (; d >= 0 && current != null; d--) {
StructuralPropertyDescriptor descriptor= path[d];
if (!descriptor.equals(current.getLocationInParent()))
break;
current= current.getParent();
}
if (d < 0)
return current;
}
return null;
}
/**
* For {@link Name} or {@link Type} nodes, returns the topmost {@link Type} node
* that shares the same type binding as the given node.
*
* @param node an ASTNode
* @return the normalized {@link Type} node or the original node
*/
public static ASTNode getNormalizedNode(ASTNode node) {
ASTNode current= node;
// normalize name
if (QualifiedName.NAME_PROPERTY.equals(current.getLocationInParent())) {
current= current.getParent();
}
// normalize type
if (QualifiedType.NAME_PROPERTY.equals(current.getLocationInParent())
|| SimpleType.NAME_PROPERTY.equals(current.getLocationInParent())
|| NameQualifiedType.NAME_PROPERTY.equals(current.getLocationInParent())) {
current= current.getParent();
}
// normalize parameterized types
if (ParameterizedType.TYPE_PROPERTY.equals(current.getLocationInParent())) {
current= current.getParent();
}
return current;
}
/**
* Returns the unique {@link VariableDeclarationFragment} declared in the
* provided {@link VariableDeclarationStatement}.
*
* @param node the statement from which to extract the unique fragment
* @return the unique fragment declared in the provided variable declaration
* statement, or {@code null} if more than one exist.
*/
public static VariableDeclarationFragment getUniqueFragment(final Statement node) {
VariableDeclarationStatement statement= as(node, VariableDeclarationStatement.class);
if (statement == null) {
return null;
}
List<VariableDeclarationFragment> fragments= statement.fragments();
return fragments.size() == 1 ? fragments.get(0) : null;
}
/**
* 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 getUnparenthesedExpression(ASTNode node) {
if (node instanceof Expression) {
return getUnparenthesedExpression((Expression) node);
}
return node;
}
/**
* Returns the same expression after removing any parentheses around it.
*
* @param expression 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 getUnparenthesedExpression(Expression expression) {
while (expression != null && expression.getNodeType() == ASTNode.PARENTHESIZED_EXPRESSION) {
expression= ((ParenthesizedExpression) expression).getExpression();
}
return expression;
}
/**
* Returns <code>true</code> iff <code>parent</code> is a true ancestor of <code>node</code>
* (i.e. returns <code>false</code> if <code>parent == node</code>).
*
* @param node node to test
* @param parent assumed parent
* @return <code>true</code> iff <code>parent</code> is a true ancestor of <code>node</code>
*/
public static boolean isParent(ASTNode node, ASTNode parent) {
Assert.isNotNull(parent);
do {
node= node.getParent();
if (node == parent)
return true;
} while (node != null);
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(final IBinding binding) {
if (binding != null && binding.getKind() == IBinding.VARIABLE) {
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 expression 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(final IBinding binding, final Expression expression) {
return isLocalVariable(binding) && expression != null && expression.getNodeType() == ASTNode.SIMPLE_NAME
// No need to use IVariableBinding.isEqualTo(IBinding) since we are looking for
// a *local* variable
&& binding.equals(((SimpleName) expression).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(final Expression expr1, final Expression expr2) {
return expr1 != null && expr1.getNodeType() == ASTNode.SIMPLE_NAME
&& isSameLocalVariable(((SimpleName) expr1).resolveBinding(), expr2);
}
/**
* Returns whether the provided variable declaration and expression represent
* the same local variable.
*
* @param varDecl the variable declaration to analyze
* @param expression the expression to analyze
* @return {@code true} if the provided nodes represent the same local variable,
* {@code false} otherwise
*/
public static boolean isSameLocalVariable(final VariableDeclaration varDecl, final Expression expression) {
return varDecl != null && isSameLocalVariable(varDecl.resolveBinding(), expression);
}
private static boolean areVariableBindingsEqual(final ASTNode node1, final ASTNode node2) {
return areBindingsEqual(varBinding(node1), varBinding(node2));
}
/**
* Returns whether to bindings are equal.
*
* @param ast1 the first binding
* @param ast2 the second binding
* @return {@code true} when bindings are equal, {@code false} otherwise
*/
public static boolean areBindingsEqual(final IBinding ast1, final IBinding ast2) {
return ast1 != null && ast2 != null && ast1.isEqualTo(ast2);
}
private static IBinding varBinding(final ASTNode node) {
switch (node.getNodeType()) {
case ASTNode.THIS_EXPRESSION:
return ((ThisExpression) node).resolveTypeBinding();
case ASTNode.FIELD_ACCESS:
return ((FieldAccess) node).resolveFieldBinding();
case ASTNode.QUALIFIED_NAME:
case ASTNode.SIMPLE_NAME:
return ((Name) node).resolveBinding();
case ASTNode.SINGLE_VARIABLE_DECLARATION:
case ASTNode.VARIABLE_DECLARATION_FRAGMENT:
return ((VariableDeclaration) node).resolveBinding();
default:
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(final SimpleName name1, final QualifiedName name2) {
return false;
}
/**
* 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(final SimpleName name1, final 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(final QualifiedName name1, final 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(final QualifiedName name1, final 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(final FieldAccess field1, final 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) {
node1= getUnparenthesedExpression(node1);
node2= getUnparenthesedExpression(node2);
if (node1 == null || node2 == null) {
return false;
}
switch (node1.getNodeType()) {
case ASTNode.THIS_EXPRESSION:
return node2.getNodeType() == ASTNode.THIS_EXPRESSION;
case ASTNode.SIMPLE_NAME:
SimpleName sn= (SimpleName) node1;
switch (node2.getNodeType()) {
case ASTNode.QUALIFIED_NAME:
return isSameVariable(sn, (QualifiedName) node2);
case ASTNode.FIELD_ACCESS:
return isSameVariable(sn, (FieldAccess) node2);
}
break;
case ASTNode.QUALIFIED_NAME:
QualifiedName qn= (QualifiedName) node1;
switch (node2.getNodeType()) {
case ASTNode.SIMPLE_NAME:
return isSameVariable((SimpleName) node2, qn);
case ASTNode.QUALIFIED_NAME:
return isSameVariable(qn, (QualifiedName) node2);
case ASTNode.FIELD_ACCESS:
return isSameVariable(qn, (FieldAccess) node2);
}
break;
case ASTNode.FIELD_ACCESS:
FieldAccess fa= (FieldAccess) node1;
switch (node2.getNodeType()) {
case ASTNode.SIMPLE_NAME:
return isSameVariable((SimpleName) node2, fa);
case ASTNode.QUALIFIED_NAME:
return isSameVariable((QualifiedName) node2, fa);
case ASTNode.FIELD_ACCESS:
return isSameVariable(fa, (FieldAccess) node2);
}
}
return areVariableBindingsEqual(node1, node2);
}
public static int getExclusiveEnd(ASTNode node){
return node.getStartPosition() + node.getLength();
}
public static int getInclusiveEnd(ASTNode node){
return node.getStartPosition() + node.getLength() - 1;
}
public static IMethodBinding getMethodBinding(Name node) {
IBinding binding= node.resolveBinding();
if (binding instanceof IMethodBinding)
return (IMethodBinding)binding;
return null;
}
public static IVariableBinding getVariableBinding(Name node) {
IBinding binding= node.resolveBinding();
if (binding instanceof IVariableBinding)
return (IVariableBinding)binding;
return null;
}
public static IVariableBinding getLocalVariableBinding(Name node) {
IVariableBinding result= getVariableBinding(node);
if (result == null || result.isField())
return null;
return result;
}
public static IVariableBinding getFieldBinding(Name node) {
IVariableBinding result= getVariableBinding(node);
if (result == null || !result.isField())
return null;
return result;
}
public static ITypeBinding getTypeBinding(Name node) {
IBinding binding= node.resolveBinding();
if (binding instanceof ITypeBinding)
return (ITypeBinding)binding;
return null;
}
/**
* Returns whether the provided expression evaluates to a primitive type.
*
* @param expression 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(final Expression expression, final String primitiveTypeName) {
return expression != null && isPrimitive(expression.resolveTypeBinding(), primitiveTypeName);
}
/**
* 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
*/
private static boolean isPrimitive(final ITypeBinding typeBinding, final String primitiveTypeName) {
return typeBinding != null && typeBinding.isPrimitive()
&& typeBinding.getQualifiedName().equals(primitiveTypeName);
}
/**
* Returns the receiver's type binding of the given method invocation.
*
* @param invocation method invocation to resolve type of
* @return the type binding of the receiver
*/
public static ITypeBinding getReceiverTypeBinding(MethodInvocation invocation) {
ITypeBinding result= null;
Expression exp= invocation.getExpression();
if(exp != null) {
return exp.resolveTypeBinding();
}
else {
AbstractTypeDeclaration type= getParent(invocation, AbstractTypeDeclaration.class);
if (type != null)
return type.resolveBinding();
}
return result;
}
public static ITypeBinding getEnclosingType(ASTNode node) {
while(node != null) {
if (node instanceof AbstractTypeDeclaration) {
return ((AbstractTypeDeclaration)node).resolveBinding();
} else if (node instanceof AnonymousClassDeclaration) {
return ((AnonymousClassDeclaration)node).resolveBinding();
}
node= node.getParent();
}
return null;
}
public static IProblem[] getProblems(ASTNode node, int scope, int severity) {
ASTNode root= node.getRoot();
if (!(root instanceof CompilationUnit))
return EMPTY_PROBLEMS;
IProblem[] problems= ((CompilationUnit)root).getProblems();
if (root == node)
return problems;
final int iterations= computeIterations(scope);
List<IProblem> result= new ArrayList<>(5);
for (IProblem problem : problems) {
boolean consider= false;
if ((severity & PROBLEMS) == PROBLEMS)
consider= true;
else if ((severity & WARNING) != 0)
consider= problem.isWarning();
else if ((severity & ERROR) != 0)
consider= problem.isError();
else if ((severity & INFO) != 0)
consider= problem.isInfo();
if (consider) {
ASTNode temp= node;
int count= iterations;
do {
int nodeOffset= temp.getStartPosition();
int problemOffset= problem.getSourceStart();
if (nodeOffset <= problemOffset && problemOffset < nodeOffset + temp.getLength()) {
result.add(problem);
count= 0;
} else {
count--;
}
} while ((temp= temp.getParent()) != null && count > 0);
}
}
return result.toArray(new IProblem[result.size()]);
}
public static Message[] getMessages(ASTNode node, int flags) {
ASTNode root= node.getRoot();
if (!(root instanceof CompilationUnit))
return EMPTY_MESSAGES;
Message[] messages= ((CompilationUnit)root).getMessages();
if (root == node)
return messages;
final int iterations= computeIterations(flags);
List<Message> result= new ArrayList<>(5);
for (Message message : messages) {
ASTNode temp= node;
int count= iterations;
do {
int nodeOffset= temp.getStartPosition();
int messageOffset= message.getStartPosition();
if (nodeOffset <= messageOffset && messageOffset < nodeOffset + temp.getLength()) {
result.add(message);
count= 0;
} else {
count--;
}
} while ((temp= temp.getParent()) != null && count > 0);
}
return result.toArray(new Message[result.size()]);
}
private static int computeIterations(int flags) {
switch (flags) {
case NODE_ONLY:
return 1;
case INCLUDE_ALL_PARENTS:
return Integer.MAX_VALUE;
case INCLUDE_FIRST_PARENT:
return 2;
default:
return 1;
}
}
public static SimpleName getLeftMostSimpleName(Name name) {
if (name instanceof SimpleName) {
return (SimpleName)name;
} else {
final SimpleName[] result= new SimpleName[1];
ASTVisitor visitor= new ASTVisitor() {
@Override
public boolean visit(QualifiedName qualifiedName) {
Name left= qualifiedName.getQualifier();
if (left instanceof SimpleName)
result[0]= (SimpleName)left;
else
left.accept(this);
return false;
}
};
name.accept(visitor);
return result[0];
}
}
/**
* Returns the topmost ancestor of <code>name</code> that is still a {@link Name}.
* <p>
* <b>Note:</b> The returned node may resolve to a different binding than the given <code>name</code>!
*
* @param name a name node
* @return the topmost name
* @see #getNormalizedNode(ASTNode)
*/
public static Name getTopMostName(Name name) {
Name result= name;
while(result.getParent() instanceof Name) {
result= (Name)result.getParent();
}
return result;
}
/**
* Returns the topmost ancestor of <code>node</code> that is a {@link Type} (but not a {@link UnionType}).
* <p>
* <b>Note:</b> The returned node often resolves to a different binding than the given <code>node</code>!
*
* @param node the starting node, can be <code>null</code>
* @return the topmost type or <code>null</code> if the node is not a descendant of a type node
* @see #getNormalizedNode(ASTNode)
*/
public static Type getTopMostType(ASTNode node) {
ASTNode result= null;
while (node instanceof Type && !(node instanceof UnionType)
|| node instanceof Name
|| node instanceof Annotation || node instanceof MemberValuePair
|| node instanceof Expression) { // Expression could maybe be reduced to expression node types that can appear in an annotation
result= node;
node= node.getParent();
}
if (result instanceof Type)
return (Type) result;
return null;
}
public static int changeVisibility(int modifiers, int visibility) {
return (modifiers & CLEAR_VISIBILITY) | visibility;
}
/**
* Adds flags to the given node and all its descendants.
* @param root The root node
* @param flags The flags to set
*/
public static void setFlagsToAST(ASTNode root, final int flags) {
root.accept(new GenericVisitor(true) {
@Override
protected boolean visitNode(ASTNode node) {
node.setFlags(node.getFlags() | flags);
return true;
}
});
}
public static String getQualifier(Name name) {
if (name.isQualifiedName()) {
return ((QualifiedName) name).getQualifier().getFullyQualifiedName();
}
return ""; //$NON-NLS-1$
}
public static String getSimpleNameIdentifier(Name name) {
if (name.isQualifiedName()) {
return ((QualifiedName) name).getName().getIdentifier();
} else {
return ((SimpleName) name).getIdentifier();
}
}
public static boolean isDeclaration(Name name) {
if (name.isQualifiedName()) {
return ((QualifiedName) name).getName().isDeclaration();
} else {
return ((SimpleName) name).isDeclaration();
}
}
/**
* 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 fieldNames the field names
* @return true if the provided qualified name matches the provided field
* signature, false otherwise
*/
public static boolean isField(final QualifiedName node, final String qualifiedTypeName, final String... fieldNames) {
return instanceOf(node, qualifiedTypeName)
&& Arrays.asList(fieldNames).contains(node.getName().getIdentifier());
}
/**
* Returns whether a checked exception is supposed to be caught.
*
* @param node the node
* @return true if a checked exception is supposed to be caught.
*/
public static boolean isExceptionExpected(final ASTNode node) {
ASTNode parentNode= getFirstAncestorOrNull(node, TryStatement.class, BodyDeclaration.class);
while (parentNode instanceof TryStatement) {
TryStatement tryStatement= (TryStatement) parentNode;
List<CatchClause> catchClauses= tryStatement.catchClauses();
for (CatchClause catchClause : catchClauses) {
if (catchClause.getException().getType() != null
&& !instanceOf(catchClause.getException().getType().resolveBinding(),
RuntimeException.class.getCanonicalName())) {
return true;
}
}
parentNode= getFirstAncestorOrNull(parentNode, TryStatement.class, BodyDeclaration.class);
}
return false;
}
/**
* 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 usesGivenSignature(final MethodInvocation node, final String typeQualifiedName, final String methodName,
final String... parameterTypesQualifiedNames) {
return node != null
&& usesGivenSignature(node.resolveMethodBinding(), typeQualifiedName, methodName, parameterTypesQualifiedNames);
}
/**
* Returns whether the provided method binding has the provided method signature. The method
* signature is compared against the erasure of the invoked method.
*
* @param methodBinding the method binding 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 usesGivenSignature(final IMethodBinding methodBinding, final String typeQualifiedName, final String methodName,
final 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
ITypeBinding declaringClass= methodBinding.getDeclaringClass();
ITypeBinding implementedType= findImplementedType(declaringClass, typeQualifiedName);
if (parameterTypesMatch(implementedType, methodBinding, parameterTypesQualifiedNames)) {
return true;
}
// A lot more heavy checks
IMethodBinding overriddenMethod= findOverridenMethod(declaringClass, typeQualifiedName, methodName,
parameterTypesQualifiedNames);
if (overriddenMethod != null && methodBinding.overrides(overriddenMethod)) {
return true;
}
IMethodBinding methodDeclaration= methodBinding.getMethodDeclaration();
return methodDeclaration != null && methodDeclaration != methodBinding
&& usesGivenSignature(methodDeclaration, typeQualifiedName, methodName, parameterTypesQualifiedNames);
}
private static boolean parameterTypesMatch(final ITypeBinding implementedType, final IMethodBinding methodBinding,
final String[] parameterTypesQualifiedNames) {
if (implementedType != null && !implementedType.isRawType()) {
ITypeBinding erasure= implementedType.getErasure();
if (erasure.isGenericType() || erasure.isParameterizedType()) {
return parameterizedTypesMatch(implementedType, erasure, methodBinding);
}
}
return implementedType != null && concreteTypesMatch(methodBinding.getParameterTypes(), parameterTypesQualifiedNames);
}
private static IMethodBinding findOverridenMethod(final ITypeBinding typeBinding, final String typeQualifiedName,
final String methodName, final 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(final String methodName, final String[] parameterTypesQualifiedNames,
final IMethodBinding[] declaredMethods) {
for (IMethodBinding methodBinding : declaredMethods) {
IMethodBinding methodDecl= methodBinding.getMethodDeclaration();
if (methodBinding.getName().equals(methodName) && methodDecl != null
&& concreteTypesMatch(methodDecl.getParameterTypes(), parameterTypesQualifiedNames)) {
return methodBinding;
}
}
return null;
}
private static boolean concreteTypesMatch(final ITypeBinding[] typeBindings, final String... typesQualifiedNames) {
if (typeBindings.length != typesQualifiedNames.length) {
return false;
}
for (int i= 0; i < typesQualifiedNames.length; i++) {
if (!typesQualifiedNames[i].equals(typeBindings[i].getQualifiedName())
&& !typesQualifiedNames[i].equals(Bindings.getBoxedTypeName(typeBindings[i].getQualifiedName()))
&& !typesQualifiedNames[i].equals(Bindings.getUnboxedTypeName(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)) {
Map<ITypeBinding, ITypeBinding> genericToConcreteTypeParamsFromClass= getGenericToConcreteTypeParamsMap(
clazz, clazzErasure);
for (IMethodBinding declaredMethod : clazzErasure.getDeclaredMethods()) {
if (declaredMethod.getName().equals(methodBinding.getName())) {
Map<ITypeBinding, ITypeBinding> genericToConcreteTypeParams= getGenericToConcreteTypeParamsMap(
methodBinding, declaredMethod);
genericToConcreteTypeParams.putAll(genericToConcreteTypeParamsFromClass);
if (parameterizedTypesMatch(genericToConcreteTypeParams, methodBinding, declaredMethod)) {
return true;
}
}
}
}
return false;
}
private static Map<ITypeBinding, ITypeBinding> getGenericToConcreteTypeParamsMap(final IMethodBinding method,
final IMethodBinding methodErasure) {
return getGenericToConcreteTypeParamsMap(method.getTypeArguments(), methodErasure.getTypeParameters());
}
private static Map<ITypeBinding, ITypeBinding> getGenericToConcreteTypeParamsMap(final ITypeBinding clazz,
final ITypeBinding clazzErasure) {
return getGenericToConcreteTypeParamsMap(clazz.getTypeArguments(), clazzErasure.getTypeParameters());
}
private static Map<ITypeBinding, ITypeBinding> getGenericToConcreteTypeParamsMap(final ITypeBinding[] typeParams,
final ITypeBinding[] genericTypeParams) {
final Map<ITypeBinding, ITypeBinding> results= new HashMap<>();
for (int i= 0; i < genericTypeParams.length && i < typeParams.length; i++) {
results.put(genericTypeParams[i], typeParams[i]);
}
return results;
}
private static boolean parameterizedTypesMatch(final Map<ITypeBinding, ITypeBinding> genericToConcreteTypeParams,
final IMethodBinding parameterizedMethod, final IMethodBinding genericMethod) {
ITypeBinding[] paramTypes= parameterizedMethod.getParameterTypes();
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= null;
if (genericParamType.isArray()) {
ITypeBinding concreteElementType= genericToConcreteTypeParams.get(genericParamType.getElementType());
if (concreteElementType != null) {
concreteParamType= concreteElementType.createArrayType(genericParamType.getDimensions());
}
} else {
concreteParamType= genericToConcreteTypeParams.get(genericParamType);
}
if (concreteParamType == null) {
concreteParamType= genericParamType;
}
final ITypeBinding erasure1= paramTypes[i].getErasure();
final String erasureName1;
if (erasure1.isPrimitive()) {
erasureName1= Bindings.getBoxedTypeName(erasure1.getQualifiedName());
} else {
erasureName1= erasure1.getQualifiedName();
}
final ITypeBinding erasure2= concreteParamType.getErasure();
final String erasureName2;
if (erasure2.isPrimitive()) {
erasureName2= Bindings.getBoxedTypeName(erasure2.getQualifiedName());
} else {
erasureName2= erasure2.getQualifiedName();
}
if (!erasureName1.equals(erasureName2)) {
return false;
}
}
return true;
}
/**
* 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(final ITypeBinding typeBinding, final String qualifiedTypeName) {
if (typeBinding == null) {
return null;
}
ITypeBinding typeErasure= typeBinding.getErasure();
if (qualifiedTypeName.equals(typeBinding.getQualifiedName())
|| qualifiedTypeName.equals(typeErasure.getQualifiedName())) {
return typeBinding;
}
return findImplementedType2(typeBinding, qualifiedTypeName);
}
private static ITypeBinding findImplementedType2(final ITypeBinding typeBinding, final String qualifiedTypeName) {
final ITypeBinding superclass= typeBinding.getSuperclass();
if (superclass != null) {
String superClassQualifiedName= superclass.getErasure().getQualifiedName();
if (qualifiedTypeName.equals(superClassQualifiedName)) {
return superclass;
}
ITypeBinding implementedType= findImplementedType2(superclass, qualifiedTypeName);
if (implementedType != null) {
return implementedType;
}
}
for (ITypeBinding itfBinding : typeBinding.getInterfaces()) {
String itfQualifiedName= itfBinding.getErasure().getQualifiedName();
if (qualifiedTypeName.equals(itfQualifiedName)) {
return itfBinding;
}
ITypeBinding implementedType= findImplementedType2(itfBinding, qualifiedTypeName);
if (implementedType != null) {
return implementedType;
}
}
return null;
}
public static Modifier findModifierNode(int flag, List<IExtendedModifier> modifiers) {
for (IExtendedModifier curr : modifiers) {
if (curr instanceof Modifier && ((Modifier) curr).getKeyword().toFlagValue() == flag) {
return (Modifier) curr;
}
}
return null;
}
/**
* Returns whether the two provided codes structurally match.
*
* @param referenceStatements the first code to compare
* @param comparedStatements the second code to compare
* @return true if the two provided codes structurally match, false otherwise
*/
public static boolean match(final List<Statement> referenceStatements, final List<Statement> comparedStatements) {
return match(ASTSemanticMatcher.INSTANCE, referenceStatements, comparedStatements);
}
/**
* Returns whether the two provided codes structurally match.
*
* @param matcher the AST matcher
* @param referenceStatements the first code to compare
* @param comparedStatements the second code to compare
* @return true if the two provided codes structurally match, false otherwise
*/
public static boolean match(final ASTSemanticMatcher matcher, final List<Statement> referenceStatements, final List<Statement> comparedStatements) {
if (referenceStatements.size() != comparedStatements.size()) {
return false;
}
for (int codeLine= 0; codeLine < referenceStatements.size(); codeLine++) {
if (!match(matcher, referenceStatements.get(codeLine), comparedStatements.get(codeLine))) {
return false;
}
}
return true;
}
/**
* Returns whether the two provided nodes structurally match.
*
* @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(final ASTNode node1, final ASTNode node2) {
return match(ASTSemanticMatcher.INSTANCE, node1, node2);
}
/**
* 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(final ASTSemanticMatcher matcher, final ASTNode node1, final ASTNode node2) {
return matcher.safeSubtreeMatch(node1, node2);
}
public static ITypeBinding getTypeBinding(CompilationUnit root, IType type) throws JavaModelException {
if (type.isAnonymous()) {
final IJavaElement parent= type.getParent();
if (parent instanceof IField && Flags.isEnum(((IMember) parent).getFlags())) {
final EnumConstantDeclaration constant= (EnumConstantDeclaration) NodeFinder.perform(root, ((ISourceReference) parent).getSourceRange());
if (constant != null) {
final AnonymousClassDeclaration declaration= constant.getAnonymousClassDeclaration();
if (declaration != null)
return declaration.resolveBinding();
}
} else {
final ClassInstanceCreation creation= getParent(NodeFinder.perform(root, type.getNameRange()), ClassInstanceCreation.class);
if (creation != null)
return creation.resolveTypeBinding();
}
} else {
final AbstractTypeDeclaration declaration= getParent(NodeFinder.perform(root, type.getNameRange()), AbstractTypeDeclaration.class);
if (declaration != null)
return declaration.resolveBinding();
}
return null;
}
/**
* Escapes a string value to a literal that can be used in Java source.
*
* @param stringValue the string value
* @return the escaped string
* @see StringLiteral#getEscapedValue()
*/
public static String getEscapedStringLiteral(String stringValue) {
StringLiteral stringLiteral= AST.newAST(IASTSharedValues.SHARED_AST_LEVEL, false).newStringLiteral();
stringLiteral.setLiteralValue(stringValue);
return stringLiteral.getEscapedValue();
}
/**
* Escapes a character value to a literal that can be used in Java source.
*
* @param ch the character value
* @return the escaped string
* @see CharacterLiteral#getEscapedValue()
*/
public static String getEscapedCharacterLiteral(char ch) {
CharacterLiteral characterLiteral= AST.newAST(IASTSharedValues.SHARED_AST_LEVEL, false).newCharacterLiteral();
characterLiteral.setCharValue(ch);
return characterLiteral.getEscapedValue();
}
/**
* If the given <code>node</code> has already been rewritten, undo that rewrite and return the
* replacement version of the node. Otherwise, return the result of
* {@link ASTRewrite#createCopyTarget(ASTNode)}.
*
* @param rewrite ASTRewrite for the given node
* @param node the node to get the replacement or to create a copy placeholder for
* @param group the edit group which collects the corresponding text edits, or <code>null</code>
* if ungrouped
* @return the replacement node if the given <code>node</code> has already been rewritten or the
* new copy placeholder node
*/
public static ASTNode getCopyOrReplacement(ASTRewrite rewrite, ASTNode node, TextEditGroup group) {
ASTNode rewrittenNode= (ASTNode) rewrite.get(node.getParent(), node.getLocationInParent());
if (rewrittenNode != node) {
// Undo previous rewrite to avoid the problem that the same node would be inserted in two places:
rewrite.replace(rewrittenNode, node, group);
return rewrittenNode;
}
return rewrite.createCopyTarget(node);
}
/**
* Type-safe variant of {@link ASTRewrite#createMoveTarget(ASTNode)}.
*
* @param rewrite ASTRewrite for the given node
* @param nodes the nodes to create a move placeholder for
* @return the new placeholder nodes
* @throws IllegalArgumentException if the node is null, or if the node
* is not part of the rewrite's AST
*/
@SuppressWarnings("unchecked")
public static <T extends ASTNode> List<T> createMoveTarget(final ASTRewrite rewrite, final Collection<T> nodes) {
if (nodes != null) {
List<T> newNodes= new ArrayList<>(nodes.size());
for (T node : nodes) {
newNodes.add((T) rewrite.createMoveTarget(node));
}
return newNodes;
}
return null;
}
/**
* Type-safe variant of {@link ASTRewrite#createMoveTarget(ASTNode)}.
*
* @param rewrite ASTRewrite for the given node
* @param node the node to create a move placeholder for
* @return the new placeholder node
* @throws IllegalArgumentException if the node is null, or if the node
* is not part of the rewrite's AST
*/
@SuppressWarnings("unchecked")
public static <T extends ASTNode> T createMoveTarget(ASTRewrite rewrite, T node) {
return (T) rewrite.createMoveTarget(node);
}
/**
* Type-safe variant of {@link ASTNode#copySubtree(AST, ASTNode)}.
*
* @param target the AST that is to own the nodes in the result
* @param node the node to copy, or <code>null</code> if none
* @return the copied node, or <code>null</code> if <code>node</code>
* is <code>null</code>
*/
@SuppressWarnings("unchecked")
public static <T extends ASTNode> T copySubtree(AST target, T node) {
return (T) ASTNode.copySubtree(target, node);
}
/**
* Removes the provided node from the AST leaving the leading comment.
*
* @param rewrite The AST Rewriter
* @param node The node to remove
* @param editGroup The edit group
* @see ASTRewrite#remove(ASTNode, org.eclipse.text.edits.TextEditGroup)
*/
public static void removeButKeepComment(final ASTRewrite rewrite, final ASTNode node, final TextEditGroup editGroup) {
node.setProperty(UNTOUCH_COMMENT, Boolean.TRUE);
rewrite.remove(node, editGroup);
}
/**
* Replaces the provided node from the AST with the provided replacement node.
*
* @param rewrite The AST Rewriter
* @param node The node to remove
* @param replacement The replacement node
* @param editGroup The edit group
* @see ASTRewrite#replace(ASTNode, ASTNode,
* org.eclipse.text.edits.TextEditGroup)
*/
public static void replaceButKeepComment(final ASTRewrite rewrite, final ASTNode node, final ASTNode replacement, final TextEditGroup editGroup) {
node.setProperty(UNTOUCH_COMMENT, Boolean.TRUE);
rewrite.replace(node, replacement, editGroup);
}
/**
* Returns a list of local variable names which are visible at the given node.
*
* @param node the AST node
* @return a list of local variable names visible at the given node
* @see ScopeAnalyzer#getDeclarationsInScope(int, int)
* @since 3.10
*/
public static List<String> getVisibleLocalVariablesInScope(ASTNode node) {
List<String> variableNames= new ArrayList<>();
CompilationUnit root= (CompilationUnit) node.getRoot();
IBinding[] bindings= new ScopeAnalyzer(root).
getDeclarationsInScope(node.getStartPosition(), ScopeAnalyzer.VARIABLES | ScopeAnalyzer.NO_FIELDS | ScopeAnalyzer.CHECK_VISIBILITY);
for (IBinding binding : bindings) {
variableNames.add(binding.getName());
}
return variableNames;
}
/**
* Checks whether the given <code>exprStatement</code> has a semicolon at the end.
*
* @param exprStatement the {@link ExpressionStatement} to check the semicolon
* @param cu the compilation unit
* @return <code>true</code> if the given <code>exprStatement</code> has a semicolon at the end,
* <code>false</code> otherwise
*/
public static boolean hasSemicolon(ExpressionStatement exprStatement, ICompilationUnit cu) {
boolean hasSemicolon= true;
if ((exprStatement.getFlags() & ASTNode.RECOVERED) != 0) {
try {
Expression expression= exprStatement.getExpression();
TokenScanner scanner= new TokenScanner(cu);
hasSemicolon= scanner.readNext(expression.getStartPosition() + expression.getLength(), true) == ITerminalSymbols.TokenNameSEMICOLON;
} catch (CoreException e) {
hasSemicolon= false;
}
}
return hasSemicolon;
}
/**
* Checks if the given <code>node</code> is a {@link VariableDeclarationStatement}
* or a {@link SimpleName} whose type is 'var'.
*
* @param node the AST node
* @param astRoot the AST node of the compilation unit
* @return <code>true</code> if the given {@link ASTNode} represents a
* {@link SimpleName} or {@link VariableDeclarationStatement} that has a 'var' type
* and <code>false</code> otherwise.
*/
public static boolean isVarType(ASTNode node, CompilationUnit astRoot) {
IJavaElement root= astRoot.getJavaElement();
if (root == null) {
return false;
}
IJavaProject javaProject= root.getJavaProject();
if (javaProject == null) {
return false;
}
if (!JavaModelUtil.is10OrHigher(javaProject)) {
return false;
}
Type type= null;
if (node instanceof SimpleName) {
IBinding binding= null;
SimpleName name= (SimpleName) node;
binding= name.resolveBinding();
if (!(binding instanceof IVariableBinding)) {
return false;
}
IVariableBinding varBinding= (IVariableBinding) binding;
if (varBinding.isField() || varBinding.isParameter()) {
return false;
}
ASTNode varDeclaration= astRoot.findDeclaringNode(varBinding);
if (varDeclaration == null) {
return false;
}
ITypeBinding typeBinding= varBinding.getType();
if (typeBinding == null || typeBinding.isAnonymous() || typeBinding.isIntersectionType() || typeBinding.isWildcardType()) {
return false;
}
if (varDeclaration instanceof SingleVariableDeclaration) {
type= ((SingleVariableDeclaration) varDeclaration).getType();
} else if (varDeclaration instanceof VariableDeclarationFragment) {
ASTNode parent= varDeclaration.getParent();
if (parent instanceof VariableDeclarationStatement) {
type= ((VariableDeclarationStatement) parent).getType();
} else if (parent instanceof VariableDeclarationExpression) {
type= ((VariableDeclarationExpression) parent).getType();
}
}
} else if (node instanceof VariableDeclarationStatement) {
type= ((VariableDeclarationStatement)node).getType();
} else {
return false;
}
return type == null ? false : type.isVar();
}
/**
* Return a list of leading comments for a specified node
*
* @param node - ASTNode in a CompilationUnit
* @return list of Comment nodes
*/
public static List<Comment> getLeadingComments(ASTNode node) {
List<Comment> comments= new ArrayList<>();
CompilationUnit cu= (CompilationUnit)node.getRoot();
List<Comment> commentList= cu.getCommentList();
for (Comment comment : commentList) {
if (comment.getStartPosition() >= cu.getExtendedStartPosition(node)
&& comment.getStartPosition() + comment.getLength() < node.getStartPosition()) {
comments.add(comment);
}
}
return comments;
}
/**
* Return a list of trailing comments for a specified node
*
* @param node - ASTNode in a CompilationUnit
* @return list of Comment nodes
*/
public static List<Comment> getTrailingComments(ASTNode node) {
List<Comment> comments= new ArrayList<>();
CompilationUnit cu= (CompilationUnit)node.getRoot();
List<Comment> commentList= cu.getCommentList();
int extendedStart= cu.getExtendedStartPosition(node);
int extendedLength= cu.getExtendedLength(node);
for (Comment comment : commentList) {
if (comment.getStartPosition() > node.getStartPosition()
&& comment.getStartPosition() < extendedStart + extendedLength) {
comments.add(comment);
}
}
return comments;
}
}