Skip to content

Commit

Permalink
Merge pull request #89 from refactorfirst/add-cycle-tables
Browse files Browse the repository at this point in the history
Generating Cycle data
  • Loading branch information
jimbethancourt authored Jul 3, 2024
2 parents ca4db07 + c82af91 commit 5ff6bc2
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 50 deletions.
4 changes: 2 additions & 2 deletions circular-reference-detector/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ public class CircularReferenceDetectorApp {

private Map<String, AsSubgraph> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public CostBenefitCalculator(String repositoryPath) {
changePronenessRanker = new ChangePronenessRanker(repository, gitLogReader);
}

public List<RankedCycle> runCycleAnalysis() {
public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean renderImages) {
List<RankedCycle> rankedCycles = new ArrayList<>();
try {
Map<String, String> classNamesAndPaths = getClassNamesAndPaths();
Expand All @@ -72,7 +72,15 @@ public List<RankedCycle> runCycleAnalysis() {
double minCut = 0;
Set<DefaultEdge> 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<String, DefaultEdge> gusfieldGomoryHuCutTree =
Expand All @@ -85,36 +93,36 @@ public List<RankedCycle> runCycleAnalysis() {
for (DefaultEdge minCutEdge : minCutEdges) {
log.info(minCutEdge.toString());
}
}

List<CycleNode> cycleNodes = subGraph.vertexSet().stream()
.map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle)))
.collect(Collectors.toList());
List<ScmLogInfo> changeRanks = getRankedChangeProneness(cycleNodes);
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<>();
Map<String, CycleNode> 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));
Expand Down Expand Up @@ -295,14 +303,16 @@ public Map<String, String> getClassNamesAndPaths() throws IOException {

Map<String, String> 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<Path> 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;
}
Expand Down
11 changes: 11 additions & 0 deletions report/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

<dependency>
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-core</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-ext</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>

</project>
18 changes: 18 additions & 0 deletions report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -77,6 +79,7 @@ void renderGithubButtons(StringBuilder stringBuilder) {
}

// TODO: Move to another class to allow use by Gradle plugin
@Override
void writeGodClassGchartJs(
List<RankedDisharmony> rankedDisharmonies, int maxPriority, String reportOutputDirectory) {
GraphDataGenerator graphDataGenerator = new GraphDataGenerator();
Expand Down Expand Up @@ -169,4 +172,19 @@ void renderCBOChart(
renderGithubButtons(stringBuilder);
stringBuilder.append(COUPLING_BETWEEN_OBJECT_CHART_LEGEND);
}

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

@Override
public void renderCycleImage(String cycleName, StringBuilder stringBuilder, String outputDirectory) {
stringBuilder.append("<div align=\"center\">");
stringBuilder.append("<img src=\"./refactorFirst/cycles/graph" + cycleName
+ ".png\" width=\"1000\" height=\"1000\" alt=\"Cycle " + cycleName + "\">");
stringBuilder.append("</div>");
stringBuilder.append("<br/>");
stringBuilder.append("<br/>");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {

Expand Down Expand Up @@ -156,7 +160,8 @@ public void execute(
}
List<RankedDisharmony> rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues();
List<RankedDisharmony> rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues();
List<RankedCycle> rankedCycles = costBenefitCalculator.runCycleAnalysis();

List<RankedCycle> rankedCycles = runCycleAnalysis(costBenefitCalculator, outputDirectory);

if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty()) {
stringBuilder
Expand All @@ -175,7 +180,15 @@ public void execute(
if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) {
stringBuilder.append("<a href=\"#GOD\">God Classes</a>");
stringBuilder.append("<br/>");
}

if (!rankedCBODisharmonies.isEmpty()) {
stringBuilder.append("<a href=\"#CBO\">Highly Coupled Classes</a>");
stringBuilder.append("<br/>");
}

if (!rankedCycles.isEmpty()) {
stringBuilder.append("<a href=\"#CYCLES\">Class Cycles</a>");
}

if (!rankedGodClassDisharmonies.isEmpty()) {
Expand All @@ -197,6 +210,13 @@ public void execute(
}

if (!rankedCycles.isEmpty()) {
if (!rankedGodClassDisharmonies.isEmpty() || !rankedCBODisharmonies.isEmpty()) {
stringBuilder.append("<br/>");
stringBuilder.append("<br/>");
stringBuilder.append("<hr/>");
stringBuilder.append("<br/>");
stringBuilder.append("<br/>");
}
renderCycles(outputDirectory, stringBuilder, rankedCycles, formatter);
}

Expand All @@ -210,13 +230,17 @@ public void execute(
log.info("Done! View the report at target/site/{}", filename);
}

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

private void renderCycles(
String outputDirectory,
StringBuilder stringBuilder,
List<RankedCycle> rankedCycles,
DateTimeFormatter formatter) {

stringBuilder.append("<div style=\"text-align: center;\"><a id=\"CBO\"><h1>Class Cycles</h1></a></div>");
stringBuilder.append("<div style=\"text-align: center;\"><a id=\"CYCLES\"><h1>Class Cycles</h1></a></div>");

stringBuilder.append(
"<h2 align=\"center\">Class Cycles by the numbers: (Refactor starting with Priority 1)</h2>");
Expand All @@ -229,17 +253,23 @@ private void renderCycles(
}

stringBuilder.append("<tbody>");
for (RankedCycle rankedCboClassDisharmony : rankedCycles) {
for (RankedCycle rankedCycle : rankedCycles) {
stringBuilder.append("<tr>");

StringBuilder edgesToCut = new StringBuilder();
for (DefaultEdge minCutEdge : rankedCycle.getMinCutEdges()) {
edgesToCut.append(minCutEdge.toString());
edgesToCut.append("</br>");
}

// "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) {
Expand All @@ -254,6 +284,70 @@ private void renderCycles(
stringBuilder.append("</tr></thead>");

stringBuilder.append("</table>");

for (RankedCycle rankedCycle : rankedCycles) {
renderCycleTable(outputDirectory, stringBuilder, rankedCycle, formatter);
}
}

private void renderCycleTable(
String outputDirectory, StringBuilder stringBuilder, RankedCycle cycle, DateTimeFormatter formatter) {

stringBuilder.append("<br/>");
stringBuilder.append("<br/>");
stringBuilder.append("<hr/>");
stringBuilder.append("<br/>");
stringBuilder.append("<br/>");

stringBuilder.append("<h2 align=\"center\">Class Cycle : " + cycle.getCycleName() + "</h2>");
renderCycleImage(cycle.getCycleName(), stringBuilder, outputDirectory);

stringBuilder.append("<div align=\"center\">");
stringBuilder.append("<strong>");
stringBuilder.append("\"*\" indicates relationship(s) to remove to decompose cycle");
stringBuilder.append("</strong>");
stringBuilder.append("</div>");

stringBuilder.append("<table align=\"center\" border=\"5px\">");

// Content
stringBuilder.append("<thead><tr>");
for (String heading : classCycleTableHeadings) {
stringBuilder.append("<th>").append(heading).append("</th>");
}

stringBuilder.append("<tbody>");

for (String vertex : cycle.getVertexSet()) {
stringBuilder.append("<tr>");
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("<strong>");
edges.append(edge + "*");
edges.append("</strong>");
} else {
edges.append(edge);
}

edges.append("<br/>");
}
}
drawTableCell(edges.toString(), stringBuilder);
stringBuilder.append("</tr>");
}

stringBuilder.append("</tbody>");

stringBuilder.append("</tr></thead>");

stringBuilder.append("</table>");
}

public void renderCycleImage(String cycleName, StringBuilder stringBuilder, String outputDirectory) {
// empty on purpose
}

private void renderGodClassInfo(
Expand Down

0 comments on commit 5ff6bc2

Please sign in to comment.