| /******************************************************************************* |
| * 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; |
| |
| }*/ |
| } |