Skip to content

Commit

Permalink
Fine-grained blame for multi-line class signature
Browse files Browse the repository at this point in the history
  • Loading branch information
tsantalis committed Aug 28, 2024
1 parent dcc9761 commit b89f7ae
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 6 deletions.
123 changes: 123 additions & 0 deletions src/main/java/org/codetracker/ClassTrackerChangeHistory.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package org.codetracker;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.codetracker.api.History;
import org.codetracker.api.History.HistoryInfo;
import org.codetracker.api.Version;
Expand All @@ -20,6 +27,12 @@
import org.codetracker.element.Package;
import org.refactoringminer.api.Refactoring;

import com.github.difflib.DiffUtils;
import com.github.difflib.patch.AbstractDelta;
import com.github.difflib.patch.Chunk;
import com.github.difflib.patch.InsertDelta;
import com.github.difflib.patch.Patch;

import gr.uom.java.xmi.UMLAbstractClass;
import gr.uom.java.xmi.UMLClass;
import gr.uom.java.xmi.UMLModel;
Expand Down Expand Up @@ -203,6 +216,7 @@ public Set<Class> analyseClassRefactorings(Collection<Refactoring> refactorings,
}
if (changeType2 != null)
classChangeHistory.addChange(classBefore, classAfter, ChangeFactory.forClass(changeType2).refactoring(refactoring));
processChange(classBefore, classAfter);
leftClassSet.add(classBefore);
}
}
Expand Down Expand Up @@ -244,6 +258,82 @@ public Set<Class> isInnerClassContainerChanged(UMLModelDiff umlModelDiffAll, Col
return Collections.emptySet();
}

private Map<Pair<Class, Class>, List<Integer>> lineChangeMap = new LinkedHashMap<>();

public void processChange(Class classBefore, Class classAfter) {
if (classBefore.isMultiLine() || classAfter.isMultiLine()) {
try {
Pair<Class, Class> pair = Pair.of(classBefore, classAfter);
Class startClass = getStart();
if (startClass != null) {
List<String> start = IOUtils.readLines(new StringReader(((UMLClass)startClass.getUmlClass()).getActualSignature()));
List<String> original = IOUtils.readLines(new StringReader(((UMLClass)classBefore.getUmlClass()).getActualSignature()));
List<String> revised = IOUtils.readLines(new StringReader(((UMLClass)classAfter.getUmlClass()).getActualSignature()));

Patch<String> patch = DiffUtils.diff(original, revised);
List<AbstractDelta<String>> deltas = patch.getDeltas();
for (int i=0; i<deltas.size(); i++) {
AbstractDelta<String> delta = deltas.get(i);
Chunk<String> target = delta.getTarget();
List<String> affectedLines = new ArrayList<>(target.getLines());
boolean subListFound = false;
if (affectedLines.size() > 1 && !(delta instanceof InsertDelta)) {
int index = Collections.indexOfSubList(start, affectedLines);
if (index != -1) {
subListFound = true;
for (int j=0; j<affectedLines.size(); j++) {
int actualLine = startClass.classSignatureStartLine() + index + j;
if (lineChangeMap.containsKey(pair)) {
lineChangeMap.get(pair).add(actualLine);
}
else {
List<Integer> list = new ArrayList<>();
list.add(actualLine);
lineChangeMap.put(pair, list);
}
}
}
}
if (!subListFound) {
for (String line : affectedLines) {
List<Integer> matchingIndices = findAllMatchingIndices(start, line);
for (Integer index : matchingIndices) {
if (original.size() > index && revised.size() > index &&
original.get(index).equals(line) && revised.get(index).equals(line)) {
continue;
}
int actualLine = startClass.classSignatureStartLine() + index;
if (lineChangeMap.containsKey(pair)) {
lineChangeMap.get(pair).add(actualLine);
}
else {
List<Integer> list = new ArrayList<>();
list.add(actualLine);
lineChangeMap.put(pair, list);
}
break;
}
}
}
}
}
} catch(IOException e) {
e.printStackTrace();
}
}
}

private List<Integer> findAllMatchingIndices(List<String> startCommentLines, String line) {
List<Integer> matchingIndices = new ArrayList<>();
for(int i=0; i<startCommentLines.size(); i++) {
String element = startCommentLines.get(i).trim();
if(line.equals(element) || element.contains(line.trim())) {
matchingIndices.add(i);
}
}
return matchingIndices;
}

public HistoryInfo<Class> blameReturn(Class startClass) {
List<HistoryInfo<Class>> history = getHistory();
for (History.HistoryInfo<Class> historyInfo : history) {
Expand All @@ -263,6 +353,39 @@ public HistoryInfo<Class> blameReturn(Class startClass) {
return null;
}

public HistoryInfo<Class> blameReturn(Class startClass, int exactLineNumber) {
List<HistoryInfo<Class>> history = getHistory();
for (History.HistoryInfo<Class> historyInfo : history) {
Pair<Class, Class> pair = Pair.of(historyInfo.getElementBefore(), historyInfo.getElementAfter());
boolean multiLine = startClass.isMultiLine();
for (Change change : historyInfo.getChangeList()) {
if (startClass.isClosingCurlyBracket()) {
if (change instanceof Introduced) {
return historyInfo;
}
}
else {
if (!(change instanceof ClassMove) && !(change instanceof ClassContainerChange) && !(change instanceof ClassAnnotationChange)) {
if (multiLine) {
if (lineChangeMap.containsKey(pair)) {
if (lineChangeMap.get(pair).contains(exactLineNumber)) {
return historyInfo;
}
}
}
else {
return historyInfo;
}
}
if (change instanceof Introduced) {
return historyInfo;
}
}
}
}
return null;
}

public HistoryInfo<Class> blameReturn(Package startPackage) {
List<HistoryInfo<Class>> history = getHistory();
for (History.HistoryInfo<Class> historyInfo : history) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,7 @@ public void processChange(Comment commentBefore, Comment commentAfter) {
lineChangeMap.get(pair).add(actualLine);
}
else {
List list = new ArrayList<>();
List<Integer> list = new ArrayList<>();
list.add(actualLine);
lineChangeMap.put(pair, list);
}
Expand All @@ -907,7 +907,7 @@ public void processChange(Comment commentBefore, Comment commentAfter) {
lineChangeMap.get(pair).add(actualLine);
}
else {
List list = new ArrayList<>();
List<Integer> list = new ArrayList<>();
list.add(actualLine);
lineChangeMap.put(pair, list);
}
Expand Down
24 changes: 23 additions & 1 deletion src/main/java/org/codetracker/FileTrackerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ else if (leftSuperclass == null && rightSuperclass != null) {
startClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.INTERFACE_LIST_CHANGE));
startClassChangeHistory.get().connectRelatedNodes();
}
startClassChangeHistory.processChange(leftClass, rightClass);
Map<Method, MethodTrackerChangeHistory> notFoundMethods = processMethodsWithSameSignature(rightModel, currentVersion, leftModel, parentVersion);
Map<Attribute, AttributeTrackerChangeHistory> notFoundAttributes = processAttributesWithSameSignature(rightModel, currentVersion, leftModel, parentVersion);
Map<Class, ClassTrackerChangeHistory> notFoundInnerClasses = new LinkedHashMap<>();
Expand Down Expand Up @@ -402,7 +403,7 @@ else if (startElement instanceof Class) {
Class clazz = (Class)startElement;
ClassTrackerChangeHistory classChangeHistory = (ClassTrackerChangeHistory) programElementMap.get(clazz);
clazz.checkClosingBracket(lineNumber);
HistoryInfo<Class> historyInfo = classChangeHistory.blameReturn(clazz);
HistoryInfo<Class> historyInfo = classChangeHistory.blameReturn(clazz, lineNumber);
blameInfo.put(lineNumber, historyInfo);
}
else if (startElement instanceof Comment) {
Expand Down Expand Up @@ -1069,6 +1070,27 @@ private void processInnerClassesWithSameSignature(UMLModel rightModel, Version c
startInnerClassChangeHistory.poll();
Class leftClass = getClass(leftModel, parentVersion, rightClass::equalIdentifierIgnoringVersion);
if (leftClass != null) {
UMLType leftSuperclass = leftClass.getUmlClass().getSuperclass();
UMLType rightSuperclass = rightClass.getUmlClass().getSuperclass();
if (leftSuperclass != null && rightSuperclass != null) {
if (!leftSuperclass.equals(rightSuperclass)) {
startInnerClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.SUPERCLASS_CHANGE));
startInnerClassChangeHistory.get().connectRelatedNodes();
}
}
else if (leftSuperclass != null && rightSuperclass == null) {
startInnerClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.SUPERCLASS_CHANGE));
startInnerClassChangeHistory.get().connectRelatedNodes();
}
else if (leftSuperclass == null && rightSuperclass != null) {
startInnerClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.SUPERCLASS_CHANGE));
startInnerClassChangeHistory.get().connectRelatedNodes();
}
if (!leftClass.getUmlClass().getImplementedInterfaces().equals(rightClass.getUmlClass().getImplementedInterfaces())) {
startInnerClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.INTERFACE_LIST_CHANGE));
startInnerClassChangeHistory.get().connectRelatedNodes();
}
startInnerClassChangeHistory.processChange(leftClass, rightClass);
startInnerClassChangeHistory.setCurrent(leftClass);
startInnerClassChangeHistory.addFirst(leftClass);
foundInnerClasses.add(Pair.of(leftClass, rightClass));
Expand Down
24 changes: 23 additions & 1 deletion src/main/java/org/codetracker/FileTrackerWithLocalFilesImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ else if (leftSuperclass == null && rightSuperclass != null) {
startClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.INTERFACE_LIST_CHANGE));
startClassChangeHistory.get().connectRelatedNodes();
}
startClassChangeHistory.processChange(leftClass, rightClass);
Map<Method, MethodTrackerChangeHistory> notFoundMethods = processMethodsWithSameSignature(rightModel, currentVersion, leftModel, parentVersion);
Map<Attribute, AttributeTrackerChangeHistory> notFoundAttributes = processAttributesWithSameSignature(rightModel, currentVersion, leftModel, parentVersion);
Map<Class, ClassTrackerChangeHistory> notFoundInnerClasses = new LinkedHashMap<>();
Expand Down Expand Up @@ -410,7 +411,7 @@ else if (startElement instanceof Class) {
Class clazz = (Class)startElement;
ClassTrackerChangeHistory classChangeHistory = (ClassTrackerChangeHistory) programElementMap.get(clazz);
clazz.checkClosingBracket(lineNumber);
HistoryInfo<Class> historyInfo = classChangeHistory.blameReturn(clazz);
HistoryInfo<Class> historyInfo = classChangeHistory.blameReturn(clazz, lineNumber);
blameInfo.put(lineNumber, historyInfo);
}
else if (startElement instanceof Comment) {
Expand Down Expand Up @@ -1076,6 +1077,27 @@ private void processInnerClassesWithSameSignature(UMLModel rightModel, Version c
startInnerClassChangeHistory.poll();
Class leftClass = getClass(leftModel, parentVersion, rightClass::equalIdentifierIgnoringVersion);
if (leftClass != null) {
UMLType leftSuperclass = leftClass.getUmlClass().getSuperclass();
UMLType rightSuperclass = rightClass.getUmlClass().getSuperclass();
if (leftSuperclass != null && rightSuperclass != null) {
if (!leftSuperclass.equals(rightSuperclass)) {
startInnerClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.SUPERCLASS_CHANGE));
startInnerClassChangeHistory.get().connectRelatedNodes();
}
}
else if (leftSuperclass != null && rightSuperclass == null) {
startInnerClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.SUPERCLASS_CHANGE));
startInnerClassChangeHistory.get().connectRelatedNodes();
}
else if (leftSuperclass == null && rightSuperclass != null) {
startInnerClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.SUPERCLASS_CHANGE));
startInnerClassChangeHistory.get().connectRelatedNodes();
}
if (!leftClass.getUmlClass().getImplementedInterfaces().equals(rightClass.getUmlClass().getImplementedInterfaces())) {
startInnerClassChangeHistory.get().addChange(leftClass, rightClass, ChangeFactory.forClass(Change.Type.INTERFACE_LIST_CHANGE));
startInnerClassChangeHistory.get().connectRelatedNodes();
}
startInnerClassChangeHistory.processChange(leftClass, rightClass);
startInnerClassChangeHistory.setCurrent(leftClass);
startInnerClassChangeHistory.addFirst(leftClass);
foundInnerClasses.add(Pair.of(leftClass, rightClass));
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/codetracker/MethodTrackerChangeHistory.java
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ public void processChange(Method methodBefore, Method methodAfter) {
lineChangeMap.get(pair).add(actualLine);
}
else {
List list = new ArrayList<>();
List<Integer> list = new ArrayList<>();
list.add(actualLine);
lineChangeMap.put(pair, list);
}
Expand All @@ -758,7 +758,7 @@ public void processChange(Method methodBefore, Method methodAfter) {
lineChangeMap.get(pair).add(actualLine);
}
else {
List list = new ArrayList<>();
List<Integer> list = new ArrayList<>();
list.add(actualLine);
lineChangeMap.put(pair, list);
}
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/org/codetracker/element/Class.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,27 @@ public static Class of(UMLAbstractClass umlClass, Version version) {
return new Class(umlClass, identifierExcludeVersion, className, umlClass.getLocationInfo().getFilePath(), version);
}

public int classSignatureStartLine() {
int classSignatureStartLine = -1;
if (umlClass instanceof UMLClass) {
UMLClass clazz = (UMLClass) umlClass;
if (clazz.getModifiers().size() > 0)
classSignatureStartLine = clazz.getModifiers().get(0).getLocationInfo().getStartLine();
else if (clazz.getTypeParameters().size() > 0)
classSignatureStartLine = clazz.getTypeParameters().get(0).getLocationInfo().getStartLine();
else if (clazz.getSuperclass() != null)
classSignatureStartLine = clazz.getSuperclass().getLocationInfo().getStartLine();
}
return classSignatureStartLine;
}

public boolean isMultiLine() {
if (umlClass instanceof UMLClass && ((UMLClass)umlClass).getActualSignature() != null) {
return ((UMLClass)umlClass).getActualSignature().contains("\n");
}
return false;
}

public Package findPackage(Predicate<Package> equalOperator) {
if (umlClass instanceof UMLClass) {
Optional<UMLPackage> umlPackage = ((UMLClass) umlClass).getPackageDeclaration();
Expand Down

0 comments on commit b89f7ae

Please sign in to comment.