Bug 571184 - [AutoRefactor immigration #62/149] [cleanup & saveaction]
Object::equals on non null
Given:
boolean result = text.equals("foo");
boolean result2 = text.equals(text1 + text2);
boolean result3 = object.equals(DayOfWeek.MONDAY);
boolean result4 = text.equalsIgnoreCase("foo");
When:
Clean up the code enabling "Avoid Object::equals or
String::equalsIgnoreCase on null objects"
Then:
boolean result = "foo".equals(text);
boolean result2 = (text1 + text2).equals(text);
boolean result3 = DayOfWeek.MONDAY.equals(object);
boolean result4 = "foo".equalsIgnoreCase(text);
Change-Id: I795af204f8dff26c9ffd0e447b434c472bf050d7
Signed-off-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/InvertEqualsCleanUpCore.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/InvertEqualsCleanUpCore.java
new file mode 100644
index 0000000..913c43b
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/InvertEqualsCleanUpCore.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.ArrayList;
+import java.util.List;
+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.CleanUpConstants;
+import org.eclipse.jdt.internal.corext.fix.InvertEqualsFixCore;
+
+public class InvertEqualsCleanUpCore extends AbstractCleanUpCore {
+ public InvertEqualsCleanUpCore(final Map<String, String> options) {
+ super(options);
+ }
+
+ public InvertEqualsCleanUpCore() {
+ }
+
+ @Override
+ public CleanUpRequirementsCore getRequirementsCore() {
+ return new CleanUpRequirementsCore(requireAST(), false, false, null);
+ }
+
+ public boolean requireAST() {
+ return isEnabled(CleanUpConstants.INVERT_EQUALS);
+ }
+
+ @Override
+ public ICleanUpFixCore createFixCore(final CleanUpContextCore context) throws CoreException {
+ CompilationUnit compilationUnit= context.getAST();
+
+ if (compilationUnit == null || !isEnabled(CleanUpConstants.INVERT_EQUALS)) {
+ return null;
+ }
+
+ return InvertEqualsFixCore.createCleanUp(compilationUnit);
+ }
+
+ @Override
+ public String[] getStepDescriptions() {
+ List<String> result= new ArrayList<>();
+
+ if (isEnabled(CleanUpConstants.INVERT_EQUALS)) {
+ result.add(MultiFixMessages.InvertEqualsCleanUp_description);
+ }
+
+ return result.toArray(new String[0]);
+ }
+
+ @Override
+ public String getPreview() {
+ if (isEnabled(CleanUpConstants.INVERT_EQUALS)) {
+ return "" //$NON-NLS-1$
+ + "boolean result = \"foo\".equals(text);\n" //$NON-NLS-1$
+ + "boolean result2 = (text1 + text2).equals(text);\n" //$NON-NLS-1$
+ + "boolean result3 = DayOfWeek.MONDAY.equals(object);\n" //$NON-NLS-1$
+ + "boolean result4 = \"foo\".equalsIgnoreCase(text);\n"; //$NON-NLS-1$
+ }
+
+ return "" //$NON-NLS-1$
+ + "boolean result = text.equals(\"foo\");\n" //$NON-NLS-1$
+ + "boolean result2 = text.equals(text1 + text2);\n" //$NON-NLS-1$
+ + "boolean result3 = object.equals(DayOfWeek.MONDAY);\n" //$NON-NLS-1$
+ + "boolean result4 = text.equalsIgnoreCase(\"foo\");\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 4f4c332..226520f 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
@@ -172,6 +172,7 @@
public static String AddAllCleanup_description;
public static String ObjectsEqualsCleanup_description;
+ public static String InvertEqualsCleanUp_description;
public static String CheckSignOfBitwiseOperation_description;
public static String StandardComparisonCleanUp_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 1b336b4..b339d0a 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
@@ -153,6 +153,8 @@
UnloopedWhileCleanUp_description=Convert loop into if
AddAllCleanup_description=Add elements in collections without loop
ObjectsEqualsCleanup_description=Use Objects.equals() in the equals method implementation
+
+InvertEqualsCleanUp_description=Avoid Object::equals or String::equalsIgnoreCase on null objects
CheckSignOfBitwiseOperation_description=Use != 0 instead of > 0 when comparing the result of a bitwise expression
StandardComparisonCleanUp_description=Compare to zero
SwitchExpressionsCleanUp_ConvertToSwitchExpressions_description=Convert to switch expression where possible
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 54b8516..820f743 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
@@ -970,6 +970,18 @@
public static final String PRECOMPILE_REGEX= "cleanup.precompile_regex"; //$NON-NLS-1$
/**
+ * Invert calls to <code>Object.equals(Object)</code> and <code>String.equalsIgnoreCase(String)</code> when it is known that the second operand is not null and the first can be null.
+ * <p>
+ * Possible values: {TRUE, FALSE}
+ * <p>
+ *
+ * @see CleanUpOptionsCore#TRUE
+ * @see CleanUpOptionsCore#FALSE
+ * @since 4.19
+ */
+ public static final String INVERT_EQUALS= "cleanup.invert_equals"; //$NON-NLS-1$
+
+ /**
* Check for sign of bitwise operation.
* <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 bef87cf..518a44e 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
@@ -115,6 +115,8 @@
public static String CodeStyleFix_ChangeAccessUsingDeclaring_description;
public static String CodeStyleFix_QualifyMethodWithDeclClass_description;
public static String CodeStyleFix_QualifyFieldWithDeclClass_description;
+
+ public static String InvertEqualsFix_invert;
public static String StandardComparisonFix_compare_to_zero;
public static String SerialVersion_group_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 9948059..3366950 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
@@ -78,6 +78,8 @@
CodeStyleFix_QualifyMethodWithDeclClass_description=Qualify method call with declaring type
CodeStyleFix_QualifyFieldWithDeclClass_description=Qualify field access with declaring type
CodeStyleFix_removeThis_groupDescription=Remove 'this' qualifier
+
+InvertEqualsFix_invert=Avoid Object::equals or String::equalsIgnoreCase on null objects
StandardComparisonFix_compare_to_zero=Compare to zero
CodeStyleFix_ChangeAccessToStaticUsingInstanceType_description=Change access to static using ''{0}'' (instance type)
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/InvertEqualsFixCore.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/InvertEqualsFixCore.java
new file mode 100644
index 0000000..a80bcb9
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/InvertEqualsFixCore.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * 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.IBinding;
+import org.eclipse.jdt.core.dom.IVariableBinding;
+import org.eclipse.jdt.core.dom.InfixExpression;
+import org.eclipse.jdt.core.dom.MethodInvocation;
+import org.eclipse.jdt.core.dom.Name;
+import org.eclipse.jdt.core.dom.ThisExpression;
+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 InvertEqualsFixCore extends CompilationUnitRewriteOperationsFixCore {
+ public static final class InvertEqualsFinder extends ASTVisitor {
+ private List<InvertEqualsFixOperation> fResult;
+
+ public InvertEqualsFinder(List<InvertEqualsFixOperation> ops) {
+ fResult= ops;
+ }
+
+ @Override
+ public boolean visit(final MethodInvocation visited) {
+ Expression expression= visited.getExpression();
+ InfixExpression concatenation= ASTNodes.as(expression, InfixExpression.class);
+
+ if (expression == null
+ || ASTNodes.is(expression, ThisExpression.class)
+ || isConstant(expression)
+ || concatenation != null && ASTNodes.hasOperator(concatenation, InfixExpression.Operator.PLUS) && ASTNodes.hasType(concatenation, String.class.getCanonicalName())) {
+ return true;
+ }
+
+ if (ASTNodes.usesGivenSignature(visited, Object.class.getCanonicalName(), "equals", Object.class.getCanonicalName()) //$NON-NLS-1$
+ || ASTNodes.usesGivenSignature(visited, String.class.getCanonicalName(), "equalsIgnoreCase", String.class.getCanonicalName())) { //$NON-NLS-1$
+ Expression arg0= (Expression) visited.arguments().get(0);
+ InfixExpression concatenationArgument= ASTNodes.as(arg0, InfixExpression.class);
+
+ if (isConstant(arg0) && arg0.resolveTypeBinding() != null && !arg0.resolveTypeBinding().isPrimitive()
+ || ASTNodes.is(arg0, ThisExpression.class)
+ || concatenationArgument != null && ASTNodes.hasOperator(concatenationArgument, InfixExpression.Operator.PLUS) && ASTNodes.hasType(concatenationArgument, String.class.getCanonicalName())) {
+ fResult.add(new InvertEqualsFixOperation(expression, arg0));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean isConstant(final Expression expression) {
+ if (expression != null && expression.resolveConstantExpressionValue() != null) {
+ return true;
+ }
+
+ if (expression instanceof Name) {
+ IBinding binding= ((Name) expression).resolveBinding();
+
+ if (binding instanceof IVariableBinding) {
+ return ((IVariableBinding) binding).isEnumConstant();
+ }
+ }
+
+ return false;
+ }
+ }
+
+ public static class InvertEqualsFixOperation extends CompilationUnitRewriteOperation {
+ private final Expression expression;
+ private final Expression arg0;
+
+ public InvertEqualsFixOperation(final Expression expression, final Expression arg0) {
+ this.expression= expression;
+ this.arg0= arg0;
+ }
+
+ @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.InvertEqualsCleanUp_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);
+ }
+ });
+
+
+ ASTNodes.replaceButKeepComment(rewrite, expression, ASTNodeFactory.parenthesizeIfNeeded(ast, ASTNodes.createMoveTarget(rewrite, arg0)), group);
+ ASTNodes.replaceButKeepComment(rewrite, arg0, ASTNodes.createMoveTarget(rewrite, ASTNodes.getUnparenthesedExpression(expression)), group);
+ }
+ }
+
+
+ public static ICleanUpFixCore createCleanUp(final CompilationUnit compilationUnit) {
+ List<InvertEqualsFixOperation> operations= new ArrayList<>();
+ InvertEqualsFinder finder= new InvertEqualsFinder(operations);
+ compilationUnit.accept(finder);
+
+ if (operations.isEmpty()) {
+ return null;
+ }
+
+ CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] ops= operations.toArray(new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[0]);
+ return new InvertEqualsFixCore(FixMessages.InvertEqualsFix_invert, compilationUnit, ops);
+ }
+
+ protected InvertEqualsFixCore(final String name, final CompilationUnit compilationUnit, final CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] fixRewriteOperations) {
+ super(name, compilationUnit, fixRewriteOperations);
+ }
+}
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 c0613cb..59677b9 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
@@ -76,6 +76,7 @@
import org.eclipse.jdt.internal.ui.fix.EvaluateNullableCleanUp;
import org.eclipse.jdt.internal.ui.fix.ExpressionsCleanUp;
import org.eclipse.jdt.internal.ui.fix.HashCleanUp;
+import org.eclipse.jdt.internal.ui.fix.InvertEqualsCleanUp;
import org.eclipse.jdt.internal.ui.fix.ImportsCleanUp;
import org.eclipse.jdt.internal.ui.fix.Java50CleanUp;
import org.eclipse.jdt.internal.ui.fix.JoinCleanUp;
@@ -815,6 +816,22 @@
}
@Test
+ public void testInvertEqualsCleanUp() throws Exception {
+ CleanUpRefactoring cleanUpRefactoring= new CleanUpRefactoring();
+ addAllCUs(cleanUpRefactoring, MyTestSetup.fJProject1.getChildren());
+
+ Map<String, String> node= getNullSettings();
+
+ node.put(CleanUpConstants.INVERT_EQUALS, CleanUpOptions.TRUE);
+
+ storeSettings(node);
+
+ cleanUpRefactoring.addCleanUp(new InvertEqualsCleanUp());
+
+ doCleanUp(cleanUpRefactoring);
+ }
+
+ @Test
public void testStandardComparisonCleanUp() 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 acc96c8..45a77b9 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
@@ -18304,6 +18304,142 @@
}
@Test
+ public void testInvertEquals() throws Exception {
+ // Given
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String given= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public static interface Itf {\n" //
+ + " int primitiveConstant = 1;\n" //
+ + " String objConstant = \"fkjfkjf\";\n" //
+ + " String objNullConstant = null;\n" //
+ + " MyEnum enumConstant = MyEnum.NOT_NULL;\n" //
+ + " MyEnum enumNullConstant = null;\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static enum MyEnum {\n" //
+ + " NOT_NULL\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean invertEquals(Object obj, String text1, String text2) {\n" //
+ + " // Keep this comment\n" //
+ + " return obj.equals(\"\")\n" //
+ + " && obj.equals(Itf.objConstant)\n" //
+ + " && obj.equals(\"\" + Itf.objConstant)\n" //
+ + " && obj.equals(MyEnum.NOT_NULL)\n" //
+ + " && obj.equals(text1 + text2)\n" //
+ + " && obj.equals(this);\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean invertEqualsIgnoreCase(String s) {\n" //
+ + " // Keep this comment\n" //
+ + " return s.equalsIgnoreCase(\"\")\n" //
+ + " && s.equalsIgnoreCase(Itf.objConstant)\n" //
+ + " && s.equalsIgnoreCase(\"\" + Itf.objConstant);\n" //
+ + " }\n" //
+ + "}\n";
+
+ String expected= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public static interface Itf {\n" //
+ + " int primitiveConstant = 1;\n" //
+ + " String objConstant = \"fkjfkjf\";\n" //
+ + " String objNullConstant = null;\n" //
+ + " MyEnum enumConstant = MyEnum.NOT_NULL;\n" //
+ + " MyEnum enumNullConstant = null;\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static enum MyEnum {\n" //
+ + " NOT_NULL\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean invertEquals(Object obj, String text1, String text2) {\n" //
+ + " // Keep this comment\n" //
+ + " return \"\".equals(obj)\n" //
+ + " && Itf.objConstant.equals(obj)\n" //
+ + " && (\"\" + Itf.objConstant).equals(obj)\n" //
+ + " && MyEnum.NOT_NULL.equals(obj)\n" //
+ + " && (text1 + text2).equals(obj)\n" //
+ + " && this.equals(obj);\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean invertEqualsIgnoreCase(String s) {\n" //
+ + " // Keep this comment\n" //
+ + " return \"\".equalsIgnoreCase(s)\n" //
+ + " && Itf.objConstant.equalsIgnoreCase(s)\n" //
+ + " && (\"\" + Itf.objConstant).equalsIgnoreCase(s);\n" //
+ + " }\n" //
+ + "}\n";
+
+ // When
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", given, false, null);
+ enable(CleanUpConstants.INVERT_EQUALS);
+
+ // Then
+ assertNotEquals("The class must be changed", given, expected);
+ assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new HashSet<>(Arrays.asList(MultiFixMessages.InvertEqualsCleanUp_description)));
+ assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { expected });
+ }
+
+ @Test
+ public void testDoNotInvertEquals() throws Exception {
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public static interface Itf {\n" //
+ + " int primitiveConstant = 1;\n" //
+ + " String objConstant = \"fkjfkjf\";\n" //
+ + " String objNullConstant = null;\n" //
+ + " MyEnum enumConstant = MyEnum.NOT_NULL;\n" //
+ + " MyEnum enumNullConstant = null;\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static enum MyEnum {\n" //
+ + " NOT_NULL\n" //
+ + " }\n" //
+ + "\n" //
+ + " private int primitiveField;\n" //
+ + "\n" //
+ + " public boolean doNotInvertEqualsOnInstance() {\n" //
+ + " return equals(\"\");\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean doNotInvertEqualsOnThis() {\n" //
+ + " return this.equals(\"\");\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean doNotInvertEqualsWhenParameterIsNull(Object obj) {\n" //
+ + " return obj.equals(Itf.objNullConstant) && obj.equals(Itf.enumNullConstant);\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean doNotInvertEqualsWithPrimitiveParameter(Object obj) {\n" //
+ + " return obj.equals(1)\n" //
+ + " && obj.equals(Itf.primitiveConstant)\n" //
+ + " && obj.equals(primitiveField);\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean doNotInvertEqualsIgnoreCaseWhenParameterIsNull(String s) {\n" //
+ + " return s.equalsIgnoreCase(Itf.objNullConstant);\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean doNotInvertEqualsOnOperationThatIsNotConcatenation(Integer number, Integer i1, Integer i2) {\n" //
+ + " return number.equals(i1 + i2);\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", sample, false, null);
+
+ enable(CleanUpConstants.INVERT_EQUALS);
+
+ assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+ }
+
+ @Test
public void testStandardComparison() throws Exception {
// Given
IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
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 76a3ed1..ea50e45 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
@@ -151,7 +151,8 @@
options.setOption(MODERNIZE_HASH, CleanUpOptions.FALSE);
options.setOption(USE_OBJECTS_EQUALS, CleanUpOptions.FALSE);
- // Code fixing
+ // Source fixing
+ options.setOption(INVERT_EQUALS, CleanUpOptions.FALSE);
options.setOption(STANDARD_COMPARISON, CleanUpOptions.FALSE);
options.setOption(CHECK_SIGN_OF_BITWISE_OPERATION, CleanUpOptions.FALSE);
@@ -313,7 +314,8 @@
options.setOption(CLEANUP_ON_SAVE_ADDITIONAL_OPTIONS, CleanUpOptions.FALSE);
- // Code fixing
+ // Source fixing
+ options.setOption(INVERT_EQUALS, CleanUpOptions.FALSE);
options.setOption(STANDARD_COMPARISON, CleanUpOptions.FALSE);
options.setOption(CHECK_SIGN_OF_BITWISE_OPERATION, CleanUpOptions.FALSE);
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index dbf508f..eac7ceb 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7318,9 +7318,14 @@
runAfter="org.eclipse.jdt.ui.cleanup.collection_cloning">
</cleanUp>
<cleanUp
+ class="org.eclipse.jdt.internal.ui.fix.InvertEqualsCleanUp"
+ id="org.eclipse.jdt.ui.cleanup.invert_equals"
+ runAfter="org.eclipse.jdt.ui.cleanup.map_cloning">
+ </cleanUp>
+ <cleanUp
class="org.eclipse.jdt.internal.ui.fix.StandardComparisonCleanUp"
id="org.eclipse.jdt.ui.cleanup.standard_comparison"
- runAfter="org.eclipse.jdt.ui.cleanup.map_cloning">
+ runAfter="org.eclipse.jdt.ui.cleanup.invert_equals">
</cleanUp>
<cleanUp
class="org.eclipse.jdt.internal.ui.fix.BitwiseConditionalExpressionCleanup"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/InvertEqualsCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/InvertEqualsCleanUp.java
new file mode 100644
index 0000000..72514f7
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/InvertEqualsCleanUp.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * 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 inverts calls to <code>Object.equals(Object)</code> and <code>String.equalsIgnoreCase(String)</code>:
+ * <ul>
+ * <li>It avoids useless null pointer exception,</li>
+ * <li>The caller must be nullable,</li>
+ * <li>The parameter must not be nullable,</li>
+ * <li>Beware! By avoiding null pointer exception, the behavior may change!</li>
+ * </ul>
+ */
+public class InvertEqualsCleanUp extends AbstractCleanUp {
+ private InvertEqualsCleanUpCore coreCleanUp= new InvertEqualsCleanUpCore();
+
+ public InvertEqualsCleanUp(final Map<String, String> options) {
+ setOptions(options);
+ }
+
+ public InvertEqualsCleanUp() {
+ }
+
+ @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 952348e..8ec3ba6 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
@@ -163,6 +163,7 @@
public static String SourceFixingTabPage_GroupName_standardCode;
+ public static String SourceFixingTabPage_CheckboxName_InvertEquals;
public static String SourceFixingTabPage_CheckboxName_StandardComparison;
public static String SourceFixingTabPage_CheckboxName_CheckSignOfBitwiseOperation;
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 dec0307..6e3afdc 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
@@ -140,6 +140,7 @@
SourceFixingTabPage_GroupName_standardCode=Code standardization
+SourceFixingTabPage_CheckboxName_InvertEquals=Avoid Object::equals or String::equalsIgnoreCase on null objects
SourceFixingTabPage_CheckboxName_StandardComparison=Compare to zero
SourceFixingTabPage_CheckboxName_CheckSignOfBitwiseOperation=&Compare with != 0 for bitwise expressions
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/SourceFixingTabPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/SourceFixingTabPage.java
index 096e5ef..ad5d312 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/SourceFixingTabPage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/SourceFixingTabPage.java
@@ -27,6 +27,7 @@
import org.eclipse.jdt.internal.ui.fix.AbstractCleanUp;
import org.eclipse.jdt.internal.ui.fix.BitwiseConditionalExpressionCleanup;
+import org.eclipse.jdt.internal.ui.fix.InvertEqualsCleanUp;
import org.eclipse.jdt.internal.ui.fix.StandardComparisonCleanUp;
public final class SourceFixingTabPage extends AbstractCleanUpTabPage {
@@ -35,6 +36,7 @@
@Override
protected AbstractCleanUp[] createPreviewCleanUps(final Map<String, String> values) {
return new AbstractCleanUp[] {
+ new InvertEqualsCleanUp(values),
new StandardComparisonCleanUp(values),
new BitwiseConditionalExpressionCleanup(values)
};
@@ -49,6 +51,9 @@
Group standardCodeGroup= createGroup(numColumns, composite, CleanUpMessages.SourceFixingTabPage_GroupName_standardCode);
+ final CheckboxPreference invertEqualsPref= createCheckboxPref(standardCodeGroup, numColumns, CleanUpMessages.SourceFixingTabPage_CheckboxName_InvertEquals, CleanUpConstants.INVERT_EQUALS, CleanUpModifyDialog.FALSE_TRUE);
+ registerPreference(invertEqualsPref);
+
final CheckboxPreference standardComparisonPref= createCheckboxPref(standardCodeGroup, numColumns, CleanUpMessages.SourceFixingTabPage_CheckboxName_StandardComparison, CleanUpConstants.STANDARD_COMPARISON, CleanUpModifyDialog.FALSE_TRUE);
registerPreference(standardComparisonPref);