Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
b57c9f3
Extracted EdgeRemovalCalculator to a separate class
jimbethancourt Jul 16, 2025
b6d21c1
Initial commit of code as-is from Perplexity output
jimbethancourt Jul 24, 2025
145c9fc
- Fixed minor issue with lambda in DirectedFeedbackVertexSetSolver
jimbethancourt Jul 24, 2025
8ff63a2
#152 Initial commit of SuperTypeToken
jimbethancourt Aug 3, 2025
d1e80d0
#152 Improved ergonomics
jimbethancourt Aug 3, 2025
0853746
#152 Now using SuperTypeToken
jimbethancourt Aug 3, 2025
12d635e
#152 Fixed some unit tests
jimbethancourt Aug 3, 2025
fb58c52
#152 Fixed more unit tests
jimbethancourt Aug 4, 2025
a5a135f
#152 All MinimumFeedbackArcSetSolverTest unit tests now passing
jimbethancourt Aug 17, 2025
0c68018
#152 Adding edges serially instead of in parallel
jimbethancourt Aug 17, 2025
18f66b7
#152 No longer adding vertices in parallel
jimbethancourt Aug 17, 2025
56a3c49
#152 Returning boolean instead of asserting within graph cycle checke…
jimbethancourt Aug 22, 2025
a631248
#152 Initial commit of eta (η) and k computation
jimbethancourt Aug 22, 2025
33a536d
#152 Initial commit of ModulatorComputer
jimbethancourt Aug 23, 2025
7635f56
#152 Adding verticex serially
jimbethancourt Aug 23, 2025
57c0b77
#152 Corrected testMultipleCycles()
jimbethancourt Aug 23, 2025
642bba4
#152 Only returning modulator calculation result(s) if present
jimbethancourt Aug 24, 2025
897f6fe
Applied Spotless
jimbethancourt Aug 24, 2025
5d6bea3
#152 Adding eta computation shortcuts
jimbethancourt Aug 24, 2025
b694988
#152 Extending timeout duration
jimbethancourt Aug 24, 2025
e7afa03
#152 Fixed incorrect unit test
jimbethancourt Aug 24, 2025
fbcc1bc
#152 Reverting testRandomGraphPerformance() back to original test
jimbethancourt Aug 24, 2025
5a57c2e
#152 Initial commit of OptimalKComputer
jimbethancourt Aug 25, 2025
36c7065
#152 Adding no-arg solve() method to DirectedFeedbackVertexSetSolver
jimbethancourt Aug 25, 2025
f24268e
#152 Corrected testTreewidthModulator()
jimbethancourt Aug 25, 2025
32130d8
#152 Updated TODO comment
jimbethancourt Aug 25, 2025
b1fb55b
#152 Updated timeout duration
jimbethancourt Aug 25, 2025
c7a700d
#152 Moved OptimalKComputer classes to their own package
jimbethancourt Aug 25, 2025
b9862f1
#152 Added comment indicating ModulatorComputer was generated
jimbethancourt Aug 25, 2025
08c3fe2
#152 Added benchmark sizes for sparse graphs
jimbethancourt Aug 26, 2025
7de4fd3
#152 Added complete addBypassEdges() implementation
jimbethancourt Aug 26, 2025
a0c5178
#152 Applied Spotless
jimbethancourt Aug 26, 2025
7f8dab1
#152 Updated comment
jimbethancourt Aug 26, 2025
0c14192
#152 Computing vertexes and edges to remove in codebase
jimbethancourt Aug 26, 2025
1cff3ed
#152 Disabled flaky performance tests
jimbethancourt Aug 28, 2025
50c91f4
#152 Highlighting nodes & edges for removal
jimbethancourt Aug 28, 2025
7205d30
#152 Replaced implementation of TreewidthComputer.fillInHeuristicTree…
jimbethancourt Sep 3, 2025
4721b86
#152 Incorporating DFAS and DFVS sets in output
jimbethancourt Sep 3, 2025
00f8d00
#152 Applied Spotless
jimbethancourt Sep 3, 2025
e9fa217
#152 Replaced computeBetweennessCentrality implementation because "sa…
jimbethancourt Sep 6, 2025
37eb668
Adding back original computeBetweenessCentrality() method
jimbethancourt Sep 7, 2025
45cd71c
#152 Intital commit of ModulatorComputer.computeBetweennessCentrality…
jimbethancourt Sep 9, 2025
fb9e017
#152 Removed use of parallelStream() in ModulatorComputer.computeBetw…
jimbethancourt Sep 9, 2025
e1c30a0
#152 Now calling computeBetweennessCentralityParallel()
jimbethancourt Sep 9, 2025
af8bca0
#152 Increased timeout on unit test
jimbethancourt Sep 9, 2025
0f55202
#152 Initial commit of PageRankFAS
jimbethancourt Sep 24, 2025
d85e876
#152 LineDigraph no longer extends DefaultDirectedGraph
jimbethancourt Sep 24, 2025
60674b2
#152 Replaced computePageRank implementation with implementation that…
jimbethancourt Sep 24, 2025
a71d916
#152 Using SuperTypeToken to create subgraphs
jimbethancourt Sep 24, 2025
5b7a443
#152 Updated implementation of computeVertexRemovalScore
jimbethancourt Sep 24, 2025
0c76d95
#152 Reverted back to updated tests
jimbethancourt Sep 24, 2025
98b7d61
#152 Removed testUpdatedThreadSafety()
jimbethancourt Sep 24, 2025
3dd4d79
#152 Skip processing if currentLineVertex is null
jimbethancourt Sep 24, 2025
8c1c2a0
#152 Applied Spotless
jimbethancourt Sep 24, 2025
6b55769
#152 Added citation
jimbethancourt Sep 24, 2025
259721e
#152 Now using PageRank FAS algo
jimbethancourt Sep 24, 2025
e81073e
#152 Filtering out null targetLineVertexes
jimbethancourt Sep 25, 2025
1cd0315
#152 Removing parallel population of subgraph
jimbethancourt Sep 26, 2025
9204abe
#152 Calculating and displaying Directed Feedback Analysis results
jimbethancourt Sep 26, 2025
ed220f5
#163 Corrected the relationship direction for generics
jimbethancourt Oct 26, 2025
0b85af1
#152 Handling ConcurrentModificationException
jimbethancourt Nov 2, 2025
418ccf6
#152 First pass of providing prioritized edges to remove
jimbethancourt Nov 20, 2025
cf40984
Applied Spotless
jimbethancourt Nov 20, 2025
3ca2c43
#152 Fixed unit tests
jimbethancourt Nov 21, 2025
53e8141
Updated JaCoCo plugin version
jimbethancourt Nov 21, 2025
11b0e62
#152 Updated edge sorting
jimbethancourt Nov 23, 2025
1b6963b
#152 Reordered edge removal columns
jimbethancourt Nov 24, 2025
6ec8fb1
#152 Fixed typo
jimbethancourt Nov 24, 2025
ae44158
#152 Applied Spotless
jimbethancourt Nov 26, 2025
943a155
#152 Computing FAS in non-parallel mode
jimbethancourt Nov 30, 2025
6656391
#152 Using HashMap instead of ConcurrentHashMap.newKeySet();
jimbethancourt Nov 30, 2025
82d9203
#162 Memoizing result of ModulatorComputer.computeBetweennessCentrali…
jimbethancourt Nov 30, 2025
446d7df
#162 Memoizing result of FeedbackVertexSetComputer.greedyFeedbackVert…
jimbethancourt Nov 30, 2025
ff3a886
Added comment
jimbethancourt Dec 10, 2025
dee5397
#152 Returning a CodebaseGraphDTO
jimbethancourt Dec 10, 2025
0c4ba56
#152 Adding className field to ScmLogInfo
jimbethancourt Dec 10, 2025
241f0b4
#152 Improved captureChangeCountByCommitTimestamp() to be more robust
jimbethancourt Dec 10, 2025
1533279
#156 Now caching the sum of all committed files in the repository at …
jimbethancourt Dec 10, 2025
be68a0a
#152 Adding className field to ScmLogInfo
jimbethancourt Dec 10, 2025
f22a2c3
#152 Added getClassName() and getPackageName() methods
jimbethancourt Dec 10, 2025
b59452e
#152 Returning a CodebaseGraphDTO
jimbethancourt Dec 10, 2025
2890409
#152 Calculating priority of edges / relationships for removal
jimbethancourt Dec 10, 2025
3ea2e87
#152 Adding HashMap as placeholder constructor value
jimbethancourt Dec 10, 2025
d69ba85
#152 Capturing edge target information
jimbethancourt Dec 10, 2025
9e0cc63
Increased timeout to 30 seconds for testLargeRandomGraphs()
jimbethancourt Dec 12, 2025
d3bce6b
Applied Spotless
jimbethancourt Dec 12, 2025
e73fb94
#152 Corrected improved getRankedChangeProneness algorithm
jimbethancourt Dec 12, 2025
5af6c04
#152 Updated sorting test to match updated sorting algorithm
jimbethancourt Dec 12, 2025
b41981c
#152 Handling null scmLogInfos better
jimbethancourt Dec 12, 2025
668d68a
#152 Minor UI Enhancements in SimpleHtmlReport
jimbethancourt Dec 18, 2025
fb72d4e
#152 Renamed DSM module to graph-algorithms
jimbethancourt Dec 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,40 @@
@Slf4j
public class ChangePronenessRanker {

private final TreeMap<Integer, Integer> changeCountsByTimeStamps = new TreeMap<>();
private final Map<Integer, Integer> changeCountsByTimeStamps = new HashMap<>();
private final Map<String, ScmLogInfo> cachedScmLogInfos = new HashMap<>();

public ChangePronenessRanker(GitLogReader repositoryLogReader) {
try {
log.info("Capturing change count based on commit timestamps");
changeCountsByTimeStamps.putAll(repositoryLogReader.captureChangeCountByCommitTimestamp());
changeCountsByTimeStamps.putAll(
computeChangeCountsByTimeStamps(repositoryLogReader.captureChangeCountByCommitTimestamp()));
} catch (IOException | GitAPIException e) {
log.error("Error reading from repository: {}", e.getMessage());
}
}

private Map<Integer, Integer> computeChangeCountsByTimeStamps(TreeMap<Integer, Integer> commitsWithChangeCounts) {
HashMap<Integer, Integer> changeCountsByTimeStamps = new HashMap<>();
int runningTotal = 0;
for (Map.Entry<Integer, Integer> commitChangeCountEntry :
commitsWithChangeCounts.descendingMap().entrySet()) {
runningTotal += commitChangeCountEntry.getValue();
changeCountsByTimeStamps.put(commitChangeCountEntry.getKey(), runningTotal);
}

return changeCountsByTimeStamps;
}

public void rankChangeProneness(List<ScmLogInfo> scmLogInfos) {
for (ScmLogInfo scmLogInfo : scmLogInfos) {
if (!cachedScmLogInfos.containsKey(scmLogInfo.getPath())) {
int commitsInRepositorySinceCreation =
changeCountsByTimeStamps.tailMap(scmLogInfo.getEarliestCommit()).values().stream()
.mapToInt(i -> i)
.sum();
if (scmLogInfo.getEarliestCommit() == 0) {
log.warn("No commits found for {}", scmLogInfo.getPath());
continue;
}

int commitsInRepositorySinceCreation = changeCountsByTimeStamps.get(scmLogInfo.getEarliestCommit());

scmLogInfo.setChangeProneness((float) scmLogInfo.getCommitCount() / commitsInRepositorySinceCreation);
cachedScmLogInfos.put(scmLogInfo.getPath(), scmLogInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public ScmLogInfo fileLog(String path) throws GitAPIException, IOException {
.next()
.getCommitTime();

return new ScmLogInfo(path, earliestCommit, mostRecentCommit, commitCount);
return new ScmLogInfo(path, null, earliestCommit, mostRecentCommit, commitCount);
}

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

int count = 0;
if (null == newCommit) {
if (null == newCommit && iterator.hasNext()) {
newCommit = oldCommit;
continue;
} else if (!iterator.hasNext()) {
// Handle first / initial commit
changesByCommitTimestamp.putAll(walkFirstCommit(oldCommit));
}

for (DiffEntry entry : getDiffEntries(newCommit, oldCommit)) {
if (entry.getNewPath().endsWith(JAVA_FILE_TYPE)
|| entry.getOldPath().endsWith(JAVA_FILE_TYPE)) {
count++;
if (null != newCommit) {
for (DiffEntry entry : getDiffEntries(newCommit, oldCommit)) {
if (entry.getNewPath().endsWith(JAVA_FILE_TYPE)
|| entry.getOldPath().endsWith(JAVA_FILE_TYPE)) {
count++;
}
}
}

if (count > 0) {
changesByCommitTimestamp.put(newCommit.getCommitTime(), count);
}

// Handle first / initial commit
if (!iterator.hasNext()) {
changesByCommitTimestamp.putAll(walkFirstCommit(oldCommit));
if (count > 0) {
changesByCommitTimestamp.put(newCommit.getCommitTime(), count);
}
newCommit = oldCommit;
}

newCommit = oldCommit;
}

return changesByCommitTimestamp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
public class ScmLogInfo {

private String path;
private String className;
private int earliestCommit;
private int mostRecentCommit;
private int commitCount;
private float changeProneness;
private int changePronenessRank;

public ScmLogInfo(String path, int earliestCommit, int mostRecentCommit, int commitCount) {
public ScmLogInfo(String path, String className, int earliestCommit, int mostRecentCommit, int commitCount) {
this.path = path;
this.className = className;
this.earliestCommit = earliestCommit;
this.mostRecentCommit = mostRecentCommit;
this.commitCount = commitCount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void setUp() {
// TODO: this should probably be a cucumber test
@Test
void testChangePronenessCalculation() throws IOException, GitAPIException {
ScmLogInfo scmLogInfo = new ScmLogInfo("path", 1595275997, 0, 1);
ScmLogInfo scmLogInfo = new ScmLogInfo("path", null, 1595275997, 0, 1);

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

@Test
void testRankChangeProneness() throws IOException, GitAPIException {
ScmLogInfo scmLogInfo = new ScmLogInfo("file1", 1595275997, 0, 1);
// more recent commit
ScmLogInfo newerCommit = new ScmLogInfo("file1", null, 1595275997, 0, 1);

TreeMap<Integer, Integer> commitsWithChangeCounts = new TreeMap<>();
commitsWithChangeCounts.put(scmLogInfo.getEarliestCommit(), scmLogInfo.getCommitCount());
commitsWithChangeCounts.put(scmLogInfo.getEarliestCommit() + 5 * 60, 3);
commitsWithChangeCounts.put(scmLogInfo.getEarliestCommit() + 10 * 60, 3);
commitsWithChangeCounts.put(newerCommit.getEarliestCommit(), newerCommit.getCommitCount());
commitsWithChangeCounts.put(newerCommit.getEarliestCommit() + 5 * 60, 3);
commitsWithChangeCounts.put(newerCommit.getEarliestCommit() + 10 * 60, 3);

ScmLogInfo scmLogInfo2 = new ScmLogInfo("file2", 1595175997, 0, 1);
// older commit
ScmLogInfo olderCommit = new ScmLogInfo("file2", null, 1595175997, 0, 1);

commitsWithChangeCounts.put(scmLogInfo2.getEarliestCommit(), scmLogInfo2.getCommitCount());
commitsWithChangeCounts.put(scmLogInfo2.getEarliestCommit() + 5 * 60, 5);
commitsWithChangeCounts.put(scmLogInfo2.getEarliestCommit() + 10 * 60, 5);
commitsWithChangeCounts.put(olderCommit.getEarliestCommit(), olderCommit.getCommitCount());
commitsWithChangeCounts.put(olderCommit.getEarliestCommit() + 5 * 60, 5);
commitsWithChangeCounts.put(olderCommit.getEarliestCommit() + 10 * 60, 5);

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

List<ScmLogInfo> scmLogInfos = new ArrayList<>();
scmLogInfos.add(scmLogInfo);
scmLogInfos.add(scmLogInfo2);
scmLogInfos.add(newerCommit);
scmLogInfos.add(olderCommit);
changePronenessRanker.rankChangeProneness(scmLogInfos);

// ranks higher since fewer commits since initial commit
Assertions.assertEquals(2, scmLogInfo.getChangePronenessRank());
Assertions.assertEquals(2, newerCommit.getChangePronenessRank());
// ranks lower since there have been more commits since initial commit
Assertions.assertEquals(1, scmLogInfo2.getChangePronenessRank());
Assertions.assertEquals(1, olderCommit.getChangePronenessRank());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,22 @@
public class JavaGraphBuilder {

/**
* Given a java source directory return a graph of class references
* Given a java source directory, return a CodebaseGraphDTO
*
* @param srcDirectory
* @return
* @return CodebaseGraphDTO
* @throws IOException
*/
public Graph<String, DefaultWeightedEdge> getClassReferences(
String srcDirectory, boolean excludeTests, String testSourceDirectory) throws IOException {
Graph<String, DefaultWeightedEdge> classReferencesGraph;
public CodebaseGraphDTO getCodebaseGraphDTO(String srcDirectory, boolean excludeTests, String testSourceDirectory)
throws IOException {
CodebaseGraphDTO codebaseGraphDTO;
if (srcDirectory == null || srcDirectory.isEmpty()) {
throw new IllegalArgumentException();
} else {
classReferencesGraph = processWithOpenRewrite(srcDirectory, excludeTests, testSourceDirectory)
.getClassReferencesGraph();
codebaseGraphDTO = processWithOpenRewrite(srcDirectory, excludeTests, testSourceDirectory);
}

return classReferencesGraph;
return codebaseGraphDTO;
}

private CodebaseGraphDTO processWithOpenRewrite(String srcDir, boolean excludeTests, String testSourceDirectory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ private void processType(String ownerFqn, JavaType.Array arrayType) {
}

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

for (JavaType bound : typeVariable.getBounds()) {
if (bound instanceof JavaType.Class) {
addType(((JavaType.Class) bound).getFullyQualifiedName(), ownerFqn);
addType(ownerFqn, ((JavaType.Class) bound).getFullyQualifiedName());
} else if (bound instanceof JavaType.Parameterized) {
addType(((JavaType.Parameterized) bound).getFullyQualifiedName(), ownerFqn);
addType(ownerFqn, ((JavaType.Parameterized) bound).getFullyQualifiedName());
} else {
LogHolder.log.debug("Unknown type bound: {}", bound);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ class JavaGraphBuilderTest {
@Test
void parseSourceDirectoryEmptyTest() {
Assertions.assertThrows(
IllegalArgumentException.class, () -> javaGraphBuilder.getClassReferences("", false, ""));
IllegalArgumentException.class, () -> javaGraphBuilder.getCodebaseGraphDTO("", false, ""));
Assertions.assertThrows(
IllegalArgumentException.class, () -> javaGraphBuilder.getClassReferences(null, false, ""));
IllegalArgumentException.class, () -> javaGraphBuilder.getCodebaseGraphDTO(null, false, ""));
}

@DisplayName("Given a valid source directory input parameter return a valid graph.")
@Test
void parseSourceDirectoryTest() throws IOException {
File srcDirectory = new File("src/test/resources/javaSrcDirectory");
Graph<String, DefaultWeightedEdge> classReferencesGraph =
javaGraphBuilder.getClassReferences(srcDirectory.getAbsolutePath(), false, "");
CodebaseGraphDTO dto = javaGraphBuilder.getCodebaseGraphDTO(srcDirectory.getAbsolutePath(), false, "");
Graph<String, DefaultWeightedEdge> classReferencesGraph = dto.getClassReferencesGraph();
assertNotNull(classReferencesGraph);
assertEquals(5, classReferencesGraph.vertexSet().size());
assertEquals(7, classReferencesGraph.edgeSet().size());
Expand Down Expand Up @@ -77,9 +77,8 @@ private static double getEdgeWeight(
@Test
void removeClassesNotInCodebase() throws IOException {
File srcDirectory = new File("src/test/resources/javaSrcDirectory");
Graph<String, DefaultWeightedEdge> classReferencesGraph =
javaGraphBuilder.getClassReferences(srcDirectory.getAbsolutePath(), false, "");

CodebaseGraphDTO dto = javaGraphBuilder.getCodebaseGraphDTO(srcDirectory.getAbsolutePath(), false, "");
Graph<String, DefaultWeightedEdge> classReferencesGraph = dto.getClassReferencesGraph();
classReferencesGraph.addVertex("org.favioriteoss.FunClass");
classReferencesGraph.addVertex("org.favioriteoss.AnotherFunClass");

Expand Down
2 changes: 1 addition & 1 deletion cost-benefit-calculator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

<dependency>
<groupId>org.hjug.refactorfirst.dsm</groupId>
<artifactId>dsm</artifactId>
<artifactId>graph-algorithms</artifactId>
</dependency>

<dependency>
Expand Down
Loading
Loading