Skip to content

Commit 827e170

Browse files
Merge pull request #105 from refactorfirst/generate-dotfiles
#101 Rendering DOT images instead of png iimages
2 parents 1cc3763 + c998bfe commit 827e170

File tree

9 files changed

+205
-190
lines changed

9 files changed

+205
-190
lines changed

circular-reference-detector/pom.xml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@
1616
<dependency>
1717
<groupId>org.jgrapht</groupId>
1818
<artifactId>jgrapht-core</artifactId>
19-
<version>1.3.0</version>
20-
</dependency>
21-
<dependency>
22-
<groupId>org.jgrapht</groupId>
23-
<artifactId>jgrapht-ext</artifactId>
24-
<version>1.3.0</version>
19+
<version>1.5.2</version>
2520
</dependency>
2621
<dependency>
2722
<groupId>com.github.javaparser</groupId>

circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.hjug.app;
22

3-
import java.io.IOException;
43
import java.util.HashMap;
54
import java.util.Map;
65
import java.util.Set;
@@ -38,44 +37,36 @@ public void launchApp(String[] args) {
3837
printCommandUsage();
3938
} else {
4039
String srcDirectoryPath = args[0];
41-
String outputDirectoryPath = args[1];
4240
JavaProjectParser javaProjectParser = new JavaProjectParser();
4341
try {
4442
Graph<String, DefaultWeightedEdge> classReferencesGraph =
4543
javaProjectParser.getClassReferences(srcDirectoryPath);
46-
detectAndStoreCyclesInDirectory(outputDirectoryPath, classReferencesGraph);
44+
detectAndStoreCyclesInDirectory(classReferencesGraph);
4745
} catch (Exception e) {
4846
e.printStackTrace();
4947
}
5048
}
5149
}
5250

53-
private void detectAndStoreCyclesInDirectory(
54-
String outputDirectoryPath, Graph<String, DefaultWeightedEdge> classReferencesGraph) {
51+
private void detectAndStoreCyclesInDirectory(Graph<String, DefaultWeightedEdge> classReferencesGraph) {
5552
CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker();
5653
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap =
5754
circularReferenceChecker.detectCycles(classReferencesGraph);
5855
cyclesForEveryVertexMap.forEach((vertex, subGraph) -> {
59-
try {
60-
int vertexCount = subGraph.vertexSet().size();
61-
int edgeCount = subGraph.edgeSet().size();
62-
if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) {
63-
circularReferenceChecker.createImage(outputDirectoryPath, subGraph, vertex);
64-
renderedSubGraphs.put(vertex, subGraph);
65-
System.out.println(
66-
"Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
67-
GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree =
68-
new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph));
69-
double minCut = gusfieldGomoryHuCutTree.calculateMinCut();
70-
System.out.println("Min cut weight: " + minCut);
71-
Set<DefaultWeightedEdge> minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();
72-
System.out.println("Minimum Cut Edges:");
73-
for (DefaultWeightedEdge minCutEdge : minCutEdges) {
74-
System.out.println(minCutEdge);
75-
}
56+
int vertexCount = subGraph.vertexSet().size();
57+
int edgeCount = subGraph.edgeSet().size();
58+
if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) {
59+
renderedSubGraphs.put(vertex, subGraph);
60+
System.out.println("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
61+
GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree =
62+
new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph));
63+
double minCut = gusfieldGomoryHuCutTree.calculateMinCut();
64+
System.out.println("Min cut weight: " + minCut);
65+
Set<DefaultWeightedEdge> minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();
66+
System.out.println("Minimum Cut Edges:");
67+
for (DefaultWeightedEdge minCutEdge : minCutEdges) {
68+
System.out.println(minCutEdge);
7669
}
77-
} catch (IOException e) {
78-
e.printStackTrace();
7970
}
8071
});
8172
}
Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
package org.hjug.cycledetector;
22

3-
import com.mxgraph.layout.mxCircleLayout;
4-
import com.mxgraph.layout.mxIGraphLayout;
5-
import com.mxgraph.util.mxCellRenderer;
6-
import java.awt.Color;
7-
import java.awt.image.BufferedImage;
8-
import java.io.File;
9-
import java.io.IOException;
103
import java.util.HashMap;
114
import java.util.Map;
12-
import javax.imageio.ImageIO;
135
import org.jgrapht.Graph;
146
import org.jgrapht.alg.cycle.CycleDetector;
15-
import org.jgrapht.ext.JGraphXAdapter;
167
import org.jgrapht.graph.AsSubgraph;
178
import org.jgrapht.graph.DefaultWeightedEdge;
189

@@ -36,29 +27,4 @@ public Map<String, AsSubgraph<String, DefaultWeightedEdge>> detectCycles(
3627
});
3728
return cyclesForEveryVertexMap;
3829
}
39-
40-
/**
41-
* Given graph and image name, use jgrapht to create .png file of graph in given outputDirectory.
42-
* Create outputDirectory if it does not exist.
43-
*
44-
* @param outputDirectoryPath
45-
* @param subGraph
46-
* @param imageName
47-
* @throws IOException
48-
*/
49-
public void createImage(String outputDirectoryPath, Graph<String, DefaultWeightedEdge> subGraph, String imageName)
50-
throws IOException {
51-
new File(outputDirectoryPath).mkdirs();
52-
File imgFile = new File(outputDirectoryPath + "/graph" + imageName + ".png");
53-
if (imgFile.createNewFile()) {
54-
JGraphXAdapter<String, DefaultWeightedEdge> graphAdapter = new JGraphXAdapter<>(subGraph);
55-
mxIGraphLayout layout = new mxCircleLayout(graphAdapter);
56-
layout.execute(graphAdapter.getDefaultParent());
57-
58-
BufferedImage image = mxCellRenderer.createBufferedImage(graphAdapter, null, 2, Color.WHITE, true, null);
59-
if (image != null) {
60-
ImageIO.write(image, "PNG", imgFile);
61-
}
62-
}
63-
}
6430
}

circular-reference-detector/src/test/java/org/hjug/cycledetector/CircularReferenceCheckerTests.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import static org.junit.jupiter.api.Assertions.assertTrue;
55

66
import java.io.File;
7-
import java.io.IOException;
87
import java.util.Map;
98
import org.jgrapht.Graph;
109
import org.jgrapht.graph.AsSubgraph;
@@ -34,16 +33,14 @@ public void detectCyclesTest() {
3433

3534
@DisplayName("Create graph image in given outputDirectory")
3635
@Test
37-
public void createImageTest() throws IOException {
36+
public void createImageTest() {
3837
Graph<String, DefaultWeightedEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultWeightedEdge.class);
3938
classReferencesGraph.addVertex("A");
4039
classReferencesGraph.addVertex("B");
4140
classReferencesGraph.addVertex("C");
4241
classReferencesGraph.addEdge("A", "B");
4342
classReferencesGraph.addEdge("B", "C");
4443
classReferencesGraph.addEdge("C", "A");
45-
sutCircularReferenceChecker.createImage(
46-
"src/test/resources/testOutputDirectory", classReferencesGraph, "testGraph");
4744
File newGraphImage = new File("src/test/resources/testOutputDirectory/graphtestGraph.png");
4845
assertTrue(newGraphImage.exists() && !newGraphImage.isDirectory());
4946
}

cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public void close() throws Exception {
6969
gitLogReader.close();
7070
}
7171

72-
public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean renderImages) {
72+
public List<RankedCycle> runCycleAnalysis() {
7373
StaticJavaParser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.BLEEDING_EDGE);
7474
List<RankedCycle> rankedCycles = new ArrayList<>();
7575
try {
@@ -84,15 +84,6 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re
8484
double minCut = 0;
8585
Set<DefaultWeightedEdge> minCutEdges;
8686
if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) {
87-
if (renderImages) {
88-
try {
89-
circularReferenceChecker.createImage(
90-
outputDirectoryPath + "/refactorFirst/cycles", subGraph, vertex);
91-
} catch (IOException e) {
92-
throw new RuntimeException(e);
93-
}
94-
}
95-
9687
renderedSubGraphs.put(vertex, subGraph);
9788
log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
9889
GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree =

refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ public void executeReport(Locale locale) throws MavenReportException {
447447
}
448448

449449
public List<RankedCycle> runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) {
450-
return costBenefitCalculator.runCycleAnalysis(outputDirectory, true);
450+
return costBenefitCalculator.runCycleAnalysis();
451451
}
452452

453453
private void renderCycles(

report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import org.hjug.cbc.RankedCycle;
1212
import org.hjug.cbc.RankedDisharmony;
1313
import org.hjug.gdg.GraphDataGenerator;
14+
import org.jgrapht.Graph;
15+
import org.jgrapht.graph.DefaultWeightedEdge;
1416

1517
@Slf4j
1618
public class HtmlReport extends SimpleHtmlReport {
@@ -39,14 +41,13 @@ public class HtmlReport extends SimpleHtmlReport {
3941

4042
@Override
4143
public void printHead(StringBuilder stringBuilder) {
42-
stringBuilder.append("<link rel=\"stylesheet\" href=\"./css/maven-base.css\" />\n"
43-
+ " <link rel=\"stylesheet\" href=\"./css/maven-theme.css\" />\n"
44-
+ " <link rel=\"stylesheet\" href=\"./css/site.css\" />\n"
45-
+ " <link rel=\"stylesheet\" href=\"./css/print.css\" media=\"print\" />\n"
46-
+ "<script async defer src=\"https://buttons.github.io/buttons.js\"></script>"
44+
stringBuilder.append("<script async defer src=\"https://buttons.github.io/buttons.js\"></script>"
4745
+ "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\">"
48-
+ "</script><script type=\"text/javascript\" src=\"./gchart.js\"></script>"
49-
+ "<script type=\"text/javascript\" src=\"./gchart2.js\"></script>"
46+
+ "</script><script type=\"text/javascript\" src=\"./gchart.js\"></script>\n"
47+
+ "<script type=\"text/javascript\" src=\"./gchart2.js\"></script>\n"
48+
+ "<script src=\"https://d3js.org/d3.v5.min.js\"></script>\n"
49+
+ "<script src=\"https://unpkg.com/[email protected]/build/d3-graphviz.min.js\"></script>\n"
50+
+ "<script src=\"https://unpkg.com/@hpcc-js/[email protected]/dist/index.min.js\"></script>\n"
5051
+ " </head>\n");
5152
}
5253

@@ -57,24 +58,24 @@ public void printTitle(String projectName, String projectVersion, StringBuilder
5758
.append(projectName)
5859
.append(" ")
5960
.append(projectVersion)
60-
.append(" </title>");
61+
.append(" </title>\n");
6162
}
6263

6364
@Override
6465
void renderGithubButtons(StringBuilder stringBuilder) {
65-
stringBuilder.append("<div align=\"center\">");
66-
stringBuilder.append("Show RefactorFirst some &#10084;&#65039;");
67-
stringBuilder.append("<br/>");
66+
stringBuilder.append("<div align=\"center\">\n");
67+
stringBuilder.append("Show RefactorFirst some &#10084;&#65039;\n");
68+
stringBuilder.append("<br/>\n");
6869
stringBuilder.append(
69-
"<a class=\"github-button\" href=\"https://github.com/refactorfirst/refactorfirst\" data-icon=\"octicon-star\" data-size=\"large\" data-show-count=\"true\" aria-label=\"Star refactorfirst/refactorfirst on GitHub\">Star</a>");
70+
"<a class=\"github-button\" href=\"https://github.com/refactorfirst/refactorfirst\" data-icon=\"octicon-star\" data-size=\"large\" data-show-count=\"true\" aria-label=\"Star refactorfirst/refactorfirst on GitHub\">Star</a>\n");
7071
stringBuilder.append(
71-
"<a class=\"github-button\" href=\"https://github.com/refactorfirst/refactorfirst/fork\" data-icon=\"octicon-repo-forked\" data-size=\"large\" data-show-count=\"true\" aria-label=\"Fork refactorfirst/refactorfirst on GitHub\">Fork</a>");
72+
"<a class=\"github-button\" href=\"https://github.com/refactorfirst/refactorfirst/fork\" data-icon=\"octicon-repo-forked\" data-size=\"large\" data-show-count=\"true\" aria-label=\"Fork refactorfirst/refactorfirst on GitHub\">Fork</a>\n");
7273
stringBuilder.append(
73-
"<a class=\"github-button\" href=\"https://github.com/refactorfirst/refactorfirst/subscription\" data-icon=\"octicon-eye\" data-size=\"large\" data-show-count=\"true\" aria-label=\"Watch refactorfirst/refactorfirst on GitHub\">Watch</a>");
74+
"<a class=\"github-button\" href=\"https://github.com/refactorfirst/refactorfirst/subscription\" data-icon=\"octicon-eye\" data-size=\"large\" data-show-count=\"true\" aria-label=\"Watch refactorfirst/refactorfirst on GitHub\">Watch</a>\n");
7475
stringBuilder.append(
75-
"<a class=\"github-button\" href=\"https://github.com/refactorfirst/refactorfirst/issues\" data-icon=\"octicon-issue-opened\" data-size=\"large\" data-show-count=\"false\" aria-label=\"Issue refactorfirst/refactorfirst on GitHub\">Issue</a>");
76+
"<a class=\"github-button\" href=\"https://github.com/refactorfirst/refactorfirst/issues\" data-icon=\"octicon-issue-opened\" data-size=\"large\" data-show-count=\"false\" aria-label=\"Issue refactorfirst/refactorfirst on GitHub\">Issue</a>\n");
7677
stringBuilder.append(
77-
"<a class=\"github-button\" href=\"https://github.com/sponsors/jimbethancourt\" data-icon=\"octicon-heart\" data-size=\"large\" aria-label=\"Sponsor @jimbethancourt on GitHub\">Sponsor</a>");
78+
"<a class=\"github-button\" href=\"https://github.com/sponsors/jimbethancourt\" data-icon=\"octicon-heart\" data-size=\"large\" aria-label=\"Sponsor @jimbethancourt on GitHub\">Sponsor</a>\n");
7879
stringBuilder.append("</div>");
7980
}
8081

@@ -156,7 +157,7 @@ void renderGodClassChart(
156157
int maxGodClassPriority,
157158
StringBuilder stringBuilder) {
158159
writeGodClassGchartJs(rankedGodClassDisharmonies, maxGodClassPriority - 1, outputDirectory);
159-
stringBuilder.append("<div id=\"series_chart_div\" align=\"center\"></div>");
160+
stringBuilder.append("<div id=\"series_chart_div\" align=\"center\"></div>\n");
160161
renderGithubButtons(stringBuilder);
161162
stringBuilder.append(GOD_CLASS_CHART_LEGEND);
162163
}
@@ -168,23 +169,72 @@ void renderCBOChart(
168169
int maxCboPriority,
169170
StringBuilder stringBuilder) {
170171
writeGCBOGchartJs(rankedCBODisharmonies, maxCboPriority - 1, outputDirectory);
171-
stringBuilder.append("<div id=\"series_chart_div_2\" align=\"center\"></div>");
172+
stringBuilder.append("<div id=\"series_chart_div_2\" align=\"center\"></div>\n");
172173
renderGithubButtons(stringBuilder);
173174
stringBuilder.append(COUPLING_BETWEEN_OBJECT_CHART_LEGEND);
174175
}
175176

176177
@Override
177178
public List<RankedCycle> runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) {
178-
return costBenefitCalculator.runCycleAnalysis(outputDirectory, true);
179+
return costBenefitCalculator.runCycleAnalysis();
179180
}
180181

181182
@Override
182-
public void renderCycleImage(String cycleName, StringBuilder stringBuilder, String outputDirectory) {
183-
stringBuilder.append("<div align=\"center\">");
184-
stringBuilder.append("<img src=\"./refactorFirst/cycles/graph" + cycleName
185-
+ ".png\" width=\"1000\" height=\"1000\" alt=\"Cycle " + cycleName + "\">");
186-
stringBuilder.append("</div>");
187-
stringBuilder.append("<br/>");
188-
stringBuilder.append("<br/>");
183+
public void renderCycleImage(
184+
Graph<String, DefaultWeightedEdge> classGraph, RankedCycle cycle, StringBuilder stringBuilder) {
185+
String dot = buildDot(classGraph, cycle);
186+
187+
stringBuilder.append("<div align=\"center\" id=\"" + cycle.getCycleName() + "\"></div>\n");
188+
stringBuilder.append("<script>\n");
189+
stringBuilder.append("d3.select(\"#" + cycle.getCycleName() + "\")\n");
190+
stringBuilder.append(".graphviz()\n");
191+
stringBuilder.append(".renderDot(" + dot + ");\n");
192+
stringBuilder.append("</script>\n");
193+
}
194+
195+
String buildDot(Graph<String, DefaultWeightedEdge> classGraph, RankedCycle cycle) {
196+
StringBuilder dot = new StringBuilder();
197+
198+
dot.append("'strict digraph G {\\n' +\n");
199+
200+
// render vertices
201+
// e.g DownloadManager;
202+
for (String vertex : cycle.getVertexSet()) {
203+
dot.append("'");
204+
dot.append(vertex);
205+
dot.append(";\\n' +\n");
206+
}
207+
208+
for (DefaultWeightedEdge edge : cycle.getEdgeSet()) {
209+
// 'DownloadManager -> Download [ label="1" color="red" ];'
210+
211+
// render edge
212+
String[] vertexes =
213+
edge.toString().replace("(", "").replace(")", "").split(":");
214+
215+
String start = vertexes[0].trim();
216+
String end = vertexes[1].trim();
217+
218+
dot.append("'");
219+
dot.append(start);
220+
dot.append(" -> ");
221+
dot.append(end);
222+
223+
// render edge attributes
224+
dot.append(" [ ");
225+
dot.append("label = \"");
226+
dot.append((int) classGraph.getEdgeWeight(edge));
227+
dot.append("\"");
228+
229+
if (cycle.getMinCutEdges().contains(edge)) {
230+
dot.append(" color = \"red\"");
231+
}
232+
233+
dot.append(" ];\\n' +\n");
234+
}
235+
236+
dot.append("'}'");
237+
238+
return dot.toString();
189239
}
190240
}

0 commit comments

Comments
 (0)