blob: 59724c2e6d02c4a986f41e79387506704af9d86c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 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
*******************************************************************************/
package org.eclipse.jdt.internal.corext.dom;
import java.util.ArrayList;
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.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CastExpression;
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.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.IfStatement;
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.InfixExpression;
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.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.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.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.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 {
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;
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);
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, new String(), typeRoot.findRecommendedLineSeparator());
}
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;
}
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;
}
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;
}
public static boolean isStatic(BodyDeclaration declaration) {
return Modifier.isStatic(declaration.getModifiers());
}
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();
}
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;
}
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) {
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 false;
}
if (methodBinding != null) {
ITypeBinding invocationTargetType;
invocationTargetType= getInvocationType(parent, methodBinding, invocationQualifier);
if (invocationTargetType != null) {
TypeBindingVisitor visitor= new AmbiguousTargetMethodAnalyzer(invocationTargetType, methodBinding, argumentIndex, argumentCount, expressionIsExplicitlyTyped);
return !(visitor.visit(invocationTargetType) && Bindings.visitHierarchy(invocationTargetType, visitor));
}
}
return true;
}
/**
* 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 AmbiguousTargetMethodAnalyzer 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 AmbiguousTargetMethodAnalyzer(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) {
IMethodBinding[] methods= type.getDeclaredMethods();
for (int i= 0; i < methods.length; i++) {
IMethodBinding candidate= methods[i];
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 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 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 (int p= 0; p < pathes.length; p++) {
StructuralPropertyDescriptor[] path= pathes[p];
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 <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;
}
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 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 (int i= 0; i < problems.length; i++) {
IProblem problem= problems[i];
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 (int i= 0; i < messages.length; i++) {
Message message= messages[i];
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 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(MethodInvocation node, String typeQualifiedName, String methodName,
String... parameterTypesQualifiedNames) {
if (node == null
|| node.resolveMethodBinding() == null
|| !node.resolveMethodBinding().getDeclaringClass().getQualifiedName().equals(typeQualifiedName)
|| !node.getName().getIdentifier().equals(methodName)) {
return false;
}
if (node.arguments() == null && parameterTypesQualifiedNames == null) {
return true;
}
if (node.arguments() == null || parameterTypesQualifiedNames == null) {
return true;
}
if (node.arguments().size() == parameterTypesQualifiedNames.length) {
return true;
}
for (int i= 0; i < parameterTypesQualifiedNames.length; i++) {
if (((Type) node.typeArguments().get(i)).resolveBinding() == null
|| ((Type) node.typeArguments().get(i)).resolveBinding().getQualifiedName() != parameterTypesQualifiedNames[i]) {
return false;
}
}
return true;
}
public static Modifier findModifierNode(int flag, List<IExtendedModifier> modifiers) {
for (int i= 0; i < modifiers.size(); i++) {
Object curr= modifiers.get(i);
if (curr instanceof Modifier && ((Modifier) curr).getKeyword().toFlagValue() == flag) {
return (Modifier) curr;
}
}
return null;
}
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 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);
}
/**
* 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();
}
}