Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabrice Tiercelin2020-10-08 04:25:54 +0000
committerFabrice Tiercelin2020-10-18 13:02:22 +0000
commit64e5a9cab23ee26c331d3fabad5e5c83e09b6f6f (patch)
tree6a750475a117358699e986f9943020879b1e6d26
parent7b4291b1222ff497f5bec510115347d0b741128a (diff)
downloadeclipse.jdt.ui-64e5a9cab23ee26c331d3fabad5e5c83e09b6f6f.tar.gz
eclipse.jdt.ui-64e5a9cab23ee26c331d3fabad5e5c83e09b6f6f.tar.xz
eclipse.jdt.ui-64e5a9cab23ee26c331d3fabad5e5c83e09b6f6f.zip
Bug 567692 - [AutoRefactor immigration #31/138] [cleanup & saveaction]Y20201018-1200
Objects.hash() Change-Id: Ia3bb7a33197ddd6d1f6a63689252634320a05e4f 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.java44
-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.java223
-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.xml9
-rw-r--r--org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/HashCleanUp.java617
-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.java5
11 files changed, 914 insertions, 2 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 792995ad47..4d7c037f59 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
@@ -122,6 +122,7 @@ public class MultiFixMessages extends NLS {
public static String TypeParametersCleanUp_InsertInferredTypeArguments_description;
public static String TypeParametersCleanUp_RemoveUnnecessaryTypeArguments_description;
+ public static String HashCleanup_description;
public static String RedundantModifiersCleanup_description;
public static String ArraysFillCleanUp_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 7f64742fb8..9a3b9fb555 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
@@ -103,6 +103,7 @@ NullAnnotationsCleanUp_add_nonnullbydefault_annotation=Add missing @NonNullByDef
NullAnnotationsCleanUp_remove_redundant_nullness_annotation=Remove redundant nullness annotation
TypeParametersCleanUp_InsertInferredTypeArguments_description=Insert inferred type arguments
TypeParametersCleanUp_RemoveUnnecessaryTypeArguments_description=Remove redundant type arguments
+HashCleanup_description=Use Objects.hash()
RedundantModifiersCleanup_description = Remove redundant modifiers
ArraysFillCleanUp_description=Use Arrays.fill() when possible
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 27f9485d1e..5a35584f2e 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
@@ -2195,6 +2195,30 @@ public class ASTNodes {
}
/**
+ * Returns the first ancestor of the provided node which has the required type.
+ *
+ * @param <T> the required ancestor's type
+ * @param node the start node
+ * @param ancestorClass the required ancestor's type
+ * @return the first ancestor of the provided node which has the required type,
+ * {@code null} if no suitable ancestor can be found
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends ASTNode> T getTypedAncestor(final ASTNode node, final Class<T> ancestorClass) {
+ if (node == null || node.getParent() == null) {
+ return null;
+ }
+
+ ASTNode parent= node.getParent();
+
+ if (ancestorClass.isAssignableFrom(parent.getClass())) {
+ return (T) parent;
+ }
+
+ return getTypedAncestor(parent, ancestorClass);
+ }
+
+ /**
* Returns the first ancestor of the provided node which has any of the required types.
*
* @param node the start node
@@ -2932,6 +2956,26 @@ public class ASTNodes {
}
/**
+ * Returns whether the provided method declaration declares a method with the
+ * provided method signature. The method signature is compared against the
+ * erasure of the declared method.
+ *
+ * @param actualMethod the actual method declaration
+ * @param typeQualifiedName the expected qualified name of the type declaring
+ * the expected method
+ * @param methodName the expected method name
+ * @param parameterTypesQualifiedNames the expected qualified names of the parameter
+ * types
+ * @return true if the provided method declaration matches the provided method
+ * signature, false otherwise
+ */
+ public static boolean usesGivenSignature(final MethodDeclaration actualMethod, final String typeQualifiedName, final String methodName,
+ final String... parameterTypesQualifiedNames) {
+ return actualMethod != null
+ && usesGivenSignature(actualMethod.resolveBinding(), typeQualifiedName, methodName, parameterTypesQualifiedNames);
+ }
+
+ /**
* Returns whether the provided method binding has the provided method signature. The method
* signature is compared against the erasure of the invoked method.
*
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 e7ee3d0935..f7adbc2abb 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
@@ -1104,6 +1104,18 @@ public class CleanUpConstants {
public static final String REMOVE_REDUNDANT_TYPE_ARGUMENTS= "cleanup.remove_redundant_type_arguments"; //$NON-NLS-1$
/**
+ * Rewrites Eclipse-autogenerated hashcode method by Eclipse-autogenerated hashcode method for Java 7.
+ * <p>
+ * Possible values: {TRUE, FALSE}
+ * <p>
+ *
+ * @see CleanUpOptionsCore#TRUE
+ * @see CleanUpOptionsCore#FALSE
+ * @since 4.18
+ */
+ public static final String MODERNIZE_HASH= "cleanup.hash"; //$NON-NLS-1$
+
+ /**
* Removes redundant modifiers.<br>
* <br>
* Possible values: {TRUE, FALSE}<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 6e8bf1d5c2..5630ee94e8 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
@@ -29,6 +29,8 @@ import org.eclipse.jdt.internal.corext.fix.FixMessages;
import org.eclipse.jdt.ui.tests.core.rules.Java1d7ProjectTestSetup;
import org.eclipse.jdt.ui.tests.core.rules.ProjectTestSetup;
+import org.eclipse.jdt.internal.ui.fix.MultiFixMessages;
+
public class CleanUpTest1d7 extends CleanUpTestCase {
@Rule
public ProjectTestSetup projectSetup= new Java1d7ProjectTestSetup();
@@ -82,6 +84,227 @@ public class CleanUpTest1d7 extends CleanUpTestCase {
}
@Test
+ public void testHash() throws Exception {
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String input= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Arrays;\n" //
+ + "import java.util.Map;\n" //
+ + "import java.util.Observable;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public class RefactoredClass {\n" //
+ + " private Map<Integer, String> innerTextById;\n" //
+ + " private Observable innerObservable;\n" //
+ + " private String innerText;\n" //
+ + " private String[] innerTexts;\n" //
+ + " private int[] innerIntegers;\n" //
+ + " private char innerChar;\n" //
+ + " private byte innerByte;\n" //
+ + " private boolean innerBoolean;\n" //
+ + " private int innerInt;\n" //
+ + " private long innerLong;\n" //
+ + " private double innerDouble;\n" //
+ + " private short innerShort;\n" //
+ + " private float innerFloat;\n" //
+ + " private double innerOtherDouble;\n" //
+ + " private Boolean innerBooleanWrapper;\n" //
+ + "\n" //
+ + " @Override\n" //
+ + " public int hashCode() {\n" //
+ + " // Keep this comment\n" //
+ + " final int prime = 31;\n" //
+ + " int result = 1;\n" //
+ + " result = prime * result + getEnclosingInstance().hashCode();\n" //
+ + " result = prime * result + (RefactoredClass.this.innerBoolean ? 1231 : 1237);\n" //
+ + " result = prime * result + this.innerByte;\n" //
+ + " result = prime * result + innerChar;\n" //
+ + " long temp = Double.doubleToLongBits(innerDouble);\n" //
+ + " result = prime * result + (int) ((temp >>> 32) ^ temp);\n" //
+ + " result = prime * result + Float.floatToIntBits(innerFloat);\n" //
+ + " result = result * prime + innerInt;\n" //
+ + " result = prime * result + Arrays.hashCode(innerIntegers);\n" //
+ + " result = prime * result + (int) (innerLong ^ (this.innerLong >>> 32));\n" //
+ + " result = prime * result + ((innerObservable == null) ? 0 : innerObservable.hashCode());\n" //
+ + " temp = Double.doubleToLongBits(innerOtherDouble);\n" //
+ + " result = prime * result + (int) (temp ^ (temp >>> 32));\n" //
+ + " result = prime * result + innerShort;\n" //
+ + " result = prime * result + ((innerText == null) ? 0 : innerText.hashCode());\n" //
+ + " result = prime * result + ((innerTextById != null) ? this.innerTextById.hashCode() : 0);\n" //
+ + " result = prime * result + ((this.innerBooleanWrapper != null) ? innerBooleanWrapper.hashCode() : 0);\n" //
+ + " return prime * result + Arrays.hashCode(innerTexts);\n" //
+ + " }\n" //
+ + "\n" //
+ + " private E getEnclosingInstance() {\n" //
+ + " return E.this;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private Map<Integer, String> textById;\n" //
+ + " private Observable anObservable;\n" //
+ + " private String aText;\n" //
+ + " private String[] texts;\n" //
+ + " private int[] integers;\n" //
+ + " private char aChar;\n" //
+ + " private byte aByte;\n" //
+ + " private boolean aBoolean;\n" //
+ + " private int anInt;\n" //
+ + " private long aLong;\n" //
+ + " private double aDouble;\n" //
+ + " private short aShort;\n" //
+ + " private float aFloat;\n" //
+ + " private double anotherDouble;\n" //
+ + "\n" //
+ + " @Override\n" //
+ + " public int hashCode() {\n" //
+ + " // Keep this comment\n" //
+ + " final int prime = 31;\n" //
+ + " int result = 1;\n" //
+ + " result = prime * result + (E.this.aBoolean ? 1231 : 1237);\n" //
+ + " result = prime * result + aByte;\n" //
+ + " result = prime * result + aChar;\n" //
+ + " result = prime * result + Float.floatToIntBits(aFloat);\n" //
+ + " result = prime * result + (int) (aLong ^ (aLong >>> 32));\n" //
+ + " long temp;\n" //
+ + " temp = Double.doubleToLongBits(aDouble);\n" //
+ + " result = prime * result + (int) (temp ^ (temp >>> 32));\n" //
+ + " result = prime * result + aShort;\n" //
+ + " result = prime * result + ((null == aText) ? 0 : aText.hashCode());\n" //
+ + " result = prime * result + anInt;\n" //
+ + " result = prime * result + ((anObservable == null) ? 0 : anObservable.hashCode());\n" //
+ + " result = prime * result + Arrays.hashCode(integers);\n" //
+ + " result = prime * result + ((textById == null) ? 0 : textById.hashCode());\n" //
+ + " result = prime * result + Arrays.hashCode(texts);\n" //
+ + " temp = Double.doubleToLongBits(anotherDouble);\n" //
+ + " result = prime * result + (int) (temp ^ (temp >>> 32));\n" //
+ + " return result;\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", input, false, null);
+
+ enable(CleanUpConstants.MODERNIZE_HASH);
+
+ String output= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Arrays;\n" //
+ + "import java.util.Map;\n" //
+ + "import java.util.Objects;\n" //
+ + "import java.util.Observable;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public class RefactoredClass {\n" //
+ + " private Map<Integer, String> innerTextById;\n" //
+ + " private Observable innerObservable;\n" //
+ + " private String innerText;\n" //
+ + " private String[] innerTexts;\n" //
+ + " private int[] innerIntegers;\n" //
+ + " private char innerChar;\n" //
+ + " private byte innerByte;\n" //
+ + " private boolean innerBoolean;\n" //
+ + " private int innerInt;\n" //
+ + " private long innerLong;\n" //
+ + " private double innerDouble;\n" //
+ + " private short innerShort;\n" //
+ + " private float innerFloat;\n" //
+ + " private double innerOtherDouble;\n" //
+ + " private Boolean innerBooleanWrapper;\n" //
+ + "\n" //
+ + " @Override\n" //
+ + " public int hashCode() {\n" //
+ + " // Keep this comment\n" //
+ + " return Objects.hash(getEnclosingInstance().hashCode(), innerBoolean, innerByte, innerChar, innerDouble,\n" //
+ + " innerFloat, innerInt, Arrays.hashCode(innerIntegers), innerLong, innerObservable, innerOtherDouble,\n" //
+ + " innerShort, innerText, innerTextById, innerBooleanWrapper, Arrays.hashCode(innerTexts));\n" //
+ + " }\n" //
+ + "\n" //
+ + " private E getEnclosingInstance() {\n" //
+ + " return E.this;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private Map<Integer, String> textById;\n" //
+ + " private Observable anObservable;\n" //
+ + " private String aText;\n" //
+ + " private String[] texts;\n" //
+ + " private int[] integers;\n" //
+ + " private char aChar;\n" //
+ + " private byte aByte;\n" //
+ + " private boolean aBoolean;\n" //
+ + " private int anInt;\n" //
+ + " private long aLong;\n" //
+ + " private double aDouble;\n" //
+ + " private short aShort;\n" //
+ + " private float aFloat;\n" //
+ + " private double anotherDouble;\n" //
+ + "\n" //
+ + " @Override\n" //
+ + " public int hashCode() {\n" //
+ + " // Keep this comment\n" //
+ + " return Objects.hash(aBoolean, aByte, aChar, aFloat, aLong,\n" //
+ + " aDouble, aShort, aText, anInt, anObservable, Arrays.hashCode(integers), textById,\n" //
+ + " Arrays.hashCode(texts), anotherDouble);\n" //
+ + " }\n" //
+ + "}\n";
+ assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new String[] { MultiFixMessages.HashCleanup_description });
+ assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { output });
+ }
+
+ @Test
+ public void testKeepHash() throws Exception {
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public class DoNotRefactorNewClass {\n" //
+ + " private boolean innerBoolean;\n" //
+ + "\n" //
+ + " @Override\n" //
+ + " public int hashCode() {\n" //
+ + " final int prime = 31;\n" //
+ + " int result = 1;\n" //
+ + " result = prime * result + getEnclosingInstance().hashCode();\n" //
+ + " result = prime * result + (innerBoolean ? 1231 : 1237);\n" //
+ + " return result;\n" //
+ + " }\n" //
+ + "\n" //
+ + " private E getEnclosingInstance() {\n" //
+ + " return new E();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class DoNotRefactorCustomHash {\n" //
+ + " private boolean innerBoolean;\n" //
+ + "\n" //
+ + " @Override\n" //
+ + " public int hashCode() {\n" //
+ + " final int prime = 63;\n" //
+ + " int result = 1;\n" //
+ + " result = prime * result + (innerBoolean ? 1231 : 1237);\n" //
+ + " return result;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private boolean innerBoolean;\n" //
+ + "\n" //
+ + " @Override\n" //
+ + " public int hashCode() {\n" //
+ + " final int prime = 31;\n" //
+ + " int result = 1;\n" //
+ + " result += prime * result + (innerBoolean ? 1231 : 1237);\n" //
+ + " return result;\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", sample, false, null);
+
+ enable(CleanUpConstants.MODERNIZE_HASH);
+
+ assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+ }
+
+ @Test
public void testObjectsEquals() throws Exception {
IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
String sample= "" //
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 2d6a47e360..3aa5594b80 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
@@ -94,6 +94,7 @@ public class CleanUpConstantsOptions extends CleanUpConstants {
options.setOption(REMOVE_UNNECESSARY_NLS_TAGS, CleanUpOptions.TRUE);
options.setOption(INSERT_INFERRED_TYPE_ARGUMENTS, CleanUpOptions.FALSE);
options.setOption(REMOVE_REDUNDANT_TYPE_ARGUMENTS, CleanUpOptions.FALSE);
+ options.setOption(MODERNIZE_HASH, CleanUpOptions.FALSE);
options.setOption(ARRAYS_FILL, CleanUpOptions.FALSE);
options.setOption(USE_AUTOBOXING, CleanUpOptions.FALSE);
options.setOption(USE_UNBOXING, CleanUpOptions.FALSE);
@@ -214,6 +215,7 @@ public class CleanUpConstantsOptions extends CleanUpConstants {
options.setOption(REMOVE_UNNECESSARY_NLS_TAGS, CleanUpOptions.FALSE);
options.setOption(INSERT_INFERRED_TYPE_ARGUMENTS, CleanUpOptions.FALSE);
options.setOption(REMOVE_REDUNDANT_TYPE_ARGUMENTS, CleanUpOptions.FALSE);
+ options.setOption(MODERNIZE_HASH, CleanUpOptions.FALSE);
options.setOption(ARRAYS_FILL, CleanUpOptions.FALSE);
options.setOption(USE_AUTOBOXING, CleanUpOptions.FALSE);
options.setOption(USE_UNBOXING, CleanUpOptions.FALSE);
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index 0c7915aa3e..a917f7599a 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7208,12 +7208,17 @@
<cleanUp
class="org.eclipse.jdt.internal.ui.fix.TypeParametersCleanUp"
id="org.eclipse.jdt.ui.cleanup.type_parameters"
- runAfter="org.eclipse.jdt.ui.cleanup.strings">
+ runAfter="org.eclipse.jdt.ui.cleanup.format">
+ </cleanUp>
+ <cleanUp
+ class="org.eclipse.jdt.internal.ui.fix.HashCleanUp"
+ id="org.eclipse.jdt.ui.cleanup.hash"
+ runAfter="org.eclipse.jdt.ui.cleanup.type_parameters">
</cleanUp>
<cleanUp
class="org.eclipse.jdt.internal.ui.fix.ArraysFillCleanUp"
id="org.eclipse.jdt.ui.cleanup.arrays_fill"
- runAfter="org.eclipse.jdt.ui.cleanup.type_parameters">
+ runAfter="org.eclipse.jdt.ui.cleanup.hash">
</cleanUp>
<cleanUp
class="org.eclipse.jdt.internal.ui.fix.AutoboxingCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/HashCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/HashCleanUp.java
new file mode 100644
index 0000000000..6e26f0f521
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/HashCleanUp.java
@@ -0,0 +1,617 @@
+/*******************************************************************************
+ * 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.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+import org.eclipse.text.edits.TextEditGroup;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.Assignment;
+import org.eclipse.jdt.core.dom.Block;
+import org.eclipse.jdt.core.dom.CastExpression;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.ConditionalExpression;
+import org.eclipse.jdt.core.dom.Expression;
+import org.eclipse.jdt.core.dom.ExpressionStatement;
+import org.eclipse.jdt.core.dom.FieldAccess;
+import org.eclipse.jdt.core.dom.InfixExpression;
+import org.eclipse.jdt.core.dom.MethodDeclaration;
+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.ParenthesizedExpression;
+import org.eclipse.jdt.core.dom.ReturnStatement;
+import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.Statement;
+import org.eclipse.jdt.core.dom.SuperFieldAccess;
+import org.eclipse.jdt.core.dom.ThisExpression;
+import org.eclipse.jdt.core.dom.TypeDeclaration;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
+import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
+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 rewrites Eclipse-autogenerated hashcode method by Eclipse-autogenerated hashcode method for Java 7:
+ * <ul>
+ * <li>It relies on the <code>Objects.hashCode()</code> method.</li>
+ * </ul>
+ */
+public class HashCleanUp extends AbstractMultiFix implements ICleanUpFix {
+ private static final String HASH_CODE_METHOD= "hashCode"; //$NON-NLS-1$
+
+ private static final class CollectedData {
+ public List<Expression> fields= new ArrayList<>();
+ public SimpleName primeId;
+ public SimpleName resultId;
+ public Iterator<Statement> stmtIterator;
+ public SimpleName tempVar;
+ public boolean tempValueUsed= true;
+ public boolean hasReturnStatement;
+ }
+
+ public HashCleanUp() {
+ this(Collections.emptyMap());
+ }
+
+ public HashCleanUp(Map<String, String> options) {
+ super(options);
+ }
+
+ @Override
+ public CleanUpRequirements getRequirements() {
+ boolean requireAST= isEnabled(CleanUpConstants.MODERNIZE_HASH);
+ return new CleanUpRequirements(requireAST, false, false, null);
+ }
+
+ @Override
+ public String[] getStepDescriptions() {
+ if (isEnabled(CleanUpConstants.MODERNIZE_HASH)) {
+ return new String[] { MultiFixMessages.HashCleanup_description };
+ }
+
+ return new String[0];
+ }
+
+ @Override
+ public String getPreview() {
+ if (isEnabled(CleanUpConstants.MODERNIZE_HASH)) {
+ return "" //$NON-NLS-1$
+ + "return Objects.hash(aShort);\n\n\n\n"; //$NON-NLS-1$
+ }
+
+ return "" //$NON-NLS-1$
+ + "final int prime = 31;\n" //$NON-NLS-1$
+ + "int result = 1;\n" //$NON-NLS-1$
+ + "result = prime * result + aShort;\n" //$NON-NLS-1$
+ + "return result;\n"; //$NON-NLS-1$
+ }
+
+ @Override
+ protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException {
+ if (!isEnabled(CleanUpConstants.MODERNIZE_HASH) || !JavaModelUtil.is17OrHigher(unit.getJavaElement().getJavaProject())) {
+ return null;
+ }
+
+ final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>();
+
+ unit.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(final MethodDeclaration node) {
+ Block body= node.getBody();
+
+ if (ASTNodes.usesGivenSignature(node, Object.class.getCanonicalName(), HASH_CODE_METHOD) && body != null) {
+ List<Statement> statements= body.statements();
+
+ if (statements.size() > 2) {
+ CollectedData data= new CollectedData();
+ data.stmtIterator= statements.iterator();
+
+ data.primeId= isVariableValid(data, 31);
+ data.resultId= isVariableValid(data, 1);
+
+ if (data.primeId != null
+ && data.resultId != null
+ && data.stmtIterator.hasNext()) {
+ while (!data.hasReturnStatement && data.stmtIterator.hasNext()) {
+ if (!isStmtValid(data)) {
+ return true;
+ }
+ }
+
+ if (data.hasReturnStatement && !data.stmtIterator.hasNext()) {
+ rewriteOperations.add(new HashOperation(node, data));
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private SimpleName isVariableValid(final CollectedData data, final int initValue) {
+ Statement statement= data.stmtIterator.next();
+ VariableDeclarationStatement varDecl= ASTNodes.as(statement, VariableDeclarationStatement.class);
+ VariableDeclarationFragment fragment= ASTNodes.getUniqueFragment(varDecl);
+
+ if (fragment != null
+ && ASTNodes.hasType(varDecl.getType().resolveBinding(), int.class.getSimpleName())
+ && Long.valueOf(initValue).equals(ASTNodes.getIntegerLiteral(fragment.getInitializer()))) {
+ return fragment.getName();
+ }
+
+ return null;
+ }
+
+ private boolean isStmtValid(final CollectedData data) {
+ Statement statement= data.stmtIterator.next();
+ ExpressionStatement exprStatement= ASTNodes.as(statement, ExpressionStatement.class);
+
+ if (exprStatement != null) {
+ return isAssignmentValid(data, exprStatement);
+ }
+
+ ReturnStatement returnStatement= ASTNodes.as(statement, ReturnStatement.class);
+
+ if (returnStatement != null) {
+ data.hasReturnStatement= true;
+ Expression expression= returnStatement.getExpression();
+
+ return isGivenVariable(expression, data.resultId) || isHashValid(data, expression);
+ }
+
+ VariableDeclarationStatement varStatement= ASTNodes.as(statement, VariableDeclarationStatement.class);
+
+ if (varStatement != null && data.tempVar == null) {
+ VariableDeclarationFragment fragment= ASTNodes.getUniqueFragment(varStatement);
+
+ if (ASTNodes.hasType(varStatement.getType().resolveBinding(), long.class.getSimpleName()) && fragment != null) {
+ data.tempVar= fragment.getName();
+ Expression initializer= fragment.getInitializer();
+
+ if (fragment.getExtraDimensions() == 0) {
+ if (initializer != null) {
+ SimpleName fieldToFind= isDoubleToLongBitsMethod(data, initializer);
+ data.tempValueUsed= false;
+
+ if (fieldToFind != null && data.stmtIterator.hasNext()) {
+ boolean assignmentValid= isStmtValid(data);
+
+ if (assignmentValid) {
+ data.fields.add(ASTNodes.getUnparenthesedExpression(fieldToFind));
+ return true;
+ }
+ }
+ } else if (data.stmtIterator.hasNext()) {
+ return isStmtValid(data);
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isAssignmentValid(final CollectedData data, final ExpressionStatement statement) {
+ Assignment assignment= ASTNodes.as(statement.getExpression(), Assignment.class);
+
+ if (assignment != null && ASTNodes.hasOperator(assignment, Assignment.Operator.ASSIGN)) {
+ Expression field= assignment.getLeftHandSide();
+ Expression resultComputation= assignment.getRightHandSide();
+
+ if (isGivenVariable(field, data.resultId)) {
+ return isHashValid(data, resultComputation);
+ }
+ if (data.tempVar != null && isGivenVariable(field, data.tempVar)) {
+ SimpleName fieldToFind= isDoubleToLongBitsMethod(data, resultComputation);
+
+ if (fieldToFind != null && data.stmtIterator.hasNext()) {
+ data.tempValueUsed= false;
+ boolean assignmentValid= isStmtValid(data);
+
+ if (assignmentValid) {
+ data.fields.add(ASTNodes.getUnparenthesedExpression(fieldToFind));
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private SimpleName isDoubleToLongBitsMethod(final CollectedData data, final Expression initializer) {
+ SimpleName fieldToFind= null;
+ MethodInvocation doubleToLongBits= ASTNodes.as(initializer, MethodInvocation.class);
+
+ if (doubleToLongBits != null && ASTNodes.usesGivenSignature(doubleToLongBits, Double.class.getCanonicalName(), "doubleToLongBits", double.class.getSimpleName())) { //$NON-NLS-1$
+ SimpleName fieldName= ASTNodes.as((Expression) doubleToLongBits.arguments().get(0), SimpleName.class);
+
+ if (fieldName != null
+ && !ASTNodes.isSameVariable(fieldName, data.primeId)
+ && !ASTNodes.isSameVariable(fieldName, data.resultId)) {
+ fieldToFind= fieldName;
+ }
+ }
+
+ return fieldToFind;
+ }
+
+ private boolean isHashValid(final CollectedData data, final Expression hashComputation) {
+ InfixExpression hashAddition= ASTNodes.as(hashComputation, InfixExpression.class);
+
+ if (hashAddition != null) {
+ InfixExpression primeTimesResult= ASTNodes.as(hashAddition.getLeftOperand(), InfixExpression.class);
+ Expression newHash= hashAddition.getRightOperand();
+
+ if (!hashAddition.hasExtendedOperands()
+ && ASTNodes.hasOperator(hashAddition, InfixExpression.Operator.PLUS)
+ && primeTimesResult != null
+ && !primeTimesResult.hasExtendedOperands()
+ && ASTNodes.hasOperator(primeTimesResult, InfixExpression.Operator.TIMES)
+ && (isGivenVariable(primeTimesResult.getLeftOperand(), data.primeId)
+ && isGivenVariable(primeTimesResult.getRightOperand(), data.resultId)
+ || isGivenVariable(primeTimesResult.getLeftOperand(), data.resultId)
+ && isGivenVariable(primeTimesResult.getRightOperand(), data.primeId))) {
+ return isNewHashValid(data, newHash);
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isNewHashValid(final CollectedData data, final Expression newHash) {
+ if (newHash instanceof ParenthesizedExpression) {
+ ParenthesizedExpression newHashWithoutBrackets= (ParenthesizedExpression) newHash;
+
+ return isNewHashValid(data, newHashWithoutBrackets.getExpression());
+ }
+
+ if ((newHash instanceof Name || newHash instanceof FieldAccess || newHash instanceof SuperFieldAccess) && data.tempValueUsed) {
+ SimpleName fieldName= getField(newHash);
+
+ if (fieldName != null
+ && !ASTNodes.isSameVariable(data.primeId, fieldName)
+ && !ASTNodes.isSameVariable(data.resultId, fieldName)) {
+ data.fields.add(ASTNodes.getUnparenthesedExpression(fieldName));
+ return true;
+ }
+ } else if (newHash instanceof ConditionalExpression && data.tempValueUsed) {
+ ConditionalExpression condition= (ConditionalExpression) newHash;
+ return isObjectValid(data, condition) || isBooleanValid(data, condition);
+ } else if (newHash instanceof MethodInvocation && data.tempValueUsed) {
+ MethodInvocation specificMethod= (MethodInvocation) newHash;
+ TypeDeclaration innerClass= ASTNodes.getTypedAncestor(newHash, TypeDeclaration.class);
+ TypeDeclaration topLevelClass= ASTNodes.getTypedAncestor(innerClass, TypeDeclaration.class);
+
+ if (ASTNodes.usesGivenSignature(specificMethod, Float.class.getCanonicalName(), "floatToIntBits", float.class.getSimpleName())) { //$NON-NLS-1$
+ SimpleName fieldName= getField((Expression) specificMethod.arguments().get(0));
+
+ if (fieldName != null
+ && !ASTNodes.isSameVariable(fieldName, data.primeId)
+ && !ASTNodes.isSameVariable(fieldName, data.resultId)) {
+ data.fields.add(ASTNodes.getUnparenthesedExpression(fieldName));
+ return true;
+ }
+ } else if (ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, boolean[].class.getCanonicalName())
+ || ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, byte[].class.getCanonicalName())
+ || ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, char[].class.getCanonicalName())
+ || ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, double[].class.getCanonicalName())
+ || ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, float[].class.getCanonicalName())
+ || ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, int[].class.getCanonicalName())
+ || ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, Object[].class.getCanonicalName())
+ || ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, long[].class.getCanonicalName())
+ || ASTNodes.usesGivenSignature(specificMethod, Arrays.class.getCanonicalName(), HASH_CODE_METHOD, short[].class.getCanonicalName())) {
+ SimpleName fieldName= getField((Expression) specificMethod.arguments().get(0));
+
+ if (fieldName != null
+ && !ASTNodes.isSameVariable(fieldName, data.primeId)
+ && !ASTNodes.isSameVariable(fieldName, data.resultId)) {
+ data.fields.add(ASTNodes.getUnparenthesedExpression(specificMethod));
+ return true;
+ }
+ } else if (innerClass != null
+ && innerClass.resolveBinding() != null
+ && topLevelClass != null
+ && topLevelClass.resolveBinding() != null
+ && ASTNodes.usesGivenSignature(specificMethod, topLevelClass.resolveBinding().getQualifiedName(), HASH_CODE_METHOD)) {
+ return isEnclosingHashCode(data, specificMethod, innerClass, topLevelClass);
+ }
+ } else if (newHash instanceof CastExpression) {
+ return isGreatNumberValid(data, (CastExpression) newHash);
+ }
+
+ return false;
+ }
+
+ private boolean isEnclosingHashCode(final CollectedData data, final MethodInvocation specificMethod,
+ final TypeDeclaration innerClass, final TypeDeclaration topLevelClass) {
+ MethodInvocation getEnclosingInstanceMethod= ASTNodes.as(specificMethod.getExpression(), MethodInvocation.class);
+
+ if (ASTNodes.usesGivenSignature(getEnclosingInstanceMethod, innerClass.resolveBinding().getQualifiedName(), "getEnclosingInstance")) { //$NON-NLS-1$
+ MethodDeclaration getEnclosingInstanceDeclaration= null;
+
+ for (MethodDeclaration innerMethod : innerClass.getMethods()) {
+ if ("getEnclosingInstance".equals(innerMethod.getName().getIdentifier()) //$NON-NLS-1$
+ && (innerMethod.parameters() == null || innerMethod.parameters().isEmpty())
+ && !innerMethod.isConstructor()
+ && innerMethod.resolveBinding() != null
+ && ASTNodes.hasType(innerMethod.resolveBinding().getReturnType(), topLevelClass.resolveBinding().getQualifiedName())) {
+ getEnclosingInstanceDeclaration= innerMethod;
+ break;
+ }
+ }
+
+ if (getEnclosingInstanceDeclaration != null) {
+ ReturnStatement returnStatement= ASTNodes.as(getEnclosingInstanceDeclaration.getBody(), ReturnStatement.class);
+
+ if (returnStatement != null) {
+ ThisExpression thisExpression= ASTNodes.as(returnStatement.getExpression(),
+ ThisExpression.class);
+
+ if (thisExpression != null) {
+ SimpleName topLevelClassReference= ASTNodes.as(thisExpression.getQualifier(),
+ SimpleName.class);
+
+ if (topLevelClassReference != null
+ && topLevelClass.getName().getIdentifier().equals(topLevelClassReference.getIdentifier())) {
+ data.fields.add(ASTNodes.getUnparenthesedExpression(specificMethod));
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private SimpleName getField(final Expression expression) {
+ SimpleName simpleName= ASTNodes.as(expression, SimpleName.class);
+
+ if (simpleName != null) {
+ return simpleName;
+ }
+
+ FieldAccess fieldName= ASTNodes.as(expression, FieldAccess.class);
+
+ if (fieldName != null) {
+ ThisExpression thisExpression= ASTNodes.as(fieldName.getExpression(), ThisExpression.class);
+
+ if (thisExpression != null) {
+ if (thisExpression.getQualifier() == null) {
+ return fieldName.getName();
+ }
+
+ if (thisExpression.getQualifier().isSimpleName()) {
+ SimpleName qualifier= (SimpleName) thisExpression.getQualifier();
+ TypeDeclaration visitedClass= ASTNodes.getTypedAncestor(expression, TypeDeclaration.class);
+
+ if (visitedClass != null
+ && ASTNodes.isSameVariable(visitedClass.getName(), qualifier)) {
+ return fieldName.getName();
+ }
+ }
+ }
+ }
+
+ SuperFieldAccess superFieldAccess= ASTNodes.as(expression, SuperFieldAccess.class);
+
+ if (superFieldAccess != null) {
+ if (superFieldAccess.getQualifier() == null) {
+ return superFieldAccess.getName();
+ }
+
+ if (superFieldAccess.getQualifier().isSimpleName()) {
+ SimpleName qualifier= (SimpleName) superFieldAccess.getQualifier();
+ TypeDeclaration visitedClass= ASTNodes.getTypedAncestor(expression, TypeDeclaration.class);
+
+ if (visitedClass != null
+ && ASTNodes.isSameVariable(visitedClass.getName(), qualifier)) {
+ return superFieldAccess.getName();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private boolean isGreatNumberValid(final CollectedData data, final CastExpression newHash) {
+ OrderedInfixExpression<Expression, InfixExpression> orderedBitwise= ASTNodes.orderedInfix(newHash.getExpression(), Expression.class, InfixExpression.class);
+
+ if (ASTNodes.hasType(newHash, int.class.getSimpleName())
+ && orderedBitwise != null
+ && ASTNodes.hasType(newHash.getExpression(), long.class.getSimpleName(), double.class.getSimpleName())
+ && InfixExpression.Operator.XOR.equals(orderedBitwise.getOperator())) {
+ SimpleName field= getField(orderedBitwise.getFirstOperand());
+ InfixExpression moveExpression= orderedBitwise.getSecondOperand();
+
+ if (field != null
+ && moveExpression != null
+ && !ASTNodes.isSameVariable(field, data.primeId)
+ && !ASTNodes.isSameVariable(field, data.resultId)
+ && ASTNodes.hasOperator(moveExpression, InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED)) {
+ SimpleName againFieldName= getField(moveExpression.getLeftOperand());
+ Long hash= ASTNodes.getIntegerLiteral(moveExpression.getRightOperand());
+
+ if (Long.valueOf(32).equals(hash)
+ && againFieldName != null
+ && ASTNodes.isSameVariable(againFieldName, field)) {
+ if (data.tempValueUsed) {
+ data.fields.add(ASTNodes.getUnparenthesedExpression(againFieldName));
+ return true;
+ }
+
+ if (ASTNodes.isSameVariable(data.tempVar, field)) {
+ data.tempValueUsed= true;
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isBooleanValid(final CollectedData data, final ConditionalExpression newHash) {
+ SimpleName booleanField= getField(newHash.getExpression());
+ Long hashForTrue= ASTNodes.getIntegerLiteral(newHash.getThenExpression());
+ Long hashForFalse= ASTNodes.getIntegerLiteral(newHash.getElseExpression());
+
+ if (booleanField != null
+ && hashForTrue != null
+ && hashForFalse != null
+ && ASTNodes.hasType(booleanField, boolean.class.getSimpleName())
+ && !ASTNodes.isSameVariable(booleanField, data.primeId)
+ && !ASTNodes.isSameVariable(booleanField, data.resultId) && Long.valueOf(1231).equals(hashForTrue)
+ && Long.valueOf(1237).equals(hashForFalse)) {
+ data.fields.add(ASTNodes.getUnparenthesedExpression(booleanField));
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean isObjectValid(final CollectedData data, final ConditionalExpression condition) {
+ OrderedInfixExpression<Expression, NullLiteral> orderedIsFieldNull= ASTNodes.orderedInfix(condition.getExpression(), Expression.class, NullLiteral.class);
+
+ if (orderedIsFieldNull != null
+ && Arrays.asList(InfixExpression.Operator.EQUALS, InfixExpression.Operator.NOT_EQUALS).contains(orderedIsFieldNull.getOperator())) {
+ SimpleName field= getField(orderedIsFieldNull.getFirstOperand());
+
+ if (field != null) {
+ Long zero;
+ MethodInvocation hashOnField;
+
+ if (InfixExpression.Operator.EQUALS.equals(orderedIsFieldNull.getOperator())) {
+ zero= ASTNodes.getIntegerLiteral(condition.getThenExpression());
+ hashOnField= ASTNodes.as(condition.getElseExpression(), MethodInvocation.class);
+ } else {
+ hashOnField= ASTNodes.as(condition.getThenExpression(), MethodInvocation.class);
+ zero= ASTNodes.getIntegerLiteral(condition.getElseExpression());
+ }
+
+ if (Long.valueOf(0).equals(zero)
+ && hashOnField != null
+ && hashOnField.getExpression() != null
+ && HASH_CODE_METHOD.equals(hashOnField.getName().getIdentifier())
+ && (hashOnField.arguments() == null || hashOnField.arguments().isEmpty())) {
+ SimpleName fieldToHash= getField(hashOnField.getExpression());
+
+ if (fieldToHash != null
+ && ASTNodes.isSameVariable(field, fieldToHash)) {
+ data.fields.add(ASTNodes.getUnparenthesedExpression(fieldToHash));
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isGivenVariable(final Expression expression, final SimpleName varId) {
+ SimpleName field= getField(expression);
+ return field != null && ASTNodes.isSameVariable(varId, field);
+ }
+ });
+
+ if (rewriteOperations.isEmpty()) {
+ return null;
+ }
+
+ return new CompilationUnitRewriteOperationsFix(MultiFixMessages.HashCleanup_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 HashOperation extends CompilationUnitRewriteOperation {
+ private final MethodDeclaration node;
+ private final CollectedData data;
+
+ public HashOperation(final MethodDeclaration node, final CollectedData data) {
+ this.node= node;
+ this.data= data;
+ }
+
+ @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();
+ TextEditGroup group= createTextEditGroup(MultiFixMessages.HashCleanup_description, cuRewrite);
+
+ String objectsNameText= importRewrite.addImport(Objects.class.getCanonicalName());
+
+ List<Statement> statements= node.getBody().statements();
+ Name objectsClassName= ASTNodeFactory.newName(ast, objectsNameText);
+
+ MethodInvocation methodInvocation= ast.newMethodInvocation();
+ methodInvocation.setExpression(objectsClassName);
+ methodInvocation.setName(ast.newSimpleName("hash")); //$NON-NLS-1$
+ methodInvocation.arguments().addAll(ASTNodes.createMoveTarget(rewrite, data.fields));
+
+ ReturnStatement newReturnStatement= ast.newReturnStatement();
+ newReturnStatement.setExpression(methodInvocation);
+ ASTNodes.replaceButKeepComment(rewrite, statements.get(0),
+ newReturnStatement, group);
+
+ for (int i= 1; i < statements.size(); i++) {
+ rewrite.remove(statements.get(i), group);
+ }
+ }
+ }
+}
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 ad6ee3dba0..7532d4fc68 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
@@ -125,6 +125,7 @@ public class CleanUpMessages extends NLS {
public static String UnnecessaryCodeTabPage_CheckboxName_UnnecessaryCasts;
public static String UnnecessaryCodeTabPage_CheckboxName_UnnecessaryNLSTags;
public static String UnnecessaryCodeTabPage_CheckboxName_RedundantTypeArguments;
+ public static String UnnecessaryCodeTabPage_CheckboxName_Hash;
public static String UnnecessaryCodeTabPage_CheckboxName_ArraysFill;
public static String UnnecessaryCodeTabPage_CheckboxName_Autoboxing;
public static String UnnecessaryCodeTabPage_CheckboxName_Unboxing;
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 fb1a9b88b3..a2d021d949 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
@@ -110,6 +110,7 @@ UnnecessaryCodeTabPage_GroupName_UnnecessaryCode=Unnecessary code
UnnecessaryCodeTabPage_CheckboxName_UnnecessaryCasts=Remove unnecessar&y casts
UnnecessaryCodeTabPage_CheckboxName_UnnecessaryNLSTags=Remove unnecessary '$NON-NLS$' ta&gs
UnnecessaryCodeTabPage_CheckboxName_RedundantTypeArguments=Remove redundant type &arguments (1.7 or higher)
+UnnecessaryCodeTabPage_CheckboxName_Hash=Use Objects.hash() (1.7 or higher)
UnnecessaryCodeTabPage_CheckboxName_ArraysFill=Use Arrays.&fill() when possible
UnnecessaryCodeTabPage_CheckboxName_Autoboxing=Use Autobo&xing (1.5 or higher)
UnnecessaryCodeTabPage_CheckboxName_Unboxing=Use Un&boxing (1.5 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 3fbcdaac3c..a12ab14936 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
@@ -26,6 +26,7 @@ import org.eclipse.jdt.internal.ui.fix.ArraysFillCleanUp;
import org.eclipse.jdt.internal.ui.fix.AutoboxingCleanUp;
import org.eclipse.jdt.internal.ui.fix.CollectionCloningCleanUp;
import org.eclipse.jdt.internal.ui.fix.EmbeddedIfCleanUp;
+import org.eclipse.jdt.internal.ui.fix.HashCleanUp;
import org.eclipse.jdt.internal.ui.fix.MapCloningCleanUp;
import org.eclipse.jdt.internal.ui.fix.MapMethodCleanUp;
import org.eclipse.jdt.internal.ui.fix.MergeConditionalBlocksCleanUp;
@@ -57,6 +58,7 @@ public final class UnnecessaryCodeTabPage extends AbstractCleanUpTabPage {
new UnnecessaryCodeCleanUp(values),
new StringCleanUp(values),
new TypeParametersCleanUp(values),
+ new HashCleanUp(values),
new ArraysFillCleanUp(values),
new AutoboxingCleanUp(values),
new UnboxingCleanUp(values),
@@ -109,6 +111,9 @@ public final class UnnecessaryCodeTabPage extends AbstractCleanUpTabPage {
CheckboxPreference typeArgs= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_RedundantTypeArguments, CleanUpConstants.REMOVE_REDUNDANT_TYPE_ARGUMENTS, CleanUpModifyDialog.FALSE_TRUE);
registerPreference(typeArgs);
+ CheckboxPreference hash= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_Hash, CleanUpConstants.MODERNIZE_HASH, CleanUpModifyDialog.FALSE_TRUE);
+ registerPreference(hash);
+
CheckboxPreference arraysFill= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_ArraysFill, CleanUpConstants.ARRAYS_FILL, CleanUpModifyDialog.FALSE_TRUE);
registerPreference(arraysFill);

Back to the top