Skip to content

Commit 75449c5

Browse files
#97 Cycle analysis included in Maven site report
1 parent 7fed315 commit 75449c5

File tree

2 files changed

+206
-11
lines changed

2 files changed

+206
-11
lines changed

refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java

Lines changed: 204 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222
import org.apache.maven.reporting.AbstractMavenReport;
2323
import org.apache.maven.reporting.MavenReportException;
2424
import org.hjug.cbc.CostBenefitCalculator;
25+
import org.hjug.cbc.RankedCycle;
2526
import org.hjug.cbc.RankedDisharmony;
2627
import org.hjug.gdg.GraphDataGenerator;
2728
import org.hjug.git.GitLogReader;
29+
import org.jgrapht.Graph;
30+
import org.jgrapht.graph.DefaultWeightedEdge;
2831

2932
@Slf4j
3033
@Mojo(
@@ -65,6 +68,14 @@ public String getDescription(Locale locale) {
6568
+ " have the highest priority values.";
6669
}
6770

71+
public final String[] cycleTableHeadings = {
72+
"Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts"
73+
};
74+
75+
public final String[] classCycleTableHeadings = {"Classes", "Relationships"};
76+
77+
private Graph<String, DefaultWeightedEdge> classGraph;
78+
6879
@Override
6980
public void executeReport(Locale locale) throws MavenReportException {
7081

@@ -127,9 +138,6 @@ public void executeReport(Locale locale) throws MavenReportException {
127138
* @See https://maven.apache.org/doxia/developers/sink.html#How_to_inject_javascript_code_into_HTML
128139
*/
129140
SinkEventAttributeSet githubButtonJS = new SinkEventAttributeSet();
130-
// githubButtonJS.addAttribute(SinkEventAttributes.TYPE, "text/javascript");
131-
// githubButtonJS.addAttribute("async", "");
132-
// githubButtonJS.addAttribute("defer", "");
133141
githubButtonJS.addAttribute(SinkEventAttributes.SRC, "https://buttons.github.io/buttons.js");
134142

135143
String script = "script";
@@ -211,18 +219,21 @@ public void executeReport(Locale locale) throws MavenReportException {
211219

212220
List<RankedDisharmony> rankedGodClassDisharmonies;
213221
List<RankedDisharmony> rankedCBODisharmonies;
222+
List<RankedCycle> rankedCycles;
214223
try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) {
215224
costBenefitCalculator.runPmdAnalysis();
216225
rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues();
217226
rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues();
227+
rankedCycles = runCycleAnalysis(costBenefitCalculator, outputDirectory.getPath());
228+
classGraph = costBenefitCalculator.getClassReferencesGraph();
218229
} catch (Exception e) {
219230
log.error("Error running analysis.");
220231
throw new RuntimeException(e);
221232
}
222233

223-
if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty()) {
234+
if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty() && rankedCycles.isEmpty()) {
224235
mainSink.text("Contratulations! " + projectName + " " + projectVersion
225-
+ " has no God classes or highly coupled classes!");
236+
+ " has no God classes, highly coupled classes, or cycles!");
226237
mainSink.section1_();
227238
renderGitHubButtons(mainSink);
228239
mainSink.body_();
@@ -418,13 +429,176 @@ public void executeReport(Locale locale) throws MavenReportException {
418429
mainSink.tableRows_();
419430
mainSink.table_();
420431

432+
if (!rankedCycles.isEmpty()) {
433+
mainSink.lineBreak();
434+
mainSink.lineBreak();
435+
mainSink.horizontalRule();
436+
mainSink.lineBreak();
437+
mainSink.lineBreak();
438+
439+
renderCycles(outputDirectory.getPath(), mainSink, rankedCycles, formatter);
440+
}
441+
421442
// Close
422443
mainSink.section1_();
423444
mainSink.body_();
424445

425446
log.info("Done! View the report at target/site/{}", filename);
426447
}
427448

449+
public List<RankedCycle> runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) {
450+
return costBenefitCalculator.runCycleAnalysis(outputDirectory, true);
451+
}
452+
453+
private void renderCycles(
454+
String outputDirectory, Sink mainSink, List<RankedCycle> rankedCycles, DateTimeFormatter formatter) {
455+
456+
SinkEventAttributeSet alignCenter = new SinkEventAttributeSet();
457+
alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center");
458+
459+
mainSink.division(alignCenter);
460+
mainSink.section1();
461+
mainSink.sectionTitle1();
462+
mainSink.text("Class Cycles");
463+
mainSink.sectionTitle1_();
464+
mainSink.section1_();
465+
mainSink.division_();
466+
467+
mainSink.division(alignCenter);
468+
mainSink.section2();
469+
mainSink.sectionTitle2();
470+
mainSink.text("Class Cycles by the numbers: (Refactor starting with Priority 1)");
471+
mainSink.sectionTitle2_();
472+
mainSink.section2_();
473+
mainSink.division_();
474+
475+
mainSink.table();
476+
mainSink.tableRows(new int[] {Sink.JUSTIFY_LEFT}, true);
477+
478+
// Content
479+
// header row
480+
481+
mainSink.tableRow();
482+
for (String heading : cycleTableHeadings) {
483+
drawTableHeaderCell(heading, mainSink);
484+
}
485+
mainSink.tableRow_();
486+
487+
for (RankedCycle rankedCycle : rankedCycles) {
488+
mainSink.tableRow();
489+
490+
StringBuilder edgesToCut = new StringBuilder();
491+
for (DefaultWeightedEdge minCutEdge : rankedCycle.getMinCutEdges()) {
492+
edgesToCut.append(minCutEdge + ":" + (int) classGraph.getEdgeWeight(minCutEdge));
493+
}
494+
495+
// "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min Cuts"
496+
String[] rankedCycleData = {
497+
rankedCycle.getCycleName(),
498+
rankedCycle.getPriority().toString(),
499+
rankedCycle.getChangePronenessRank().toString(),
500+
String.valueOf(rankedCycle.getCycleNodes().size()),
501+
String.valueOf(rankedCycle.getEdgeSet().size()),
502+
edgesToCut.toString()
503+
};
504+
505+
for (String rowData : rankedCycleData) {
506+
drawCycleTableCell(rowData, mainSink);
507+
}
508+
509+
mainSink.tableRow_();
510+
}
511+
mainSink.tableRows_();
512+
513+
mainSink.table_();
514+
515+
for (RankedCycle rankedCycle : rankedCycles) {
516+
renderCycleTable(outputDirectory, mainSink, rankedCycle, formatter);
517+
}
518+
}
519+
520+
private void renderCycleTable(
521+
String outputDirectory, Sink mainSink, RankedCycle cycle, DateTimeFormatter formatter) {
522+
523+
mainSink.lineBreak();
524+
mainSink.lineBreak();
525+
mainSink.lineBreak();
526+
mainSink.lineBreak();
527+
mainSink.lineBreak();
528+
529+
SinkEventAttributeSet alignCenter = new SinkEventAttributeSet();
530+
alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center");
531+
532+
mainSink.division(alignCenter);
533+
mainSink.section2();
534+
mainSink.sectionTitle2();
535+
mainSink.text("Class Cycle : " + cycle.getCycleName());
536+
mainSink.sectionTitle2_();
537+
mainSink.section2_();
538+
mainSink.division_();
539+
540+
renderCycleImage(cycle.getCycleName(), mainSink, outputDirectory);
541+
542+
mainSink.division(alignCenter);
543+
mainSink.bold();
544+
mainSink.text("\"*\" indicates relationship(s) to remove to decompose cycle");
545+
mainSink.bold_();
546+
mainSink.division_();
547+
548+
mainSink.table();
549+
mainSink.tableRows(new int[] {Sink.JUSTIFY_LEFT}, true);
550+
551+
// Content
552+
mainSink.tableRow();
553+
for (String heading : classCycleTableHeadings) {
554+
drawTableHeaderCell(heading, mainSink);
555+
}
556+
mainSink.tableRow_();
557+
558+
for (String vertex : cycle.getVertexSet()) {
559+
mainSink.tableRow();
560+
drawTableCell(vertex, mainSink);
561+
StringBuilder edges = new StringBuilder();
562+
for (org.jgrapht.graph.DefaultWeightedEdge edge : cycle.getEdgeSet()) {
563+
if (edge.toString().startsWith("(" + vertex + " :")) {
564+
if (cycle.getMinCutEdges().contains(edge)) {
565+
edges.append(edge);
566+
edges.append(":")
567+
.append((int) classGraph.getEdgeWeight(edge))
568+
.append("*");
569+
} else {
570+
edges.append(edge);
571+
edges.append(":").append((int) classGraph.getEdgeWeight(edge));
572+
}
573+
}
574+
}
575+
drawCycleTableCell(edges.toString(), mainSink);
576+
mainSink.tableRow_();
577+
}
578+
579+
mainSink.tableRows_();
580+
mainSink.table_();
581+
}
582+
583+
public void renderCycleImage(String cycleName, Sink mainSink, String outputDirectory) {
584+
SinkEventAttributeSet alignCenter = new SinkEventAttributeSet();
585+
alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center");
586+
mainSink.division(alignCenter);
587+
588+
SinkEventAttributeSet imageAttributes = new SinkEventAttributeSet();
589+
imageAttributes.addAttribute(SinkEventAttributes.TYPE, "img");
590+
imageAttributes.addAttribute(SinkEventAttributes.SRC, "./refactorFirst/cycles/graph" + cycleName + ".png");
591+
imageAttributes.addAttribute(SinkEventAttributes.WIDTH, 1000);
592+
imageAttributes.addAttribute(SinkEventAttributes.HEIGHT, 1000);
593+
imageAttributes.addAttribute(SinkEventAttributes.ALT, "Cycle " + cycleName);
594+
595+
mainSink.unknown("img", new Object[] {HtmlMarkup.TAG_TYPE_SIMPLE}, imageAttributes);
596+
597+
mainSink.division_();
598+
mainSink.lineBreak();
599+
mainSink.lineBreak();
600+
}
601+
428602
private void renderLegend(Sink mainSink, String legendHeading, String xAxis) {
429603
SinkEventAttributeSet width = new SinkEventAttributeSet();
430604
width.addAttribute(SinkEventAttributes.STYLE, "width:350px");
@@ -479,6 +653,31 @@ void drawTableCell(Object cellText, Sink mainSink) {
479653
mainSink.tableCell_();
480654
}
481655

656+
void drawCycleTableCell(String cellText, Sink mainSink) {
657+
SinkEventAttributeSet align = new SinkEventAttributeSet();
658+
align.addAttribute(SinkEventAttributes.ALIGN, "left");
659+
660+
mainSink.tableCell(align);
661+
662+
for (String string : cellText.split("\\(")) {
663+
if (string.contains("*")) {
664+
mainSink.bold();
665+
mainSink.text("(" + string);
666+
mainSink.bold_();
667+
} else {
668+
if (string.contains(")")) {
669+
mainSink.text("(" + string);
670+
} else {
671+
mainSink.text(string);
672+
}
673+
}
674+
675+
mainSink.lineBreak();
676+
}
677+
678+
mainSink.tableCell_();
679+
}
680+
482681
/*
483682
<a class="github-button" href="https://github.com/jimbethancourt/refactorfirst" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star jimbethancourt/refactorfirst on GitHub">Star</a>
484683
<a class="github-button" href="https://github.com/jimbethancourt/refactorfirst/fork" data-icon="octicon-repo-forked" data-size="large" data-show-count="true" aria-label="Fork jimbethancourt/refactorfirst on GitHub">Fork</a>

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ public class SimpleHtmlReport {
8383
"Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts"
8484
};
8585

86-
// public final String[] classCycleTableHeadings = {"Classes", "Relationships", "Min Cut Edges"};
8786
public final String[] classCycleTableHeadings = {"Classes", "Relationships"};
8887

8988
private Graph<String, DefaultWeightedEdge> classGraph;
@@ -255,6 +254,7 @@ private void renderCycles(
255254
for (String heading : cycleTableHeadings) {
256255
stringBuilder.append("<th>").append(heading).append("</th>");
257256
}
257+
stringBuilder.append("</thead>");
258258

259259
stringBuilder.append("<tbody>");
260260
for (RankedCycle rankedCycle : rankedCycles) {
@@ -284,9 +284,6 @@ private void renderCycles(
284284
}
285285

286286
stringBuilder.append("</tbody>");
287-
288-
stringBuilder.append("</tr></thead>");
289-
290287
stringBuilder.append("</table>");
291288

292289
for (RankedCycle rankedCycle : rankedCycles) {
@@ -319,6 +316,7 @@ private void renderCycleTable(
319316
for (String heading : classCycleTableHeadings) {
320317
stringBuilder.append("<th>").append(heading).append("</th>");
321318
}
319+
stringBuilder.append("</thead>");
322320

323321
stringBuilder.append("<tbody>");
324322

@@ -349,8 +347,6 @@ private void renderCycleTable(
349347

350348
stringBuilder.append("</tbody>");
351349

352-
stringBuilder.append("</tr></thead>");
353-
354350
stringBuilder.append("</table>");
355351
}
356352

0 commit comments

Comments
 (0)