mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-251722 - remove plain hashCode method during class-to-record conversion
GitOrigin-RevId: 1db63c1bdeec14f3ec6ef2aced3e089572633dcc
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1745c22fce
commit
d8a3f626b6
+22
@@ -22,6 +22,7 @@ import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.siyeh.ig.InspectionGadgetsFix;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -116,6 +117,9 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
private final MultiMap<PsiField, FieldAccessorCandidate> myFieldAccessors = new MultiMap<>(new LinkedHashMap<>());
|
||||
private final List<PsiMethod> myOrdinaryMethods = new SmartList<>();
|
||||
private final List<RecordConstructorCandidate> myConstructors = new SmartList<>();
|
||||
|
||||
private PsiMethod myEqualsMethod;
|
||||
private PsiMethod myHashCodeMethod;
|
||||
|
||||
private Map<PsiField, FieldAccessorCandidate> myFieldAccessorsCache;
|
||||
|
||||
@@ -153,6 +157,16 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
return myConstructors.size() == 1 ? myConstructors.get(0).myConstructor : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
PsiMethod getEqualsMethod() {
|
||||
return myEqualsMethod;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
PsiMethod getHashCodeMethod() {
|
||||
return myHashCodeMethod;
|
||||
}
|
||||
|
||||
private boolean isValid() {
|
||||
if (myConstructors.size() > 1) return false;
|
||||
if (myConstructors.size() == 1) {
|
||||
@@ -190,6 +204,14 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
myConstructors.add(new RecordConstructorCandidate(method, myFieldAccessors.keySet()));
|
||||
continue;
|
||||
}
|
||||
if (MethodUtils.isEquals(method)) {
|
||||
myEqualsMethod = method;
|
||||
continue;
|
||||
}
|
||||
if (MethodUtils.isHashCode(method)) {
|
||||
myHashCodeMethod = method;
|
||||
continue;
|
||||
}
|
||||
if (!throwsOnlyUncheckedExceptions(method)) {
|
||||
myOrdinaryMethods.add(method);
|
||||
continue;
|
||||
|
||||
+122
-1
@@ -14,6 +14,7 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.javadoc.PsiDocTag;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor;
|
||||
import com.intellij.refactoring.rename.RenameProcessor;
|
||||
@@ -28,6 +29,7 @@ import com.intellij.util.*;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.containers.SmartHashSet;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -219,9 +221,10 @@ public class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
nextElement = nextElement.getNextSibling();
|
||||
}
|
||||
|
||||
List<PsiMethod> redundantObjectMethods = findRedundantObjectMethods();
|
||||
PsiClass result = (PsiClass)psiClass.replace(recordBuilder.build());
|
||||
tryToCompactCanonicalCtor(result);
|
||||
removeRedundantObjectMethods(result, redundantObjectMethods);
|
||||
generateJavaDocForDocumentedFields(result);
|
||||
CodeStyleManager.getInstance(myProject).reformat(result);
|
||||
}
|
||||
@@ -237,6 +240,118 @@ public class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<PsiMethod> findRedundantObjectMethods() {
|
||||
PsiMethod equalsMethod = myRecordCandidate.getEqualsMethod();
|
||||
PsiMethod hashCodeMethod = myRecordCandidate.getHashCodeMethod();
|
||||
if (equalsMethod == null && hashCodeMethod == null) return Collections.emptyList();
|
||||
List<PsiMethod> result = new SmartList<>();
|
||||
Set<PsiField> fields = myRecordCandidate.getFieldAccessors().keySet();
|
||||
// todo for equals method
|
||||
if (hashCodeMethod != null) {
|
||||
var hashCodeVisitor = new HashCodeVisitor(fields);
|
||||
hashCodeMethod.accept(hashCodeVisitor);
|
||||
if (hashCodeVisitor.myNonVisitedFields.isEmpty()) {
|
||||
result.add(hashCodeMethod);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class HashCodeVisitor extends JavaRecursiveElementWalkingVisitor {
|
||||
private final CallMatcher OBJECTS_CALL = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OBJECTS, "hash", "hashCode");
|
||||
private final CallMatcher FLOAT_CALL = CallMatcher.staticCall(CommonClassNames.JAVA_LANG_FLOAT, "floatToIntBits");
|
||||
private final CallMatcher DOUBLE_CALL = CallMatcher.staticCall(CommonClassNames.JAVA_LANG_DOUBLE, "doubleToLongBits");
|
||||
|
||||
private final CallMatcher INSTANCE_CALL = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "hashCode");
|
||||
|
||||
private final Set<PsiField> myNonVisitedFields;
|
||||
|
||||
private HashCodeVisitor(@NotNull Set<PsiField> fields) {
|
||||
myNonVisitedFields = new SmartHashSet<>(fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
PsiField field = ObjectUtils.tryCast(expression.resolve(), PsiField.class);
|
||||
if (field == null) return;
|
||||
PsiType fieldType = field.getType();
|
||||
if (PsiType.CHAR.equals(fieldType) || PsiType.SHORT.equals(fieldType)) {
|
||||
PsiElement parent = PsiTreeUtil.skipParentsOfType(expression, PsiParenthesizedExpression.class);
|
||||
if (parent instanceof PsiTypeCastExpression) {
|
||||
PsiTypeElement castType = ((PsiTypeCastExpression)parent).getCastType();
|
||||
if (castType != null && PsiType.INT.equals(castType.getType())) {
|
||||
myNonVisitedFields.remove(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
myNonVisitedFields.remove(field);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
|
||||
if (OBJECTS_CALL.test(expression)) {
|
||||
for (PsiExpression argument : expression.getArgumentList().getExpressions()) {
|
||||
PsiReferenceExpression fieldRef = ObjectUtils.tryCast(argument, PsiReferenceExpression.class);
|
||||
if (fieldRef == null || !myNonVisitedFields.remove(fieldRef.resolve())) {
|
||||
stopWalking();
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PsiType expectedType = null;
|
||||
if (FLOAT_CALL.test(expression)) {
|
||||
expectedType = PsiType.FLOAT;
|
||||
}
|
||||
else if (DOUBLE_CALL.test(expression)) {
|
||||
expectedType = PsiType.DOUBLE;
|
||||
}
|
||||
if (expectedType != null) {
|
||||
PsiExpression[] expressions = expression.getArgumentList().getExpressions();
|
||||
if (expressions.length == 1) {
|
||||
PsiReferenceExpression refExpr = ObjectUtils.tryCast(expressions[0], PsiReferenceExpression.class);
|
||||
if (refExpr != null) {
|
||||
PsiField field = ObjectUtils.tryCast(refExpr.resolve(), PsiField.class);
|
||||
if (field != null && expectedType.equals(field.getType())) {
|
||||
myNonVisitedFields.remove(field);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
stopWalking();
|
||||
return;
|
||||
}
|
||||
|
||||
if (INSTANCE_CALL.test(expression)) {
|
||||
PsiExpression qualifier = expression.getMethodExpression().getQualifierExpression();
|
||||
PsiReferenceExpression fieldRef = ObjectUtils.tryCast(qualifier, PsiReferenceExpression.class);
|
||||
if (fieldRef != null) {
|
||||
PsiField field = ObjectUtils.tryCast(fieldRef.resolve(), PsiField.class);
|
||||
if (field != null) {
|
||||
myNonVisitedFields.remove(field);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
stopWalking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPolyadicExpression(PsiPolyadicExpression expression) {
|
||||
super.visitPolyadicExpression(expression);
|
||||
IElementType operationType = expression.getOperationTokenType();
|
||||
if (!JavaTokenType.PLUS.equals(operationType) && !JavaTokenType.MINUS.equals(operationType) &&
|
||||
!JavaTokenType.ASTERISK.equals(operationType) && !JavaTokenType.GTGTGT.equals(operationType) &&
|
||||
!JavaTokenType.EQEQ.equals(operationType) && !JavaTokenType.NE.equals(operationType)) {
|
||||
stopWalking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void tryToCompactCanonicalCtor(@NotNull PsiClass record) {
|
||||
PsiMethod canonicalCtor = ArrayUtil.getFirstElement(record.getConstructors());
|
||||
if (canonicalCtor != null) {
|
||||
@@ -254,6 +369,12 @@ public class ConvertToRecordProcessor extends BaseRefactoringProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeRedundantObjectMethods(@NotNull PsiClass record, @NotNull List<PsiMethod> redundantObjectMethods) {
|
||||
redundantObjectMethods.stream().map(method -> record.findMethodBySignature(method, false))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(PsiMethod::delete);
|
||||
}
|
||||
|
||||
private void generateJavaDocForDocumentedFields(@NotNull PsiClass record) {
|
||||
Map<String, String> comments = new LinkedHashMap<>();
|
||||
for (PsiField field : myRecordCandidate.getFieldAccessors().keySet()) {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Convert to a record" "true"
|
||||
import java.util.Objects;
|
||||
|
||||
record Test(boolean booleanValue, char charValue, String stringValue, long longValue, float floatValue,
|
||||
double doubleValue, double[] arrayValue) {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(booleanValue, stringValue, longValue, floatValue, doubleValue, arrayValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Convert to a record" "true"
|
||||
|
||||
record Test(boolean booleanValue, char charValue, String stringValue, long longValue, float floatValue,
|
||||
double doubleValue, double[] arrayValue) {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Convert to a record" "true"
|
||||
record Test(double[] arrayValue) {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
|
||||
result = 37 * result + Arrays.hashCode(arrayValue);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Convert to a record" "true"
|
||||
record Test(boolean booleanValue, char charValue, String stringValue, long longValue, float floatValue,
|
||||
double doubleValue, double[] arrayValue) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// "Convert to a record" "true"
|
||||
import java.util.Objects;
|
||||
|
||||
class <caret>Test {
|
||||
final boolean booleanValue;
|
||||
final char charValue;
|
||||
final String stringValue;
|
||||
final long longValue;
|
||||
final float floatValue;
|
||||
final double doubleValue;
|
||||
final double[] arrayValue;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(booleanValue, stringValue, longValue, floatValue, doubleValue, arrayValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// "Convert to a record" "true"
|
||||
import java.util.Objects;
|
||||
|
||||
class <caret>Test {
|
||||
final boolean booleanValue;
|
||||
final char charValue;
|
||||
final String stringValue;
|
||||
final long longValue;
|
||||
final float floatValue;
|
||||
final double doubleValue;
|
||||
final double[] arrayValue;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(booleanValue, charValue, stringValue, longValue, floatValue, doubleValue, arrayValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Convert to a record" "true"
|
||||
class <caret>Test {
|
||||
final double[] arrayValue;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
|
||||
result = 37 * result + Arrays.hashCode(arrayValue);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// "Convert to a record" "true"
|
||||
class <caret>Test {
|
||||
final boolean booleanValue;
|
||||
final char charValue;
|
||||
final String stringValue;
|
||||
final long longValue;
|
||||
final float floatValue;
|
||||
final double doubleValue;
|
||||
final double[] arrayValue;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
|
||||
result = 37 * result + (booleanValue ? 1 : 0);
|
||||
result = 37 * result + (int) charValue;
|
||||
result = 37 * result + (stringValue == null ? 0 : stringValue.hashCode());
|
||||
result = 37 * result + (int) (longValue - (longValue >>> 32));
|
||||
result = 37 * result + Float.floatToIntBits(floatValue);
|
||||
result = 37 * result + (int) (Double.doubleToLongBits(doubleValue) - (Double.doubleToLongBits(doubleValue) >>> 32));
|
||||
|
||||
result = 37 * result + arrayValue.hashCode();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user