Skip to content

Commit 9ddd7f2

Browse files
Merge pull request #165 from refactorfirst/#152-add-minimum-feedback-set-algorithms
#152 Add minimum feedback set algorithms - Added multiple Directed Feedback Edge Set identification algorithms. Currently using the PageRank DFES - Added two Directed Feedback Vertex Set identification algorithms. - Prioritizing relationships between classes to remove based on the number of cycles each edge is in and the relative SCM churn each class in the relationship has had.
2 parents efccd28 + 0a23a8b commit 9ddd7f2

File tree

74 files changed

+11955
-803
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+11955
-803
lines changed

.github/FUNDING.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
github: jimbethancourt
2-
open_collective: RefactorFirst
3-
ko_fi: jimbethancourt
4-
liberapay: jimbethancourt
5-
patreon: jimbethancourt
2+
#open_collective: RefactorFirst
3+
#ko_fi: jimbethancourt
4+
#liberapay: jimbethancourt
5+
#patreon: jimbethancourt

change-proneness-ranker/src/main/java/org/hjug/git/ChangePronenessRanker.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,40 @@
88
@Slf4j
99
public class ChangePronenessRanker {
1010

11-
private final TreeMap<Integer, Integer> changeCountsByTimeStamps = new TreeMap<>();
11+
private final Map<Integer, Integer> changeCountsByTimeStamps = new HashMap<>();
1212
private final Map<String, ScmLogInfo> cachedScmLogInfos = new HashMap<>();
1313

1414
public ChangePronenessRanker(GitLogReader repositoryLogReader) {
1515
try {
1616
log.info("Capturing change count based on commit timestamps");
17-
changeCountsByTimeStamps.putAll(repositoryLogReader.captureChangeCountByCommitTimestamp());
17+
changeCountsByTimeStamps.putAll(
18+
computeChangeCountsByTimeStamps(repositoryLogReader.captureChangeCountByCommitTimestamp()));
1819
} catch (IOException | GitAPIException e) {
1920
log.error("Error reading from repository: {}", e.getMessage());
2021
}
2122
}
2223

24+
private Map<Integer, Integer> computeChangeCountsByTimeStamps(TreeMap<Integer, Integer> commitsWithChangeCounts) {
25+
HashMap<Integer, Integer> changeCountsByTimeStamps = new HashMap<>();
26+
int runningTotal = 0;
27+
for (Map.Entry<Integer, Integer> commitChangeCountEntry :
28+
commitsWithChangeCounts.descendingMap().entrySet()) {
29+
runningTotal += commitChangeCountEntry.getValue();
30+
changeCountsByTimeStamps.put(commitChangeCountEntry.getKey(), runningTotal);
31+
}
32+
33+
return changeCountsByTimeStamps;
34+
}
35+
2336
public void rankChangeProneness(List<ScmLogInfo> scmLogInfos) {
2437
for (ScmLogInfo scmLogInfo : scmLogInfos) {
2538
if (!cachedScmLogInfos.containsKey(scmLogInfo.getPath())) {
26-
int commitsInRepositorySinceCreation =
27-
changeCountsByTimeStamps.tailMap(scmLogInfo.getEarliestCommit()).values().stream()
28-
.mapToInt(i -> i)
29-
.sum();
39+
if (scmLogInfo.getEarliestCommit() == 0) {
40+
log.warn("No commits found for {}", scmLogInfo.getPath());
41+
continue;
42+
}
43+
44+
int commitsInRepositorySinceCreation = changeCountsByTimeStamps.get(scmLogInfo.getEarliestCommit());
3045

3146
scmLogInfo.setChangeProneness((float) scmLogInfo.getCommitCount() / commitsInRepositorySinceCreation);
3247
cachedScmLogInfos.put(scmLogInfo.getPath(), scmLogInfo);

change-proneness-ranker/src/main/java/org/hjug/git/GitLogReader.java

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public ScmLogInfo fileLog(String path) throws GitAPIException, IOException {
8787
.next()
8888
.getCommitTime();
8989

90-
return new ScmLogInfo(path, earliestCommit, mostRecentCommit, commitCount);
90+
return new ScmLogInfo(path, null, earliestCommit, mostRecentCommit, commitCount);
9191
}
9292

9393
// based on https://stackoverflow.com/questions/27361538/how-to-show-changes-between-commits-with-jgit
@@ -104,28 +104,27 @@ public TreeMap<Integer, Integer> captureChangeCountByCommitTimestamp() throws IO
104104
RevCommit oldCommit = iterator.next();
105105

106106
int count = 0;
107-
if (null == newCommit) {
107+
if (null == newCommit && iterator.hasNext()) {
108108
newCommit = oldCommit;
109109
continue;
110+
} else if (!iterator.hasNext()) {
111+
// Handle first / initial commit
112+
changesByCommitTimestamp.putAll(walkFirstCommit(oldCommit));
110113
}
111114

112-
for (DiffEntry entry : getDiffEntries(newCommit, oldCommit)) {
113-
if (entry.getNewPath().endsWith(JAVA_FILE_TYPE)
114-
|| entry.getOldPath().endsWith(JAVA_FILE_TYPE)) {
115-
count++;
115+
if (null != newCommit) {
116+
for (DiffEntry entry : getDiffEntries(newCommit, oldCommit)) {
117+
if (entry.getNewPath().endsWith(JAVA_FILE_TYPE)
118+
|| entry.getOldPath().endsWith(JAVA_FILE_TYPE)) {
119+
count++;
120+
}
116121
}
117-
}
118-
119-
if (count > 0) {
120-
changesByCommitTimestamp.put(newCommit.getCommitTime(), count);
121-
}
122122

123-
// Handle first / initial commit
124-
if (!iterator.hasNext()) {
125-
changesByCommitTimestamp.putAll(walkFirstCommit(oldCommit));
123+
if (count > 0) {
124+
changesByCommitTimestamp.put(newCommit.getCommitTime(), count);
125+
}
126+
newCommit = oldCommit;
126127
}
127-
128-
newCommit = oldCommit;
129128
}
130129

131130
return changesByCommitTimestamp;

change-proneness-ranker/src/main/java/org/hjug/git/ScmLogInfo.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
public class ScmLogInfo {
77

88
private String path;
9+
private String className;
910
private int earliestCommit;
1011
private int mostRecentCommit;
1112
private int commitCount;
1213
private float changeProneness;
1314
private int changePronenessRank;
1415

15-
public ScmLogInfo(String path, int earliestCommit, int mostRecentCommit, int commitCount) {
16+
public ScmLogInfo(String path, String className, int earliestCommit, int mostRecentCommit, int commitCount) {
1617
this.path = path;
18+
this.className = className;
1719
this.earliestCommit = earliestCommit;
1820
this.mostRecentCommit = mostRecentCommit;
1921
this.commitCount = commitCount;

change-proneness-ranker/src/test/java/org/hjug/git/ChangePronenessRankerTest.java

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public void setUp() {
2323
// TODO: this should probably be a cucumber test
2424
@Test
2525
void testChangePronenessCalculation() throws IOException, GitAPIException {
26-
ScmLogInfo scmLogInfo = new ScmLogInfo("path", 1595275997, 0, 1);
26+
ScmLogInfo scmLogInfo = new ScmLogInfo("path", null, 1595275997, 0, 1);
2727

2828
TreeMap<Integer, Integer> commitsWithChangeCounts = new TreeMap<>();
2929
commitsWithChangeCounts.put(scmLogInfo.getEarliestCommit(), scmLogInfo.getCommitCount());
@@ -43,30 +43,32 @@ void testChangePronenessCalculation() throws IOException, GitAPIException {
4343

4444
@Test
4545
void testRankChangeProneness() throws IOException, GitAPIException {
46-
ScmLogInfo scmLogInfo = new ScmLogInfo("file1", 1595275997, 0, 1);
46+
// more recent commit
47+
ScmLogInfo newerCommit = new ScmLogInfo("file1", null, 1595275997, 0, 1);
4748

4849
TreeMap<Integer, Integer> commitsWithChangeCounts = new TreeMap<>();
49-
commitsWithChangeCounts.put(scmLogInfo.getEarliestCommit(), scmLogInfo.getCommitCount());
50-
commitsWithChangeCounts.put(scmLogInfo.getEarliestCommit() + 5 * 60, 3);
51-
commitsWithChangeCounts.put(scmLogInfo.getEarliestCommit() + 10 * 60, 3);
50+
commitsWithChangeCounts.put(newerCommit.getEarliestCommit(), newerCommit.getCommitCount());
51+
commitsWithChangeCounts.put(newerCommit.getEarliestCommit() + 5 * 60, 3);
52+
commitsWithChangeCounts.put(newerCommit.getEarliestCommit() + 10 * 60, 3);
5253

53-
ScmLogInfo scmLogInfo2 = new ScmLogInfo("file2", 1595175997, 0, 1);
54+
// older commit
55+
ScmLogInfo olderCommit = new ScmLogInfo("file2", null, 1595175997, 0, 1);
5456

55-
commitsWithChangeCounts.put(scmLogInfo2.getEarliestCommit(), scmLogInfo2.getCommitCount());
56-
commitsWithChangeCounts.put(scmLogInfo2.getEarliestCommit() + 5 * 60, 5);
57-
commitsWithChangeCounts.put(scmLogInfo2.getEarliestCommit() + 10 * 60, 5);
57+
commitsWithChangeCounts.put(olderCommit.getEarliestCommit(), olderCommit.getCommitCount());
58+
commitsWithChangeCounts.put(olderCommit.getEarliestCommit() + 5 * 60, 5);
59+
commitsWithChangeCounts.put(olderCommit.getEarliestCommit() + 10 * 60, 5);
5860

5961
when(repositoryLogReader.captureChangeCountByCommitTimestamp()).thenReturn(commitsWithChangeCounts);
6062
changePronenessRanker = new ChangePronenessRanker(repositoryLogReader);
6163

6264
List<ScmLogInfo> scmLogInfos = new ArrayList<>();
63-
scmLogInfos.add(scmLogInfo);
64-
scmLogInfos.add(scmLogInfo2);
65+
scmLogInfos.add(newerCommit);
66+
scmLogInfos.add(olderCommit);
6567
changePronenessRanker.rankChangeProneness(scmLogInfos);
6668

6769
// ranks higher since fewer commits since initial commit
68-
Assertions.assertEquals(2, scmLogInfo.getChangePronenessRank());
70+
Assertions.assertEquals(2, newerCommit.getChangePronenessRank());
6971
// ranks lower since there have been more commits since initial commit
70-
Assertions.assertEquals(1, scmLogInfo2.getChangePronenessRank());
72+
Assertions.assertEquals(1, olderCommit.getChangePronenessRank());
7173
}
7274
}

codebase-graph-builder/src/main/java/org/hjug/graphbuilder/JavaGraphBuilder.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,22 @@
2323
public class JavaGraphBuilder {
2424

2525
/**
26-
* Given a java source directory return a graph of class references
26+
* Given a java source directory, return a CodebaseGraphDTO
2727
*
2828
* @param srcDirectory
29-
* @return
29+
* @return CodebaseGraphDTO
3030
* @throws IOException
3131
*/
32-
public Graph<String, DefaultWeightedEdge> getClassReferences(
33-
String srcDirectory, boolean excludeTests, String testSourceDirectory) throws IOException {
34-
Graph<String, DefaultWeightedEdge> classReferencesGraph;
32+
public CodebaseGraphDTO getCodebaseGraphDTO(String srcDirectory, boolean excludeTests, String testSourceDirectory)
33+
throws IOException {
34+
CodebaseGraphDTO codebaseGraphDTO;
3535
if (srcDirectory == null || srcDirectory.isEmpty()) {
3636
throw new IllegalArgumentException();
3737
} else {
38-
classReferencesGraph = processWithOpenRewrite(srcDirectory, excludeTests, testSourceDirectory)
39-
.getClassReferencesGraph();
38+
codebaseGraphDTO = processWithOpenRewrite(srcDirectory, excludeTests, testSourceDirectory);
4039
}
4140

42-
return classReferencesGraph;
41+
return codebaseGraphDTO;
4342
}
4443

4544
private CodebaseGraphDTO processWithOpenRewrite(String srcDir, boolean excludeTests, String testSourceDirectory)

codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/TypeProcessor.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@ private void processType(String ownerFqn, JavaType.Array arrayType) {
5050
}
5151

5252
private void processType(String ownerFqn, JavaType.GenericTypeVariable typeVariable) {
53-
LogHolder.log.debug("Type parameter type name: " + typeVariable.getName());
53+
LogHolder.log.debug("Type parameter type name: {}", typeVariable.getName());
5454

5555
for (JavaType bound : typeVariable.getBounds()) {
5656
if (bound instanceof JavaType.Class) {
57-
addType(((JavaType.Class) bound).getFullyQualifiedName(), ownerFqn);
57+
addType(ownerFqn, ((JavaType.Class) bound).getFullyQualifiedName());
5858
} else if (bound instanceof JavaType.Parameterized) {
59-
addType(((JavaType.Parameterized) bound).getFullyQualifiedName(), ownerFqn);
59+
addType(ownerFqn, ((JavaType.Parameterized) bound).getFullyQualifiedName());
60+
} else {
61+
LogHolder.log.debug("Unknown type bound: {}", bound);
6062
}
6163
}
6264
}

codebase-graph-builder/src/test/java/org/hjug/graphbuilder/JavaGraphBuilderTest.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ class JavaGraphBuilderTest {
2020
@Test
2121
void parseSourceDirectoryEmptyTest() {
2222
Assertions.assertThrows(
23-
IllegalArgumentException.class, () -> javaGraphBuilder.getClassReferences("", false, ""));
23+
IllegalArgumentException.class, () -> javaGraphBuilder.getCodebaseGraphDTO("", false, ""));
2424
Assertions.assertThrows(
25-
IllegalArgumentException.class, () -> javaGraphBuilder.getClassReferences(null, false, ""));
25+
IllegalArgumentException.class, () -> javaGraphBuilder.getCodebaseGraphDTO(null, false, ""));
2626
}
2727

2828
@DisplayName("Given a valid source directory input parameter return a valid graph.")
2929
@Test
3030
void parseSourceDirectoryTest() throws IOException {
3131
File srcDirectory = new File("src/test/resources/javaSrcDirectory");
32-
Graph<String, DefaultWeightedEdge> classReferencesGraph =
33-
javaGraphBuilder.getClassReferences(srcDirectory.getAbsolutePath(), false, "");
32+
CodebaseGraphDTO dto = javaGraphBuilder.getCodebaseGraphDTO(srcDirectory.getAbsolutePath(), false, "");
33+
Graph<String, DefaultWeightedEdge> classReferencesGraph = dto.getClassReferencesGraph();
3434
assertNotNull(classReferencesGraph);
3535
assertEquals(5, classReferencesGraph.vertexSet().size());
3636
assertEquals(7, classReferencesGraph.edgeSet().size());
@@ -77,9 +77,8 @@ private static double getEdgeWeight(
7777
@Test
7878
void removeClassesNotInCodebase() throws IOException {
7979
File srcDirectory = new File("src/test/resources/javaSrcDirectory");
80-
Graph<String, DefaultWeightedEdge> classReferencesGraph =
81-
javaGraphBuilder.getClassReferences(srcDirectory.getAbsolutePath(), false, "");
82-
80+
CodebaseGraphDTO dto = javaGraphBuilder.getCodebaseGraphDTO(srcDirectory.getAbsolutePath(), false, "");
81+
Graph<String, DefaultWeightedEdge> classReferencesGraph = dto.getClassReferencesGraph();
8382
classReferencesGraph.addVertex("org.favioriteoss.FunClass");
8483
classReferencesGraph.addVertex("org.favioriteoss.AnotherFunClass");
8584

cost-benefit-calculator/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
<dependency>
3737
<groupId>org.hjug.refactorfirst.dsm</groupId>
38-
<artifactId>dsm</artifactId>
38+
<artifactId>graph-algorithms</artifactId>
3939
</dependency>
4040

4141
<dependency>

0 commit comments

Comments
 (0)