Skip to content

Commit f9b1034

Browse files
Generating Cycle summary data
Generating cycle summary data
1 parent 97a9b23 commit f9b1034

File tree

12 files changed

+316
-26
lines changed

12 files changed

+316
-26
lines changed

circular-reference-detector/pom.xml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@
1313
<artifactId>circular-reference-detector</artifactId>
1414

1515
<description>Tool to help detecting circular references by parsing a java project.</description>
16-
<properties>
17-
<maven.compiler.target>11</maven.compiler.target>
18-
<maven.compiler.source>11</maven.compiler.source>
19-
</properties>
16+
2017
<dependencies>
2118
<dependency>
2219
<groupId>org.jgrapht</groupId>

circular-reference-detector/src/main/java/com/ideacrest/app/CircularReferenceDetectorApp.java renamed to circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
package com.ideacrest.app;
1+
package org.hjug.app;
22

3-
import com.ideacrest.cycledetector.CircularReferenceChecker;
4-
import com.ideacrest.parser.JavaProjectParser;
53
import java.io.IOException;
64
import java.util.HashMap;
75
import java.util.Map;
86
import java.util.Set;
7+
import org.hjug.cycledetector.CircularReferenceChecker;
8+
import org.hjug.parser.JavaProjectParser;
99
import org.jgrapht.Graph;
1010
import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree;
1111
import org.jgrapht.graph.AsSubgraph;
@@ -17,7 +17,6 @@
1717
* Takes two arguments : source folder of java project, directory to store images of the circular reference graphs.
1818
*
1919
* @author nikhil_pereira
20-
*
2120
*/
2221
public class CircularReferenceDetectorApp {
2322

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.ideacrest.cycledetector;
1+
package org.hjug.cycledetector;
22

33
import com.mxgraph.layout.mxCircleLayout;
44
import com.mxgraph.layout.mxIGraphLayout;

circular-reference-detector/src/main/java/com/ideacrest/parser/JavaProjectParser.java renamed to circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.ideacrest.parser;
1+
package org.hjug.parser;
22

33
import com.github.javaparser.StaticJavaParser;
44
import com.github.javaparser.ast.CompilationUnit;
@@ -11,9 +11,7 @@
1111
import java.nio.file.Files;
1212
import java.nio.file.Path;
1313
import java.nio.file.Paths;
14-
import java.util.HashSet;
15-
import java.util.List;
16-
import java.util.Set;
14+
import java.util.*;
1715
import java.util.stream.Collectors;
1816
import java.util.stream.Stream;
1917
import org.jgrapht.Graph;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.ideacrest.cycledetector;
1+
package org.hjug.cycledetector;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.junit.jupiter.api.Assertions.assertTrue;

circular-reference-detector/src/test/java/com/ideacrest/parser/JavaProjectParserTests.java renamed to circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.ideacrest.parser;
1+
package org.hjug.parser;
22

33
import static org.junit.jupiter.api.Assertions.*;
44

cost-benefit-calculator/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
<artifactId>effort-ranker</artifactId>
3030
</dependency>
3131

32+
<dependency>
33+
<groupId>org.hjug.refactorfirst.circularreferencedetector</groupId>
34+
<artifactId>circular-reference-detector</artifactId>
35+
</dependency>
36+
3237
<dependency>
3338
<groupId>org.hjug.refactorfirst.testresources</groupId>
3439
<artifactId>test-resources</artifactId>

cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,30 @@
1616
import net.sourceforge.pmd.lang.LanguageRegistry;
1717
import org.eclipse.jgit.api.errors.GitAPIException;
1818
import org.eclipse.jgit.lib.Repository;
19+
import org.hjug.cycledetector.CircularReferenceChecker;
1920
import org.hjug.git.ChangePronenessRanker;
2021
import org.hjug.git.GitLogReader;
2122
import org.hjug.git.ScmLogInfo;
2223
import org.hjug.metrics.*;
2324
import org.hjug.metrics.rules.CBORule;
25+
import org.hjug.parser.JavaProjectParser;
26+
import org.jgrapht.Graph;
27+
import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree;
28+
import org.jgrapht.graph.AsSubgraph;
29+
import org.jgrapht.graph.AsUndirectedGraph;
30+
import org.jgrapht.graph.DefaultEdge;
2431

2532
@Slf4j
2633
public class CostBenefitCalculator {
2734

35+
private final Map<String, AsSubgraph> renderedSubGraphs = new HashMap<>();
36+
2837
private Report report;
2938
private String repositoryPath;
3039
private final GitLogReader gitLogReader = new GitLogReader();
3140
private Repository repository = null;
3241
private final ChangePronenessRanker changePronenessRanker;
42+
private final JavaProjectParser javaProjectParser = new JavaProjectParser();
3343

3444
public CostBenefitCalculator(String repositoryPath) {
3545
this.repositoryPath = repositoryPath;
@@ -48,6 +58,100 @@ public CostBenefitCalculator(String repositoryPath) {
4858
changePronenessRanker = new ChangePronenessRanker(repository, gitLogReader);
4959
}
5060

61+
public List<RankedCycle> runCycleAnalysis() {
62+
List<RankedCycle> rankedCycles = new ArrayList<>();
63+
try {
64+
Map<String, String> classNamesAndPaths = getClassNamesAndPaths();
65+
Graph<String, DefaultEdge> classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath);
66+
CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker();
67+
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap =
68+
circularReferenceChecker.detectCycles(classReferencesGraph);
69+
cyclesForEveryVertexMap.forEach((vertex, subGraph) -> {
70+
int vertexCount = subGraph.vertexSet().size();
71+
int edgeCount = subGraph.edgeSet().size();
72+
double minCut = 0;
73+
Set<DefaultEdge> minCutEdges = null;
74+
if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) {
75+
// circularReferenceChecker.createImage(outputDirectoryPath, subGraph, vertex);
76+
renderedSubGraphs.put(vertex, subGraph);
77+
log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
78+
GusfieldGomoryHuCutTree<String, DefaultEdge> gusfieldGomoryHuCutTree =
79+
new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph));
80+
minCut = gusfieldGomoryHuCutTree.calculateMinCut();
81+
log.info("Min cut weight: " + minCut);
82+
minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();
83+
84+
log.info("Minimum Cut Edges:");
85+
for (DefaultEdge minCutEdge : minCutEdges) {
86+
log.info(minCutEdge.toString());
87+
}
88+
}
89+
90+
List<CycleNode> cycleNodes = subGraph.vertexSet().stream()
91+
.map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle)))
92+
.collect(Collectors.toList());
93+
List<ScmLogInfo> changeRanks = getRankedChangeProneness(cycleNodes);
94+
95+
Map<String, CycleNode> cycleNodeMap = new HashMap<>();
96+
97+
for (CycleNode cycleNode : cycleNodes) {
98+
cycleNodeMap.put(cycleNode.getFileName(), cycleNode);
99+
}
100+
101+
for (ScmLogInfo changeRank : changeRanks) {
102+
CycleNode cn = cycleNodeMap.get(changeRank.getPath());
103+
cn.setScmLogInfo(changeRank);
104+
}
105+
106+
// sum change proneness ranks
107+
int changePronenessRankSum = changeRanks.stream()
108+
.mapToInt(ScmLogInfo::getChangePronenessRank)
109+
.sum();
110+
rankedCycles.add(new RankedCycle(
111+
vertex,
112+
changePronenessRankSum,
113+
subGraph.vertexSet(),
114+
subGraph.edgeSet(),
115+
minCut,
116+
minCutEdges,
117+
cycleNodes));
118+
});
119+
120+
rankedCycles.sort(Comparator.comparing(RankedCycle::getAverageChangeProneness));
121+
int cpr = 1;
122+
for (RankedCycle rankedCycle : rankedCycles) {
123+
rankedCycle.setChangePronenessRank(cpr++);
124+
}
125+
126+
rankedCycles.sort(Comparator.comparing(RankedCycle::getRawPriority).reversed());
127+
128+
int priority = 1;
129+
for (RankedCycle rankedCycle : rankedCycles) {
130+
rankedCycle.setPriority(priority++);
131+
}
132+
133+
} catch (IOException e) {
134+
throw new RuntimeException(e);
135+
}
136+
137+
return rankedCycles;
138+
}
139+
140+
private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultEdge> subGraph, String vertex) {
141+
if (!renderedSubGraphs.isEmpty()) {
142+
for (AsSubgraph renderedSubGraph : renderedSubGraphs.values()) {
143+
if (renderedSubGraph.vertexSet().size() == subGraph.vertexSet().size()
144+
&& renderedSubGraph.edgeSet().size()
145+
== subGraph.edgeSet().size()
146+
&& renderedSubGraph.vertexSet().contains(vertex)) {
147+
return true;
148+
}
149+
}
150+
}
151+
152+
return false;
153+
}
154+
51155
// copied from PMD's PmdTaskImpl.java and modified
52156
public void runPmdAnalysis() throws IOException {
53157
PMDConfiguration configuration = new PMDConfiguration();
@@ -186,4 +290,31 @@ private List<CBOClass> getCBOClasses() {
186290
private String getFileName(RuleViolation violation) {
187291
return violation.getFileId().getUriString().replace("file:///" + repositoryPath.replace("\\", "/") + "/", "");
188292
}
293+
294+
public Map<String, String> getClassNamesAndPaths() throws IOException {
295+
296+
Map<String, String> fileNamePaths = new HashMap<>();
297+
298+
Files.walk(Paths.get(repositoryPath)).forEach(path -> {
299+
String filename = path.getFileName().toString();
300+
if (filename.endsWith(".java")) {
301+
fileNamePaths.put(
302+
getClassName(filename),
303+
path.toUri().toString().replace("file:///" + repositoryPath.replace("\\", "/") + "/", ""));
304+
}
305+
});
306+
307+
return fileNamePaths;
308+
}
309+
310+
/**
311+
* Extract class name from java file name
312+
* Example : MyJavaClass.java becomes MyJavaClass
313+
*
314+
* @param javaFileName
315+
* @return
316+
*/
317+
private String getClassName(String javaFileName) {
318+
return javaFileName.substring(0, javaFileName.indexOf('.'));
319+
}
189320
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.hjug.cbc;
2+
3+
import java.time.Instant;
4+
import lombok.Data;
5+
import org.hjug.git.ScmLogInfo;
6+
import org.hjug.metrics.Disharmony;
7+
8+
@Data
9+
public class CycleNode implements Disharmony {
10+
11+
private final String className;
12+
private final String fileName;
13+
private Integer changePronenessRank;
14+
15+
private Instant firstCommitTime;
16+
private Instant mostRecentCommitTime;
17+
private Integer commitCount;
18+
19+
public void setScmLogInfo(ScmLogInfo scmLogInfo) {
20+
firstCommitTime = Instant.ofEpochSecond(scmLogInfo.getEarliestCommit());
21+
mostRecentCommitTime = Instant.ofEpochSecond(scmLogInfo.getMostRecentCommit());
22+
commitCount = scmLogInfo.getCommitCount();
23+
}
24+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.hjug.cbc;
2+
3+
import java.util.HashSet;
4+
import java.util.List;
5+
import java.util.Set;
6+
import lombok.Data;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.jgrapht.graph.DefaultEdge;
9+
10+
@Data
11+
@Slf4j
12+
public class RankedCycle {
13+
14+
private final String cycleName;
15+
private final Integer changePronenessRankSum;
16+
17+
private final Set<String> vertexSet;
18+
private final Set<DefaultEdge> edgeSet;
19+
private final double minCutCount;
20+
private final Set<DefaultEdge> minCutEdges;
21+
private final List<CycleNode> cycleNodes;
22+
23+
private float rawPriority;
24+
private Integer priority = 0;
25+
private float averageChangeProneness;
26+
private Integer changePronenessRank;
27+
private float impact;
28+
29+
public RankedCycle(
30+
String cycleName,
31+
Integer changePronenessRankSum,
32+
Set<String> vertexSet,
33+
Set<DefaultEdge> edgeSet,
34+
double minCutCount,
35+
Set<DefaultEdge> minCutEdges,
36+
List<CycleNode> cycleNodes) {
37+
this.cycleNodes = cycleNodes;
38+
this.cycleName = cycleName;
39+
this.changePronenessRankSum = changePronenessRankSum;
40+
this.vertexSet = vertexSet;
41+
this.edgeSet = edgeSet;
42+
this.minCutCount = minCutCount;
43+
44+
if (null == minCutEdges) {
45+
this.minCutEdges = new HashSet<>();
46+
} else {
47+
this.minCutEdges = minCutEdges;
48+
}
49+
50+
if (minCutCount == 0.0) {
51+
this.impact = (float) (vertexSet.size());
52+
} else {
53+
this.impact = (float) (vertexSet.size() / minCutCount);
54+
}
55+
56+
this.averageChangeProneness = (float) changePronenessRankSum / vertexSet.size();
57+
this.rawPriority = this.impact + averageChangeProneness;
58+
}
59+
}

0 commit comments

Comments
 (0)