Skip to content

Commit

Permalink
#97 Cycle analysis included in Maven site report
Browse files Browse the repository at this point in the history
  • Loading branch information
jimbethancourt committed Sep 25, 2024
1 parent 7fed315 commit 75449c5
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.hjug.cbc.CostBenefitCalculator;
import org.hjug.cbc.RankedCycle;
import org.hjug.cbc.RankedDisharmony;
import org.hjug.gdg.GraphDataGenerator;
import org.hjug.git.GitLogReader;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultWeightedEdge;

@Slf4j
@Mojo(
Expand Down Expand Up @@ -65,6 +68,14 @@ public String getDescription(Locale locale) {
+ " have the highest priority values.";
}

public final String[] cycleTableHeadings = {
"Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts"
};

public final String[] classCycleTableHeadings = {"Classes", "Relationships"};

private Graph<String, DefaultWeightedEdge> classGraph;

@Override
public void executeReport(Locale locale) throws MavenReportException {

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

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

List<RankedDisharmony> rankedGodClassDisharmonies;
List<RankedDisharmony> rankedCBODisharmonies;
List<RankedCycle> rankedCycles;
try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) {
costBenefitCalculator.runPmdAnalysis();
rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues();
rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues();
rankedCycles = runCycleAnalysis(costBenefitCalculator, outputDirectory.getPath());
classGraph = costBenefitCalculator.getClassReferencesGraph();
} catch (Exception e) {
log.error("Error running analysis.");
throw new RuntimeException(e);
}

if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty()) {
if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty() && rankedCycles.isEmpty()) {
mainSink.text("Contratulations! " + projectName + " " + projectVersion
+ " has no God classes or highly coupled classes!");
+ " has no God classes, highly coupled classes, or cycles!");
mainSink.section1_();
renderGitHubButtons(mainSink);
mainSink.body_();
Expand Down Expand Up @@ -418,13 +429,176 @@ public void executeReport(Locale locale) throws MavenReportException {
mainSink.tableRows_();
mainSink.table_();

if (!rankedCycles.isEmpty()) {
mainSink.lineBreak();
mainSink.lineBreak();
mainSink.horizontalRule();
mainSink.lineBreak();
mainSink.lineBreak();

renderCycles(outputDirectory.getPath(), mainSink, rankedCycles, formatter);
}

// Close
mainSink.section1_();
mainSink.body_();

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

public List<RankedCycle> runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) {
return costBenefitCalculator.runCycleAnalysis(outputDirectory, true);
}

private void renderCycles(
String outputDirectory, Sink mainSink, List<RankedCycle> rankedCycles, DateTimeFormatter formatter) {

SinkEventAttributeSet alignCenter = new SinkEventAttributeSet();
alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center");

mainSink.division(alignCenter);
mainSink.section1();
mainSink.sectionTitle1();
mainSink.text("Class Cycles");
mainSink.sectionTitle1_();
mainSink.section1_();
mainSink.division_();

mainSink.division(alignCenter);
mainSink.section2();
mainSink.sectionTitle2();
mainSink.text("Class Cycles by the numbers: (Refactor starting with Priority 1)");
mainSink.sectionTitle2_();
mainSink.section2_();
mainSink.division_();

mainSink.table();
mainSink.tableRows(new int[] {Sink.JUSTIFY_LEFT}, true);

// Content
// header row

mainSink.tableRow();
for (String heading : cycleTableHeadings) {
drawTableHeaderCell(heading, mainSink);
}
mainSink.tableRow_();

for (RankedCycle rankedCycle : rankedCycles) {
mainSink.tableRow();

StringBuilder edgesToCut = new StringBuilder();
for (DefaultWeightedEdge minCutEdge : rankedCycle.getMinCutEdges()) {
edgesToCut.append(minCutEdge + ":" + (int) classGraph.getEdgeWeight(minCutEdge));
}

// "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min Cuts"
String[] rankedCycleData = {
rankedCycle.getCycleName(),
rankedCycle.getPriority().toString(),
rankedCycle.getChangePronenessRank().toString(),
String.valueOf(rankedCycle.getCycleNodes().size()),
String.valueOf(rankedCycle.getEdgeSet().size()),
edgesToCut.toString()
};

for (String rowData : rankedCycleData) {
drawCycleTableCell(rowData, mainSink);
}

mainSink.tableRow_();
}
mainSink.tableRows_();

mainSink.table_();

for (RankedCycle rankedCycle : rankedCycles) {
renderCycleTable(outputDirectory, mainSink, rankedCycle, formatter);
}
}

private void renderCycleTable(
String outputDirectory, Sink mainSink, RankedCycle cycle, DateTimeFormatter formatter) {

mainSink.lineBreak();
mainSink.lineBreak();
mainSink.lineBreak();
mainSink.lineBreak();
mainSink.lineBreak();

SinkEventAttributeSet alignCenter = new SinkEventAttributeSet();
alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center");

mainSink.division(alignCenter);
mainSink.section2();
mainSink.sectionTitle2();
mainSink.text("Class Cycle : " + cycle.getCycleName());
mainSink.sectionTitle2_();
mainSink.section2_();
mainSink.division_();

renderCycleImage(cycle.getCycleName(), mainSink, outputDirectory);

mainSink.division(alignCenter);
mainSink.bold();
mainSink.text("\"*\" indicates relationship(s) to remove to decompose cycle");
mainSink.bold_();
mainSink.division_();

mainSink.table();
mainSink.tableRows(new int[] {Sink.JUSTIFY_LEFT}, true);

// Content
mainSink.tableRow();
for (String heading : classCycleTableHeadings) {
drawTableHeaderCell(heading, mainSink);
}
mainSink.tableRow_();

for (String vertex : cycle.getVertexSet()) {
mainSink.tableRow();
drawTableCell(vertex, mainSink);
StringBuilder edges = new StringBuilder();
for (org.jgrapht.graph.DefaultWeightedEdge edge : cycle.getEdgeSet()) {
if (edge.toString().startsWith("(" + vertex + " :")) {
if (cycle.getMinCutEdges().contains(edge)) {
edges.append(edge);
edges.append(":")
.append((int) classGraph.getEdgeWeight(edge))
.append("*");
} else {
edges.append(edge);
edges.append(":").append((int) classGraph.getEdgeWeight(edge));
}
}
}
drawCycleTableCell(edges.toString(), mainSink);
mainSink.tableRow_();
}

mainSink.tableRows_();
mainSink.table_();
}

public void renderCycleImage(String cycleName, Sink mainSink, String outputDirectory) {
SinkEventAttributeSet alignCenter = new SinkEventAttributeSet();
alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center");
mainSink.division(alignCenter);

SinkEventAttributeSet imageAttributes = new SinkEventAttributeSet();
imageAttributes.addAttribute(SinkEventAttributes.TYPE, "img");
imageAttributes.addAttribute(SinkEventAttributes.SRC, "./refactorFirst/cycles/graph" + cycleName + ".png");
imageAttributes.addAttribute(SinkEventAttributes.WIDTH, 1000);
imageAttributes.addAttribute(SinkEventAttributes.HEIGHT, 1000);
imageAttributes.addAttribute(SinkEventAttributes.ALT, "Cycle " + cycleName);

mainSink.unknown("img", new Object[] {HtmlMarkup.TAG_TYPE_SIMPLE}, imageAttributes);

mainSink.division_();
mainSink.lineBreak();
mainSink.lineBreak();
}

private void renderLegend(Sink mainSink, String legendHeading, String xAxis) {
SinkEventAttributeSet width = new SinkEventAttributeSet();
width.addAttribute(SinkEventAttributes.STYLE, "width:350px");
Expand Down Expand Up @@ -479,6 +653,31 @@ void drawTableCell(Object cellText, Sink mainSink) {
mainSink.tableCell_();
}

void drawCycleTableCell(String cellText, Sink mainSink) {
SinkEventAttributeSet align = new SinkEventAttributeSet();
align.addAttribute(SinkEventAttributes.ALIGN, "left");

mainSink.tableCell(align);

for (String string : cellText.split("\\(")) {
if (string.contains("*")) {
mainSink.bold();
mainSink.text("(" + string);
mainSink.bold_();
} else {
if (string.contains(")")) {
mainSink.text("(" + string);
} else {
mainSink.text(string);
}
}

mainSink.lineBreak();
}

mainSink.tableCell_();
}

/*
<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>
<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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ public class SimpleHtmlReport {
"Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts"
};

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

private Graph<String, DefaultWeightedEdge> classGraph;
Expand Down Expand Up @@ -255,6 +254,7 @@ private void renderCycles(
for (String heading : cycleTableHeadings) {
stringBuilder.append("<th>").append(heading).append("</th>");
}
stringBuilder.append("</thead>");

stringBuilder.append("<tbody>");
for (RankedCycle rankedCycle : rankedCycles) {
Expand Down Expand Up @@ -284,9 +284,6 @@ private void renderCycles(
}

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

stringBuilder.append("</tr></thead>");

stringBuilder.append("</table>");

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

stringBuilder.append("<tbody>");

Expand Down Expand Up @@ -349,8 +347,6 @@ private void renderCycleTable(

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

stringBuilder.append("</tr></thead>");

stringBuilder.append("</table>");
}

Expand Down

0 comments on commit 75449c5

Please sign in to comment.