From c786fbb48e6aa858ffff83238b8937bb0e373896 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 29 Sep 2024 16:07:25 -0500 Subject: [PATCH] #93 Only calculating cycle class churn when `showDetails = true` Only calculating cycle class churn when `showDetails = true` --- .../org/hjug/cbc/CostBenefitCalculator.java | 160 ++++++++++++------ .../main/java/org/hjug/cbc/RankedCycle.java | 32 +++- .../hjug/refactorfirst/report/HtmlReport.java | 6 - .../report/SimpleHtmlReport.java | 61 ++++--- 4 files changed, 178 insertions(+), 81 deletions(-) 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 eea9c14..fa09812 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 @@ -73,73 +73,125 @@ public List runCycleAnalysis() { StaticJavaParser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.BLEEDING_EDGE); List rankedCycles = new ArrayList<>(); try { - Map classNamesAndPaths = getClassNamesAndPaths(); - classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath); - CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); - Map> cyclesForEveryVertexMap = - circularReferenceChecker.detectCycles(classReferencesGraph); - cyclesForEveryVertexMap.forEach((vertex, subGraph) -> { - int vertexCount = subGraph.vertexSet().size(); - int edgeCount = subGraph.edgeSet().size(); - double minCut = 0; - Set minCutEdges; - if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) { - renderedSubGraphs.put(vertex, subGraph); - log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount); - GusfieldGomoryHuCutTree gusfieldGomoryHuCutTree = - new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph)); - minCut = gusfieldGomoryHuCutTree.calculateMinCut(); - minCutEdges = gusfieldGomoryHuCutTree.getCutEdges(); - - List cycleNodes = subGraph.vertexSet().stream() - .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) - .collect(Collectors.toList()); - List changeRanks = getRankedChangeProneness(cycleNodes); - - Map cycleNodeMap = new HashMap<>(); - - for (CycleNode cycleNode : cycleNodes) { - cycleNodeMap.put(cycleNode.getFileName(), cycleNode); - } + boolean calculateCycleChurn = false; + idenfifyRankedCycles(rankedCycles, calculateCycleChurn); + sortRankedCycles(rankedCycles, calculateCycleChurn); + setPriorities(rankedCycles); + } catch (IOException e) { + throw new RuntimeException(e); + } - for (ScmLogInfo changeRank : changeRanks) { - CycleNode cycleNode = cycleNodeMap.get(changeRank.getPath()); - cycleNode.setScmLogInfo(changeRank); - } + return rankedCycles; + } - // 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)); - } - }); + public List runCycleAnalysisAndCalculateCycleChurn() { + StaticJavaParser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.BLEEDING_EDGE); + List rankedCycles = new ArrayList<>(); + try { + boolean calculateCycleChurn = true; + idenfifyRankedCycles(rankedCycles, calculateCycleChurn); + sortRankedCycles(rankedCycles, calculateCycleChurn); + setPriorities(rankedCycles); + } catch (IOException e) { + throw new RuntimeException(e); + } + return rankedCycles; + } + + private void idenfifyRankedCycles(List rankedCycles, boolean calculateChurnForCycles) + throws IOException { + Map> cycles = getCycles(); + Map classNamesAndPaths = getClassNamesAndPaths(); + cycles.forEach((vertex, subGraph) -> { + int vertexCount = subGraph.vertexSet().size(); + int edgeCount = subGraph.edgeSet().size(); + + if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) { + renderedSubGraphs.put(vertex, subGraph); + log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount); + GusfieldGomoryHuCutTree gusfieldGomoryHuCutTree = + new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph)); + double minCut = gusfieldGomoryHuCutTree.calculateMinCut(); + Set minCutEdges = gusfieldGomoryHuCutTree.getCutEdges(); + + List cycleNodes = subGraph.vertexSet().stream() + .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) + .collect(Collectors.toList()); + + rankedCycles.add( + createRankedCycle(calculateChurnForCycles, vertex, subGraph, cycleNodes, minCut, minCutEdges)); + } + }); + } + + private static void setPriorities(List rankedCycles) { + int priority = 1; + for (RankedCycle rankedCycle : rankedCycles) { + rankedCycle.setPriority(priority++); + } + } + + private Map> getCycles() throws IOException { + classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath); + CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); + Map> cycles = + circularReferenceChecker.detectCycles(classReferencesGraph); + return cycles; + } + + private static void sortRankedCycles(List rankedCycles, boolean calculateChurnForCycles) { + if (calculateChurnForCycles) { rankedCycles.sort(Comparator.comparing(RankedCycle::getAverageChangeProneness)); + int cpr = 1; for (RankedCycle rankedCycle : rankedCycles) { rankedCycle.setChangePronenessRank(cpr++); } - + } else { rankedCycles.sort(Comparator.comparing(RankedCycle::getRawPriority).reversed()); + } + } - int priority = 1; - for (RankedCycle rankedCycle : rankedCycles) { - rankedCycle.setPriority(priority++); + private RankedCycle createRankedCycle( + boolean calculateChurnForCycles, + String vertex, + AsSubgraph subGraph, + List cycleNodes, + double minCut, + Set minCutEdges) { + RankedCycle rankedCycle; + if (calculateChurnForCycles) { + List changeRanks = getRankedChangeProneness(cycleNodes); + + Map cycleNodeMap = new HashMap<>(); + + for (CycleNode cycleNode : cycleNodes) { + cycleNodeMap.put(cycleNode.getFileName(), cycleNode); } - } catch (IOException e) { - throw new RuntimeException(e); - } + for (ScmLogInfo changeRank : changeRanks) { + CycleNode cycleNode = cycleNodeMap.get(changeRank.getPath()); + cycleNode.setScmLogInfo(changeRank); + } - return rankedCycles; + // sum change proneness ranks + int changePronenessRankSum = changeRanks.stream() + .mapToInt(ScmLogInfo::getChangePronenessRank) + .sum(); + rankedCycle = new RankedCycle( + vertex, + changePronenessRankSum, + subGraph.vertexSet(), + subGraph.edgeSet(), + minCut, + minCutEdges, + cycleNodes); + } else { + rankedCycle = + new RankedCycle(vertex, subGraph.vertexSet(), subGraph.edgeSet(), minCut, minCutEdges, cycleNodes); + } + return rankedCycle; } private boolean isDuplicateSubGraph(AsSubgraph subGraph, String vertex) { diff --git a/cost-benefit-calculator/src/main/java/org/hjug/cbc/RankedCycle.java b/cost-benefit-calculator/src/main/java/org/hjug/cbc/RankedCycle.java index 8cce914..54b65ba 100644 --- a/cost-benefit-calculator/src/main/java/org/hjug/cbc/RankedCycle.java +++ b/cost-benefit-calculator/src/main/java/org/hjug/cbc/RankedCycle.java @@ -12,7 +12,7 @@ public class RankedCycle { private final String cycleName; - private final Integer changePronenessRankSum; + private Integer changePronenessRankSum = 0; private final Set vertexSet; private final Set edgeSet; @@ -23,9 +23,37 @@ public class RankedCycle { private float rawPriority; private Integer priority = 0; private float averageChangeProneness; - private Integer changePronenessRank; + private Integer changePronenessRank = 0; private float impact; + public RankedCycle( + String cycleName, + Set vertexSet, + Set edgeSet, + double minCutCount, + Set minCutEdges, + List cycleNodes) { + this.cycleNodes = cycleNodes; + this.cycleName = cycleName; + this.vertexSet = vertexSet; + this.edgeSet = edgeSet; + this.minCutCount = minCutCount; + + if (null == minCutEdges) { + this.minCutEdges = new HashSet<>(); + } else { + this.minCutEdges = minCutEdges; + } + + if (minCutCount == 0.0) { + this.impact = (float) (vertexSet.size()); + } else { + this.impact = (float) (vertexSet.size() / minCutCount); + } + + this.rawPriority = this.impact; + } + public RankedCycle( String cycleName, Integer changePronenessRankSum, 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 a96b77b..45bb08d 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -7,7 +7,6 @@ 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; @@ -174,11 +173,6 @@ void renderCBOChart( stringBuilder.append(COUPLING_BETWEEN_OBJECT_CHART_LEGEND); } - @Override - public List runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) { - return costBenefitCalculator.runCycleAnalysis(); - } - @Override public void renderCycleImage( Graph classGraph, RankedCycle cycle, StringBuilder stringBuilder) { 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 3628890..5f1e5d7 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -63,17 +63,17 @@ public class SimpleHtmlReport { "Class", "Priority", "Change Proneness Rank", "Coupling Count", "Most Recent Commit Date", "Commit Count" }; - 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; + private boolean showDetails = false; + public void execute( boolean showDetails, String projectName, String projectVersion, String outputDirectory, File baseDir) { + this.showDetails = showDetails; + final String[] godClassTableHeadings = showDetails ? godClassDetailedTableHeadings : godClassSimpleTableHeadings; @@ -143,7 +143,12 @@ public void execute( costBenefitCalculator.runPmdAnalysis(); rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); - rankedCycles = runCycleAnalysis(costBenefitCalculator, outputDirectory); + if (showDetails) { + rankedCycles = costBenefitCalculator.runCycleAnalysisAndCalculateCycleChurn(); + } else { + rankedCycles = costBenefitCalculator.runCycleAnalysis(); + } + classGraph = costBenefitCalculator.getClassReferencesGraph(); } catch (Exception e) { log.error("Error running analysis."); @@ -217,10 +222,6 @@ public void execute( log.info("Done! View the report at target/site/{}", filename); } - public List runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) { - return costBenefitCalculator.runCycleAnalysis(); - } - private void renderCycles( String outputDirectory, StringBuilder stringBuilder, @@ -233,6 +234,16 @@ private void renderCycles( "

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

\n"); stringBuilder.append("\n"); + String[] cycleTableHeadings; + if (showDetails) { + cycleTableHeadings = new String[] { + "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts" + }; + } else { + cycleTableHeadings = + new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count", "Minimum Cuts"}; + } + // Content stringBuilder.append("\n\n"); for (String heading : cycleTableHeadings) { @@ -250,16 +261,28 @@ private void renderCycles( edgesToCut.append("
\n"); } - // "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() - }; - + String[] rankedCycleData; + if (showDetails) { + rankedCycleData = new String[] { + // "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min + // Cuts" + rankedCycle.getCycleName(), + rankedCycle.getPriority().toString(), + rankedCycle.getChangePronenessRank().toString(), + String.valueOf(rankedCycle.getCycleNodes().size()), + String.valueOf(rankedCycle.getEdgeSet().size()), + edgesToCut.toString() + }; + } else { + rankedCycleData = new String[] { + // "Cycle Name", "Priority", "Class Count", "Relationship Count", "Min Cuts" + rankedCycle.getCycleName(), + rankedCycle.getPriority().toString(), + String.valueOf(rankedCycle.getCycleNodes().size()), + String.valueOf(rankedCycle.getEdgeSet().size()), + edgesToCut.toString() + }; + } for (String rowData : rankedCycleData) { drawTableCell(rowData, stringBuilder); }