blob: 20cc79c0a4cab8c589c73608af00a97359a87ac0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2021 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Copied from package org.eclipse.jdt.internal.corext.fix.LambdaExpressionsFix
*
* Contributors:
* IBM Corporation - initial API and implementation
* Jerome Cambon <jerome.cambon@oracle.com> - [1.8][clean up][quick assist] Convert lambda to anonymous must qualify references to 'this'/'super' - https://bugs.eclipse.org/430573
* Stephan Herrmann - Contribution for Bug 463360 - [override method][null] generating method override should not create redundant null annotations
* Red Hat Inc. - modified to create core class in jdt.core.manipulation
* Microsoft Corporation - read preferences from the compilation unit
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.jdt.core.ICompilationUnit;
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.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
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.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
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.LambdaExpression;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.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.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.TypeLocation;
import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
import org.eclipse.jdt.core.util.IModifierConstants;
import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2Core;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.AbortSearchException;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.HierarchicalASTVisitor;
import org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.refactoring.structure.ImportRemover;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings;
public class LambdaExpressionsFixCore extends CompilationUnitRewriteOperationsFixCore {
public static final class FunctionalAnonymousClassesFinder extends ASTVisitor {
private final ArrayList<ClassInstanceCreation> fNodes= new ArrayList<>();
public static ArrayList<ClassInstanceCreation> perform(ASTNode node) {
FunctionalAnonymousClassesFinder finder= new FunctionalAnonymousClassesFinder();
node.accept(finder);
return finder.fNodes;
}
@Override
public boolean visit(ClassInstanceCreation node) {
if (isFunctionalAnonymous(node) && !fConversionRemovesAnnotations) {
fNodes.add(node);
}
return true;
}
}
public static final class LambdaExpressionsFinder extends ASTVisitor {
private final ArrayList<LambdaExpression> fNodes= new ArrayList<>();
public static ArrayList<LambdaExpression> perform(ASTNode node) {
LambdaExpressionsFinder finder= new LambdaExpressionsFinder();
node.accept(finder);
return finder.fNodes;
}
@Override
public boolean visit(LambdaExpression node) {
ITypeBinding typeBinding= node.resolveTypeBinding();
if (typeBinding != null && typeBinding.getFunctionalInterfaceMethod() != null) {
fNodes.add(node);
}
return true;
}
}
public static final class SuperThisReferenceFinder extends HierarchicalASTVisitor {
private ITypeBinding fFunctionalInterface;
private MethodDeclaration fMethodDeclaration;
static boolean hasReference(MethodDeclaration node) {
try {
SuperThisReferenceFinder finder= new SuperThisReferenceFinder();
ClassInstanceCreation cic= (ClassInstanceCreation) node.getParent().getParent();
finder.fFunctionalInterface= cic.getType().resolveBinding();
finder.fMethodDeclaration= node;
node.accept(finder);
} catch (AbortSearchException e) {
return true;
}
return false;
}
@Override
public boolean visit(AnonymousClassDeclaration node) {
return false;
}
@Override
public boolean visit(BodyDeclaration node) {
return false;
}
@Override
public boolean visit(MethodDeclaration node) {
return node == fMethodDeclaration;
}
@Override
public boolean visit(ThisExpression node) {
if (node.getQualifier() == null) {
throw new AbortSearchException();
}
return true; // references to outer scope are harmless
}
@Override
public boolean visit(SuperMethodInvocation node) {
if (node.getQualifier() == null) {
throw new AbortSearchException();
} else {
IBinding qualifierType= node.getQualifier().resolveBinding();
if (qualifierType instanceof ITypeBinding && ((ITypeBinding) qualifierType).isInterface()) {
throw new AbortSearchException(); // JLS8: new overloaded meaning of 'interface'.super.'method'(..)
}
}
return true; // references to outer scopes are harmless
}
@Override
public boolean visit(SuperFieldAccess node) {
if (node.getQualifier() == null) {
throw new AbortSearchException();
}
return true; // references to outer scope are harmless
}
@Override
public boolean visit(MethodInvocation node) {
IMethodBinding binding= node.resolveMethodBinding();
if (binding != null && !JdtFlags.isStatic(binding) && node.getExpression() == null && Bindings.isSuperType(binding.getDeclaringClass(), fFunctionalInterface, false)) {
throw new AbortSearchException();
}
return true;
}
}
public static final class FinalFieldAccessInFieldDeclarationFinder extends HierarchicalASTVisitor {
private MethodDeclaration fMethodDeclaration;
private ASTNode fFieldDeclaration;
static boolean hasReference(MethodDeclaration node) {
try {
FinalFieldAccessInFieldDeclarationFinder finder= new FinalFieldAccessInFieldDeclarationFinder();
finder.fMethodDeclaration= node;
finder.fFieldDeclaration= finder.findFieldDeclaration(node);
if (finder.fFieldDeclaration == null) {
return false;
}
node.accept(finder);
} catch (AbortSearchException e) {
return true;
}
return false;
}
private ASTNode findFieldDeclaration(ASTNode node) {
while (node != null) {
if (node instanceof FieldDeclaration) {
return node;
}
if (node instanceof AbstractTypeDeclaration) {
return null;
}
node= node.getParent();
}
return null;
}
@Override
public boolean visit(AnonymousClassDeclaration node) {
return false;
}
@Override
public boolean visit(BodyDeclaration node) {
return false;
}
@Override
public boolean visit(MethodDeclaration node) {
return node == fMethodDeclaration;
}
private void checkForUninitializedFinalReference(IBinding binding) {
if (binding instanceof IVariableBinding) {
int modifiers= ((IVariableBinding)binding).getModifiers();
if ((modifiers & IModifierConstants.ACC_FINAL) == IModifierConstants.ACC_FINAL) {
if (((IVariableBinding) binding).isField()) {
ASTNode decl= ((CompilationUnit)fMethodDeclaration.getRoot()).findDeclaringNode(binding);
if (decl instanceof VariableDeclaration && ((VariableDeclaration)decl).getInitializer() == null) {
throw new AbortSearchException();
}
}
}
}
}
@Override
public boolean visit(SuperFieldAccess node) {
IVariableBinding binding= node.resolveFieldBinding();
if (binding == null) {
return true;
}
IVariableBinding decl= binding.getVariableDeclaration();
checkForUninitializedFinalReference(decl);
return true;
}
@Override
public boolean visit(SimpleName node) {
IBinding binding= node.resolveBinding();
checkForUninitializedFinalReference(binding);
return true;
}
@Override
public boolean visit(QualifiedName node) {
IBinding binding= node.resolveBinding();
checkForUninitializedFinalReference(binding);
return true;
}
@Override
public boolean visit(FieldAccess node) {
IVariableBinding binding= node.resolveFieldBinding();
if (binding == null) {
return true;
}
IVariableBinding decl= binding.getVariableDeclaration();
checkForUninitializedFinalReference(decl);
return true;
}
}
public static final class SuperThisQualifier extends HierarchicalASTVisitor {
private ITypeBinding fQualifierTypeBinding;
private ImportRewrite fImportRewrite;
private ASTRewrite fASTRewrite;
private TextEditGroup fGroup;
public static void perform(LambdaExpression lambdaExpression, ITypeBinding parentTypeBinding, CompilationUnitRewrite cuRewrite, TextEditGroup group) {
SuperThisQualifier qualifier= new SuperThisQualifier();
qualifier.fQualifierTypeBinding= parentTypeBinding;
qualifier.fImportRewrite= cuRewrite.getImportRewrite();
qualifier.fASTRewrite= cuRewrite.getASTRewrite();
qualifier.fGroup= group;
lambdaExpression.accept(qualifier);
}
public Name getQualifierTypeName() {
String typeName= fImportRewrite.addImport(fQualifierTypeBinding);
return fASTRewrite.getAST().newName(typeName);
}
@Override
public boolean visit(AnonymousClassDeclaration node) {
return false;
}
@Override
public boolean visit(BodyDeclaration node) {
return false;
}
@Override
public boolean visit(SuperFieldAccess node) {
if (node.getQualifier() == null) {
fASTRewrite.set(node, SuperFieldAccess.QUALIFIER_PROPERTY, getQualifierTypeName(), fGroup);
}
return true;
}
@Override
public boolean visit(SuperMethodInvocation node) {
if (node.getQualifier() == null) {
fASTRewrite.set(node, SuperMethodInvocation.QUALIFIER_PROPERTY, getQualifierTypeName(), fGroup);
}
return true;
}
@Override
public boolean visit(ThisExpression node) {
if (node.getQualifier() == null) {
fASTRewrite.set(node, ThisExpression.QUALIFIER_PROPERTY, getQualifierTypeName(), fGroup);
}
return true;
}
}
public static final class AnnotationsFinder extends ASTVisitor {
public static boolean hasAnnotations(SingleVariableDeclaration methodParameter) {
try {
AnnotationsFinder finder= new AnnotationsFinder();
methodParameter.accept(finder);
} catch (AbortSearchException e) {
return true;
}
return false;
}
@Override
public boolean visit(MarkerAnnotation node) {
throw new AbortSearchException();
}
@Override
public boolean visit(NormalAnnotation node) {
throw new AbortSearchException();
}
@Override
public boolean visit(SingleMemberAnnotation node) {
throw new AbortSearchException();
}
}
private static final class MethodRecursionFinder extends HierarchicalASTVisitor {
private MethodDeclaration fMethodDeclaration;
private IMethodBinding fMethodBinding;
private ASTNode fFieldDeclaration;
private static boolean isRecursiveLocal(MethodDeclaration node) {
try {
MethodRecursionFinder finder= new MethodRecursionFinder();
finder.fMethodDeclaration= node;
finder.fFieldDeclaration= finder.findFieldDeclaration(node);
if (finder.fFieldDeclaration != null) {
return false;
}
finder.fMethodBinding= finder.fMethodDeclaration.resolveBinding();
if (finder.fMethodBinding == null) {
return false;
}
node.accept(finder);
} catch (AbortSearchException e) {
return true;
}
return false;
}
private ASTNode findFieldDeclaration(ASTNode node) {
ASTNode originalNode= node;
while (node != null) {
if (node instanceof FieldDeclaration) {
return node;
}
if (node instanceof AbstractTypeDeclaration) {
return null;
}
if (node instanceof MethodDeclaration && node != originalNode) {
return null;
}
node= node.getParent();
}
return null;
}
@Override
public boolean visit(MethodInvocation node) {
if (node.getExpression() == null) {
IMethodBinding binding= node.resolveMethodBinding();
if (binding != null && binding.isEqualTo(fMethodBinding)) {
throw new AbortSearchException();
}
}
return true;
}
}
public static class CreateLambdaOperation extends CompilationUnitRewriteOperation {
private final List<ClassInstanceCreation> fExpressions;
public CreateLambdaOperation(List<ClassInstanceCreation> expressions) {
fExpressions= expressions;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
final ASTRewrite rewrite= cuRewrite.getASTRewrite();
ImportRemover importRemover= cuRewrite.getImportRemover();
final AST ast= rewrite.getAST();
HashMap<ClassInstanceCreation, HashSet<String>> cicToNewNames= new HashMap<>();
for (int i= 0; i < fExpressions.size(); i++) {
ClassInstanceCreation classInstanceCreation= fExpressions.get(i);
AnonymousClassDeclaration anonymTypeDecl= classInstanceCreation.getAnonymousClassDeclaration();
List<BodyDeclaration> bodyDeclarations= anonymTypeDecl.bodyDeclarations();
Object object= bodyDeclarations.get(0);
if (!(object instanceof MethodDeclaration)) {
continue;
}
HashSet<String> excludedNames= new HashSet<>();
if (i != 0) {
for (ClassInstanceCreation convertedCic : fExpressions.subList(0, i)) {
if (ASTNodes.isParent(convertedCic, classInstanceCreation)) {
excludedNames.addAll(cicToNewNames.get(convertedCic));
}
}
}
final MethodDeclaration methodDeclaration= (MethodDeclaration) object;
TextEditGroup group= createTextEditGroup(FixMessages.LambdaExpressionsFix_convert_to_lambda_expression, cuRewrite);
HashSet<String> newNames= makeNamesUnique(excludedNames, methodDeclaration, rewrite, group);
cicToNewNames.put(classInstanceCreation, new HashSet<>(newNames));
List<SingleVariableDeclaration> methodParameters= methodDeclaration.parameters();
// use short form with inferred parameter types and without parentheses if possible
boolean createExplicitlyTypedParameters= false;
for (SingleVariableDeclaration methodParameter : methodParameters) {
if (AnnotationsFinder.hasAnnotations(methodParameter)) {
createExplicitlyTypedParameters= true;
break;
}
}
LambdaExpression lambdaExpression= ast.newLambdaExpression();
List<VariableDeclaration> lambdaParameters= lambdaExpression.parameters();
lambdaExpression.setParentheses(createExplicitlyTypedParameters || methodParameters.size() != 1);
for (SingleVariableDeclaration methodParameter : methodParameters) {
if (createExplicitlyTypedParameters) {
lambdaParameters.add((SingleVariableDeclaration) rewrite.createCopyTarget(methodParameter));
importRemover.registerRetainedNode(methodParameter);
} else {
VariableDeclarationFragment lambdaParameter= ast.newVariableDeclarationFragment();
lambdaParameter.setName((SimpleName) rewrite.createCopyTarget(methodParameter.getName()));
lambdaParameters.add(lambdaParameter);
}
}
Block body= methodDeclaration.getBody();
List<Statement> statements= body.statements();
ASTNode lambdaBody= body;
if (statements.size() == 1) {
// use short form with just an expression body if possible
Statement statement= statements.get(0);
if (statement instanceof ExpressionStatement) {
lambdaBody= ((ExpressionStatement) statement).getExpression();
} else if (statement instanceof ReturnStatement) {
Expression returnExpression= ((ReturnStatement) statement).getExpression();
if (returnExpression != null) {
lambdaBody= returnExpression;
}
}
}
final Set<ITypeBinding> inheritedTypes= new HashSet<>();
collectInheritedTypes(anonymTypeDecl.resolveBinding(), inheritedTypes);
ASTVisitor inheritedFieldsVisitor= new ASTVisitor() {
@Override
public boolean visit(final SimpleName node) {
if ((!(node.getParent() instanceof QualifiedName) || node.getLocationInParent() != QualifiedName.NAME_PROPERTY)
&& (!(node.getParent() instanceof FieldAccess) || node.getLocationInParent() != FieldAccess.NAME_PROPERTY)
&& (!(node.getParent() instanceof SuperFieldAccess) || node.getLocationInParent() != SuperFieldAccess.NAME_PROPERTY)
&& node.resolveBinding() != null
&& node.resolveBinding().getKind() == IBinding.VARIABLE) {
IVariableBinding variableBinding= (IVariableBinding) node.resolveBinding();
if (variableBinding != null
&& (variableBinding.getModifiers() & Modifier.STATIC) != 0
&& variableBinding.isField()
&& inheritedTypes.contains(variableBinding.getDeclaringClass())) {
Type copyOfClassName= (Type) rewrite.createCopyTarget(classInstanceCreation.getType());
QualifiedType replacement= ast.newQualifiedType(copyOfClassName, ASTNodes.createMoveTarget(rewrite, node));
rewrite.replace(node, replacement, group);
return false;
}
}
return true;
}
};
lambdaBody.accept(inheritedFieldsVisitor);
ASTNode fragment= ASTNodes.getFirstAncestorOrNull(classInstanceCreation, VariableDeclarationFragment.class, BodyDeclaration.class);
if (fragment instanceof VariableDeclarationFragment) {
final VariableDeclarationFragment actualFragment= (VariableDeclarationFragment) fragment;
if (actualFragment.getParent() instanceof FieldDeclaration) {
FieldDeclaration fieldDeclaration= (FieldDeclaration) actualFragment.getParent();
ASTNode declarationClass= ASTNodes.getFirstAncestorOrNull(fieldDeclaration, TypeDeclaration.class);
if (declarationClass instanceof TypeDeclaration) {
TypeDeclaration typeDeclaration= (TypeDeclaration) declarationClass;
final List<FieldDeclaration> nextFields= new ArrayList<>(typeDeclaration.getFields().length);
boolean isBefore= true;
for (FieldDeclaration oneField : typeDeclaration.getFields()) {
if (oneField == fieldDeclaration) {
isBefore= false;
}
if (!isBefore) {
nextFields.add(oneField);
}
}
ASTVisitor visitor= new ASTVisitor() {
@Override
public boolean visit(final MethodInvocation node) {
ITypeBinding fieldType= fieldDeclaration.getType().resolveBinding();
ASTNode declaration= ASTNodes.findDeclaration(node.resolveMethodBinding(), declarationClass);
if (node.getExpression() == null && fieldType != null && methodDeclaration == declaration) {
ASTNode replacement;
if ((fieldDeclaration.getModifiers() & Modifier.STATIC) != 0) {
SimpleName copyOfClassName= (SimpleName) rewrite.createCopyTarget(typeDeclaration.getName());
replacement= ast.newQualifiedName(copyOfClassName, (SimpleName) rewrite.createCopyTarget(actualFragment.getName()));
} else {
FieldAccess newFieldAccess= ast.newFieldAccess();
newFieldAccess.setExpression(ast.newThisExpression());
newFieldAccess.setName((SimpleName) rewrite.createCopyTarget(actualFragment.getName()));
replacement= newFieldAccess;
}
rewrite.set(node, MethodInvocation.EXPRESSION_PROPERTY, replacement, group);
return false;
}
return true;
}
@Override
public boolean visit(final SimpleName node) {
if ((!(node.getParent() instanceof QualifiedName) || node.getLocationInParent() != QualifiedName.NAME_PROPERTY)
&& (!(node.getParent() instanceof FieldAccess) || node.getLocationInParent() != FieldAccess.NAME_PROPERTY)
&& (!(node.getParent() instanceof SuperFieldAccess) || node.getLocationInParent() != SuperFieldAccess.NAME_PROPERTY)) {
ASTNode declaration= ASTNodes.findDeclaration(node.resolveBinding(), declarationClass);
if (declaration instanceof VariableDeclarationFragment && declaration.getParent() instanceof FieldDeclaration) {
FieldDeclaration currentField= (FieldDeclaration) declaration.getParent();
if (nextFields.contains(currentField)) {
if ((currentField.getModifiers() & Modifier.STATIC) != 0) {
SimpleName copyOfClassName= (SimpleName) rewrite.createCopyTarget(typeDeclaration.getName());
QualifiedName replacement= ast.newQualifiedName(copyOfClassName, ASTNodes.createMoveTarget(rewrite, node));
rewrite.replace(node, replacement, group);
} else {
FieldAccess newFieldAccess= ast.newFieldAccess();
newFieldAccess.setExpression(ast.newThisExpression());
newFieldAccess.setName(ASTNodes.createMoveTarget(rewrite, node));
rewrite.replace(node, newFieldAccess, group);
}
return false;
}
}
}
return true;
}
@Override
public boolean visit(final ThisExpression node) {
Name qualifier= node.getQualifier();
if (qualifier != null
&& qualifier.resolveBinding() != null
&& qualifier.resolveBinding().getKind() == IBinding.TYPE
&& Objects.equals(qualifier.resolveBinding(), typeDeclaration.resolveBinding())) {
rewrite.remove(qualifier, group);
}
return true;
}
};
lambdaBody.accept(visitor);
}
}
}
//TODO: Bug 421479: [1.8][clean up][quick assist] convert anonymous to lambda must consider lost scope of interface
// lambdaBody.accept(new InterfaceAccessQualifier(rewrite, classInstanceCreation.getType().resolveBinding())); //TODO: maybe need a separate ASTRewrite and string placeholder
lambdaExpression.setBody(ASTNodes.getCopyOrReplacement(rewrite, lambdaBody, group));
Expression replacement= lambdaExpression;
ITypeBinding targetTypeBinding= ASTNodes.getTargetType(classInstanceCreation);
if (ASTNodes.isTargetAmbiguous(classInstanceCreation, ASTNodes.isExplicitlyTypedLambda(lambdaExpression)) || targetTypeBinding.getFunctionalInterfaceMethod() == null) {
CastExpression cast= ast.newCastExpression();
cast.setExpression(lambdaExpression);
ImportRewrite importRewrite= cuRewrite.getImportRewrite();
ImportRewriteContext importRewriteContext= new ContextSensitiveImportRewriteContext(classInstanceCreation, importRewrite);
Type castType= importRewrite.addImport(classInstanceCreation.getType().resolveBinding(), ast, importRewriteContext, TypeLocation.CAST);
cast.setType(castType);
importRemover.registerAddedImports(castType);
replacement= cast;
}
rewrite.replace(classInstanceCreation, replacement, group);
importRemover.registerRemovedNode(classInstanceCreation);
importRemover.registerRetainedNode(lambdaBody);
}
}
private static void collectInheritedTypes(final ITypeBinding anonymType, final Set<ITypeBinding> inheritedTypes) {
if (anonymType != null) {
ITypeBinding motherType= anonymType.getSuperclass();
if (motherType != null) {
inheritedTypes.add(motherType);
collectInheritedTypes(motherType, inheritedTypes);
}
ITypeBinding[] interfaces= anonymType.getInterfaces();
if (interfaces != null && interfaces.length > 0) {
Collections.addAll(inheritedTypes, interfaces);
for (ITypeBinding iTypeBinding : interfaces) {
collectInheritedTypes(iTypeBinding, inheritedTypes);
}
}
}
}
private HashSet<String> makeNamesUnique(HashSet<String> namesFromUpperScope, MethodDeclaration methodDeclaration, ASTRewrite rewrite, TextEditGroup group) {
HashSet<String> newNames= new HashSet<>();
namesFromUpperScope.addAll(ASTNodes.getVisibleLocalVariablesInScope(methodDeclaration));
List<SimpleName> simpleNamesInMethod= getNamesInMethod(methodDeclaration);
List<String> namesInMethod= new ArrayList<>();
for (SimpleName name : simpleNamesInMethod) {
namesInMethod.add(name.getIdentifier());
}
for (int i= 0; i < simpleNamesInMethod.size(); i++) {
SimpleName name= simpleNamesInMethod.get(i);
String identifier= namesInMethod.get(i);
if (namesFromUpperScope.contains(identifier)) {
String newIdentifier= createName(identifier, namesFromUpperScope, namesInMethod, newNames);
namesFromUpperScope.add(newIdentifier);
newNames.add(newIdentifier);
SimpleName[] references= LinkedNodeFinder.findByNode(name.getRoot(), name);
for (SimpleName ref : references) {
rewrite.set(ref, SimpleName.IDENTIFIER_PROPERTY, newIdentifier, group);
}
}
}
return newNames;
}
private List<SimpleName> getNamesInMethod(MethodDeclaration methodDeclaration) {
class NamesCollector extends HierarchicalASTVisitor {
private int fTypeCounter;
private List<SimpleName> fNames= new ArrayList<>();
@Override
public boolean visit(AbstractTypeDeclaration node) {
if (fTypeCounter++ == 0) {
fNames.add(node.getName());
}
return true;
}
@Override
public void endVisit(AbstractTypeDeclaration node) {
fTypeCounter--;
}
@Override
public boolean visit(AnonymousClassDeclaration node) {
fTypeCounter++;
return true;
}
@Override
public void endVisit(AnonymousClassDeclaration node) {
fTypeCounter--;
}
@Override
public boolean visit(VariableDeclaration node) {
if (fTypeCounter == 0) {
fNames.add(node.getName());
}
return true;
}
}
NamesCollector namesCollector= new NamesCollector();
methodDeclaration.accept(namesCollector);
return namesCollector.fNames;
}
private String createName(String candidate, HashSet<String> excludedNames, List<String> namesInMethod, HashSet<String> newNames) {
int i= 1;
String result= candidate;
while (excludedNames.contains(result) || newNames.contains(result) || namesInMethod.contains(result)) {
result= candidate + i++;
}
return result;
}
}
public interface IAnonymousClassCreationOperation {
MethodDeclaration getMethodDeclaration(ICompilationUnit cu, ASTRewrite rewrite, ImportRewrite rewrites,
ImportRewriteContext context, IMethodBinding binding, String[] parameterNames, ITypeBinding targetType,
boolean inInterface, ASTNode astNode) throws CoreException;
}
public static class CreateAnonymousClassCreationOperation extends CompilationUnitRewriteOperation implements IAnonymousClassCreationOperation {
private final List<LambdaExpression> fExpressions;
public CreateAnonymousClassCreationOperation(List<LambdaExpression> changedNodes) {
fExpressions= changedNodes;
}
@Override
public MethodDeclaration getMethodDeclaration(ICompilationUnit cu, ASTRewrite rewrite, ImportRewrite rewrites,
ImportRewriteContext context, IMethodBinding binding, String[] parameterNames, ITypeBinding targetType,
boolean inInterface, ASTNode astNode) throws CoreException {
final CodeGenerationSettings settings= JavaPreferencesSettings.getCodeGenerationSettings(cu);
MethodDeclaration methodDeclaration= StubUtility2Core.createImplementationStubCore(cu, rewrite, rewrites, context, binding, parameterNames, targetType, settings, inInterface,
astNode,
false);
return methodDeclaration;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
rewriteAST(this, cuRewrite, model);
}
public void rewriteAST(IAnonymousClassCreationOperation op, CompilationUnitRewrite cuRewrite, @SuppressWarnings("unused") LinkedProposalModelCore model) throws CoreException {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
AST ast= rewrite.getAST();
for (LambdaExpression lambdaExpression : fExpressions) {
TextEditGroup group= createTextEditGroup(FixMessages.LambdaExpressionsFix_convert_to_anonymous_class_creation, cuRewrite);
ITypeBinding lambdaTypeBinding= lambdaExpression.resolveTypeBinding();
IMethodBinding methodBinding= lambdaTypeBinding.getFunctionalInterfaceMethod();
List<VariableDeclaration> parameters= lambdaExpression.parameters();
String[] parameterNames= new String[parameters.size()];
for (int i= 0; i < parameterNames.length; i++) {
parameterNames[i]= parameters.get(i).getName().getIdentifier();
}
ImportRewrite importRewrite= cuRewrite.getImportRewrite();
ImportRewriteContext importContext= new ContextSensitiveImportRewriteContext(lambdaExpression, importRewrite);
MethodDeclaration methodDeclaration= op.getMethodDeclaration(cuRewrite.getCu(), rewrite, importRewrite, importContext, methodBinding, parameterNames, lambdaTypeBinding, false,
lambdaExpression);
// Qualify reference to this or super
ASTNode parentType= ASTResolving.findParentType(lambdaExpression);
ITypeBinding parentTypeBinding= null;
if (parentType instanceof AbstractTypeDeclaration) {
parentTypeBinding= ((AbstractTypeDeclaration) parentType).resolveBinding();
} else if (parentType instanceof AnonymousClassDeclaration) {
parentTypeBinding= ((AnonymousClassDeclaration) parentType).resolveBinding();
}
if (parentTypeBinding != null) {
parentTypeBinding= Bindings.normalizeTypeBinding(parentTypeBinding);
if (parentTypeBinding != null) {
SuperThisQualifier.perform(lambdaExpression, parentTypeBinding.getTypeDeclaration(), cuRewrite, group);
}
}
Block block;
ASTNode lambdaBody= lambdaExpression.getBody();
if (lambdaBody instanceof Block) {
block= (Block) ASTNodes.getCopyOrReplacement(rewrite, lambdaBody, group);
} else {
block= ast.newBlock();
List<Statement> statements= block.statements();
ITypeBinding returnType= methodBinding.getReturnType();
Expression copyTarget= (Expression) ASTNodes.getCopyOrReplacement(rewrite, lambdaBody, group);
if (Bindings.isVoidType(returnType)) {
ExpressionStatement newExpressionStatement= ast.newExpressionStatement(copyTarget);
statements.add(newExpressionStatement);
} else {
ReturnStatement returnStatement= ast.newReturnStatement();
returnStatement.setExpression(copyTarget);
statements.add(returnStatement);
}
}
methodDeclaration.setBody(block);
AnonymousClassDeclaration anonymousClassDeclaration= ast.newAnonymousClassDeclaration();
List<BodyDeclaration> bodyDeclarations= anonymousClassDeclaration.bodyDeclarations();
bodyDeclarations.add(methodDeclaration);
Type creationType= ASTNodeFactory.newCreationType(ast, lambdaTypeBinding, importRewrite, importContext);
ClassInstanceCreation classInstanceCreation= ast.newClassInstanceCreation();
classInstanceCreation.setType(creationType);
classInstanceCreation.setAnonymousClassDeclaration(anonymousClassDeclaration);
ASTNode toReplace= lambdaExpression;
if (lambdaExpression.getLocationInParent() == CastExpression.EXPRESSION_PROPERTY && lambdaTypeBinding.isEqualTo(((CastExpression) lambdaExpression.getParent()).resolveTypeBinding())) {
// remove cast to same type as the anonymous will use
toReplace= lambdaExpression.getParent();
}
rewrite.replace(toReplace, classInstanceCreation, group);
}
}
}
private static boolean fConversionRemovesAnnotations;
public static LambdaExpressionsFixCore createConvertToLambdaFix(ClassInstanceCreation cic) {
CompilationUnit root= (CompilationUnit) cic.getRoot();
if (!JavaModelUtil.is1d8OrHigher(root.getJavaElement().getJavaProject())) {
return null;
}
if (!LambdaExpressionsFixCore.isFunctionalAnonymous(cic)) {
return null;
}
CreateLambdaOperation op= new CreateLambdaOperation(Collections.singletonList(cic));
String message;
if (fConversionRemovesAnnotations) {
message= FixMessages.LambdaExpressionsFix_convert_to_lambda_expression_removes_annotations;
} else {
message= FixMessages.LambdaExpressionsFix_convert_to_lambda_expression;
}
return new LambdaExpressionsFixCore(message, root, new CompilationUnitRewriteOperation[] { op });
}
public static IProposableFix createConvertToAnonymousClassCreationsFix(LambdaExpression lambda) {
// offer the quick assist at pre 1.8 levels as well to get rid of the compilation error (TODO: offer this as a quick fix in that case)
if (lambda.resolveTypeBinding() == null || lambda.resolveTypeBinding().getFunctionalInterfaceMethod() == null) {
return null;
}
CreateAnonymousClassCreationOperation op= new CreateAnonymousClassCreationOperation(Collections.singletonList(lambda));
CompilationUnit root= (CompilationUnit) lambda.getRoot();
return new LambdaExpressionsFixCore(FixMessages.LambdaExpressionsFix_convert_to_anonymous_class_creation, root, new CompilationUnitRewriteOperation[] { op });
}
public static ICleanUpFixCore createCleanUp(CompilationUnit compilationUnit, boolean useLambda, boolean useAnonymous) {
if (!JavaModelUtil.is1d8OrHigher(compilationUnit.getJavaElement().getJavaProject())) {
return null;
}
if (useLambda) {
ArrayList<ClassInstanceCreation> convertibleNodes= FunctionalAnonymousClassesFinder.perform(compilationUnit);
if (convertibleNodes.isEmpty()) {
return null;
}
Collections.reverse(convertibleNodes); // process nested anonymous classes first
CompilationUnitRewriteOperation op= new CreateLambdaOperation(convertibleNodes);
return new LambdaExpressionsFixCore(FixMessages.LambdaExpressionsFix_convert_to_lambda_expression, compilationUnit, new CompilationUnitRewriteOperation[] { op });
} else if (useAnonymous) {
ArrayList<LambdaExpression> convertibleNodes= LambdaExpressionsFinder.perform(compilationUnit);
if (convertibleNodes.isEmpty()) {
return null;
}
Collections.reverse(convertibleNodes); // process nested lambdas first
CompilationUnitRewriteOperation op= new CreateAnonymousClassCreationOperation(convertibleNodes);
return new LambdaExpressionsFixCore(FixMessages.LambdaExpressionsFix_convert_to_anonymous_class_creation, compilationUnit, new CompilationUnitRewriteOperation[] { op });
}
return null;
}
public LambdaExpressionsFixCore(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] fixRewriteOperations) {
super(name, compilationUnit, fixRewriteOperations);
}
public static boolean isFunctionalAnonymous(ClassInstanceCreation node) {
ITypeBinding typeBinding= node.resolveTypeBinding();
if (typeBinding == null) {
return false;
}
ITypeBinding[] interfaces= typeBinding.getInterfaces();
if (interfaces.length != 1) {
return false;
}
if (interfaces[0].getFunctionalInterfaceMethod() == null) {
return false;
}
AnonymousClassDeclaration anonymTypeDecl= node.getAnonymousClassDeclaration();
if (anonymTypeDecl == null || anonymTypeDecl.resolveBinding() == null) {
return false;
}
List<BodyDeclaration> bodyDeclarations= anonymTypeDecl.bodyDeclarations();
// cannot convert if there are fields or additional methods
if (bodyDeclarations.size() != 1) {
return false;
}
BodyDeclaration bodyDeclaration= bodyDeclarations.get(0);
if (!(bodyDeclaration instanceof MethodDeclaration)) {
return false;
}
MethodDeclaration methodDecl= (MethodDeclaration) bodyDeclaration;
IMethodBinding methodBinding= methodDecl.resolveBinding();
if (methodBinding == null) {
return false;
}
// generic lambda expressions are not allowed
if (methodBinding.isGenericMethod()) {
return false;
}
int modifiers= methodBinding.getModifiers();
if (Modifier.isSynchronized(modifiers) || Modifier.isStrictfp(modifiers)) {
return false;
}
// lambda cannot refer to 'this'/'super' literals
if (SuperThisReferenceFinder.hasReference(methodDecl)) {
return false;
}
// lambda cannot access this and so we should avoid lambda conversion
// when anonymous class is used to initialize field and refers to
// final fields that may or may not be initialized
if (FinalFieldAccessInFieldDeclarationFinder.hasReference(methodDecl)) {
return false;
}
if (ASTNodes.getTargetType(node) == null) {
return false;
}
// Cannot handle recursive calls in a locally declared anonymous class
if (MethodRecursionFinder.isRecursiveLocal(methodDecl)) {
return false;
}
// Check if annotations other than @Override and @Deprecated will be removed
checkAnnotationsRemoval(methodBinding);
return true;
}
public static void checkAnnotationsRemoval(IMethodBinding methodBinding) {
fConversionRemovesAnnotations= false;
IAnnotationBinding[] declarationAnnotations= methodBinding.getAnnotations();
for (IAnnotationBinding declarationAnnotation : declarationAnnotations) {
ITypeBinding annotationType= declarationAnnotation.getAnnotationType();
if (annotationType != null) {
String qualifiedName= annotationType.getQualifiedName();
if (!"java.lang.Override".equals(qualifiedName) && !"java.lang.Deprecated".equals(qualifiedName)) { //$NON-NLS-1$ //$NON-NLS-2$
fConversionRemovesAnnotations= true;
return;
}
}
}
}
/*private static boolean isInTargetTypeContext(ClassInstanceCreation node) {
ITypeBinding targetType= ASTNodes.getTargetType(node);
return targetType != null && targetType.getFunctionalInterfaceMethod() != null;
//TODO: probably incomplete, should reuse https://bugs.eclipse.org/bugs/show_bug.cgi?id=408966#c6
StructuralPropertyDescriptor locationInParent= node.getLocationInParent();
if (locationInParent == ReturnStatement.EXPRESSION_PROPERTY) {
MethodDeclaration methodDeclaration= ASTResolving.findParentMethodDeclaration(node);
if (methodDeclaration == null)
return false;
IMethodBinding methodBinding= methodDeclaration.resolveBinding();
if (methodBinding == null)
return false;
//TODO: could also cast to the CIC type instead of aborting...
return methodBinding.getReturnType().getFunctionalInterfaceMethod() != null;
}
//TODO: should also check whether variable is of a functional type
return locationInParent == SingleVariableDeclaration.INITIALIZER_PROPERTY
|| locationInParent == VariableDeclarationFragment.INITIALIZER_PROPERTY
|| locationInParent == Assignment.RIGHT_HAND_SIDE_PROPERTY
|| locationInParent == ArrayInitializer.EXPRESSIONS_PROPERTY
|| locationInParent == MethodInvocation.ARGUMENTS_PROPERTY
|| locationInParent == SuperMethodInvocation.ARGUMENTS_PROPERTY
|| locationInParent == ConstructorInvocation.ARGUMENTS_PROPERTY
|| locationInParent == SuperConstructorInvocation.ARGUMENTS_PROPERTY
|| locationInParent == ClassInstanceCreation.ARGUMENTS_PROPERTY
|| locationInParent == EnumConstantDeclaration.ARGUMENTS_PROPERTY
|| locationInParent == LambdaExpression.BODY_PROPERTY
|| locationInParent == ConditionalExpression.THEN_EXPRESSION_PROPERTY
|| locationInParent == ConditionalExpression.ELSE_EXPRESSION_PROPERTY
|| locationInParent == CastExpression.EXPRESSION_PROPERTY;
}*/
}