diff --git a/circular-reference-detector/pom.xml b/circular-reference-detector/pom.xml index 62a29003..955d9c56 100644 --- a/circular-reference-detector/pom.xml +++ b/circular-reference-detector/pom.xml @@ -33,13 +33,13 @@ org.junit.jupiter junit-jupiter-api - 5.5.2 + 5.9.0 test org.junit.jupiter junit-jupiter-engine - 5.5.2 + 5.9.0 test diff --git a/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java b/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java index 8f34c6ab..b5b5db6f 100644 --- a/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java +++ b/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java @@ -22,10 +22,10 @@ public class CircularReferenceDetectorApp { private Map renderedSubGraphs = new HashMap<>(); - public static void main(String[] args) { - CircularReferenceDetectorApp circularReferenceDetectorApp = new CircularReferenceDetectorApp(); - circularReferenceDetectorApp.launchApp(args); - } + // public static void main(String[] args) { + // CircularReferenceDetectorApp circularReferenceDetectorApp = new CircularReferenceDetectorApp(); + // circularReferenceDetectorApp.launchApp(args); + // } /** * Parses source project files and creates a graph of class references of the java project. diff --git a/cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java index 5a49fdb9..d61733e1 100644 --- a/cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java +++ b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java @@ -58,7 +58,7 @@ public CostBenefitCalculator(String repositoryPath) { changePronenessRanker = new ChangePronenessRanker(repository, gitLogReader); } - public List runCycleAnalysis() { + public List runCycleAnalysis(String outputDirectoryPath, boolean renderImages) { List rankedCycles = new ArrayList<>(); try { Map classNamesAndPaths = getClassNamesAndPaths(); @@ -72,7 +72,15 @@ public List runCycleAnalysis() { double minCut = 0; Set minCutEdges = null; if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) { - // circularReferenceChecker.createImage(outputDirectoryPath, subGraph, vertex); + if (renderImages) { + try { + circularReferenceChecker.createImage( + outputDirectoryPath + "/refactorFirst/cycles", subGraph, vertex); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + renderedSubGraphs.put(vertex, subGraph); log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount); GusfieldGomoryHuCutTree gusfieldGomoryHuCutTree = @@ -85,36 +93,36 @@ public List runCycleAnalysis() { for (DefaultEdge minCutEdge : minCutEdges) { log.info(minCutEdge.toString()); } - } - List cycleNodes = subGraph.vertexSet().stream() - .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) - .collect(Collectors.toList()); - List changeRanks = getRankedChangeProneness(cycleNodes); + List cycleNodes = subGraph.vertexSet().stream() + .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) + .collect(Collectors.toList()); + List changeRanks = getRankedChangeProneness(cycleNodes); - Map cycleNodeMap = new HashMap<>(); + Map cycleNodeMap = new HashMap<>(); - for (CycleNode cycleNode : cycleNodes) { - cycleNodeMap.put(cycleNode.getFileName(), cycleNode); - } + for (CycleNode cycleNode : cycleNodes) { + cycleNodeMap.put(cycleNode.getFileName(), cycleNode); + } - for (ScmLogInfo changeRank : changeRanks) { - CycleNode cn = cycleNodeMap.get(changeRank.getPath()); - cn.setScmLogInfo(changeRank); - } + for (ScmLogInfo changeRank : changeRanks) { + CycleNode cn = cycleNodeMap.get(changeRank.getPath()); + cn.setScmLogInfo(changeRank); + } - // sum change proneness ranks - int changePronenessRankSum = changeRanks.stream() - .mapToInt(ScmLogInfo::getChangePronenessRank) - .sum(); - rankedCycles.add(new RankedCycle( - vertex, - changePronenessRankSum, - subGraph.vertexSet(), - subGraph.edgeSet(), - minCut, - minCutEdges, - cycleNodes)); + // sum change proneness ranks + int changePronenessRankSum = changeRanks.stream() + .mapToInt(ScmLogInfo::getChangePronenessRank) + .sum(); + rankedCycles.add(new RankedCycle( + vertex, + changePronenessRankSum, + subGraph.vertexSet(), + subGraph.edgeSet(), + minCut, + minCutEdges, + cycleNodes)); + } }); rankedCycles.sort(Comparator.comparing(RankedCycle::getAverageChangeProneness)); @@ -295,14 +303,16 @@ public Map getClassNamesAndPaths() throws IOException { Map fileNamePaths = new HashMap<>(); - Files.walk(Paths.get(repositoryPath)).forEach(path -> { - String filename = path.getFileName().toString(); - if (filename.endsWith(".java")) { - fileNamePaths.put( - getClassName(filename), - path.toUri().toString().replace("file:///" + repositoryPath.replace("\\", "/") + "/", "")); - } - }); + try (Stream walk = Files.walk(Paths.get(repositoryPath))) { + walk.forEach(path -> { + String filename = path.getFileName().toString(); + if (filename.endsWith(".java")) { + fileNamePaths.put( + getClassName(filename), + path.toUri().toString().replace("file:///" + repositoryPath.replace("\\", "/") + "/", "")); + } + }); + } return fileNamePaths; } diff --git a/report/pom.xml b/report/pom.xml index 6d99bb7a..08559653 100644 --- a/report/pom.xml +++ b/report/pom.xml @@ -19,6 +19,17 @@ com.fasterxml.jackson.core jackson-databind + + + org.jgrapht + jgrapht-core + 1.3.0 + + + org.jgrapht + jgrapht-ext + 1.3.0 + \ No newline at end of file diff --git a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java index af54f71c..7fc64e1d 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -7,6 +7,8 @@ import java.util.List; import java.util.Locale; import lombok.extern.slf4j.Slf4j; +import org.hjug.cbc.CostBenefitCalculator; +import org.hjug.cbc.RankedCycle; import org.hjug.cbc.RankedDisharmony; import org.hjug.gdg.GraphDataGenerator; @@ -77,6 +79,7 @@ void renderGithubButtons(StringBuilder stringBuilder) { } // TODO: Move to another class to allow use by Gradle plugin + @Override void writeGodClassGchartJs( List rankedDisharmonies, int maxPriority, String reportOutputDirectory) { GraphDataGenerator graphDataGenerator = new GraphDataGenerator(); @@ -169,4 +172,19 @@ void renderCBOChart( renderGithubButtons(stringBuilder); stringBuilder.append(COUPLING_BETWEEN_OBJECT_CHART_LEGEND); } + + @Override + public List runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) { + return costBenefitCalculator.runCycleAnalysis(outputDirectory, true); + } + + @Override + public void renderCycleImage(String cycleName, StringBuilder stringBuilder, String outputDirectory) { + stringBuilder.append("
"); + stringBuilder.append("\"Cycle"); + stringBuilder.append("
"); + stringBuilder.append("
"); + stringBuilder.append("
"); + } } 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 c99637c3..b8a6c5b9 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -17,6 +17,7 @@ import org.hjug.cbc.RankedCycle; import org.hjug.cbc.RankedDisharmony; import org.hjug.git.GitLogReader; +import org.jgrapht.graph.DefaultEdge; /** * Strictly HTML report that contains no JavaScript @@ -82,6 +83,9 @@ 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"}; + public void execute( boolean showDetails, String projectName, String projectVersion, String outputDirectory, File baseDir) { @@ -156,7 +160,8 @@ public void execute( } List rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); List rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); - List rankedCycles = costBenefitCalculator.runCycleAnalysis(); + + List rankedCycles = runCycleAnalysis(costBenefitCalculator, outputDirectory); if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty()) { stringBuilder @@ -175,7 +180,15 @@ public void execute( if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) { stringBuilder.append("God Classes"); stringBuilder.append("
"); + } + + if (!rankedCBODisharmonies.isEmpty()) { stringBuilder.append("Highly Coupled Classes"); + stringBuilder.append("
"); + } + + if (!rankedCycles.isEmpty()) { + stringBuilder.append("Class Cycles"); } if (!rankedGodClassDisharmonies.isEmpty()) { @@ -197,6 +210,13 @@ public void execute( } if (!rankedCycles.isEmpty()) { + if (!rankedGodClassDisharmonies.isEmpty() || !rankedCBODisharmonies.isEmpty()) { + stringBuilder.append("
"); + stringBuilder.append("
"); + stringBuilder.append("
"); + stringBuilder.append("
"); + stringBuilder.append("
"); + } renderCycles(outputDirectory, stringBuilder, rankedCycles, formatter); } @@ -210,13 +230,17 @@ public void execute( log.info("Done! View the report at target/site/{}", filename); } + public List runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) { + return costBenefitCalculator.runCycleAnalysis(outputDirectory, false); + } + private void renderCycles( String outputDirectory, StringBuilder stringBuilder, List rankedCycles, DateTimeFormatter formatter) { - stringBuilder.append(""); + stringBuilder.append(""); stringBuilder.append( "

Class Cycles by the numbers: (Refactor starting with Priority 1)

"); @@ -229,17 +253,23 @@ private void renderCycles( } stringBuilder.append(""); - for (RankedCycle rankedCboClassDisharmony : rankedCycles) { + for (RankedCycle rankedCycle : rankedCycles) { stringBuilder.append(""); + StringBuilder edgesToCut = new StringBuilder(); + for (DefaultEdge minCutEdge : rankedCycle.getMinCutEdges()) { + edgesToCut.append(minCutEdge.toString()); + edgesToCut.append("
"); + } + // "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min Cuts" String[] rankedCycleData = { - rankedCboClassDisharmony.getCycleName(), - rankedCboClassDisharmony.getPriority().toString(), - rankedCboClassDisharmony.getChangePronenessRank().toString(), - String.valueOf(rankedCboClassDisharmony.getCycleNodes().size()), - String.valueOf(rankedCboClassDisharmony.getEdgeSet().size()), - rankedCboClassDisharmony.getMinCutEdges().toString() + rankedCycle.getCycleName(), + rankedCycle.getPriority().toString(), + rankedCycle.getChangePronenessRank().toString(), + String.valueOf(rankedCycle.getCycleNodes().size()), + String.valueOf(rankedCycle.getEdgeSet().size()), + edgesToCut.toString() }; for (String rowData : rankedCycleData) { @@ -254,6 +284,70 @@ private void renderCycles( stringBuilder.append(""); stringBuilder.append(""); + + for (RankedCycle rankedCycle : rankedCycles) { + renderCycleTable(outputDirectory, stringBuilder, rankedCycle, formatter); + } + } + + private void renderCycleTable( + String outputDirectory, StringBuilder stringBuilder, RankedCycle cycle, DateTimeFormatter formatter) { + + stringBuilder.append("
"); + stringBuilder.append("
"); + stringBuilder.append("
"); + stringBuilder.append("
"); + stringBuilder.append("
"); + + stringBuilder.append("

Class Cycle : " + cycle.getCycleName() + "

"); + renderCycleImage(cycle.getCycleName(), stringBuilder, outputDirectory); + + stringBuilder.append("
"); + stringBuilder.append(""); + stringBuilder.append("\"*\" indicates relationship(s) to remove to decompose cycle"); + stringBuilder.append(""); + stringBuilder.append("
"); + + stringBuilder.append(""); + + // Content + stringBuilder.append(""); + for (String heading : classCycleTableHeadings) { + stringBuilder.append(""); + } + + stringBuilder.append(""); + + for (String vertex : cycle.getVertexSet()) { + stringBuilder.append(""); + drawTableCell(vertex, stringBuilder); + StringBuilder edges = new StringBuilder(); + for (org.jgrapht.graph.DefaultEdge edge : cycle.getEdgeSet()) { + if (edge.toString().startsWith("(" + vertex + " :")) { + if (cycle.getMinCutEdges().contains(edge)) { + edges.append(""); + edges.append(edge + "*"); + edges.append(""); + } else { + edges.append(edge); + } + + edges.append("
"); + } + } + drawTableCell(edges.toString(), stringBuilder); + stringBuilder.append(""); + } + + stringBuilder.append(""); + + stringBuilder.append(""); + + stringBuilder.append("
").append(heading).append("
"); + } + + public void renderCycleImage(String cycleName, StringBuilder stringBuilder, String outputDirectory) { + // empty on purpose } private void renderGodClassInfo(