Bug 572851 - [AutoRefactor #75/153] Redundant truth

Directly checks boolean values instead of comparing them with
true/false:
 - The operator can be equals, not equals or XOR,
 - The constants can be a literal or a java.lang.Boolean constant,
 - One operand should be primitive so no new null pointer exceptions may
occur.

Given:
        if (isValid == true) {
            int i = 0;
        }

When:
Applying "Boolean value rather than comparison" clean up...

Then:
        if (isValid) {
            int i = 0;
        }

Change-Id: If40bd448d4654c66c10587a5c75d1358f385e59d
Signed-off-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.ui/+/179318
Reviewed-by: Holger Voormann <eclipse@voormann.de>
Tested-by: JDT Bot <jdt-bot@eclipse.org>
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/BooleanValueRatherThanComparisonCleanUpCore.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/BooleanValueRatherThanComparisonCleanUpCore.java
new file mode 100644
index 0000000..1450251
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/BooleanValueRatherThanComparisonCleanUpCore.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * 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.ui.fix;
+
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.manipulation.CleanUpContextCore;
+import org.eclipse.jdt.core.manipulation.CleanUpRequirementsCore;
+import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
+
+import org.eclipse.jdt.internal.corext.fix.BooleanValueRatherThanComparisonFixCore;
+import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
+
+public class BooleanValueRatherThanComparisonCleanUpCore extends AbstractCleanUpCore {
+	public BooleanValueRatherThanComparisonCleanUpCore(final Map<String, String> options) {
+		super(options);
+	}
+
+	public BooleanValueRatherThanComparisonCleanUpCore() {
+	}
+
+	@Override
+	public CleanUpRequirementsCore getRequirementsCore() {
+		return new CleanUpRequirementsCore(requireAST(), false, false, null);
+	}
+
+	public boolean requireAST() {
+		return isEnabled(CleanUpConstants.BOOLEAN_VALUE_RATHER_THAN_COMPARISON);
+	}
+
+	@Override
+	public ICleanUpFixCore createFixCore(final CleanUpContextCore context) throws CoreException {
+		CompilationUnit compilationUnit= context.getAST();
+
+		if (compilationUnit == null || !isEnabled(CleanUpConstants.BOOLEAN_VALUE_RATHER_THAN_COMPARISON)) {
+			return null;
+		}
+
+		return BooleanValueRatherThanComparisonFixCore.createCleanUp(compilationUnit);
+	}
+
+	@Override
+	public String[] getStepDescriptions() {
+		if (isEnabled(CleanUpConstants.BOOLEAN_VALUE_RATHER_THAN_COMPARISON)) {
+			return new String[] {MultiFixMessages.BooleanValueRatherThanComparisonCleanUp_description};
+		}
+
+		return new String[0];
+	}
+
+	@Override
+	public String getPreview() {
+		if (isEnabled(CleanUpConstants.BOOLEAN_VALUE_RATHER_THAN_COMPARISON)) {
+			return "" //$NON-NLS-1$
+					+ "boolean booleanValue = isValid;\n" //$NON-NLS-1$
+					+ "boolean booleanValue2 = !isValid;\n" //$NON-NLS-1$
+					+ "boolean booleanValue3 = i <= 0;\n" //$NON-NLS-1$
+					+ "boolean booleanValue4 = !booleanObject;\n"; //$NON-NLS-1$
+		}
+
+		return "" //$NON-NLS-1$
+				+ "boolean booleanValue = isValid == true;\n" //$NON-NLS-1$
+				+ "boolean booleanValue2 = isValid == false;\n" //$NON-NLS-1$
+				+ "boolean booleanValue3 = Boolean.FALSE.equals(i > 0);\n" //$NON-NLS-1$
+				+ "boolean booleanValue4 = booleanObject.equals(Boolean.FALSE);\n"; //$NON-NLS-1$
+	}
+}
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
index 03c17c4..97bd75e 100644
--- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
@@ -159,6 +159,7 @@
 	public static String AutoboxingCleanup_description;
 	public static String UnboxingCleanup_description;
 	public static String PushDownNegationCleanup_description;
+	public static String BooleanValueRatherThanComparisonCleanUp_description;
 	public static String DoubleNegationCleanUp_description;
 	public static String RedundantComparisonStatementCleanup_description;
 	public static String RedundantSuperCallCleanup_description;
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
index 02f1f39..5912204 100644
--- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
@@ -141,6 +141,7 @@
 AutoboxingCleanup_description=Use Autoboxing
 UnboxingCleanup_description=Use Unboxing
 PushDownNegationCleanup_description=Push down negation
+BooleanValueRatherThanComparisonCleanUp_description=Boolean value rather than comparison
 DoubleNegationCleanUp_description=Avoid double negation
 RedundantComparisonStatementCleanup_description=Remove redundant comparison statement
 RedundantSuperCallCleanup_description=Remove redundant super() call in constructor
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/BooleanValueRatherThanComparisonFixCore.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/BooleanValueRatherThanComparisonFixCore.java
new file mode 100644
index 0000000..21a3280
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/BooleanValueRatherThanComparisonFixCore.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * 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);
+	}
+}
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
index 652118c..23b05ba 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
@@ -1486,6 +1486,18 @@
 	public static final String PUSH_DOWN_NEGATION= "cleanup.push_down_negation"; //$NON-NLS-1$
 
 	/**
+	 * Directly checks boolean values instead of comparing them with <code>true</code>/<code>false</code>.
+	 * <p>
+	 * Possible values: {TRUE, FALSE}
+	 * <p>
+	 *
+	 * @see CleanUpOptionsCore#TRUE
+	 * @see CleanUpOptionsCore#FALSE
+	 * @since 4.20
+	 */
+	public static final String BOOLEAN_VALUE_RATHER_THAN_COMPARISON= "cleanup.boolean_value_rather_than_comparison"; //$NON-NLS-1$
+
+	/**
 	 * Reduces double negation in boolean expression.
 	 * <p>
 	 * Possible values: {TRUE, FALSE}
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
index 8458b4b..b350c89 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
@@ -167,6 +167,7 @@
 	public static String TypeParametersFix_insert_inferred_type_arguments_name;
 	public static String TypeParametersFix_remove_redundant_type_arguments_description;
 	public static String TypeParametersFix_remove_redundant_type_arguments_name;
+	public static String BooleanValueRatherThanComparisonFix_description;
 	public static String RedundantComparatorFix_remove_comparator;
 	public static String ArrayWithCurlyFix_description;
 	public static String ReturnExpressionFix_description;
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
index 43a5fb2..bd6a905 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
@@ -154,6 +154,7 @@
 TypeParametersFix_insert_inferred_type_arguments_name=Insert inferred type arguments
 TypeParametersFix_remove_redundant_type_arguments_description=Remove redundant type arguments
 TypeParametersFix_remove_redundant_type_arguments_name=Remove redundant type arguments
+BooleanValueRatherThanComparisonFix_description=Boolean value rather than comparison
 RedundantComparatorFix_remove_comparator=Implicit comparator
 ArrayWithCurlyFix_description=Create array with curly
 ReturnExpressionFix_description=Remove variable assignment before return
diff --git a/org.eclipse.jdt.ui.tests/performance/org/eclipse/jdt/ui/tests/performance/views/CleanUpPerfTest.java b/org.eclipse.jdt.ui.tests/performance/org/eclipse/jdt/ui/tests/performance/views/CleanUpPerfTest.java
index ec3d9fd..02e592a 100644
--- a/org.eclipse.jdt.ui.tests/performance/org/eclipse/jdt/ui/tests/performance/views/CleanUpPerfTest.java
+++ b/org.eclipse.jdt.ui.tests/performance/org/eclipse/jdt/ui/tests/performance/views/CleanUpPerfTest.java
@@ -64,6 +64,7 @@
 import org.eclipse.jdt.internal.ui.fix.AutoboxingCleanUp;
 import org.eclipse.jdt.internal.ui.fix.BitwiseConditionalExpressionCleanup;
 import org.eclipse.jdt.internal.ui.fix.BooleanLiteralCleanUp;
+import org.eclipse.jdt.internal.ui.fix.BooleanValueRatherThanComparisonCleanUp;
 import org.eclipse.jdt.internal.ui.fix.BreakLoopCleanUp;
 import org.eclipse.jdt.internal.ui.fix.CodeFormatCleanUp;
 import org.eclipse.jdt.internal.ui.fix.CodeStyleCleanUp;
@@ -676,6 +677,22 @@
 	}
 
 	@Test
+	public void testBooleanValueRatherThanComparisonCleanUp() throws Exception {
+		CleanUpRefactoring cleanUpRefactoring= new CleanUpRefactoring();
+		addAllCUs(cleanUpRefactoring, MyTestSetup.fJProject1.getChildren());
+
+		Map<String, String> node= getNullSettings();
+
+		node.put(CleanUpConstants.BOOLEAN_VALUE_RATHER_THAN_COMPARISON, CleanUpOptions.TRUE);
+
+		storeSettings(node);
+
+		cleanUpRefactoring.addCleanUp(new BooleanValueRatherThanComparisonCleanUp());
+
+		doCleanUp(cleanUpRefactoring);
+	}
+
+	@Test
 	public void testDoubleNegationCleanUp() throws Exception {
 		CleanUpRefactoring cleanUpRefactoring= new CleanUpRefactoring();
 		addAllCUs(cleanUpRefactoring, MyTestSetup.fJProject1.getChildren());
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java
index 45150ce..be31434 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java
@@ -7924,6 +7924,311 @@
 	}
 
 	@Test
+	public void testBooleanValueRatherThanComparison() throws Exception {
+		// Given
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String given= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    public void removeMethodCall(boolean isValid, int number) {\n" //
+				+ "        if (Boolean.TRUE.equals(isValid)) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (Boolean.FALSE.equals(isValid)) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (Boolean.TRUE.equals(number > 0) && isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (Boolean.FALSE.equals(number > 0) && isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public boolean removeMethodCallOnObject(Boolean isValid) {\n" //
+				+ "        return isValid.equals(Boolean.TRUE);\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public Boolean removeMethodCallOnNegativeExpression(Boolean isValid) {\n" //
+				+ "        return isValid.equals(Boolean.FALSE);\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public void simplifyPrimitiveBooleanExpression(boolean isValid) {\n" //
+				+ "        if (isValid == true) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid != false) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid == false) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid != true) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid == Boolean.TRUE) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid != Boolean.FALSE) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid == Boolean.FALSE) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid != Boolean.TRUE) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public void removeParenthesis(boolean isValid, boolean isActive) {\n" //
+				+ "        if ((isValid == true) == isActive) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isActive == (isValid == true)) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if ((isValid == true) != isActive) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isActive != (isValid == true)) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if ((isValid == false) == isActive) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isActive == (isValid == false)) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if ((isValid == false) != isActive) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isActive != (isValid == false)) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public void simplifyBooleanWrapperExpression(Boolean isValid) {\n" //
+				+ "        if (isValid == true) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid != false) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid == false) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid != true) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "}\n";
+
+		String expected= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    public void removeMethodCall(boolean isValid, int number) {\n" //
+				+ "        if (isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (!isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if ((number > 0) && isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if ((number <= 0) && isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public boolean removeMethodCallOnObject(Boolean isValid) {\n" //
+				+ "        return isValid;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public Boolean removeMethodCallOnNegativeExpression(Boolean isValid) {\n" //
+				+ "        return !isValid;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public void simplifyPrimitiveBooleanExpression(boolean isValid) {\n" //
+				+ "        if (isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (!isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (!isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (!isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (!isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public void removeParenthesis(boolean isValid, boolean isActive) {\n" //
+				+ "        if (isValid == isActive) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isActive == isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid != isActive) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isActive != isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (!isValid == isActive) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isActive == !isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (!isValid != isActive) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isActive != !isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public void simplifyBooleanWrapperExpression(Boolean isValid) {\n" //
+				+ "        if (isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (!isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (!isValid) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "}\n";
+
+		// When
+		ICompilationUnit cu= pack.createCompilationUnit("E.java", given, false, null);
+		enable(CleanUpConstants.BOOLEAN_VALUE_RATHER_THAN_COMPARISON);
+
+		// Then
+		assertNotEquals("The class must be changed", given, expected);
+		assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { expected }, new HashSet<>(Arrays.asList(MultiFixMessages.BooleanValueRatherThanComparisonCleanUp_description)));
+	}
+
+	@Test
+	public void testDoNotUseBooleanValueRatherThanComparison() throws Exception {
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String sample= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    public void doNotRemoveMethodCallOnObjectParameter(Boolean isValid) {\n" //
+				+ "        if (Boolean.TRUE.equals(isValid)) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (Boolean.FALSE.equals(isValid)) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public void doNotRemoveMethodCallOnObjectParameter(boolean isValid, Boolean isActive) {\n" //
+				+ "        if (isActive.equals(isValid)) {\n" //
+				+ "            int i = 0;\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public Boolean doNotAvoidNPESkippingExpression(Boolean isValid) {\n" //
+				+ "        return isValid == true;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public Boolean doNotAvoidNPESkippingMethod(Boolean isValid) {\n" //
+				+ "        return isValid.equals(Boolean.TRUE);\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public int doNotSimplifyBooleanWrapperExpression(Boolean isValid) {\n" //
+				+ "        if (isValid == Boolean.TRUE) {\n" //
+				+ "            return 1;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid != Boolean.FALSE) {\n" //
+				+ "            return 2;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid == Boolean.FALSE) {\n" //
+				+ "            return 3;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isValid != Boolean.TRUE) {\n" //
+				+ "            return 4;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return 0;\n" //
+				+ "    }\n" //
+				+ "}\n";
+		ICompilationUnit cu= pack.createCompilationUnit("E.java", sample, false, null);
+
+		enable(CleanUpConstants.BOOLEAN_VALUE_RATHER_THAN_COMPARISON);
+
+		assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+	}
+
+	@Test
 	public void testDoubleNegation() throws Exception {
 		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
 		String input= "" //
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
index aa59a3d..6e9a86d 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
@@ -104,6 +104,7 @@
 		options.setOption(ARRAYS_FILL, CleanUpOptions.FALSE);
 		options.setOption(EVALUATE_NULLABLE, CleanUpOptions.FALSE);
 		options.setOption(PUSH_DOWN_NEGATION, CleanUpOptions.FALSE);
+		options.setOption(BOOLEAN_VALUE_RATHER_THAN_COMPARISON, CleanUpOptions.TRUE);
 		options.setOption(DOUBLE_NEGATION, CleanUpOptions.FALSE);
 		options.setOption(REMOVE_REDUNDANT_COMPARISON_STATEMENT, CleanUpOptions.FALSE);
 		options.setOption(REDUNDANT_SUPER_CALL, CleanUpOptions.FALSE);
@@ -275,6 +276,7 @@
 		options.setOption(ARRAYS_FILL, CleanUpOptions.FALSE);
 		options.setOption(EVALUATE_NULLABLE, CleanUpOptions.FALSE);
 		options.setOption(PUSH_DOWN_NEGATION, CleanUpOptions.FALSE);
+		options.setOption(BOOLEAN_VALUE_RATHER_THAN_COMPARISON, CleanUpOptions.FALSE);
 		options.setOption(DOUBLE_NEGATION, CleanUpOptions.FALSE);
 		options.setOption(REMOVE_REDUNDANT_COMPARISON_STATEMENT, CleanUpOptions.FALSE);
 		options.setOption(REDUNDANT_SUPER_CALL, CleanUpOptions.FALSE);
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index 97211e2..4e080d8 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7328,9 +7328,14 @@
             runAfter="org.eclipse.jdt.ui.cleanup.evaluate_nullable">
       </cleanUp>
       <cleanUp
+            class="org.eclipse.jdt.internal.ui.fix.BooleanValueRatherThanComparisonCleanUp"
+            id="org.eclipse.jdt.ui.boolean_value_rather_than_comparison"
+            runAfter="org.eclipse.jdt.ui.cleanup.push_down_negation">
+      </cleanUp>
+      <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.DoubleNegationCleanUp"
             id="org.eclipse.jdt.ui.cleanup.double_negation"
-            runAfter="org.eclipse.jdt.ui.cleanup.push_down_negation">
+            runAfter="org.eclipse.jdt.ui.boolean_value_rather_than_comparison">
       </cleanUp>
       <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.OverriddenAssignmentCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/BooleanValueRatherThanComparisonCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/BooleanValueRatherThanComparisonCleanUp.java
new file mode 100644
index 0000000..d0e1059
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/BooleanValueRatherThanComparisonCleanUp.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * 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.ui.fix;
+
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
+
+import org.eclipse.jdt.ui.cleanup.CleanUpContext;
+import org.eclipse.jdt.ui.cleanup.CleanUpOptions;
+import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
+import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
+
+/**
+ * A fix that directly checks boolean values instead of comparing them with <code>true</code>/<code>false</code>:
+ * <ul>
+ * <li>The operator can be equals, not equals or XOR,</li>
+ * <li>The constants can be a literal or a <code>java.lang.Boolean</code> constant,</li>
+ * <li>One operand should be primitive so no new null pointer exceptions may occur,</li>
+ * <li>We should never fix NPE as it may trigger zombie code.
+ * A zombie code is a dead code that is dead because an error occurs before.
+ * The day someone fixes the error, the zombie code comes back to life and alters the behavior.</li>
+ * </ul>
+ */
+public class BooleanValueRatherThanComparisonCleanUp extends AbstractCleanUp {
+	private BooleanValueRatherThanComparisonCleanUpCore coreCleanUp= new BooleanValueRatherThanComparisonCleanUpCore();
+
+	public BooleanValueRatherThanComparisonCleanUp(final Map<String, String> options) {
+		setOptions(options);
+	}
+
+	public BooleanValueRatherThanComparisonCleanUp() {
+	}
+
+	@Override
+	public void setOptions(final CleanUpOptions options) {
+		coreCleanUp.setOptions(options);
+	}
+
+	@Override
+	public CleanUpRequirements getRequirements() {
+		return new CleanUpRequirements(coreCleanUp.getRequirementsCore());
+	}
+
+	@Override
+	public ICleanUpFix createFix(final CleanUpContext context) throws CoreException {
+		ICleanUpFixCore fixCore= coreCleanUp.createFixCore(context);
+		return fixCore != null ? new CleanUpFixWrapper(fixCore) : null;
+	}
+
+	@Override
+	public String[] getStepDescriptions() {
+		return coreCleanUp.getStepDescriptions();
+	}
+
+	@Override
+	public String getPreview() {
+		return coreCleanUp.getPreview();
+	}
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
index d473f7e..ec5d591 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
@@ -134,6 +134,7 @@
 	public static String UnnecessaryCodeTabPage_CheckboxName_ArraysFill;
 	public static String UnnecessaryCodeTabPage_CheckboxName_EvaluateNullable;
 	public static String UnnecessaryCodeTabPage_CheckboxName_PushDownNegation;
+	public static String UnnecessaryCodeTabPage_CheckboxName_BooleanValueRatherThanComparison;
 	public static String UnnecessaryCodeTabPage_CheckboxName_DoubleNegation;
 	public static String UnnecessaryCodeTabPage_CheckboxName_ComparisonStatement;
 	public static String UnnecessaryCodeTabPage_CheckboxName_RedundantSuperCall;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
index 107645f..8b4f1a9 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
@@ -120,6 +120,7 @@
 UnnecessaryCodeTabPage_CheckboxName_ArraysFill=Use Arrays.&fill() when possible
 UnnecessaryCodeTabPage_CheckboxName_EvaluateNullable=Evaluate without null check
 UnnecessaryCodeTabPage_CheckboxName_PushDownNegation=Push d&own negation
+UnnecessaryCodeTabPage_CheckboxName_BooleanValueRatherThanComparison=Boolean value rather than comparison
 UnnecessaryCodeTabPage_CheckboxName_DoubleNegation=Avoid double negation
 UnnecessaryCodeTabPage_CheckboxName_ComparisonStatement=Remove redundant comparison statement
 UnnecessaryCodeTabPage_CheckboxName_RedundantSuperCall=Remove redundant s&uper() call in constructor
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/UnnecessaryCodeTabPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/UnnecessaryCodeTabPage.java
index c5c1d90..0b04d2a 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/UnnecessaryCodeTabPage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/UnnecessaryCodeTabPage.java
@@ -24,6 +24,7 @@
 import org.eclipse.jdt.internal.ui.fix.AbstractCleanUp;
 import org.eclipse.jdt.internal.ui.fix.ArrayWithCurlyCleanUp;
 import org.eclipse.jdt.internal.ui.fix.ArraysFillCleanUp;
+import org.eclipse.jdt.internal.ui.fix.BooleanValueRatherThanComparisonCleanUp;
 import org.eclipse.jdt.internal.ui.fix.CollectionCloningCleanUp;
 import org.eclipse.jdt.internal.ui.fix.DoubleNegationCleanUp;
 import org.eclipse.jdt.internal.ui.fix.EmbeddedIfCleanUp;
@@ -61,6 +62,7 @@
 				new ArraysFillCleanUp(values),
 				new EvaluateNullableCleanUp(values),
 				new PushDownNegationCleanUp(values),
+				new BooleanValueRatherThanComparisonCleanUp(values),
 				new DoubleNegationCleanUp(values),
 				new RedundantComparisonStatementCleanUp(values),
 				new RedundantSuperCallCleanUp(values),
@@ -121,6 +123,9 @@
 				CleanUpModifyDialog.FALSE_TRUE);
 		registerPreference(pushDownNegation);
 
+		CheckboxPreference booleanValueRatherThanComparison= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_BooleanValueRatherThanComparison, CleanUpConstants.BOOLEAN_VALUE_RATHER_THAN_COMPARISON, CleanUpModifyDialog.FALSE_TRUE);
+		registerPreference(booleanValueRatherThanComparison);
+
 		CheckboxPreference doubleNegation= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_DoubleNegation, CleanUpConstants.DOUBLE_NEGATION, CleanUpModifyDialog.FALSE_TRUE);
 		registerPreference(doubleNegation);