Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabrice Tiercelin2020-04-13 17:59:37 +0000
committerFabrice Tiercelin2020-07-15 20:23:35 +0000
commit258e2bf2d72ff5aa025b6350b9f0abbec3a3be13 (patch)
treefe377b50d29dd5bc3e3c153aa39780c1b8746673
parent72fb4f4067a7501238b890f24ea476e3b0fdc2ea (diff)
downloadeclipse.jdt.ui-258e2bf2d72ff5aa025b6350b9f0abbec3a3be13.tar.gz
eclipse.jdt.ui-258e2bf2d72ff5aa025b6350b9f0abbec3a3be13.tar.xz
eclipse.jdt.ui-258e2bf2d72ff5aa025b6350b9f0abbec3a3be13.zip
Bug 562077 - [AutoRefactor immigration #16/133] [cleanup & saveaction]
Use Objects.equals() in the equals method implementation Change-Id: Ie4463d947859f2cdcff45c55cf927d870bb6f8b8 Signed-off-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
-rw-r--r--org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java1
-rw-r--r--org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties1
-rw-r--r--org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java86
-rw-r--r--org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/OrderedInfixExpression.java69
-rw-r--r--org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java12
-rw-r--r--org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java231
-rw-r--r--org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java2
-rw-r--r--org.eclipse.jdt.ui/plugin.xml7
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/ObjectsEqualsCleanUp.java296
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java1
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties1
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/UnnecessaryCodeTabPage.java7
12 files changed, 710 insertions, 4 deletions
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 88897598a3..0fec8b6585 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
@@ -127,6 +127,7 @@ public class MultiFixMessages extends NLS {
public static String UseDirectlyMapMethodCleanup_description;
public static String RedundantSemicolonsCleanup_description;
public static String UnnecessaryArrayCreationCleanup_description;
+ public static String ObjectsEqualsCleanup_description;
static {
// initialize resource bundle
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 07c1160716..aba66e673e 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
@@ -107,3 +107,4 @@ MergeConditionalBlocksCleanup_description=Merge conditions of if/else if/else th
UseDirectlyMapMethodCleanup_description = Operate on Maps directly
RedundantSemicolonsCleanup_description= Remove redundant semicolons
UnnecessaryArrayCreationCleanup_description=Remove unnecessary array creation for varargs
+ObjectsEqualsCleanup_description=Use Objects.equals() in the equals method implementation
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java
index dc9b9dbeb9..65198ddd99 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java
@@ -595,6 +595,20 @@ public class ASTNodes {
}
/**
+ * Return true if the node changes nothing.
+ *
+ * @param node The node to visit.
+ *
+ * @return True if the node changes nothing.
+ */
+ public static boolean isPassive(final ASTNode node) {
+ ExprActivityVisitor visitor= new ExprActivityVisitor();
+ visitor.traverseNodeInterruptibly(node);
+ return ExprActivity.PASSIVE_WITHOUT_FALLING_THROUGH.equals(visitor.getActivityLevel())
+ || ExprActivity.PASSIVE.equals(visitor.getActivityLevel());
+ }
+
+ /**
* True if the method is static, false if it is not or null if it is unknown.
*
* @param method The method
@@ -834,6 +848,78 @@ public class ASTNodes {
}
/**
+ * Return the items of an infix expression in the order it is specified. It reverses the operator if needed.
+ *
+ * @param <F> the required expression type
+ * @param <S> the required expression type
+ * @param node the supposed infix expression
+ * @param firstClass the class representing the required expression type
+ * @param secondClass the class representing the required expression type
+ * @return the items of an infix expression in the order it is specified. It reverses the operator if needed.
+ */
+ public static <F extends Expression, S extends Expression> OrderedInfixExpression<F, S> orderedInfix(final Expression node, final Class<F> firstClass, final Class<S> secondClass) {
+ InfixExpression expression= as(node, InfixExpression.class);
+
+ if (expression == null || expression.hasExtendedOperands()) {
+ return null;
+ }
+
+ if (firstClass != null && firstClass.equals(secondClass)) {
+ F first= as(expression.getLeftOperand(), firstClass);
+ S second= as(expression.getRightOperand(), secondClass);
+
+ if (first != null && second != null) {
+ return new OrderedInfixExpression<>(first, expression.getOperator(), second);
+ }
+ } else {
+ F leftFirst= as(expression.getLeftOperand(), firstClass);
+ S rightSecond= as(expression.getRightOperand(), secondClass);
+
+ if (leftFirst != null && rightSecond != null) {
+ return new OrderedInfixExpression<>(leftFirst, expression.getOperator(), rightSecond);
+ }
+
+ InfixExpression.Operator mirroredOperator= mirrorOperator(expression);
+
+ if (mirroredOperator != null) {
+ F rightFirst= as(expression.getRightOperand(), firstClass);
+ S leftSecond= as(expression.getLeftOperand(), secondClass);
+
+ if (rightFirst != null && leftSecond != null) {
+ return new OrderedInfixExpression<>(rightFirst, mirroredOperator, leftSecond);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static InfixExpression.Operator mirrorOperator(final InfixExpression expression) {
+ if (Arrays.asList(
+ InfixExpression.Operator.AND,
+ InfixExpression.Operator.CONDITIONAL_AND,
+ InfixExpression.Operator.CONDITIONAL_OR,
+ InfixExpression.Operator.EQUALS,
+ InfixExpression.Operator.NOT_EQUALS,
+ InfixExpression.Operator.OR,
+ InfixExpression.Operator.PLUS,
+ InfixExpression.Operator.TIMES,
+ InfixExpression.Operator.XOR).contains(expression.getOperator())) {
+ return expression.getOperator();
+ } else if (InfixExpression.Operator.GREATER.equals(expression.getOperator())) {
+ return InfixExpression.Operator.LESS;
+ } else if (InfixExpression.Operator.GREATER_EQUALS.equals(expression.getOperator())) {
+ return InfixExpression.Operator.LESS_EQUALS;
+ } else if (InfixExpression.Operator.LESS.equals(expression.getOperator())) {
+ return InfixExpression.Operator.GREATER;
+ } else if (InfixExpression.Operator.LESS_EQUALS.equals(expression.getOperator())) {
+ return InfixExpression.Operator.GREATER_EQUALS;
+ }
+
+ return null;
+ }
+
+ /**
* Returns all the operands from the provided infix expressions.
*
* @param node the infix expression
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/OrderedInfixExpression.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/OrderedInfixExpression.java
new file mode 100644
index 0000000000..10e2fe8740
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/OrderedInfixExpression.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.corext.dom;
+
+import org.eclipse.jdt.core.dom.Expression;
+import org.eclipse.jdt.core.dom.InfixExpression;
+
+/**
+ * Ordered infix expression for commutative operations.
+ *
+ * @param <F> First operand
+ * @param <S> Second operand
+ */
+public class OrderedInfixExpression<F extends Expression, S extends Expression> {
+ private F firstOperand;
+ private InfixExpression.Operator operator;
+ private S secondOperand;
+
+ /**
+ * Ordered infix expression.
+ *
+ * @param firstOperand first operand
+ * @param operator operator
+ * @param secondOperand second operand
+ */
+ public OrderedInfixExpression(final F firstOperand, InfixExpression.Operator operator, final S secondOperand) {
+ this.firstOperand= firstOperand;
+ this.operator= operator;
+ this.secondOperand= secondOperand;
+ }
+
+ /**
+ * Get the first operand.
+ *
+ * @return the first operand.
+ */
+ public F getFirstOperand() {
+ return firstOperand;
+ }
+
+ /**
+ * Get the operator.
+ *
+ * @return the operator.
+ */
+ public InfixExpression.Operator getOperator() {
+ return operator;
+ }
+
+ /**
+ * Get the second operand.
+ *
+ * @return the second operand.
+ */
+ public S getSecondOperand() {
+ return secondOperand;
+ }
+}
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 7ceec707c9..617ca81d7c 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
@@ -1008,6 +1008,18 @@ public class CleanUpConstants {
public static final String REMOVE_UNNECESSARY_ARRAY_CREATION= "cleanup.remove_unnecessary_array_creation"; //$NON-NLS-1$
/**
+ * Reduces the code of the equals method implementation by using Objects.equals().
+ * <p>
+ * Possible values: {TRUE, FALSE}
+ * <p>
+ *
+ * @see CleanUpOptionsCore#TRUE
+ * @see CleanUpOptionsCore#FALSE
+ * @since 4.17
+ */
+ public static final String USE_OBJECTS_EQUALS= "cleanup.objects_equals"; //$NON-NLS-1$
+
+ /**
* Controls whether missing annotations should be added to the code. For detailed settings use:<br>
* {@link #ADD_MISSING_ANNOTATIONS_DEPRECATED}<br> {@value #ADD_MISSING_ANNOTATIONS_OVERRIDE} <br>
* <br>
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java
index f7c5e885e0..b73af12e5b 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d7.java
@@ -32,9 +32,8 @@ import org.eclipse.jdt.ui.tests.core.rules.ProjectTestSetup;
@RunWith(JUnit4.class)
public class CleanUpTest1d7 extends CleanUpTestCase {
-
@Rule
- public ProjectTestSetup projectsetup = new Java1d7ProjectTestSetup();
+ public ProjectTestSetup projectsetup= new Java1d7ProjectTestSetup();
@Override
protected IJavaProject getProject() {
@@ -84,4 +83,232 @@ public class CleanUpTest1d7 extends CleanUpTestCase {
assertRefactoringResultAsExpected(new ICompilationUnit[] { cu1 }, new String[] { expected1 });
}
+ @Test
+ public void testObjectsEquals() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ String sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Map;\n" //
+ + "import java.util.Observable;\n" //
+ + "\n" //
+ + "public class E1 {\n" //
+ + " private Map<Integer, String> textById;\n" //
+ + " private Observable anObservable;\n" //
+ + " private String aText;\n" //
+ + "\n" //
+ + " /* (non-Javadoc)\n" //
+ + " * @see java.lang.Object#equals(java.lang.Object)\n" //
+ + " */\n" //
+ + " @Override\n" //
+ + " public boolean equals(Object obj) {\n" //
+ + " if (this == obj)\n" //
+ + " return true;\n" //
+ + " if (obj == null)\n" //
+ + " return false;\n" //
+ + " if (getClass() != obj.getClass())\n" //
+ + " return false;\n" //
+ + " E1 other = (E1) obj;\n" //
+ + " if (aText == null) {\n" //
+ + " if (other.aText != null)\n" //
+ + " return false;\n" //
+ + " } else if (!aText.equals(other.aText))\n" //
+ + " return false;\n" //
+ + " if (null == anObservable) {\n" //
+ + " if (null != other.anObservable)\n" //
+ + " return false;\n" //
+ + " } else if (!anObservable.equals(other.anObservable))\n" //
+ + " return false;\n" //
+ + " if (this.textById == null) {\n" //
+ + " if (other.textById != null)\n" //
+ + " return false;\n" //
+ + " } else if (!this.textById.equals(other.textById)) {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu1= pack1.createCompilationUnit("E1.java", sample, false, null);
+
+ enable(CleanUpConstants.USE_OBJECTS_EQUALS);
+
+ sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Map;\n" //
+ + "import java.util.Objects;\n" //
+ + "import java.util.Observable;\n" //
+ + "\n" //
+ + "public class E1 {\n" //
+ + " private Map<Integer, String> textById;\n" //
+ + " private Observable anObservable;\n" //
+ + " private String aText;\n" //
+ + "\n" //
+ + " /* (non-Javadoc)\n" //
+ + " * @see java.lang.Object#equals(java.lang.Object)\n" //
+ + " */\n" //
+ + " @Override\n" //
+ + " public boolean equals(Object obj) {\n" //
+ + " if (this == obj)\n" //
+ + " return true;\n" //
+ + " if (obj == null)\n" //
+ + " return false;\n" //
+ + " if (getClass() != obj.getClass())\n" //
+ + " return false;\n" //
+ + " E1 other = (E1) obj;\n" //
+ + " if (!Objects.equals(aText, other.aText)) {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + " if (!Objects.equals(anObservable, other.anObservable)) {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + " if (!Objects.equals(this.textById, other.textById)) {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + "}\n";
+ String expected1= sample;
+
+ assertRefactoringResultAsExpected(new ICompilationUnit[] { cu1 }, new String[] { expected1 });
+ }
+
+ @Test
+ public void testDoNotRefactorObjectsEquals() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ String sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Map;\n" //
+ + "import java.util.Observable;\n" //
+ + "\n" //
+ + "public class E1 {\n" //
+ + " private Map<Integer, String> textById;\n" //
+ + " private Observable anObservable;\n" //
+ + " private String aText;\n" //
+ + "\n" //
+ + " /* (non-Javadoc)\n" //
+ + " * @see java.lang.Object#equals(java.lang.Object)\n" //
+ + " */\n" //
+ + " @Override\n" //
+ + " public boolean equals(Object obj) {\n" //
+ + " if (this == obj)\n" //
+ + " return true;\n" //
+ + " if (obj == null)\n" //
+ + " return false;\n" //
+ + " if (getClass() != obj.getClass())\n" //
+ + " return false;\n" //
+ + " E1 other = (E1) obj;\n" //
+ + " if (aText == null) {\n" //
+ + " if (other.aText != null)\n" //
+ + " return true;\n" //
+ + " } else if (!aText.equals(other.aText))\n" //
+ + " return false;\n" //
+ + " if (null == anObservable) {\n" //
+ + " if (null != other.anObservable)\n" //
+ + " return false;\n" //
+ + " } else if (!anObservable.equals(other.anObservable))\n" //
+ + " return true;\n" //
+ + " if (this.textById == null) {\n" //
+ + " if (other.textById != null)\n" //
+ + " return false;\n" //
+ + " } else if (this.textById.equals(other.textById))\n" //
+ + " return false;\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu= pack1.createCompilationUnit("E1.java", sample, false, null);
+
+ enable(CleanUpConstants.USE_OBJECTS_EQUALS);
+
+ assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+ }
+
+ @Test
+ public void testObjectsEqualsWithImportConflict() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ String sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Map;\n" //
+ + "import java.util.Observable;\n" //
+ + "\n" //
+ + "public class Objects {\n" //
+ + " private Map<Integer, String> textById;\n" //
+ + " private Observable anObservable;\n" //
+ + " private String aText;\n" //
+ + "\n" //
+ + " /* (non-Javadoc)\n" //
+ + " * @see java.lang.Object#equals(java.lang.Object)\n" //
+ + " */\n" //
+ + " @Override\n" //
+ + " public boolean equals(Object obj) {\n" //
+ + " if (this == obj)\n" //
+ + " return true;\n" //
+ + " if (obj == null)\n" //
+ + " return false;\n" //
+ + " if (getClass() != obj.getClass())\n" //
+ + " return false;\n" //
+ + " Objects other = (Objects) obj;\n" //
+ + " if (aText == null) {\n" //
+ + " if (other.aText != null)\n" //
+ + " return false;\n" //
+ + " } else if (!aText.equals(other.aText))\n" //
+ + " return false;\n" //
+ + " if (null == anObservable) {\n" //
+ + " if (null != other.anObservable)\n" //
+ + " return false;\n" //
+ + " } else if (!anObservable.equals(other.anObservable))\n" //
+ + " return false;\n" //
+ + " if (this.textById == null) {\n" //
+ + " if (other.textById != null)\n" //
+ + " return false;\n" //
+ + " } else if (!this.textById.equals(other.textById))\n" //
+ + " return false;\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu1= pack1.createCompilationUnit("Objects.java", sample, false, null);
+
+ enable(CleanUpConstants.USE_OBJECTS_EQUALS);
+
+ sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Map;\n" //
+ + "import java.util.Observable;\n" //
+ + "\n" //
+ + "public class Objects {\n" //
+ + " private Map<Integer, String> textById;\n" //
+ + " private Observable anObservable;\n" //
+ + " private String aText;\n" //
+ + "\n" //
+ + " /* (non-Javadoc)\n" //
+ + " * @see java.lang.Object#equals(java.lang.Object)\n" //
+ + " */\n" //
+ + " @Override\n" //
+ + " public boolean equals(Object obj) {\n" //
+ + " if (this == obj)\n" //
+ + " return true;\n" //
+ + " if (obj == null)\n" //
+ + " return false;\n" //
+ + " if (getClass() != obj.getClass())\n" //
+ + " return false;\n" //
+ + " Objects other = (Objects) obj;\n" //
+ + " if (!java.util.Objects.equals(aText, other.aText)) {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + " if (!java.util.Objects.equals(anObservable, other.anObservable)) {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + " if (!java.util.Objects.equals(this.textById, other.textById)) {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + "}\n";
+ String expected1= sample;
+
+ assertRefactoringResultAsExpected(new ICompilationUnit[] { cu1 }, new String[] { expected1 });
+ }
}
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 d4f3436a09..5853fe215d 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
@@ -95,6 +95,7 @@ public class CleanUpConstantsOptions extends CleanUpConstants {
options.setOption(REMOVE_REDUNDANT_MODIFIERS, CleanUpOptions.FALSE);
options.setOption(REMOVE_REDUNDANT_SEMICOLONS, CleanUpOptions.FALSE);
options.setOption(REMOVE_UNNECESSARY_ARRAY_CREATION, CleanUpOptions.FALSE);
+ options.setOption(USE_OBJECTS_EQUALS, CleanUpOptions.FALSE);
//Missing Code
options.setOption(ADD_MISSING_ANNOTATIONS, CleanUpOptions.TRUE);
@@ -196,6 +197,7 @@ public class CleanUpConstantsOptions extends CleanUpConstants {
options.setOption(REMOVE_REDUNDANT_MODIFIERS, CleanUpOptions.FALSE);
options.setOption(REMOVE_REDUNDANT_SEMICOLONS, CleanUpOptions.FALSE);
options.setOption(REMOVE_UNNECESSARY_ARRAY_CREATION, CleanUpOptions.FALSE);
+ options.setOption(USE_OBJECTS_EQUALS, CleanUpOptions.FALSE);
//Missing Code
options.setOption(ADD_MISSING_ANNOTATIONS, CleanUpOptions.TRUE);
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index 4501c9b99c..ecd7fbaa94 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7116,9 +7116,14 @@
runAfter="org.eclipse.jdt.ui.cleanup.unnecessary_semicolons">
</cleanUp>
<cleanUp
+ class="org.eclipse.jdt.internal.ui.fix.ObjectsEqualsCleanUp"
+ id="org.eclipse.jdt.ui.cleanup.precompile_regex"
+ runAfter="org.eclipse.jdt.ui.cleanup.unnecessary_array_creation">
+ </cleanUp>
+ <cleanUp
class="org.eclipse.jdt.internal.ui.fix.StringCleanUp"
id="org.eclipse.jdt.ui.cleanup.strings"
- runAfter="org.eclipse.jdt.ui.cleanup.unnecessary_array_creation">
+ runAfter="org.eclipse.jdt.ui.cleanup.precompile_regex">
</cleanUp>
<cleanUp
class="org.eclipse.jdt.internal.ui.fix.UnimplementedCodeCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/ObjectsEqualsCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/ObjectsEqualsCleanUp.java
new file mode 100644
index 0000000000..2d61d4b24b
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/ObjectsEqualsCleanUp.java
@@ -0,0 +1,296 @@
+/*******************************************************************************
+ * 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.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTMatcher;
+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.BooleanLiteral;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.Expression;
+import org.eclipse.jdt.core.dom.IfStatement;
+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.NullLiteral;
+import org.eclipse.jdt.core.dom.PrefixExpression;
+import org.eclipse.jdt.core.dom.ReturnStatement;
+import org.eclipse.jdt.core.dom.Statement;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
+import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.dom.OrderedInfixExpression;
+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;
+
+/**
+ * A fix that reduces the code of the equals method implementation by using Objects.equals().
+ */
+public class ObjectsEqualsCleanUp extends AbstractMultiFix implements ICleanUpFix {
+ private static final String EQUALS_METHOD= "equals"; //$NON-NLS-1$
+
+ public ObjectsEqualsCleanUp() {
+ this(Collections.emptyMap());
+ }
+
+ public ObjectsEqualsCleanUp(Map<String, String> options) {
+ super(options);
+ }
+
+ @Override
+ public CleanUpRequirements getRequirements() {
+ boolean requireAST= isEnabled(CleanUpConstants.USE_OBJECTS_EQUALS);
+ return new CleanUpRequirements(requireAST, false, false, null);
+ }
+
+ @Override
+ public String[] getStepDescriptions() {
+ if (isEnabled(CleanUpConstants.USE_OBJECTS_EQUALS)) {
+ return new String[] { MultiFixMessages.ObjectsEqualsCleanup_description };
+ }
+
+ return new String[0];
+ }
+
+ @Override
+ public String getPreview() {
+ if (isEnabled(CleanUpConstants.USE_OBJECTS_EQUALS)) {
+ return "" //$NON-NLS-1$
+ + "if (!Objects.equals(aText, other.aText)) {\n" //$NON-NLS-1$
+ + " return false;\n" //$NON-NLS-1$
+ + "}\n\n\n\n\n"; //$NON-NLS-1$
+ } else {
+ return "" //$NON-NLS-1$
+ + "if (aText == null) {\n" //$NON-NLS-1$
+ + " if (other.aText != null) {\n" //$NON-NLS-1$
+ + " return false;\n" //$NON-NLS-1$
+ + " }\n" //$NON-NLS-1$
+ + "} else if (!aText.equals(other.aText)) {\n" //$NON-NLS-1$
+ + " return false;\n" //$NON-NLS-1$
+ + "}\n"; //$NON-NLS-1$
+ }
+ }
+
+ @Override
+ protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException {
+ if (!isEnabled(CleanUpConstants.USE_OBJECTS_EQUALS) || !JavaModelUtil.is17OrHigher(unit.getJavaElement().getJavaProject())) {
+ return null;
+ }
+
+ final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>();
+
+ unit.accept(new ASTVisitor() {
+
+ @Override
+ public boolean visit(final IfStatement node) {
+ if (node.getElseStatement() != null) {
+ InfixExpression condition= ASTNodes.as(node.getExpression(), InfixExpression.class);
+ List<Statement> thenStatements= ASTNodes.asList(node.getThenStatement());
+ List<Statement> elseStatements= ASTNodes.asList(node.getElseStatement());
+
+ if (condition != null && !condition.hasExtendedOperands()
+ && ASTNodes.hasOperator(condition, InfixExpression.Operator.EQUALS, InfixExpression.Operator.NOT_EQUALS)
+ && thenStatements != null && thenStatements.size() == 1 && elseStatements != null && elseStatements.size() == 1) {
+ OrderedInfixExpression<Expression, NullLiteral> nullityTypedCondition= ASTNodes.orderedInfix(condition, Expression.class, NullLiteral.class);
+
+ if (nullityTypedCondition != null && ASTNodes.isPassive(nullityTypedCondition.getFirstOperand())) {
+ return maybeReplaceCode(node, condition, thenStatements, elseStatements, nullityTypedCondition.getFirstOperand());
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private boolean maybeReplaceCode(final IfStatement node, final InfixExpression condition,
+ final List<Statement> thenStatements, final List<Statement> elseStatements, final Expression firstField) {
+ IfStatement checkNullityStatement;
+ IfStatement checkEqualsStatement;
+
+ if (ASTNodes.hasOperator(condition, InfixExpression.Operator.EQUALS)) {
+ checkNullityStatement= ASTNodes.as(thenStatements.get(0), IfStatement.class);
+ checkEqualsStatement= ASTNodes.as(elseStatements.get(0), IfStatement.class);
+ } else {
+ checkEqualsStatement= ASTNodes.as(thenStatements.get(0), IfStatement.class);
+ checkNullityStatement= ASTNodes.as(elseStatements.get(0), IfStatement.class);
+ }
+
+ if (checkNullityStatement != null && checkNullityStatement.getElseStatement() == null && checkEqualsStatement != null
+ && checkEqualsStatement.getElseStatement() == null) {
+ InfixExpression nullityCondition= ASTNodes.as(checkNullityStatement.getExpression(), InfixExpression.class);
+ List<Statement> nullityStatements= ASTNodes.asList(checkNullityStatement.getThenStatement());
+
+ PrefixExpression equalsCondition= ASTNodes.as(checkEqualsStatement.getExpression(), PrefixExpression.class);
+ List<Statement> equalsStatements= ASTNodes.asList(checkEqualsStatement.getThenStatement());
+
+ if (nullityCondition != null && !nullityCondition.hasExtendedOperands()
+ && ASTNodes.hasOperator(nullityCondition, InfixExpression.Operator.NOT_EQUALS) && nullityStatements != null
+ && nullityStatements.size() == 1 && equalsCondition != null
+ && ASTNodes.hasOperator(equalsCondition, PrefixExpression.Operator.NOT) && equalsStatements != null
+ && equalsStatements.size() == 1) {
+ return maybeReplaceEquals(node, firstField, nullityCondition, nullityStatements, equalsCondition,
+ equalsStatements);
+ }
+ }
+
+ return true;
+ }
+
+ private boolean maybeReplaceEquals(final IfStatement node, final Expression firstField,
+ final InfixExpression nullityCondition, final List<Statement> nullityStatements,
+ final PrefixExpression equalsCondition, final List<Statement> equalsStatements) {
+ OrderedInfixExpression<Expression, NullLiteral> nullityTypedCondition= ASTNodes.orderedInfix(nullityCondition, Expression.class, NullLiteral.class);
+ ReturnStatement returnStatement1= ASTNodes.as(nullityStatements.get(0), ReturnStatement.class);
+ ReturnStatement returnStatement2= ASTNodes.as(equalsStatements.get(0), ReturnStatement.class);
+ MethodInvocation equalsMethod= ASTNodes.as(equalsCondition.getOperand(), MethodInvocation.class);
+
+ if (nullityTypedCondition != null && returnStatement1 != null && returnStatement2 != null && equalsMethod != null
+ && equalsMethod.getExpression() != null && EQUALS_METHOD.equals(equalsMethod.getName().getIdentifier())
+ && equalsMethod.arguments() != null && equalsMethod.arguments().size() == 1) {
+ Expression secondField= nullityTypedCondition.getFirstOperand();
+
+ if (secondField != null
+ && (match(firstField, secondField, equalsMethod.getExpression(),
+ (ASTNode) equalsMethod.arguments().get(0))
+ || match(secondField, firstField, equalsMethod.getExpression(),
+ (ASTNode) equalsMethod.arguments().get(0)))) {
+ BooleanLiteral returnFalse1= ASTNodes.as(returnStatement1.getExpression(), BooleanLiteral.class);
+ BooleanLiteral returnFalse2= ASTNodes.as(returnStatement2.getExpression(), BooleanLiteral.class);
+
+ if (returnFalse1 != null && !returnFalse1.booleanValue() && returnFalse2 != null
+ && !returnFalse2.booleanValue()) {
+ rewriteOperations.add(new ObjectsEqualsOperation(node, firstField, secondField, returnStatement1));
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private boolean match(final Expression firstField, final Expression secondField, final Expression thisObject,
+ final ASTNode otherObject) {
+ ASTMatcher astMatcher= new ASTMatcher();
+ return astMatcher.safeSubtreeMatch(thisObject, firstField) && astMatcher.safeSubtreeMatch(otherObject, secondField);
+ }
+ });
+
+ if (rewriteOperations.isEmpty()) {
+ return null;
+ }
+
+ return new CompilationUnitRewriteOperationsFix(MultiFixMessages.ObjectsEqualsCleanup_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 ObjectsEqualsOperation extends CompilationUnitRewriteOperation {
+ private final IfStatement node;
+ private final Expression firstField;
+ private final Expression secondField;
+ private final ReturnStatement returnStatement;
+
+ public ObjectsEqualsOperation(final IfStatement node, final Expression firstField,
+ final Expression secondField,
+ final ReturnStatement returnStatement) {
+ this.node= node;
+ this.firstField= firstField;
+ this.secondField= secondField;
+ this.returnStatement= returnStatement;
+ }
+
+ @Override
+ public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException {
+ ASTRewrite rewrite= cuRewrite.getASTRewrite();
+ AST ast= cuRewrite.getRoot().getAST();
+ ImportRewrite importRewrite= cuRewrite.getImportRewrite();
+
+ String objectNameText= importRewrite.addImport(Objects.class.getCanonicalName());
+
+ MethodInvocation newEqualsMethod= ast.newMethodInvocation();
+ newEqualsMethod.setExpression(newTypeName(ast, objectNameText));
+ newEqualsMethod.setName(ast.newSimpleName(EQUALS_METHOD));
+ newEqualsMethod.arguments().add(ASTNodes.createMoveTarget(rewrite, firstField));
+ newEqualsMethod.arguments().add(ASTNodes.createMoveTarget(rewrite, secondField));
+
+ PrefixExpression notExpression= ast.newPrefixExpression();
+ notExpression.setOperator(PrefixExpression.Operator.NOT);
+ notExpression.setOperand(newEqualsMethod);
+
+ ReturnStatement copyOfReturnStatement= ASTNodes.createMoveTarget(rewrite, returnStatement);
+
+ rewrite.replace(node.getExpression(), notExpression, null);
+
+ if (node.getThenStatement() instanceof Block) {
+ rewrite.replace((ASTNode) ((Block) node.getThenStatement()).statements().get(0), copyOfReturnStatement, null);
+ } else {
+ rewrite.replace(node.getThenStatement(), copyOfReturnStatement, null);
+ }
+
+ if (node.getElseStatement() != null) {
+ rewrite.remove(node.getElseStatement(), null);
+ }
+ }
+
+ private Name newTypeName(final AST ast, final String objectNameText) {
+ Name qualifiedName= null;
+
+ for (String packageName : objectNameText.split("\\.")) { //$NON-NLS-1$
+ if (qualifiedName == null) {
+ qualifiedName= ast.newSimpleName(packageName);
+ } else {
+ qualifiedName= ast.newQualifiedName(qualifiedName, ast.newSimpleName(packageName));
+ }
+ }
+
+ return qualifiedName;
+ }
+ }
+}
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 8bcbb0965d..a3f8de8cbe 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
@@ -128,6 +128,7 @@ public class CleanUpMessages extends NLS {
public static String UnnecessaryCodeTabPage_CheckboxName_RedundantSemicolons;
public static String UnnecessaryCodeTabPage_CheckboxName_RedundantSemicolons_description;
public static String UnnecessaryCodeTabPage_CheckboxName_RedundantArrayCreation_description;
+ public static String UnnecessaryCodeTabPage_CheckboxName_ObjectsEquals;
public static String UnnecessaryCodeTabPage_CheckboxName_UnnecessaryVarargsArrayCreation;
public static String UnnecessaryCodeTabPage_CheckboxName_UnusedConstructors;
public static String UnnecessaryCodeTabPage_CheckboxName_UnusedFields;
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 d066227fba..5a4a1ec1a2 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
@@ -113,3 +113,4 @@ UnnecessaryCodeTabPage_CheckboxName_RedundantSemicolons=Remove redundant semicol
UnnecessaryCodeTabPage_CheckboxName_RedundantSemicolons_description=Removes unnecessary semicolons on statements and declarations
UnnecessaryCodeTabPage_CheckboxName_RedundantArrayCreation_description=Removes unnecessary array creation used to supply varargs in a method invocation
UnnecessaryCodeTabPage_CheckboxName_UnnecessaryVarargsArrayCreation=Remove unnecessary &array creation (1.5 or higher)
+UnnecessaryCodeTabPage_CheckboxName_ObjectsEquals=Use Objects.e&quals() in the equals method implementation (1.7 or higher)
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 6a3b77aa16..50613f7d4d 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
@@ -25,6 +25,7 @@ import org.eclipse.jdt.internal.ui.fix.AbstractCleanUp;
import org.eclipse.jdt.internal.ui.fix.AutoboxingCleanUp;
import org.eclipse.jdt.internal.ui.fix.MapMethodCleanUp;
import org.eclipse.jdt.internal.ui.fix.MergeConditionalBlocksCleanUp;
+import org.eclipse.jdt.internal.ui.fix.ObjectsEqualsCleanUp;
import org.eclipse.jdt.internal.ui.fix.PushDownNegationCleanUp;
import org.eclipse.jdt.internal.ui.fix.RedundantModifiersCleanUp;
import org.eclipse.jdt.internal.ui.fix.RedundantSemicolonsCleanUp;
@@ -52,7 +53,8 @@ public final class UnnecessaryCodeTabPage extends AbstractCleanUpTabPage {
new MapMethodCleanUp(values),
new RedundantModifiersCleanUp(values),
new RedundantSemicolonsCleanUp(values),
- new UnnecessaryArrayCreationCleanUp(values)
+ new UnnecessaryArrayCreationCleanUp(values),
+ new ObjectsEqualsCleanUp(values)
};
}
@@ -110,5 +112,8 @@ public final class UnnecessaryCodeTabPage extends AbstractCleanUpTabPage {
CheckboxPreference arrayCreation= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_UnnecessaryVarargsArrayCreation, CleanUpConstants.REMOVE_UNNECESSARY_ARRAY_CREATION, CleanUpModifyDialog.FALSE_TRUE);
registerPreference(arrayCreation);
+
+ CheckboxPreference objectsEquals= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_ObjectsEquals, CleanUpConstants.USE_OBJECTS_EQUALS, CleanUpModifyDialog.FALSE_TRUE);
+ registerPreference(objectsEquals);
}
}

Back to the top