diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index c25ec7a..cdeaccc 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -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( @@ -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 classGraph; + @Override public void executeReport(Locale locale) throws MavenReportException { @@ -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"; @@ -211,18 +219,21 @@ public void executeReport(Locale locale) throws MavenReportException { List rankedGodClassDisharmonies; List rankedCBODisharmonies; + List 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_(); @@ -418,6 +429,16 @@ 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_(); @@ -425,6 +446,159 @@ public void executeReport(Locale locale) throws MavenReportException { log.info("Done! View the report at target/site/{}", filename); } + public List runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) { + return costBenefitCalculator.runCycleAnalysis(outputDirectory, true); + } + + private void renderCycles( + String outputDirectory, Sink mainSink, List 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"); @@ -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_(); + } + /* Star Fork diff --git a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java index d727104..d4afae0 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -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 classGraph; @@ -255,6 +254,7 @@ private void renderCycles( for (String heading : cycleTableHeadings) { stringBuilder.append("").append(heading).append(""); } + stringBuilder.append(""); stringBuilder.append(""); for (RankedCycle rankedCycle : rankedCycles) { @@ -284,9 +284,6 @@ private void renderCycles( } stringBuilder.append(""); - - stringBuilder.append(""); - stringBuilder.append(""); for (RankedCycle rankedCycle : rankedCycles) { @@ -319,6 +316,7 @@ private void renderCycleTable( for (String heading : classCycleTableHeadings) { stringBuilder.append("").append(heading).append(""); } + stringBuilder.append(""); stringBuilder.append(""); @@ -349,8 +347,6 @@ private void renderCycleTable( stringBuilder.append(""); - stringBuilder.append(""); - stringBuilder.append(""); }