Skip to content

Commit

Permalink
Merge pull request #106 from refactorfirst/optionally-calculate-commi…
Browse files Browse the repository at this point in the history
…t-count-of-cycle-classes

#93 Only calculating cycle class churn when `showDetails = true`
  • Loading branch information
jimbethancourt authored Sep 29, 2024
2 parents 827e170 + c786fbb commit 691e632
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,73 +73,125 @@ public List<RankedCycle> runCycleAnalysis() {
StaticJavaParser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.BLEEDING_EDGE);
List<RankedCycle> rankedCycles = new ArrayList<>();
try {
Map<String, String> classNamesAndPaths = getClassNamesAndPaths();
classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath);
CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker();
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap =
circularReferenceChecker.detectCycles(classReferencesGraph);
cyclesForEveryVertexMap.forEach((vertex, subGraph) -> {
int vertexCount = subGraph.vertexSet().size();
int edgeCount = subGraph.edgeSet().size();
double minCut = 0;
Set<DefaultWeightedEdge> minCutEdges;
if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) {
renderedSubGraphs.put(vertex, subGraph);
log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree =
new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph));
minCut = gusfieldGomoryHuCutTree.calculateMinCut();
minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();

List<CycleNode> cycleNodes = subGraph.vertexSet().stream()
.map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle)))
.collect(Collectors.toList());
List<ScmLogInfo> changeRanks = getRankedChangeProneness(cycleNodes);

Map<String, CycleNode> 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<RankedCycle> runCycleAnalysisAndCalculateCycleChurn() {
StaticJavaParser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.BLEEDING_EDGE);
List<RankedCycle> 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<RankedCycle> rankedCycles, boolean calculateChurnForCycles)
throws IOException {
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cycles = getCycles();
Map<String, String> 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<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree =
new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph));
double minCut = gusfieldGomoryHuCutTree.calculateMinCut();
Set<DefaultWeightedEdge> minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();

List<CycleNode> 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<RankedCycle> rankedCycles) {
int priority = 1;
for (RankedCycle rankedCycle : rankedCycles) {
rankedCycle.setPriority(priority++);
}
}

private Map<String, AsSubgraph<String, DefaultWeightedEdge>> getCycles() throws IOException {
classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath);
CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker();
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cycles =
circularReferenceChecker.detectCycles(classReferencesGraph);
return cycles;
}

private static void sortRankedCycles(List<RankedCycle> 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<String, DefaultWeightedEdge> subGraph,
List<CycleNode> cycleNodes,
double minCut,
Set<DefaultWeightedEdge> minCutEdges) {
RankedCycle rankedCycle;
if (calculateChurnForCycles) {
List<ScmLogInfo> changeRanks = getRankedChangeProneness(cycleNodes);

Map<String, CycleNode> 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<String, DefaultWeightedEdge> subGraph, String vertex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
public class RankedCycle {

private final String cycleName;
private final Integer changePronenessRankSum;
private Integer changePronenessRankSum = 0;

private final Set<String> vertexSet;
private final Set<DefaultWeightedEdge> edgeSet;
Expand All @@ -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<String> vertexSet,
Set<DefaultWeightedEdge> edgeSet,
double minCutCount,
Set<DefaultWeightedEdge> minCutEdges,
List<CycleNode> 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -174,11 +173,6 @@ void renderCBOChart(
stringBuilder.append(COUPLING_BETWEEN_OBJECT_CHART_LEGEND);
}

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

@Override
public void renderCycleImage(
Graph<String, DefaultWeightedEdge> classGraph, RankedCycle cycle, StringBuilder stringBuilder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, DefaultWeightedEdge> 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;

Expand Down Expand Up @@ -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.");
Expand Down Expand Up @@ -217,10 +222,6 @@ public void execute(
log.info("Done! View the report at target/site/{}", filename);
}

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

private void renderCycles(
String outputDirectory,
StringBuilder stringBuilder,
Expand All @@ -233,6 +234,16 @@ private void renderCycles(
"<h2 align=\"center\">Class Cycles by the numbers: (Refactor starting with Priority 1)</h2>\n");
stringBuilder.append("<table align=\"center\" border=\"5px\">\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("<thead>\n<tr>\n");
for (String heading : cycleTableHeadings) {
Expand All @@ -250,16 +261,28 @@ private void renderCycles(
edgesToCut.append("</br>\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);
}
Expand Down

0 comments on commit 691e632

Please sign in to comment.