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
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