Skip to content

Commit 1f7ade0

Browse files
authored
Do not remove used private fields after type change (#648)
* Do not remove used private fields after type change - Fixes #321 * Adopt `TypeUtils.fullyQualifiedNamesAreEqual`
1 parent 1820626 commit 1f7ade0

File tree

2 files changed

+121
-54
lines changed

2 files changed

+121
-54
lines changed

src/main/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFields.java

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@
2424
import org.openrewrite.java.JavaVisitor;
2525
import org.openrewrite.java.NoMissingTypes;
2626
import org.openrewrite.java.service.AnnotationService;
27-
import org.openrewrite.java.tree.J;
28-
import org.openrewrite.java.tree.Space;
29-
import org.openrewrite.java.tree.Statement;
30-
import org.openrewrite.java.tree.TypeUtils;
27+
import org.openrewrite.java.tree.*;
3128

3229
import java.util.*;
3330
import java.util.concurrent.atomic.AtomicBoolean;
@@ -59,7 +56,8 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
5956
class CheckField {
6057
J.VariableDeclarations declarations;
6158

62-
@Nullable Statement nextStatement;
59+
@Nullable
60+
Statement nextStatement;
6361
}
6462

6563
@Override
@@ -75,7 +73,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex
7573
List<CheckField> checkFields = new ArrayList<>();
7674
// Do not remove fields with `serialVersionUID` name.
7775
boolean skipSerialVersionUID = cd.getType() == null ||
78-
cd.getType().isAssignableTo("java.io.Serializable");
76+
cd.getType().isAssignableTo("java.io.Serializable");
7977

8078
List<Statement> statements = cd.getBody().getStatements();
8179
for (int i = 0; i < statements.size(); i++) {
@@ -84,8 +82,8 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex
8482
J.VariableDeclarations vd = (J.VariableDeclarations) statement;
8583
// RSPEC-S1068 does not apply serialVersionUID of Serializable classes, or fields with annotations.
8684
if (!(skipSerialVersionUID && isSerialVersionUid(vd)) &&
87-
vd.getLeadingAnnotations().isEmpty() &&
88-
vd.hasModifier(J.Modifier.Type.Private)) {
85+
vd.getLeadingAnnotations().isEmpty() &&
86+
vd.hasModifier(J.Modifier.Type.Private)) {
8987
Statement nextStatement = i < statements.size() - 1 ? statements.get(i + 1) : null;
9088
checkFields.add(new CheckField(vd, nextStatement));
9189
}
@@ -133,10 +131,10 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex
133131

134132
private boolean isSerialVersionUid(J.VariableDeclarations vd) {
135133
return vd.hasModifier(J.Modifier.Type.Private) &&
136-
vd.hasModifier(J.Modifier.Type.Static) &&
137-
vd.hasModifier(J.Modifier.Type.Final) &&
138-
TypeUtils.isOfClassType(vd.getType(), "long") &&
139-
vd.getVariables().stream().anyMatch(it -> "serialVersionUID".equals(it.getSimpleName()));
134+
vd.hasModifier(J.Modifier.Type.Static) &&
135+
vd.hasModifier(J.Modifier.Type.Final) &&
136+
TypeUtils.isOfClassType(vd.getType(), "long") &&
137+
vd.getVariables().stream().anyMatch(it -> "serialVersionUID".equals(it.getSimpleName()));
140138
}
141139
};
142140
return Preconditions.check(new NoMissingTypes(), Repeat.repeatUntilStable(visitor));
@@ -146,12 +144,15 @@ private static class VariableUses {
146144
public static Map<J.VariableDeclarations.NamedVariable, List<J.Identifier>> find(J.VariableDeclarations declarations, J.ClassDeclaration parent) {
147145
Map<J.VariableDeclarations.NamedVariable, List<J.Identifier>> found = new IdentityHashMap<>(declarations.getVariables().size());
148146
Map<String, J.VariableDeclarations.NamedVariable> signatureMap = new HashMap<>();
147+
Map<String, J.VariableDeclarations.NamedVariable> nameMap = new HashMap<>();
149148

150149
for (J.VariableDeclarations.NamedVariable variable : declarations.getVariables()) {
151150
if (variable.getVariableType() != null) {
152151
found.computeIfAbsent(variable, k -> new ArrayList<>());
153152
// Note: Using a variable type signature is only safe to find uses of class fields.
154153
signatureMap.put(variable.getVariableType().toString(), variable);
154+
// Also map by name for fallback matching
155+
nameMap.put(variable.getSimpleName(), variable);
155156
}
156157
}
157158

@@ -161,17 +162,36 @@ public static Map<J.VariableDeclarations.NamedVariable, List<J.Identifier>> find
161162
@Override
162163
public J.Identifier visitIdentifier(J.Identifier identifier,
163164
Map<J.VariableDeclarations.NamedVariable, List<J.Identifier>> identifiers) {
164-
if (identifier.getFieldType() != null && signatureMap.containsKey(identifier.getFieldType().toString())) {
165-
Cursor parent = getCursor().dropParentUntil(is ->
166-
is instanceof J.VariableDeclarations ||
167-
is instanceof J.ClassDeclaration);
168-
169-
if (!(parent.getValue() instanceof J.VariableDeclarations && parent.getValue() == declarations)) {
170-
J.VariableDeclarations.NamedVariable name = signatureMap.get(identifier.getFieldType().toString());
171-
if (declarations.getVariables().contains(name)) {
172-
J.VariableDeclarations.NamedVariable used = signatureMap.get(identifier.getFieldType().toString());
173-
identifiers.computeIfAbsent(used, k -> new ArrayList<>())
174-
.add(identifier);
165+
if (identifier.getFieldType() != null) {
166+
J.VariableDeclarations.NamedVariable match = null;
167+
168+
// First try exact type signature match
169+
String fieldTypeSignature = identifier.getFieldType().toString();
170+
if (signatureMap.containsKey(fieldTypeSignature)) {
171+
match = signatureMap.get(fieldTypeSignature);
172+
} else if (identifier.getFieldType().getOwner() != null) {
173+
// Fallback: match by name if it's a field reference from the same class
174+
J.VariableDeclarations.NamedVariable nameMatch = nameMap.get(identifier.getSimpleName());
175+
if (nameMatch != null && nameMatch.getVariableType() != null) {
176+
// Check if the owner type matches the parent class type
177+
JavaType.FullyQualified ownerType = TypeUtils.asFullyQualified(identifier.getFieldType().getOwner());
178+
JavaType.FullyQualified parentType = parent.getType();
179+
if (ownerType != null && parentType != null &&
180+
TypeUtils.fullyQualifiedNamesAreEqual(ownerType.getFullyQualifiedName(), parentType.getFullyQualifiedName())) {
181+
match = nameMatch;
182+
}
183+
}
184+
}
185+
186+
if (match != null) {
187+
Cursor parentCursor = getCursor().dropParentUntil(is ->
188+
is instanceof J.VariableDeclarations || is instanceof J.ClassDeclaration);
189+
190+
if (!(parentCursor.getValue() instanceof J.VariableDeclarations && parentCursor.getValue() == declarations)) {
191+
if (declarations.getVariables().contains(match)) {
192+
identifiers.computeIfAbsent(match, k -> new ArrayList<>())
193+
.add(identifier);
194+
}
175195
}
176196
}
177197
}

0 commit comments

Comments
 (0)