1 /******************************************************************************* 2 * Copyright (c) 2020 Fabrice TIERCELIN and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Fabrice TIERCELIN - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.ui.fix; 15 16 import java.util.ArrayList; 17 import java.util.Collections; 18 import java.util.List; 19 import java.util.Map; 20 21 import org.eclipse.core.runtime.CoreException; 22 23 import org.eclipse.jdt.core.ICompilationUnit; 24 import org.eclipse.jdt.core.dom.AST; 25 import org.eclipse.jdt.core.dom.ASTNode; 26 import org.eclipse.jdt.core.dom.ASTVisitor; 27 import org.eclipse.jdt.core.dom.Block; 28 import org.eclipse.jdt.core.dom.ClassInstanceCreation; 29 import org.eclipse.jdt.core.dom.CompilationUnit; 30 import org.eclipse.jdt.core.dom.CreationReference; 31 import org.eclipse.jdt.core.dom.Expression; 32 import org.eclipse.jdt.core.dom.ExpressionMethodReference; 33 import org.eclipse.jdt.core.dom.FieldAccess; 34 import org.eclipse.jdt.core.dom.IMethodBinding; 35 import org.eclipse.jdt.core.dom.ITypeBinding; 36 import org.eclipse.jdt.core.dom.LambdaExpression; 37 import org.eclipse.jdt.core.dom.MethodInvocation; 38 import org.eclipse.jdt.core.dom.Modifier; 39 import org.eclipse.jdt.core.dom.NumberLiteral; 40 import org.eclipse.jdt.core.dom.ReturnStatement; 41 import org.eclipse.jdt.core.dom.SimpleName; 42 import org.eclipse.jdt.core.dom.Statement; 43 import org.eclipse.jdt.core.dom.StringLiteral; 44 import org.eclipse.jdt.core.dom.SuperFieldAccess; 45 import org.eclipse.jdt.core.dom.SuperMethodInvocation; 46 import org.eclipse.jdt.core.dom.SuperMethodReference; 47 import org.eclipse.jdt.core.dom.ThisExpression; 48 import org.eclipse.jdt.core.dom.Type; 49 import org.eclipse.jdt.core.dom.TypeMethodReference; 50 import org.eclipse.jdt.core.dom.VariableDeclarationFragment; 51 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; 52 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; 53 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; 54 55 import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; 56 import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory; 57 import org.eclipse.jdt.internal.corext.dom.ASTNodes; 58 import org.eclipse.jdt.internal.corext.dom.Bindings; 59 import org.eclipse.jdt.internal.corext.fix.CleanUpConstants; 60 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix; 61 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation; 62 import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel; 63 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite; 64 import org.eclipse.jdt.internal.corext.util.JavaModelUtil; 65 66 import org.eclipse.jdt.ui.cleanup.CleanUpRequirements; 67 import org.eclipse.jdt.ui.cleanup.ICleanUpFix; 68 import org.eclipse.jdt.ui.text.java.IProblemLocation; 69 70 /** 71 * A fix that simplifies the lambda expression and the method reference syntax: 72 * <ul> 73 * <li>Parenthesis are not needed for a single untyped parameter,</li> 74 * <li>Return statement is not needed for a single expression,</li> 75 * <li>Brackets are not needed for a single statement,</li> 76 * <li>A lambda expression can be replaced by a creation or a method reference in some cases.</li> 77 * </ul> 78 */ 79 public class LambdaExpressionAndMethodRefCleanUp extends AbstractMultiFix { LambdaExpressionAndMethodRefCleanUp()80 public LambdaExpressionAndMethodRefCleanUp() { 81 this(Collections.emptyMap()); 82 } 83 LambdaExpressionAndMethodRefCleanUp(Map<String, String> options)84 public LambdaExpressionAndMethodRefCleanUp(Map<String, String> options) { 85 super(options); 86 } 87 88 @Override getRequirements()89 public CleanUpRequirements getRequirements() { 90 boolean requireAST= isEnabled(CleanUpConstants.SIMPLIFY_LAMBDA_EXPRESSION_AND_METHOD_REF); 91 Map<String, String> requiredOptions= null; 92 return new CleanUpRequirements(requireAST, false, false, requiredOptions); 93 } 94 95 @Override getStepDescriptions()96 public String[] getStepDescriptions() { 97 if (isEnabled(CleanUpConstants.SIMPLIFY_LAMBDA_EXPRESSION_AND_METHOD_REF)) { 98 return new String[] { MultiFixMessages.LambdaExpressionAndMethodRefCleanUp_description }; 99 } 100 return new String[0]; 101 } 102 103 @Override getPreview()104 public String getPreview() { 105 StringBuilder bld= new StringBuilder(); 106 107 if (isEnabled(CleanUpConstants.SIMPLIFY_LAMBDA_EXPRESSION_AND_METHOD_REF)) { 108 bld.append("someString -> someString.trim().toLowerCase();\n"); //$NON-NLS-1$ 109 bld.append("someString -> someString.trim().toLowerCase();\n"); //$NON-NLS-1$ 110 bld.append("someString -> (someString.trim().toLowerCase() + \"bar\");\n"); //$NON-NLS-1$ 111 bld.append("ArrayList::new;\n"); //$NON-NLS-1$ 112 bld.append("Date::getTime;\n"); //$NON-NLS-1$ 113 } else { 114 bld.append("(someString) -> someString.trim().toLowerCase();\n"); //$NON-NLS-1$ 115 bld.append("someString -> {return someString.trim().toLowerCase();};\n"); //$NON-NLS-1$ 116 bld.append("someString -> {return someString.trim().toLowerCase() + \"bar\";};\n"); //$NON-NLS-1$ 117 bld.append("() -> new ArrayList<>();\n"); //$NON-NLS-1$ 118 bld.append("date -> date.getTime();\n"); //$NON-NLS-1$ 119 } 120 121 return bld.toString(); 122 } 123 124 @Override createFix(CompilationUnit unit)125 protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException { 126 if (!isEnabled(CleanUpConstants.SIMPLIFY_LAMBDA_EXPRESSION_AND_METHOD_REF) || !JavaModelUtil.is18OrHigher(unit.getJavaElement().getJavaProject())) { 127 return null; 128 } 129 130 final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>(); 131 132 unit.accept(new ASTVisitor() { 133 @Override 134 public boolean visit(final LambdaExpression node) { 135 if (node.hasParentheses() && node.parameters().size() == 1 136 && node.parameters().get(0) instanceof VariableDeclarationFragment) { 137 rewriteOperations.add(new RemoveParamParenthesesOperation(node)); 138 return false; 139 } else if (node.getBody() instanceof Block) { 140 List<Statement> statements= ((Block) node.getBody()).statements(); 141 142 if (statements.size() == 1 && statements.get(0) instanceof ReturnStatement) { 143 rewriteOperations.add(new RemoveReturnAndBracketsOperation(node, statements)); 144 return false; 145 } 146 } else if (node.getBody() instanceof ClassInstanceCreation) { 147 ClassInstanceCreation ci= (ClassInstanceCreation) node.getBody(); 148 149 List<Expression> arguments= ci.arguments(); 150 if (node.parameters().size() == arguments.size() 151 && areSameIdentifiers(node, arguments) 152 && ci.getAnonymousClassDeclaration() == null) { 153 rewriteOperations.add(new ReplaceByCreationReferenceOperation(node, ci)); 154 return false; 155 } 156 } else if (node.getBody() instanceof SuperMethodInvocation) { 157 SuperMethodInvocation smi= (SuperMethodInvocation) node.getBody(); 158 List<Expression> arguments= smi.arguments(); 159 160 if (node.parameters().size() == arguments.size() && areSameIdentifiers(node, arguments)) { 161 rewriteOperations.add(new ReplaceBySuperMethodReferenceOperation(node, smi)); 162 return false; 163 } 164 } else if (node.getBody() instanceof MethodInvocation) { 165 MethodInvocation methodInvocation= (MethodInvocation) node.getBody(); 166 Expression calledExpression= methodInvocation.getExpression(); 167 List<Expression> arguments= methodInvocation.arguments(); 168 169 if (node.parameters().size() == arguments.size()) { 170 if (!areSameIdentifiers(node, arguments)) { 171 return true; 172 } 173 174 IMethodBinding methodBinding= methodInvocation.resolveMethodBinding(); 175 176 if (Boolean.TRUE.equals(ASTNodes.isStatic(methodInvocation))) { 177 if (methodBinding == null || methodBinding.getDeclaringClass() == null) { 178 return true; 179 } 180 181 ITypeBinding calledType= methodBinding.getDeclaringClass(); 182 183 if (!arguments.isEmpty()) { 184 String[] remainingParams= new String[arguments.size() - 1]; 185 186 for (int i= 0; i < arguments.size() - 1; i++) { 187 ITypeBinding resolveTypeBinding= arguments.get(i + 1).resolveTypeBinding(); 188 189 if (resolveTypeBinding == null) { 190 return true; 191 } 192 193 remainingParams[i]= resolveTypeBinding.getQualifiedName(); 194 } 195 196 for (IMethodBinding declaredMethodBinding : calledType.getDeclaredMethods()) { 197 if ((declaredMethodBinding.getModifiers() & Modifier.STATIC) == 0 && ASTNodes.usesGivenSignature(declaredMethodBinding, 198 calledType.getQualifiedName(), methodInvocation.getName().getIdentifier(), remainingParams)) { 199 return true; 200 } 201 } 202 } 203 204 rewriteOperations.add(new ReplaceByTypeReferenceOperation(node, methodInvocation, calledType)); 205 return false; 206 } 207 208 if (calledExpression == null) { 209 if (methodBinding != null) { 210 ITypeBinding calledType= methodBinding.getDeclaringClass(); 211 ITypeBinding enclosingType= Bindings.getBindingOfParentType(node); 212 213 if (calledType != null && Bindings.isSuperType(calledType, enclosingType)) { 214 rewriteOperations.add(new ReplaceByMethodReferenceOperation(node, methodInvocation)); 215 return false; 216 } 217 } 218 } else if (calledExpression instanceof StringLiteral || calledExpression instanceof NumberLiteral 219 || calledExpression instanceof ThisExpression) { 220 rewriteOperations.add(new ReplaceByMethodReferenceOperation(node, methodInvocation)); 221 return false; 222 } else if (calledExpression instanceof FieldAccess) { 223 FieldAccess fieldAccess= (FieldAccess) calledExpression; 224 225 if (fieldAccess.resolveFieldBinding() != null && fieldAccess.resolveFieldBinding().isEffectivelyFinal()) { 226 rewriteOperations.add(new ReplaceByMethodReferenceOperation(node, methodInvocation)); 227 return false; 228 } 229 } else if (calledExpression instanceof SuperFieldAccess) { 230 SuperFieldAccess fieldAccess= (SuperFieldAccess) calledExpression; 231 232 if (fieldAccess.resolveFieldBinding() != null && fieldAccess.resolveFieldBinding().isEffectivelyFinal()) { 233 rewriteOperations.add(new ReplaceByMethodReferenceOperation(node, methodInvocation)); 234 return false; 235 } 236 } 237 } else if (calledExpression instanceof SimpleName && node.parameters().size() == arguments.size() + 1) { 238 SimpleName calledObject= (SimpleName) calledExpression; 239 240 if (isSameIdentifier(node, 0, calledObject)) { 241 for (int i= 0; i < arguments.size(); i++) { 242 ASTNode expression= ASTNodes.getUnparenthesedExpression(arguments.get(i)); 243 244 if (!(expression instanceof SimpleName) || !isSameIdentifier(node, i + 1, (SimpleName) expression)) { 245 return true; 246 } 247 } 248 249 ITypeBinding clazz= null; 250 if (calledExpression.resolveTypeBinding() != null) { 251 clazz= calledExpression.resolveTypeBinding(); 252 } else if (methodInvocation.resolveMethodBinding() != null && methodInvocation.resolveMethodBinding().getDeclaringClass() != null) { 253 clazz= methodInvocation.resolveMethodBinding().getDeclaringClass(); 254 } else { 255 return true; 256 } 257 258 String[] cumulativeParams= new String[arguments.size() + 1]; 259 cumulativeParams[0]= clazz.getQualifiedName(); 260 261 for (int i= 0; i < arguments.size(); i++) { 262 ITypeBinding resolveTypeBinding= arguments.get(i).resolveTypeBinding(); 263 264 if (resolveTypeBinding == null) { 265 return true; 266 } 267 268 cumulativeParams[i + 1]= resolveTypeBinding.getQualifiedName(); 269 } 270 271 for (IMethodBinding declaredMethodBinding : clazz.getDeclaredMethods()) { 272 if ((declaredMethodBinding.getModifiers() & Modifier.STATIC) > 0 && ASTNodes.usesGivenSignature(declaredMethodBinding, 273 clazz.getQualifiedName(), methodInvocation.getName().getIdentifier(), cumulativeParams)) { 274 return true; 275 } 276 } 277 278 rewriteOperations.add(new ReplaceByTypeReferenceOperation(node, methodInvocation, clazz)); 279 return false; 280 } 281 } 282 } 283 284 return true; 285 } 286 287 /** 288 * In order to make parameters implicit, those ones should be the same in the same 289 * order. 290 * 291 * @param node the lambda expression 292 * @param arguments The arguments in the expression 293 * @return true if the parameters are obvious 294 */ 295 private boolean areSameIdentifiers(LambdaExpression node, List<Expression> arguments) { 296 for (int i= 0; i < node.parameters().size(); i++) { 297 Expression expression= ASTNodes.getUnparenthesedExpression(arguments.get(i)); 298 299 if (!(expression instanceof SimpleName) || !isSameIdentifier(node, i, (SimpleName) expression)) { 300 return false; 301 } 302 } 303 304 return true; 305 } 306 307 private boolean isSameIdentifier(final LambdaExpression node, final int i, final SimpleName argument) { 308 Object param0= node.parameters().get(i); 309 310 if (param0 instanceof VariableDeclarationFragment) { 311 VariableDeclarationFragment vdf= (VariableDeclarationFragment) param0; 312 return vdf.getName().getIdentifier().equals(argument.getIdentifier()); 313 } 314 315 return false; 316 } 317 }); 318 319 if (rewriteOperations.isEmpty()) { 320 return null; 321 } 322 323 return new CompilationUnitRewriteOperationsFix(MultiFixMessages.LambdaExpressionAndMethodRefCleanUp_description, unit, 324 rewriteOperations.toArray(new CompilationUnitRewriteOperation[rewriteOperations.size()])); 325 } 326 327 @Override canFix(ICompilationUnit compilationUnit, IProblemLocation problem)328 public boolean canFix(ICompilationUnit compilationUnit, IProblemLocation problem) { 329 return false; 330 } 331 332 @Override createFix(CompilationUnit unit, IProblemLocation[] problems)333 protected ICleanUpFix createFix(CompilationUnit unit, IProblemLocation[] problems) throws CoreException { 334 return null; 335 } 336 337 private static class RemoveParamParenthesesOperation extends CompilationUnitRewriteOperation { 338 private final LambdaExpression node; 339 RemoveParamParenthesesOperation(final LambdaExpression node)340 public RemoveParamParenthesesOperation(final LambdaExpression node) { 341 this.node= node; 342 } 343 344 @Override rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel)345 public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException { 346 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 347 AST ast= cuRewrite.getRoot().getAST(); 348 349 LambdaExpression copyOfLambdaExpression= ast.newLambdaExpression(); 350 ASTNode copyOfParameter= rewrite.createCopyTarget((ASTNode) node.parameters().get(0)); 351 copyOfLambdaExpression.parameters().add(copyOfParameter); 352 copyOfLambdaExpression.setBody(rewrite.createCopyTarget(node.getBody())); 353 copyOfLambdaExpression.setParentheses(false); 354 rewrite.replace(node, copyOfLambdaExpression, null); 355 } 356 } 357 358 private static class RemoveReturnAndBracketsOperation extends CompilationUnitRewriteOperation { 359 private final LambdaExpression node; 360 361 private final List<Statement> statements; 362 RemoveReturnAndBracketsOperation(final LambdaExpression node, final List<Statement> statements)363 public RemoveReturnAndBracketsOperation(final LambdaExpression node, final List<Statement> statements) { 364 this.node= node; 365 this.statements= statements; 366 } 367 368 @Override rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel)369 public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException { 370 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 371 AST ast= cuRewrite.getRoot().getAST(); 372 373 ReturnStatement returnStatement= (ReturnStatement) statements.get(0); 374 Expression copyOfExpression= (Expression) rewrite.createCopyTarget(returnStatement.getExpression()); 375 rewrite.replace(node.getBody(), ASTNodeFactory.parenthesizeIfNeeded(ast, copyOfExpression), null); 376 } 377 } 378 379 private static class ReplaceByCreationReferenceOperation extends CompilationUnitRewriteOperation { 380 private final LambdaExpression node; 381 382 private final ClassInstanceCreation classInstanceCreation; 383 ReplaceByCreationReferenceOperation(final LambdaExpression node, final ClassInstanceCreation classInstanceCreation)384 public ReplaceByCreationReferenceOperation(final LambdaExpression node, final ClassInstanceCreation classInstanceCreation) { 385 this.node= node; 386 this.classInstanceCreation= classInstanceCreation; 387 } 388 389 @Override rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel)390 public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException { 391 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 392 AST ast= cuRewrite.getRoot().getAST(); 393 394 CreationReference creationRef= ast.newCreationReference(); 395 creationRef.setType(copyType(cuRewrite, ast, classInstanceCreation, classInstanceCreation.resolveTypeBinding())); 396 rewrite.replace(node, creationRef, null); 397 } 398 } 399 400 private static class ReplaceBySuperMethodReferenceOperation extends CompilationUnitRewriteOperation { 401 private final LambdaExpression node; 402 403 private final SuperMethodInvocation superMethodInvocation; 404 ReplaceBySuperMethodReferenceOperation(final LambdaExpression node, final SuperMethodInvocation superMethodInvocation)405 public ReplaceBySuperMethodReferenceOperation(final LambdaExpression node, final SuperMethodInvocation superMethodInvocation) { 406 this.node= node; 407 this.superMethodInvocation= superMethodInvocation; 408 } 409 410 @Override rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel)411 public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException { 412 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 413 AST ast= cuRewrite.getRoot().getAST(); 414 415 SuperMethodReference creationRef= ast.newSuperMethodReference(); 416 creationRef.setName((SimpleName) rewrite.createCopyTarget(superMethodInvocation.getName())); 417 rewrite.replace(node, creationRef, null); 418 } 419 } 420 421 private static class ReplaceByTypeReferenceOperation extends CompilationUnitRewriteOperation { 422 private final LambdaExpression node; 423 private final MethodInvocation methodInvocation; 424 425 private final ITypeBinding type; 426 ReplaceByTypeReferenceOperation(LambdaExpression node, MethodInvocation methodInvocation, ITypeBinding type)427 public ReplaceByTypeReferenceOperation(LambdaExpression node, MethodInvocation methodInvocation, ITypeBinding type) { 428 this.node= node; 429 this.methodInvocation= methodInvocation; 430 this.type= type; 431 } 432 433 @Override rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel)434 public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException { 435 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 436 AST ast= cuRewrite.getRoot().getAST(); 437 438 TypeMethodReference typeMethodRef= ast.newTypeMethodReference(); 439 440 typeMethodRef.setType(copyType(cuRewrite, ast, methodInvocation, type)); 441 typeMethodRef.setName((SimpleName) rewrite.createCopyTarget(methodInvocation.getName())); 442 rewrite.replace(node, typeMethodRef, null); 443 } 444 } 445 446 private static class ReplaceByMethodReferenceOperation extends CompilationUnitRewriteOperation { 447 private final LambdaExpression node; 448 449 private final MethodInvocation methodInvocation; 450 ReplaceByMethodReferenceOperation(LambdaExpression node, MethodInvocation methodInvocation)451 public ReplaceByMethodReferenceOperation(LambdaExpression node, MethodInvocation methodInvocation) { 452 this.node= node; 453 this.methodInvocation= methodInvocation; 454 } 455 456 @Override rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel)457 public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException { 458 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 459 AST ast= cuRewrite.getRoot().getAST(); 460 ExpressionMethodReference typeMethodRef= ast.newExpressionMethodReference(); 461 462 if (methodInvocation.getExpression() != null) { 463 typeMethodRef.setExpression((Expression) rewrite.createCopyTarget(methodInvocation.getExpression())); 464 } else { 465 typeMethodRef.setExpression(ast.newThisExpression()); 466 } 467 468 typeMethodRef.setName((SimpleName) rewrite.createCopyTarget(methodInvocation.getName())); 469 rewrite.replace(node, typeMethodRef, null); 470 } 471 } 472 copyType(final CompilationUnitRewrite cuRewrite, final AST ast, final ASTNode node, final ITypeBinding typeBinding)473 private static Type copyType(final CompilationUnitRewrite cuRewrite, final AST ast, final ASTNode node, final ITypeBinding typeBinding) { 474 ImportRewrite importRewrite= cuRewrite.getImportRewrite(); 475 ImportRewriteContext importContext= new ContextSensitiveImportRewriteContext(node, importRewrite); 476 ITypeBinding modifiedType; 477 478 if (typeBinding.getTypeParameters().length == 0) { 479 modifiedType= typeBinding.getErasure(); 480 } else { 481 modifiedType= typeBinding; 482 } 483 484 return ASTNodeFactory.newCreationType(ast, modifiedType, importRewrite, importContext); 485 } 486 }