Skip to content

Commit 5ff6bc2

Browse files
Merge pull request #89 from refactorfirst/add-cycle-tables
Generating Cycle data
2 parents ca4db07 + c82af91 commit 5ff6bc2

File tree

6 files changed

+183
-50
lines changed

6 files changed

+183
-50
lines changed

circular-reference-detector/pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@
3333
<dependency>
3434
<groupId>org.junit.jupiter</groupId>
3535
<artifactId>junit-jupiter-api</artifactId>
36-
<version>5.5.2</version>
36+
<version>5.9.0</version>
3737
<scope>test</scope>
3838
</dependency>
3939
<dependency>
4040
<groupId>org.junit.jupiter</groupId>
4141
<artifactId>junit-jupiter-engine</artifactId>
42-
<version>5.5.2</version>
42+
<version>5.9.0</version>
4343
<scope>test</scope>
4444
</dependency>
4545
</dependencies>

circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ public class CircularReferenceDetectorApp {
2222

2323
private Map<String, AsSubgraph> renderedSubGraphs = new HashMap<>();
2424

25-
public static void main(String[] args) {
26-
CircularReferenceDetectorApp circularReferenceDetectorApp = new CircularReferenceDetectorApp();
27-
circularReferenceDetectorApp.launchApp(args);
28-
}
25+
// public static void main(String[] args) {
26+
// CircularReferenceDetectorApp circularReferenceDetectorApp = new CircularReferenceDetectorApp();
27+
// circularReferenceDetectorApp.launchApp(args);
28+
// }
2929

3030
/**
3131
* Parses source project files and creates a graph of class references of the java project.

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

+45-35
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public CostBenefitCalculator(String repositoryPath) {
5858
changePronenessRanker = new ChangePronenessRanker(repository, gitLogReader);
5959
}
6060

61-
public List<RankedCycle> runCycleAnalysis() {
61+
public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean renderImages) {
6262
List<RankedCycle> rankedCycles = new ArrayList<>();
6363
try {
6464
Map<String, String> classNamesAndPaths = getClassNamesAndPaths();
@@ -72,7 +72,15 @@ public List<RankedCycle> runCycleAnalysis() {
7272
double minCut = 0;
7373
Set<DefaultEdge> minCutEdges = null;
7474
if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) {
75-
// circularReferenceChecker.createImage(outputDirectoryPath, subGraph, vertex);
75+
if (renderImages) {
76+
try {
77+
circularReferenceChecker.createImage(
78+
outputDirectoryPath + "/refactorFirst/cycles", subGraph, vertex);
79+
} catch (IOException e) {
80+
throw new RuntimeException(e);
81+
}
82+
}
83+
7684
renderedSubGraphs.put(vertex, subGraph);
7785
log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
7886
GusfieldGomoryHuCutTree<String, DefaultEdge> gusfieldGomoryHuCutTree =
@@ -85,36 +93,36 @@ public List<RankedCycle> runCycleAnalysis() {
8593
for (DefaultEdge minCutEdge : minCutEdges) {
8694
log.info(minCutEdge.toString());
8795
}
88-
}
8996

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);
97+
List<CycleNode> cycleNodes = subGraph.vertexSet().stream()
98+
.map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle)))
99+
.collect(Collectors.toList());
100+
List<ScmLogInfo> changeRanks = getRankedChangeProneness(cycleNodes);
94101

95-
Map<String, CycleNode> cycleNodeMap = new HashMap<>();
102+
Map<String, CycleNode> cycleNodeMap = new HashMap<>();
96103

97-
for (CycleNode cycleNode : cycleNodes) {
98-
cycleNodeMap.put(cycleNode.getFileName(), cycleNode);
99-
}
104+
for (CycleNode cycleNode : cycleNodes) {
105+
cycleNodeMap.put(cycleNode.getFileName(), cycleNode);
106+
}
100107

101-
for (ScmLogInfo changeRank : changeRanks) {
102-
CycleNode cn = cycleNodeMap.get(changeRank.getPath());
103-
cn.setScmLogInfo(changeRank);
104-
}
108+
for (ScmLogInfo changeRank : changeRanks) {
109+
CycleNode cn = cycleNodeMap.get(changeRank.getPath());
110+
cn.setScmLogInfo(changeRank);
111+
}
105112

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));
113+
// sum change proneness ranks
114+
int changePronenessRankSum = changeRanks.stream()
115+
.mapToInt(ScmLogInfo::getChangePronenessRank)
116+
.sum();
117+
rankedCycles.add(new RankedCycle(
118+
vertex,
119+
changePronenessRankSum,
120+
subGraph.vertexSet(),
121+
subGraph.edgeSet(),
122+
minCut,
123+
minCutEdges,
124+
cycleNodes));
125+
}
118126
});
119127

120128
rankedCycles.sort(Comparator.comparing(RankedCycle::getAverageChangeProneness));
@@ -295,14 +303,16 @@ public Map<String, String> getClassNamesAndPaths() throws IOException {
295303

296304
Map<String, String> fileNamePaths = new HashMap<>();
297305

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+
try (Stream<Path> walk = Files.walk(Paths.get(repositoryPath))) {
307+
walk.forEach(path -> {
308+
String filename = path.getFileName().toString();
309+
if (filename.endsWith(".java")) {
310+
fileNamePaths.put(
311+
getClassName(filename),
312+
path.toUri().toString().replace("file:///" + repositoryPath.replace("\\", "/") + "/", ""));
313+
}
314+
});
315+
}
306316

307317
return fileNamePaths;
308318
}

report/pom.xml

+11
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@
1919
<groupId>com.fasterxml.jackson.core</groupId>
2020
<artifactId>jackson-databind</artifactId>
2121
</dependency>
22+
23+
<dependency>
24+
<groupId>org.jgrapht</groupId>
25+
<artifactId>jgrapht-core</artifactId>
26+
<version>1.3.0</version>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.jgrapht</groupId>
30+
<artifactId>jgrapht-ext</artifactId>
31+
<version>1.3.0</version>
32+
</dependency>
2233
</dependencies>
2334

2435
</project>

report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java

+18
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import java.util.List;
88
import java.util.Locale;
99
import lombok.extern.slf4j.Slf4j;
10+
import org.hjug.cbc.CostBenefitCalculator;
11+
import org.hjug.cbc.RankedCycle;
1012
import org.hjug.cbc.RankedDisharmony;
1113
import org.hjug.gdg.GraphDataGenerator;
1214

@@ -77,6 +79,7 @@ void renderGithubButtons(StringBuilder stringBuilder) {
7779
}
7880

7981
// TODO: Move to another class to allow use by Gradle plugin
82+
@Override
8083
void writeGodClassGchartJs(
8184
List<RankedDisharmony> rankedDisharmonies, int maxPriority, String reportOutputDirectory) {
8285
GraphDataGenerator graphDataGenerator = new GraphDataGenerator();
@@ -169,4 +172,19 @@ void renderCBOChart(
169172
renderGithubButtons(stringBuilder);
170173
stringBuilder.append(COUPLING_BETWEEN_OBJECT_CHART_LEGEND);
171174
}
175+
176+
@Override
177+
public List<RankedCycle> runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) {
178+
return costBenefitCalculator.runCycleAnalysis(outputDirectory, true);
179+
}
180+
181+
@Override
182+
public void renderCycleImage(String cycleName, StringBuilder stringBuilder, String outputDirectory) {
183+
stringBuilder.append("<div align=\"center\">");
184+
stringBuilder.append("<img src=\"./refactorFirst/cycles/graph" + cycleName
185+
+ ".png\" width=\"1000\" height=\"1000\" alt=\"Cycle " + cycleName + "\">");
186+
stringBuilder.append("</div>");
187+
stringBuilder.append("<br/>");
188+
stringBuilder.append("<br/>");
189+
}
172190
}

report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java

+103-9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.hjug.cbc.RankedCycle;
1818
import org.hjug.cbc.RankedDisharmony;
1919
import org.hjug.git.GitLogReader;
20+
import org.jgrapht.graph.DefaultEdge;
2021

2122
/**
2223
* Strictly HTML report that contains no JavaScript
@@ -82,6 +83,9 @@ public class SimpleHtmlReport {
8283
"Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts"
8384
};
8485

86+
// public final String[] classCycleTableHeadings = {"Classes", "Relationships", "Min Cut Edges"};
87+
public final String[] classCycleTableHeadings = {"Classes", "Relationships"};
88+
8589
public void execute(
8690
boolean showDetails, String projectName, String projectVersion, String outputDirectory, File baseDir) {
8791

@@ -156,7 +160,8 @@ public void execute(
156160
}
157161
List<RankedDisharmony> rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues();
158162
List<RankedDisharmony> rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues();
159-
List<RankedCycle> rankedCycles = costBenefitCalculator.runCycleAnalysis();
163+
164+
List<RankedCycle> rankedCycles = runCycleAnalysis(costBenefitCalculator, outputDirectory);
160165

161166
if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty()) {
162167
stringBuilder
@@ -175,7 +180,15 @@ public void execute(
175180
if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) {
176181
stringBuilder.append("<a href=\"#GOD\">God Classes</a>");
177182
stringBuilder.append("<br/>");
183+
}
184+
185+
if (!rankedCBODisharmonies.isEmpty()) {
178186
stringBuilder.append("<a href=\"#CBO\">Highly Coupled Classes</a>");
187+
stringBuilder.append("<br/>");
188+
}
189+
190+
if (!rankedCycles.isEmpty()) {
191+
stringBuilder.append("<a href=\"#CYCLES\">Class Cycles</a>");
179192
}
180193

181194
if (!rankedGodClassDisharmonies.isEmpty()) {
@@ -197,6 +210,13 @@ public void execute(
197210
}
198211

199212
if (!rankedCycles.isEmpty()) {
213+
if (!rankedGodClassDisharmonies.isEmpty() || !rankedCBODisharmonies.isEmpty()) {
214+
stringBuilder.append("<br/>");
215+
stringBuilder.append("<br/>");
216+
stringBuilder.append("<hr/>");
217+
stringBuilder.append("<br/>");
218+
stringBuilder.append("<br/>");
219+
}
200220
renderCycles(outputDirectory, stringBuilder, rankedCycles, formatter);
201221
}
202222

@@ -210,13 +230,17 @@ public void execute(
210230
log.info("Done! View the report at target/site/{}", filename);
211231
}
212232

233+
public List<RankedCycle> runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) {
234+
return costBenefitCalculator.runCycleAnalysis(outputDirectory, false);
235+
}
236+
213237
private void renderCycles(
214238
String outputDirectory,
215239
StringBuilder stringBuilder,
216240
List<RankedCycle> rankedCycles,
217241
DateTimeFormatter formatter) {
218242

219-
stringBuilder.append("<div style=\"text-align: center;\"><a id=\"CBO\"><h1>Class Cycles</h1></a></div>");
243+
stringBuilder.append("<div style=\"text-align: center;\"><a id=\"CYCLES\"><h1>Class Cycles</h1></a></div>");
220244

221245
stringBuilder.append(
222246
"<h2 align=\"center\">Class Cycles by the numbers: (Refactor starting with Priority 1)</h2>");
@@ -229,17 +253,23 @@ private void renderCycles(
229253
}
230254

231255
stringBuilder.append("<tbody>");
232-
for (RankedCycle rankedCboClassDisharmony : rankedCycles) {
256+
for (RankedCycle rankedCycle : rankedCycles) {
233257
stringBuilder.append("<tr>");
234258

259+
StringBuilder edgesToCut = new StringBuilder();
260+
for (DefaultEdge minCutEdge : rankedCycle.getMinCutEdges()) {
261+
edgesToCut.append(minCutEdge.toString());
262+
edgesToCut.append("</br>");
263+
}
264+
235265
// "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min Cuts"
236266
String[] rankedCycleData = {
237-
rankedCboClassDisharmony.getCycleName(),
238-
rankedCboClassDisharmony.getPriority().toString(),
239-
rankedCboClassDisharmony.getChangePronenessRank().toString(),
240-
String.valueOf(rankedCboClassDisharmony.getCycleNodes().size()),
241-
String.valueOf(rankedCboClassDisharmony.getEdgeSet().size()),
242-
rankedCboClassDisharmony.getMinCutEdges().toString()
267+
rankedCycle.getCycleName(),
268+
rankedCycle.getPriority().toString(),
269+
rankedCycle.getChangePronenessRank().toString(),
270+
String.valueOf(rankedCycle.getCycleNodes().size()),
271+
String.valueOf(rankedCycle.getEdgeSet().size()),
272+
edgesToCut.toString()
243273
};
244274

245275
for (String rowData : rankedCycleData) {
@@ -254,6 +284,70 @@ private void renderCycles(
254284
stringBuilder.append("</tr></thead>");
255285

256286
stringBuilder.append("</table>");
287+
288+
for (RankedCycle rankedCycle : rankedCycles) {
289+
renderCycleTable(outputDirectory, stringBuilder, rankedCycle, formatter);
290+
}
291+
}
292+
293+
private void renderCycleTable(
294+
String outputDirectory, StringBuilder stringBuilder, RankedCycle cycle, DateTimeFormatter formatter) {
295+
296+
stringBuilder.append("<br/>");
297+
stringBuilder.append("<br/>");
298+
stringBuilder.append("<hr/>");
299+
stringBuilder.append("<br/>");
300+
stringBuilder.append("<br/>");
301+
302+
stringBuilder.append("<h2 align=\"center\">Class Cycle : " + cycle.getCycleName() + "</h2>");
303+
renderCycleImage(cycle.getCycleName(), stringBuilder, outputDirectory);
304+
305+
stringBuilder.append("<div align=\"center\">");
306+
stringBuilder.append("<strong>");
307+
stringBuilder.append("\"*\" indicates relationship(s) to remove to decompose cycle");
308+
stringBuilder.append("</strong>");
309+
stringBuilder.append("</div>");
310+
311+
stringBuilder.append("<table align=\"center\" border=\"5px\">");
312+
313+
// Content
314+
stringBuilder.append("<thead><tr>");
315+
for (String heading : classCycleTableHeadings) {
316+
stringBuilder.append("<th>").append(heading).append("</th>");
317+
}
318+
319+
stringBuilder.append("<tbody>");
320+
321+
for (String vertex : cycle.getVertexSet()) {
322+
stringBuilder.append("<tr>");
323+
drawTableCell(vertex, stringBuilder);
324+
StringBuilder edges = new StringBuilder();
325+
for (org.jgrapht.graph.DefaultEdge edge : cycle.getEdgeSet()) {
326+
if (edge.toString().startsWith("(" + vertex + " :")) {
327+
if (cycle.getMinCutEdges().contains(edge)) {
328+
edges.append("<strong>");
329+
edges.append(edge + "*");
330+
edges.append("</strong>");
331+
} else {
332+
edges.append(edge);
333+
}
334+
335+
edges.append("<br/>");
336+
}
337+
}
338+
drawTableCell(edges.toString(), stringBuilder);
339+
stringBuilder.append("</tr>");
340+
}
341+
342+
stringBuilder.append("</tbody>");
343+
344+
stringBuilder.append("</tr></thead>");
345+
346+
stringBuilder.append("</table>");
347+
}
348+
349+
public void renderCycleImage(String cycleName, StringBuilder stringBuilder, String outputDirectory) {
350+
// empty on purpose
257351
}
258352

259353
private void renderGodClassInfo(

0 commit comments

Comments
 (0)