Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading