blob: 21a328089e0e4fcf62907750ef83bf68f5c99048 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2021 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.corext.fix;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.text.edits.TextEditGroup;
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.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.TargetSourceRangeComputer;
import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.ui.fix.MultiFixMessages;
public class BooleanValueRatherThanComparisonFixCore extends CompilationUnitRewriteOperationsFixCore {
public static final class BooleanValueRatherThanComparisonFinder extends ASTVisitor {
private List<CompilationUnitRewriteOperation> fResult;
public BooleanValueRatherThanComparisonFinder(List<CompilationUnitRewriteOperation> ops) {
fResult= ops;
}
@Override
public boolean visit(final MethodInvocation visited) {
if (ASTNodes.usesGivenSignature(visited, Boolean.class.getCanonicalName(), "equals", Object.class.getCanonicalName())) { //$NON-NLS-1$
Boolean isExpressionTrue= ASTNodes.getBooleanLiteral(visited.getExpression());
Expression argument= (Expression) visited.arguments().get(0);
if (isExpressionTrue != null
// A primitive may not create a NPE
&& ASTNodes.isPrimitive(argument, boolean.class.getSimpleName())) {
fResult.add(new BooleanValueRatherThanComparisonOperation(visited, argument, isExpressionTrue));
return false;
}
Boolean isArgumentTrue= ASTNodes.getBooleanLiteral(argument);
// The result has as many NPE threads as the original code so it's OK
if (visited.getExpression() != null
&& (Boolean.FALSE.equals(isArgumentTrue)
|| Boolean.TRUE.equals(isArgumentTrue) && visited.getLocationInParent() != MethodInvocation.ARGUMENTS_PROPERTY && ASTNodes.hasType(ASTNodes.getTargetType(visited), boolean.class.getSimpleName()))) {
fResult.add(new BooleanValueRatherThanComparisonOperation(visited, visited.getExpression(), isArgumentTrue));
return false;
}
}
return true;
}
@Override
public boolean visit(final ParenthesizedExpression visited) {
InfixExpression originalCondition= ASTNodes.as(visited, InfixExpression.class);
if (originalCondition != null) {
return maybeRefactorInfixExpression(visited, originalCondition);
}
return true;
}
@Override
public boolean visit(final InfixExpression visited) {
return maybeRefactorInfixExpression(visited, visited);
}
private boolean maybeRefactorInfixExpression(final Expression visited, final InfixExpression originalCondition) {
if (!originalCondition.hasExtendedOperands()
&& ASTNodes.hasOperator(originalCondition, InfixExpression.Operator.EQUALS, InfixExpression.Operator.NOT_EQUALS, InfixExpression.Operator.XOR)
// Either:
// - Two boolean primitives: no possible NPE
// - One boolean primitive and one Boolean object, this code already run
// the risk of an NPE, so we can replace the infix expression without
// fearing we would introduce a previously non existing NPE.
&& (ASTNodes.isPrimitive(originalCondition.getLeftOperand(), boolean.class.getSimpleName()) || ASTNodes.isPrimitive(originalCondition.getRightOperand(), boolean.class.getSimpleName()))) {
Expression leftExpression= originalCondition.getLeftOperand();
Expression rightExpression= originalCondition.getRightOperand();
boolean isEquals= ASTNodes.hasOperator(originalCondition, InfixExpression.Operator.EQUALS);
return maybeRemoveConstantOperand(visited, leftExpression, rightExpression, isEquals)
&& maybeRemoveConstantOperand(visited, rightExpression, leftExpression, isEquals);
}
return true;
}
private boolean maybeRemoveConstantOperand(final Expression visited, final Expression dynamicOperand,
final Expression hardCodedOperand, final boolean isEquals) {
Boolean booleanLiteral= ASTNodes.getBooleanLiteral(hardCodedOperand);
if (booleanLiteral != null) {
boolean isTrue= booleanLiteral == isEquals;
if (!isTrue
|| ASTNodes.isPrimitive(dynamicOperand, boolean.class.getSimpleName())
|| (visited.getLocationInParent() != MethodInvocation.ARGUMENTS_PROPERTY && ASTNodes.hasType(ASTNodes.getTargetType(visited), boolean.class.getSimpleName()))) {
fResult.add(new BooleanValueRatherThanComparisonOperation(visited, dynamicOperand, isTrue));
return false;
}
}
return true;
}
}
private static class BooleanValueRatherThanComparisonOperation extends CompilationUnitRewriteOperation {
private final ASTNode visited;
private final Expression expressionToCopy;
private final boolean isTrue;
public BooleanValueRatherThanComparisonOperation(final ASTNode visited, final Expression expressionToCopy,
final boolean isTrue) {
this.visited= visited;
this.expressionToCopy= expressionToCopy;
this.isTrue= isTrue;
}
@Override
public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModelCore linkedModel) throws CoreException {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
AST ast= cuRewrite.getRoot().getAST();
TextEditGroup group= createTextEditGroup(MultiFixMessages.BooleanValueRatherThanComparisonCleanUp_description, cuRewrite);
rewrite.setTargetSourceRangeComputer(new TargetSourceRangeComputer() {
@Override
public SourceRange computeSourceRange(final ASTNode nodeWithComment) {
if (Boolean.TRUE.equals(nodeWithComment.getProperty(ASTNodes.UNTOUCH_COMMENT))) {
return new SourceRange(nodeWithComment.getStartPosition(), nodeWithComment.getLength());
}
return super.computeSourceRange(nodeWithComment);
}
});
Expression operand;
if (isTrue) {
operand= ASTNodes.createMoveTarget(rewrite, expressionToCopy);
} else {
operand= ASTNodeFactory.negate(ast, rewrite, expressionToCopy, true);
}
rewrite.replace(visited, ASTNodeFactory.parenthesizeIfNeeded(ast, operand), group);
}
}
public static ICleanUpFixCore createCleanUp(final CompilationUnit compilationUnit) {
List<CompilationUnitRewriteOperation> operations= new ArrayList<>();
BooleanValueRatherThanComparisonFinder finder= new BooleanValueRatherThanComparisonFinder(operations);
compilationUnit.accept(finder);
if (operations.isEmpty()) {
return null;
}
CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] ops= operations.toArray(new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[0]);
return new BooleanValueRatherThanComparisonFixCore(FixMessages.BooleanValueRatherThanComparisonFix_description, compilationUnit, ops);
}
protected BooleanValueRatherThanComparisonFixCore(final String name, final CompilationUnit compilationUnit, final CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] fixRewriteOperations) {
super(name, compilationUnit, fixRewriteOperations);
}
}