blob: 9fd3eb94f475f9e8c8cf3c6f410b85dc7d7ebe44 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 Fabrice TIERCELIN and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Fabrice TIERCELIN - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.ui.fix;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
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.Block;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.InstanceofExpression;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.internal.ui.text.correction.PreviewFeaturesSubProcessor;
/**
* A fix that uses pattern matching for the instanceof expression when possible.
*/
public class PatternMatchingForInstanceofCleanUp extends AbstractMultiFix implements ICleanUpFix {
public PatternMatchingForInstanceofCleanUp() {
this(Collections.emptyMap());
}
public PatternMatchingForInstanceofCleanUp(Map<String, String> options) {
super(options);
}
@Override
public CleanUpRequirements getRequirements() {
boolean requireAST= isEnabled(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF);
return new CleanUpRequirements(requireAST, false, false, null);
}
@Override
public String[] getStepDescriptions() {
if (isEnabled(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF)) {
return new String[] { MultiFixMessages.PatternMatchingForInstanceofCleanup_description };
}
return new String[0];
}
@Override
public String getPreview() {
if (isEnabled(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF)) {
return "" //$NON-NLS-1$
+ "if (object instanceof Integer i) {\n" //$NON-NLS-1$
+ " return i.intValue();\n" //$NON-NLS-1$
+ "}\n\n"; //$NON-NLS-1$
}
return "" //$NON-NLS-1$
+ "if (object instanceof Integer) {\n" //$NON-NLS-1$
+ " final Integer i = (Integer) object;\n" //$NON-NLS-1$
+ " return i.intValue();\n" //$NON-NLS-1$
+ "}\n"; //$NON-NLS-1$
}
@Override
protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException {
if (!isEnabled(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF)
|| !PreviewFeaturesSubProcessor.isPreviewFeatureEnabled(unit.getJavaElement().getJavaProject())
|| !JavaModelUtil.is14OrHigher(unit.getJavaElement().getJavaProject())) {
return null;
}
final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>();
unit.accept(new ASTVisitor() {
@Override
public boolean visit(final Block node) {
InstanceofVisitor instanceofVisitor= new InstanceofVisitor(node);
node.accept(instanceofVisitor);
return instanceofVisitor.getResult();
}
final class InstanceofVisitor extends ASTVisitor {
private final Block startNode;
private boolean result= true;
public InstanceofVisitor(final Block startNode) {
this.startNode= startNode;
}
/**
* @return The result
*/
public boolean getResult() {
return result;
}
@Override
public boolean visit(final Block node) {
return startNode == node;
}
@Override
public boolean visit(final InstanceofExpression node) {
if (node.getPatternVariable() != null
|| !ASTNodes.isPassive(node.getLeftOperand())
|| node.getRightOperand().resolveBinding() == null) {
return true;
}
boolean isPositiveCaseToAnalyze= true;
ASTNode currentNode= node;
while (currentNode.getParent() != null
&& (!(currentNode.getParent() instanceof IfStatement)
|| currentNode.getLocationInParent() != IfStatement.EXPRESSION_PROPERTY)) {
switch (currentNode.getParent().getNodeType()) {
case ASTNode.PARENTHESIZED_EXPRESSION:
break;
case ASTNode.PREFIX_EXPRESSION:
if (!ASTNodes.hasOperator((PrefixExpression) currentNode.getParent(), PrefixExpression.Operator.NOT)) {
return true;
}
isPositiveCaseToAnalyze= !isPositiveCaseToAnalyze;
break;
case ASTNode.INFIX_EXPRESSION:
if (isPositiveCaseToAnalyze
? !ASTNodes.hasOperator((InfixExpression) currentNode.getParent(), InfixExpression.Operator.CONDITIONAL_AND, InfixExpression.Operator.AND)
: !ASTNodes.hasOperator((InfixExpression) currentNode.getParent(), InfixExpression.Operator.CONDITIONAL_OR, InfixExpression.Operator.OR)) {
return true;
}
break;
default:
return true;
}
currentNode= currentNode.getParent();
}
if (currentNode.getParent() == null) {
return true;
}
IfStatement ifStatement= (IfStatement) currentNode.getParent();
if (isPositiveCaseToAnalyze) {
return maybeMatchPattern(node, ifStatement.getThenStatement());
}
if (ifStatement.getElseStatement() != null) {
return maybeMatchPattern(node, ifStatement.getElseStatement());
}
if (ASTNodes.fallsThrough(ifStatement.getThenStatement())) {
return maybeMatchPattern(node, ASTNodes.getNextSibling(ifStatement));
}
return true;
}
private boolean maybeMatchPattern(final InstanceofExpression node, final Statement expression) {
List<Statement> statements= ASTNodes.asList(expression);
if (!statements.isEmpty()) {
VariableDeclarationStatement variableDeclarationExpression= ASTNodes.as(statements.get(0), VariableDeclarationStatement.class);
VariableDeclarationFragment variableDeclarationFragment= ASTNodes.getUniqueFragment(variableDeclarationExpression);
if (variableDeclarationFragment != null && Modifier.isFinal(variableDeclarationExpression.getModifiers())) {
CastExpression castExpression= ASTNodes.as(variableDeclarationFragment.getInitializer(), CastExpression.class);
if (castExpression != null
&& Objects.equals(node.getRightOperand().resolveBinding(), variableDeclarationExpression.getType().resolveBinding())
&& Objects.equals(node.getRightOperand().resolveBinding(), castExpression.getType().resolveBinding())
&& ASTNodes.isPassive(node.getLeftOperand())
&& ASTNodes.match(node.getLeftOperand(), castExpression.getExpression())) {
rewriteOperations.add(new PatternMatchingForInstanceofOperation(node, variableDeclarationExpression, variableDeclarationFragment.getName()));
return false;
}
}
}
return true;
}
}
});
if (rewriteOperations.isEmpty()) {
return null;
}
return new CompilationUnitRewriteOperationsFix(MultiFixMessages.PatternMatchingForInstanceofCleanup_description, unit,
rewriteOperations.toArray(new CompilationUnitRewriteOperation[0]));
}
@Override
public CompilationUnitChange createChange(IProgressMonitor progressMonitor) throws CoreException {
return null;
}
@Override
public boolean canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem) {
return false;
}
@Override
protected ICleanUpFix createFix(final CompilationUnit unit, final IProblemLocation[] problems) throws CoreException {
return null;
}
private static class PatternMatchingForInstanceofOperation extends CompilationUnitRewriteOperation {
private final InstanceofExpression nodeToComplete;
private final VariableDeclarationStatement statementToRemove;
private final SimpleName expressionToMove;
public PatternMatchingForInstanceofOperation(final InstanceofExpression nodeToComplete, final VariableDeclarationStatement statementToRemove, final SimpleName expressionToMove) {
this.nodeToComplete= nodeToComplete;
this.statementToRemove= statementToRemove;
this.expressionToMove= expressionToMove;
}
@Override
public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
AST ast= cuRewrite.getRoot().getAST();
TextEditGroup group= createTextEditGroup(MultiFixMessages.PatternMatchingForInstanceofCleanup_description, cuRewrite);
SingleVariableDeclaration newSingleVariableDeclaration= ast.newSingleVariableDeclaration();
newSingleVariableDeclaration.setName(ASTNodes.createMoveTarget(rewrite, expressionToMove));
newSingleVariableDeclaration.setType(ASTNodes.createMoveTarget(rewrite, nodeToComplete.getRightOperand()));
InstanceofExpression newInstanceof= ast.newInstanceofExpression();
newInstanceof.setLeftOperand(ASTNodes.createMoveTarget(rewrite, nodeToComplete.getLeftOperand()));
newInstanceof.setPatternVariable(newSingleVariableDeclaration);
ASTNodes.replaceButKeepComment(rewrite, nodeToComplete, newInstanceof, group);
if (ASTNodes.canHaveSiblings(statementToRemove)) {
rewrite.remove(statementToRemove, group);
} else {
ASTNodes.replaceButKeepComment(rewrite, statementToRemove, ast.newBlock(), group);
}
}
}
}