Skip to content

Commit 895651e

Browse files
committed
Infer class move/rename from CandidateAttributeRefactoring
apache/flink@2940848
1 parent 9eb8e31 commit 895651e

File tree

6 files changed

+203
-8
lines changed

6 files changed

+203
-8
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -513,18 +513,18 @@ As of **November 14, 2025** the precision and recall of RefactoringMiner on this
513513

514514
| Refactoring Type | TP | FP | FN | Precision | Recall |
515515
|:-----------------------|-----------:|--------:|--------:|--------:|--------:|
516-
|**Total**|3436 | 47 | 90 | 0.987 | 0.974|
516+
|**Total**|3443 | 46 | 86 | 0.987 | 0.976|
517517
|Extract Method|373 | 1 | 4 | 0.997 | 0.989|
518518
|Rename Class|233 | 0 | 1 | 1.000 | 0.996|
519519
|Move Attribute|72 | 0 | 7 | 1.000 | 0.911|
520520
|Move And Rename Attribute| 7 | 0 | 1 | 1.000 | 0.875|
521-
|Rename Method|299 | 7 | 6 | 0.977 | 0.980|
521+
|Rename Method|299 | 7 | 5 | 0.977 | 0.984|
522522
|Inline Method|68 | 3 | 2 | 0.958 | 0.971|
523523
|Move Method|266 | 1 | 0 | 0.996 | 1.000|
524524
|Move And Rename Method|24 | 4 | 1 | 0.857 | 0.960|
525525
|Pull Up Method|45 | 0 | 1 | 1.000 | 0.978|
526526
|Move Class|141 | 4 | 2 | 0.972 | 0.986|
527-
|Move And Rename Class|27 | 0 | 1 | 1.000 | 0.964|
527+
|Move And Rename Class|28 | 0 | 0 | 1.000 | 1.000|
528528
|Pull Up Attribute|12 | 0 | 0 | 1.000 | 1.000|
529529
|Push Down Attribute| 6 | 0 | 0 | 1.000 | 1.000|
530530
|Push Down Method|22 | 0 | 0 | 1.000 | 1.000|
@@ -539,10 +539,10 @@ As of **November 14, 2025** the precision and recall of RefactoringMiner on this
539539
|Extract Attribute| 3 | 0 | 0 | 1.000 | 1.000|
540540
|Inline Variable|127 | 0 | 11 | 1.000 | 0.920|
541541
|Rename Variable|305 | 5 | 8 | 0.984 | 0.974|
542-
|Rename Attribute|107 | 4 | 8 | 0.964 | 0.930|
542+
|Rename Attribute|112 | 4 | 6 | 0.966 | 0.949|
543543
|Replace Variable With Attribute|11 | 0 | 0 | 1.000 | 1.000|
544544
|Replace Attribute With Variable|30 | 0 | 0 | 1.000 | 1.000|
545-
|Change Return Type|166 | 2 | 7 | 0.988 | 0.960|
545+
|Change Return Type|167 | 1 | 7 | 0.994 | 0.960|
546546
|Change Variable Type|376 | 7 | 4 | 0.982 | 0.989|
547547
|Change Attribute Type|164 | 2 | 3 | 0.988 | 0.982|
548548
|Change Type Declaration Kind| 4 | 0 | 0 | 1.000 | 1.000|

src/main/java/gr/uom/java/xmi/UMLAbstractClass.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,18 @@ public boolean containsAttributeWithName(String attributeName) {
666666
return false;
667667
}
668668

669+
public UMLAttribute attributeWithName(String attributeName) {
670+
for(UMLAttribute originalAttribute : attributes) {
671+
if(originalAttribute.getName().equals(attributeName))
672+
return originalAttribute;
673+
}
674+
for(UMLAttribute originalAttribute : enumConstants) {
675+
if(originalAttribute.getName().equals(attributeName))
676+
return originalAttribute;
677+
}
678+
return null;
679+
}
680+
669681
public MatchResult hasAttributesAndOperationsWithCommonNames(UMLAbstractClass umlClass) {
670682
List<UMLOperation> commonOperations = new ArrayList<UMLOperation>();
671683
List<UMLOperation> identicalOperations = new ArrayList<UMLOperation>();

src/main/java/gr/uom/java/xmi/diff/UMLModelDiff.java

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,44 @@ public UMLClassBaseDiff getUMLClassDiff(UMLType type) {
401401
return null;
402402
}
403403

404+
private Set<UMLClass> getRemovedUMLClassWithAttribute(String attributeName) {
405+
Set<UMLClass> classes = new LinkedHashSet<>();
406+
for(UMLClass umlClass : removedClasses) {
407+
for(UMLAttribute attribute : umlClass.getAttributes()) {
408+
if(attribute.getName().equals(attributeName)) {
409+
classes.add(umlClass);
410+
break;
411+
}
412+
}
413+
for(UMLAttribute attribute : umlClass.getEnumConstants()) {
414+
if(attribute.getName().equals(attributeName)) {
415+
classes.add(umlClass);
416+
break;
417+
}
418+
}
419+
}
420+
return classes;
421+
}
422+
423+
private Set<UMLClass> getAddedUMLClassWithAttribute(String attributeName) {
424+
Set<UMLClass> classes = new LinkedHashSet<>();
425+
for(UMLClass umlClass : addedClasses) {
426+
for(UMLAttribute attribute : umlClass.getAttributes()) {
427+
if(attribute.getName().equals(attributeName)) {
428+
classes.add(umlClass);
429+
break;
430+
}
431+
}
432+
for(UMLAttribute attribute : umlClass.getEnumConstants()) {
433+
if(attribute.getName().equals(attributeName)) {
434+
classes.add(umlClass);
435+
break;
436+
}
437+
}
438+
}
439+
return classes;
440+
}
441+
404442
private UMLClassBaseDiff getUMLClassDiffWithAttribute(Replacement pattern) {
405443
String before = new String(pattern.getBefore());
406444
String after = new String(pattern.getAfter());
@@ -3737,9 +3775,24 @@ public List<Refactoring> getRefactorings() throws RefactoringMinerTimedOutExcept
37373775
}
37383776
}
37393777
}
3778+
Map<Pair<UMLClass, UMLClass>, Set<CandidateAttributeRefactoring>> inferredClassRenames = new LinkedHashMap<>();
37403779
for(Replacement pattern : renameMap.keySet()) {
37413780
UMLClassBaseDiff diff = getUMLClassDiffWithAttribute(pattern);
37423781
Set<CandidateAttributeRefactoring> set = renameMap.get(pattern);
3782+
if(diff == null) {
3783+
Set<UMLClass> removedClassesWithAttribute = getRemovedUMLClassWithAttribute(pattern.getBefore());
3784+
Set<UMLClass> addedClassesWithAttribute = getAddedUMLClassWithAttribute(pattern.getAfter());
3785+
if(removedClassesWithAttribute.size() == 1 && addedClassesWithAttribute.size() == 1) {
3786+
Pair<UMLClass, UMLClass> pair = Pair.of(removedClassesWithAttribute.iterator().next(), addedClassesWithAttribute.iterator().next());
3787+
if(inferredClassRenames.containsKey(pair)) {
3788+
inferredClassRenames.get(pair).addAll(set);
3789+
}
3790+
else {
3791+
Set<CandidateAttributeRefactoring> newSet = new LinkedHashSet<>(set);
3792+
inferredClassRenames.put(pair, newSet);
3793+
}
3794+
}
3795+
}
37433796
String before = new String(pattern.getBefore());
37443797
String after = new String(pattern.getAfter());
37453798
if(before.contains(".") && after.contains(".")) {
@@ -3952,6 +4005,121 @@ else if(candidate.getOriginalAttribute().getName().equals(a2.getName())) {
39524005
}
39534006
}
39544007
}
4008+
if(!partialModel()) {
4009+
for(Pair<UMLClass, UMLClass> pair : inferredClassRenames.keySet()) {
4010+
UMLClass parentClass = pair.getLeft();
4011+
UMLClass childClass = pair.getRight();
4012+
Set<CandidateAttributeRefactoring> set = inferredClassRenames.get(pair);
4013+
if(set.size() > 1) {
4014+
if(!parentClass.getNonQualifiedName().equals(childClass.getNonQualifiedName())) {
4015+
int totalOperations = parentClass.getOperations().size() + childClass.getOperations().size();
4016+
int totalAttributes = parentClass.getAttributes().size() + childClass.getAttributes().size();
4017+
MatchResult matchResult = new MatchResult(0, set.size(), 0, totalOperations, totalAttributes, true);
4018+
UMLClassRenameDiff renameDiff = new UMLClassRenameDiff(parentClass, childClass, this, matchResult);
4019+
renameDiff.process();
4020+
refactorings.addAll(renameDiff.getRefactorings());
4021+
classRenameDiffList.add(renameDiff);
4022+
Refactoring refactoring = null;
4023+
if(renameDiff.samePackage())
4024+
refactoring = new RenameClassRefactoring(renameDiff.getOriginalClass(), renameDiff.getRenamedClass());
4025+
else
4026+
refactoring = new MoveAndRenameClassRefactoring(renameDiff.getOriginalClass(), renameDiff.getRenamedClass());
4027+
refactorings.add(refactoring);
4028+
removedClasses.remove(parentClass);
4029+
addedClasses.remove(childClass);
4030+
for(CandidateAttributeRefactoring candidate : set) {
4031+
UMLAttribute a1 = parentClass.attributeWithName(candidate.getOriginalVariableName());
4032+
UMLAttribute a2 = childClass.attributeWithName(candidate.getRenamedVariableName());
4033+
if(a1 instanceof UMLEnumConstant && a2 instanceof UMLEnumConstant) {
4034+
UMLEnumConstantDiff enumConstantDiff = new UMLEnumConstantDiff((UMLEnumConstant)a1, (UMLEnumConstant)a2, renameDiff, this);
4035+
if(!renameDiff.getEnumConstantDiffList().contains(enumConstantDiff)) {
4036+
renameDiff.getEnumConstantDiffList().add(enumConstantDiff);
4037+
}
4038+
Set<Refactoring> enumConstantDiffRefactorings = enumConstantDiff.getRefactorings(set);
4039+
if(!refactorings.containsAll(enumConstantDiffRefactorings)) {
4040+
refactorings.addAll(enumConstantDiffRefactorings);
4041+
}
4042+
}
4043+
else {
4044+
UMLAttributeDiff attributeDiff = new UMLAttributeDiff(a1, a2, renameDiff, this);
4045+
if(!renameDiff.getAttributeDiffList().contains(attributeDiff)) {
4046+
renameDiff.getAttributeDiffList().add(attributeDiff);
4047+
}
4048+
Set<Refactoring> attributeDiffRefactorings = attributeDiff.getRefactorings(set);
4049+
if(!refactorings.containsAll(attributeDiffRefactorings)) {
4050+
refactorings.addAll(attributeDiffRefactorings);
4051+
}
4052+
}
4053+
}
4054+
//eliminate inner classes being reported as moved
4055+
List<MoveClassRefactoring> toBeRemoved = new ArrayList<MoveClassRefactoring>();
4056+
for(Refactoring r : refactorings) {
4057+
if(r instanceof MoveClassRefactoring) {
4058+
MoveClassRefactoring moveClass = (MoveClassRefactoring)r;
4059+
if(moveClass.getOriginalClassName().startsWith(renameDiff.getOriginalClassName() + ".") &&
4060+
moveClass.getMovedClassName().startsWith(renameDiff.getNextClassName() + ".")) {
4061+
toBeRemoved.add(moveClass);
4062+
}
4063+
}
4064+
}
4065+
this.refactorings.removeAll(toBeRemoved);
4066+
}
4067+
else {
4068+
int totalOperations = parentClass.getOperations().size() + childClass.getOperations().size();
4069+
int totalAttributes = parentClass.getAttributes().size() + childClass.getAttributes().size();
4070+
MatchResult matchResult = new MatchResult(0, set.size(), 0, totalOperations, totalAttributes, true);
4071+
UMLClassMoveDiff moveDiff = new UMLClassMoveDiff(parentClass, childClass, this, matchResult);
4072+
moveDiff.process();
4073+
refactorings.addAll(moveDiff.getRefactorings());
4074+
classMoveDiffList.add(moveDiff);
4075+
UMLClassBaseDiff parentClassDiff = getUMLClassDiff(parentClass.getPackageName());
4076+
boolean parentClassIsMoved = parentClassDiff instanceof UMLClassMoveDiff m && m.getNextClassName().equals(childClass.getPackageName());
4077+
if(!parentClassIsMoved) {
4078+
Refactoring refactoring = new MoveClassRefactoring(moveDiff.getOriginalClass(), moveDiff.getMovedClass());
4079+
refactorings.add(refactoring);
4080+
}
4081+
removedClasses.remove(parentClass);
4082+
addedClasses.remove(childClass);
4083+
for(CandidateAttributeRefactoring candidate : set) {
4084+
UMLAttribute a1 = parentClass.attributeWithName(candidate.getOriginalVariableName());
4085+
UMLAttribute a2 = childClass.attributeWithName(candidate.getRenamedVariableName());
4086+
if(a1 instanceof UMLEnumConstant && a2 instanceof UMLEnumConstant) {
4087+
UMLEnumConstantDiff enumConstantDiff = new UMLEnumConstantDiff((UMLEnumConstant)a1, (UMLEnumConstant)a2, moveDiff, this);
4088+
if(!moveDiff.getEnumConstantDiffList().contains(enumConstantDiff)) {
4089+
moveDiff.getEnumConstantDiffList().add(enumConstantDiff);
4090+
}
4091+
Set<Refactoring> enumConstantDiffRefactorings = enumConstantDiff.getRefactorings(set);
4092+
if(!refactorings.containsAll(enumConstantDiffRefactorings)) {
4093+
refactorings.addAll(enumConstantDiffRefactorings);
4094+
}
4095+
}
4096+
else {
4097+
UMLAttributeDiff attributeDiff = new UMLAttributeDiff(a1, a2, moveDiff, this);
4098+
if(!moveDiff.getAttributeDiffList().contains(attributeDiff)) {
4099+
moveDiff.getAttributeDiffList().add(attributeDiff);
4100+
}
4101+
Set<Refactoring> attributeDiffRefactorings = attributeDiff.getRefactorings(set);
4102+
if(!refactorings.containsAll(attributeDiffRefactorings)) {
4103+
refactorings.addAll(attributeDiffRefactorings);
4104+
}
4105+
}
4106+
}
4107+
//eliminate inner classes being reported as moved
4108+
List<MoveClassRefactoring> toBeRemoved = new ArrayList<MoveClassRefactoring>();
4109+
for(Refactoring r : refactorings) {
4110+
if(r instanceof MoveClassRefactoring) {
4111+
MoveClassRefactoring moveClass = (MoveClassRefactoring)r;
4112+
if(moveClass.getOriginalClassName().startsWith(moveDiff.getOriginalClassName() + ".") &&
4113+
moveClass.getMovedClassName().startsWith(moveDiff.getNextClassName() + ".")) {
4114+
toBeRemoved.add(moveClass);
4115+
}
4116+
}
4117+
}
4118+
this.refactorings.removeAll(toBeRemoved);
4119+
}
4120+
}
4121+
}
4122+
}
39554123
refactorings.addAll(identifyExtractSuperclassRefactorings());
39564124
refactorings.addAll(identifyCollapseHierarchyRefactorings());
39574125
refactorings.addAll(identifyExtractClassRefactorings(commonClassDiffList));

src/test/java/org/refactoringminer/test/TestNewDatasetRefactorings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@ public void testAllRefactorings() throws Exception {
5252
.or(Refactorings.SplitClass.getValue());
5353
TestBuilder test = new TestBuilder(detector, REPOS, types);
5454
RefactoringPopulator.feedTSERefactoringInstances(test);
55-
test.assertExpectationsWithGitHubAPI(3436, 47, 90);
55+
test.assertExpectationsWithGitHubAPI(3443, 46, 86);
5656
}
5757
}

src/test/resources/oracle/tse-dataset/flink.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
{
8686
"type": "RENAME_METHOD",
8787
"description": "Rename Method\tpublic available(record R, key K) : boolean renamed to public occupy(record R, key K) : boolean in class org.apache.flink.runtime.asyncprocessing.KeyAccountingUnit",
88-
"validation": true
88+
"validation": false
8989
},
9090
{
9191
"type": "RENAME_ATTRIBUTE",
@@ -107,7 +107,7 @@
107107
{
108108
"type": "CHANGE_RETURN_TYPE",
109109
"description": "Change Return Type\tvoid to boolean in method public occupy(record R, key K) : boolean from class org.apache.flink.runtime.asyncprocessing.KeyAccountingUnit",
110-
"validation": false
110+
"validation": true
111111
},
112112
{
113113
"type": "CHANGE_VARIABLE_TYPE",

src/test/resources/oracle/tse-dataset/intellij-community.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,21 @@
392392
"type": "REPLACE_VARIABLE_WITH_ATTRIBUTE",
393393
"description": "Replace Variable With Attribute\tchosenFiles : var to myChosenFiles : VirtualFile[] in method public choose(project Project, toSelect VirtualFile...) : VirtualFile @NotNull [] from class com.intellij.openapi.fileChooser.impl.NewFileChooserDialogImpl",
394394
"validation": true
395+
},
396+
{
397+
"type": "RENAME_ATTRIBUTE",
398+
"description": "Rename Attribute\tWINDOWS : Type to NEW : Type in class com.intellij.openapi.fileChooser.impl.FileChooserUsageCollector.Type",
399+
"validation": true
400+
},
401+
{
402+
"type": "RENAME_ATTRIBUTE",
403+
"description": "Rename Attribute\tNEW : Type to OTHER : Type in class com.intellij.openapi.fileChooser.impl.FileChooserUsageCollector.Type",
404+
"validation": true
405+
},
406+
{
407+
"type": "RENAME_ATTRIBUTE",
408+
"description": "Rename Attribute\tMAC : Type to NATIVE : Type in class com.intellij.openapi.fileChooser.impl.FileChooserUsageCollector.Type",
409+
"validation": true
395410
}
396411
]
397412
},

0 commit comments

Comments
 (0)