diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorFullTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorFullTest.java new file mode 100644 index 00000000..5701e9ff --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorFullTest.java @@ -0,0 +1,69 @@ +package org.hjug.graphbuilder.visitor; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +public class JavaNewClassVisitorFullTest { + + @Test + void visitNewClass() throws IOException { + + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass"); + + JavaParser javaParser = JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + Graph classReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + Graph packageReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + final JavaVisitor javaVisitor = + new JavaVisitor<>(classReferencesGraph, packageReferencesGraph); + final JavaVariableTypeVisitor javaVariableTypeVisitor = + new JavaVariableTypeVisitor<>(classReferencesGraph, packageReferencesGraph); + final JavaMethodDeclarationVisitor javaMethodDeclarationVisitor = + new JavaMethodDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); + + List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + + // Parse sources with all visitors, not only + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + javaVisitor.visit(cu, ctx); + javaVariableTypeVisitor.visit(cu, ctx); + javaMethodDeclarationVisitor.visit(cu, ctx); + }); + + Graph graph = javaVisitor.getClassReferencesGraph(); + Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.newClass.A")); + Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.newClass.B")); + Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.newClass.C")); + + // capturing counts of all types + Assertions.assertEquals( + 6, + graph.getEdgeWeight(graph.getEdge( + "org.hjug.graphbuilder.visitor.testclasses.newClass.A", + "org.hjug.graphbuilder.visitor.testclasses.newClass.B"))); + + Assertions.assertEquals( + 3, + graph.getEdgeWeight(graph.getEdge( + "org.hjug.graphbuilder.visitor.testclasses.newClass.A", + "org.hjug.graphbuilder.visitor.testclasses.newClass.C"))); + } +} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorTest.java index 8612b12b..504dacb9 100644 --- a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorTest.java @@ -48,7 +48,13 @@ void visitNewClass() throws IOException { Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.newClass.B")); Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.newClass.C")); - // TODO: Investigate further to confirm correctness + // only looking for what was visited by classDeclarationVisitor and variableTypeVisitor + Assertions.assertEquals( + 5, + graph.getEdgeWeight(graph.getEdge( + "org.hjug.graphbuilder.visitor.testclasses.newClass.A", + "org.hjug.graphbuilder.visitor.testclasses.newClass.B"))); + Assertions.assertEquals( 3, graph.getEdgeWeight(graph.getEdge( diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/A.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/A.java index f99d037e..72852649 100644 --- a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/A.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/A.java @@ -1,11 +1,19 @@ package org.hjug.graphbuilder.visitor.testclasses.newClass; +import java.util.ArrayList; +import java.util.List; + public class A { B newClassMethod() { new C(); C c = new C(); + // var treated like "B", counts as 2 + var b = new B(null); + // <> treated like , counts as 2 + List listB = new ArrayList<>(); + // TODO: add visitor for J.ReturnType return new B(c); } diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index 26c27930..8c7ecc10 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -78,8 +78,7 @@ public void executeReport(Locale locale) { testSourceDirectory, projectName, projectVersion, - project.getBasedir(), - 300) + project.getBasedir()) .toString(); mainSink.rawText(report); @@ -95,24 +94,17 @@ private void printHead(Sink mainSink) { renderJsDeclaration(mainSink, "https://buttons.github.io/buttons.js"); // google chart import renderJsDeclaration(mainSink, "https://www.gstatic.com/charts/loader.js"); - // d3 dot graph imports - renderJsDeclaration(mainSink, "https://d3js.org/d3.v5.min.js"); - renderJsDeclaration(mainSink, "https://cdnjs.cloudflare.com/ajax/libs/d3-graphviz/3.0.5/d3-graphviz.min.js"); - renderJsDeclaration(mainSink, "https://unpkg.com/@hpcc-js/wasm@0.3.11/dist/index.min.js"); + // for DOT graph zooming + renderJsDeclaration(mainSink, "https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"); // sigma graph imports - sigma, graphology, graphlib, and graphlib-dot renderJsDeclaration(mainSink, "https://cdnjs.cloudflare.com/ajax/libs/sigma.js/2.4.0/sigma.min.js"); renderJsDeclaration(mainSink, "https://cdnjs.cloudflare.com/ajax/libs/graphology/0.25.4/graphology.umd.min.js"); + // may only need graphlib-dot renderJsDeclaration(mainSink, "https://cdnjs.cloudflare.com/ajax/libs/graphlib/2.1.8/graphlib.min.js"); renderJsDeclaration(mainSink, "https://cdn.jsdelivr.net/npm/graphlib-dot@0.6.4/dist/graphlib-dot.min.js"); - renderJsDeclaration(mainSink, "https://unpkg.com/3d-force-graph"); - - // renderJsDeclaration(mainSink, HtmlReport.SUGIYAMA_SIGMA_GRAPH); - // renderJsDeclaration(mainSink, HtmlReport.FORCE_3D_GRAPH); - // renderJsDeclaration(mainSink, HtmlReport.POPUP_FUNCTIONS); - - // renderStyle(mainSink); + renderJsDeclaration(mainSink, "https://cdn.jsdelivr.net/npm/3d-force-graph"); mainSink.head_(); } diff --git a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java index c09526d6..9e35a6e1 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -390,16 +390,23 @@ public String printHead() { // google chart import + "\n" // d3 dot graph imports - + "\n" - + "\n" - + "\n" + // + "\n" + // + "\n" + // + "\n" + + // + "\n" + + "" + // sigma graph imports - sigma, graphology, graphlib, and graphlib-dot + "\n" + "\n" // may only need graphlib-dot + "\n" + "\n" - + "\n"; + + "\n"; } String printScripts() { @@ -489,86 +496,88 @@ String renderCBOChart(List rankedCBODisharmonies, int maxCboPr } @Override - public String renderClassGraphDotImage() { + public String renderClassGraphVisuals() { String dot = buildClassGraphDot(classGraph); - String classGraphName = "classGraph"; StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("

Class Map

"); + stringBuilder.append(generateGraphButtons(classGraphName, dot)); + stringBuilder.append( "
Excludes classes that have no incoming and outgoing edges
"); + + int classCount = classGraph.vertexSet().size(); + int relationshipCount = classGraph.edgeSet().size(); + stringBuilder.append("
Number of classes: " + classCount + " Number of relationships: " + + relationshipCount + "
"); + if (classCount + relationshipCount < d3Threshold) { + stringBuilder.append(generateDotImage(classGraphName)); + } else { + // revisit and add DOT SVG popup button + stringBuilder.append("
\nSVG is too big to render quickly
\n"); + } + + return stringBuilder.toString(); + } + + private StringBuilder generateGraphButtons(String graphName, String dot) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("

Class Map

"); stringBuilder.append("\n"); - stringBuilder.append(generateForce3DPopup(classGraphName)); - stringBuilder.append(generate2DPopup(classGraphName)); - stringBuilder.append(generateHidePopup(classGraphName)); + stringBuilder.append(generateForce3DPopup(graphName)); + stringBuilder.append(generate2DPopup(graphName)); + stringBuilder.append(generateHidePopup(graphName)); stringBuilder.append("
\nRed lines represent back edges to remove.
\n"); stringBuilder.append("Zoom in / out with your mouse wheel and click/move to drag the image.\n"); stringBuilder.append("
\n"); + return stringBuilder; + } + private static String generateDotImage(String graphName) { // revisit and add D3 popup button as well - if (classGraph.vertexSet().size() + classGraph.edgeSet().size() < d3Threshold) { - stringBuilder.append( - "
\n"); - stringBuilder.append("\n"); - } else { - // revisit and add D3 SVG popup button - stringBuilder.append("
\nClass Map SVG is too big to render SVG quickly
\n"); - } - - return stringBuilder.toString(); + return "
\n" + + "\n"; } String buildClassGraphDot(Graph classGraph) { StringBuilder dot = new StringBuilder(); dot.append("`strict digraph G {\n"); - Set vertexesToRender = new HashSet<>(); for (DefaultWeightedEdge edge : classGraph.edgeSet()) { - // DownloadManager -> Download [ label="1" color="red" ]; + renderEdge(classGraph, edge, dot); + } - // render edge + // capture only classes that have a relationship with one or more other classes + Set vertexesToRender = new HashSet<>(); + for (DefaultWeightedEdge edge : classGraph.edgeSet()) { String[] vertexes = extractVertexes(edge); - String start = getClassName(vertexes[0].trim()).replace("$", "_"); - String end = getClassName(vertexes[1].trim()).replace("$", "_"); - vertexesToRender.add(vertexes[0].trim()); vertexesToRender.add(vertexes[1].trim()); - - dot.append(start); - dot.append(" -> "); - dot.append(end); - - // render edge attributes - int edgeWeight = (int) classGraph.getEdgeWeight(edge); - dot.append(" [ "); - dot.append("label = \""); - dot.append(edgeWeight); - dot.append("\" "); - dot.append("weight = \""); - dot.append(edgeWeight); - dot.append("\""); - - if (edgesAboveDiagonal.contains(edge)) { - dot.append(" color = \"red\""); - } - - dot.append(" ];\n"); } // render vertices - // e.g DownloadManager; - // for (String vertex : classGraph.vertexSet()) { for (String vertex : vertexesToRender) { dot.append(getClassName(vertex).replace("$", "_")); dot.append(";\n"); @@ -578,40 +587,49 @@ String buildClassGraphDot(Graph classGraph) { return dot.toString(); } + private void renderEdge( + Graph classGraph, DefaultWeightedEdge edge, StringBuilder dot) { + // render edge + String[] vertexes = extractVertexes(edge); + + String start = getClassName(vertexes[0].trim()).replace("$", "_"); + String end = getClassName(vertexes[1].trim()).replace("$", "_"); + + dot.append(start); + dot.append(" -> "); + dot.append(end); + + // render edge attributes + int edgeWeight = (int) classGraph.getEdgeWeight(edge); + dot.append(" [ "); + dot.append("label = \""); + dot.append(edgeWeight); + dot.append("\" "); + dot.append("weight = \""); + dot.append(edgeWeight); + dot.append("\""); + + if (edgesAboveDiagonal.contains(edge)) { + dot.append(" color = \"red\""); + } + + dot.append(" ];\n"); + } + @Override - public String renderCycleDotImage(RankedCycle cycle) { + public String renderCycleVisuals(RankedCycle cycle) { String dot = buildCycleDot(classGraph, cycle); String cycleName = getClassName(cycle.getCycleName()).replace("$", "_"); StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("\n"); - stringBuilder.append(generateForce3DPopup(cycleName)); - stringBuilder.append(generate2DPopup(cycleName)); - stringBuilder.append(generateHidePopup(cycleName)); - - stringBuilder.append("
\n"); - stringBuilder.append("Red lines represent back edges to remove.
\n"); - stringBuilder.append("Zoom in / out with your mouse wheel and click/move to drag the image.\n"); - stringBuilder.append("
\n"); + stringBuilder.append(generateGraphButtons(cycleName, dot)); if (cycle.getCycleNodes().size() + cycle.getEdgeSet().size() < d3Threshold) { - stringBuilder.append( - "
\n"); - stringBuilder.append("\n"); + stringBuilder.append(generateDotImage(cycleName)); } else { - // revisit and add D3 SVG popup button - stringBuilder.append( - "
\nCycle " + cycleName + " SVG is too big to render SVG quickly
\n"); + // revisit and add DOT SVG popup button + stringBuilder.append("
\nSVG is too big to render quickly
\n"); } stringBuilder.append("
\n"); @@ -622,49 +640,18 @@ public String renderCycleDotImage(RankedCycle cycle) { String buildCycleDot(Graph classGraph, RankedCycle cycle) { StringBuilder dot = new StringBuilder(); - dot.append("`strict digraph G {\n"); + for (DefaultWeightedEdge edge : cycle.getEdgeSet()) { + renderEdge(classGraph, edge, dot); + } + // render vertices - // e.g DownloadManager; for (String vertex : cycle.getVertexSet()) { dot.append(getClassName(vertex).replace("$", "_")); dot.append(";\n"); } - for (DefaultWeightedEdge edge : cycle.getEdgeSet()) { - // DownloadManager -> Download [ label="1" color="red" ]; - - // render edge - String[] vertexes = extractVertexes(edge); - String start = getClassName(vertexes[0].trim()).replace("$", "_"); - String end = getClassName(vertexes[1].trim()).replace("$", "_"); - - dot.append(start); - dot.append(" -> "); - dot.append(end); - - // render edge attributes - int edgeWeight = (int) classGraph.getEdgeWeight(edge); - dot.append(" [ "); - dot.append("label = \""); - dot.append(edgeWeight); - dot.append("\" "); - dot.append("weight = \""); - dot.append(edgeWeight); - dot.append("\""); - - if (edgesAboveDiagonal.contains(edge)) { - dot.append(" color = \"red\""); - } - - if (cycle.getMinCutEdges().contains(edge) && !edgesAboveDiagonal.contains(edge)) { - dot.append(" color = \"blue\""); - } - - dot.append(" ];\n"); - } - dot.append("}`;"); return dot.toString().replace("$", "_"); diff --git a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java index 0523fe08..b96d16f5 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -74,8 +74,6 @@ public class SimpleHtmlReport { DSM dsm; List edgesAboveDiagonal = List.of(); // initialize for unit tests - int pixels; - DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) .withLocale(Locale.getDefault()) .withZone(ZoneId.systemDefault()); @@ -141,35 +139,11 @@ public StringBuilder generateReport( String projectName, String projectVersion, File baseDir) { - return generateReport( - showDetails, - edgeAnalysisCount, - analyzeCycles, - excludeTests, - testSourceDirectory, - projectName, - projectVersion, - baseDir, - 200); - } - - // pixels param is for SVG image pixel padding - public StringBuilder generateReport( - boolean showDetails, - int edgeAnalysisCount, - boolean analyzeCycles, - boolean excludeTests, - String testSourceDirectory, - String projectName, - String projectVersion, - File baseDir, - int pixels) { if (testSourceDirectory == null || testSourceDirectory.isEmpty()) { testSourceDirectory = "src" + File.separator + "test"; } - this.pixels = pixels; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(printOpenBodyTag()); stringBuilder.append(printScripts()); @@ -280,7 +254,7 @@ public StringBuilder generateReport( log.info("Generating HTML Report"); - stringBuilder.append(renderClassGraphDotImage()); + stringBuilder.append(renderClassGraphVisuals()); stringBuilder.append("
\n"); stringBuilder.append(renderGithubButtons()); @@ -322,7 +296,8 @@ private String renderCycles(List rankedCycles) { stringBuilder.append("
\n"); - rankedCycles.stream().limit(10).map(this::renderSingleCycle).forEach(stringBuilder::append); + // rankedCycles.stream().limit(10).map(this::renderSingleCycle).forEach(stringBuilder::append); + rankedCycles.stream().map(this::renderSingleCycle).forEach(stringBuilder::append); return stringBuilder.toString(); } @@ -490,13 +465,16 @@ private String renderSingleCycle(RankedCycle cycle) { stringBuilder.append("
\n"); stringBuilder.append("

Class Cycle : " + getClassName(cycle.getCycleName()) + "

\n"); - stringBuilder.append(renderCycleDotImage(cycle)); + stringBuilder.append(renderCycleVisuals(cycle)); stringBuilder.append("
"); stringBuilder.append(""); stringBuilder.append("Bold text indicates back edge to remove to decompose cycle"); - // stringBuilder.append("
\"*\" indicates that edge is also a minimum cut edge in the cycle"); stringBuilder.append("
"); + int classCount = cycle.getCycleNodes().size(); + int relationshipCount = cycle.getEdgeSet().size(); + stringBuilder.append("
Number of classes: " + classCount + " Number of relationships: " + + relationshipCount + "
"); stringBuilder.append("
\n"); stringBuilder.append("\n"); @@ -542,11 +520,11 @@ private String renderSingleCycle(RankedCycle cycle) { return stringBuilder.toString(); } - public String renderClassGraphDotImage() { + public String renderClassGraphVisuals() { return ""; // empty on purpose } - public String renderCycleDotImage(RankedCycle cycle) { + public String renderCycleVisuals(RankedCycle cycle) { return ""; // empty on purpose } diff --git a/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java index 734c6507..514e791b 100644 --- a/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java +++ b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java @@ -6,8 +6,6 @@ import org.hjug.cbc.CycleNode; import org.hjug.cbc.RankedCycle; import org.jgrapht.Graph; -import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree; -import org.jgrapht.graph.AsUndirectedGraph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; import org.junit.jupiter.api.Test; @@ -48,29 +46,23 @@ void buildCycleDot() { classGraph.addEdge("C", "A"); classGraph.setEdgeWeight("A", "B", 2); - GusfieldGomoryHuCutTree gusfieldGomoryHuCutTree = - new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(classGraph)); - int minCutCount = (int) gusfieldGomoryHuCutTree.calculateMinCut(); - Set minCutEdges = gusfieldGomoryHuCutTree.getCutEdges(); - String cycleName = "Test"; List cycleNodes = new ArrayList<>(); - RankedCycle rankedCycle = new RankedCycle( - cycleName, 0, classGraph.vertexSet(), classGraph.edgeSet(), minCutCount, minCutEdges, cycleNodes); + RankedCycle rankedCycle = + new RankedCycle(cycleName, 0, classGraph.vertexSet(), classGraph.edgeSet(), 0, null, cycleNodes); HtmlReport htmlReport = new HtmlReport(); String dot = htmlReport.buildCycleDot(classGraph, rankedCycle); - StringBuilder expectedDot = new StringBuilder(); - expectedDot.append("`strict digraph G {\n" + String expectedDot = "`strict digraph G {\n" + + "A -> B [ label = \"2\" weight = \"2\" ];\n" + + "B -> C [ label = \"1\" weight = \"1\" ];\n" + + "C -> A [ label = \"1\" weight = \"1\" ];\n" + "A;\n" + "B;\n" + "C;\n" - + "A -> B [ label = \"2\" weight = \"2\" ];\n" - + "B -> C [ label = \"1\" weight = \"1\" color = \"blue\" ];\n" - + "C -> A [ label = \"1\" weight = \"1\" color = \"blue\" ];\n" - + "}`;"); + + "}`;"; - assertEquals(expectedDot.toString(), dot); + assertEquals(expectedDot, dot); } }