Skip to content

Commit

Permalink
#111 Rendering large graphs with Sigma.js
Browse files Browse the repository at this point in the history
- Rendering large graphs (> 700 nodes + edges) on demand with Sigma.js, Graphology, graphlib, and graphlib-dot in the HTML report.  Renders using WebGL.
- Touched up type processing in JavaVariableVisitor
- Removed the use of JavaParser
  • Loading branch information
jimbethancourt committed Nov 28, 2024
1 parent c1ff015 commit f4a4384
Show file tree
Hide file tree
Showing 14 changed files with 2,499 additions and 222 deletions.
14 changes: 8 additions & 6 deletions circular-reference-detector/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-recipe-bom</artifactId>
<version>2.21.1</version>
<version>2.23.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down Expand Up @@ -56,16 +56,18 @@
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java</artifactId>
</dependency>

<!--gizmo 1.0.11, used by rewrite-core has a CVSS score > 8.0 -->
<dependency>
<groupId>io.quarkus.gizmo</groupId>
<artifactId>gizmo</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-core</artifactId>
</dependency>

<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-symbol-solver-core</artifactId>
<version>3.26.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.hjug.parser;

import java.util.HashMap;
import java.util.Map;
import java.util.*;
import lombok.Getter;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J;
Expand All @@ -13,14 +12,65 @@
* @param <P>
*/
@Getter
public class JavaFqnCapturingVisitor<P> extends JavaIsoVisitor<P> implements FqnCapturingProcessor {
public class JavaFqnCapturingVisitor<P> extends JavaIsoVisitor<P> {

// consider using ConcurrentHashMap to scale performance
// package -> name, FQN
private final Map<String, Map<String, String>> fqns = new HashMap<>();
private final Map<String, Map<String, String>> fqnMap = new HashMap<>();
private final Set<String> fqns = new HashSet<>();

@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) {
return super.visitClassDeclaration(captureClassDeclarations(classDecl, fqns), p);
captureClassDeclarations(classDecl, fqnMap);
return classDecl;
}

J.ClassDeclaration captureClassDeclarations(J.ClassDeclaration classDecl, Map<String, Map<String, String>> fqnMap) {
// get class fqn (including "$")
String fqn = classDecl.getType().getFullyQualifiedName();
fqns.add(fqn);

/* String currentPackage = getPackage(fqn);
String className = getClassName(fqn);
Map<String, String> classesInPackage = fqnMap.getOrDefault(currentPackage, new HashMap<>());
if (className.contains("$")) {
String normalizedClassName = className.replace('$', '.');
List<String> parts = Arrays.asList(normalizedClassName.split("\\."));
for (int i = 0; i < parts.size(); i++) {
String key = String.join(".", parts.subList(i, parts.size()));
classesInPackage.put(key, currentPackage + "." + normalizedClassName);
}
} else {
classesInPackage.put(className, fqn);
}
fqnMap.put(currentPackage, classesInPackage);*/
return classDecl;
}

String getPackage(String fqn) {
// handle no package
if (!fqn.contains(".")) {
return "";
}

int lastIndex = fqn.lastIndexOf(".");
return fqn.substring(0, lastIndex);
}

/**
*
* @param fqn
* @return Class name (including "$") after last period in FQN
*/
String getClassName(String fqn) {
// handle no package
if (!fqn.contains(".")) {
return fqn;
}

int lastIndex = fqn.lastIndexOf(".");
return fqn.substring(lastIndex + 1);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
package org.hjug.parser;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -27,6 +21,7 @@ public class JavaProjectParser {

/**
* Given a java source directory return a graph of class references
*
* @param srcDirectory
* @return
* @throws IOException
Expand All @@ -37,125 +32,28 @@ public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory
if (srcDirectory == null || srcDirectory.isEmpty()) {
throw new IllegalArgumentException();
} else {
List<String> classNames = getClassNames(srcDirectory);
try (Stream<Path> filesStream = Files.walk(Paths.get(srcDirectory))) {
filesStream
.filter(path -> path.getFileName().toString().endsWith(".java"))
.forEach(path -> {
log.info("Parsing {}", path);
List<String> types = getInstanceVarTypes(classNames, path.toFile());
types.addAll(getMethodArgumentTypes(classNames, path.toFile()));
if (!types.isEmpty()) {
String className =
getClassName(path.getFileName().toString());
classReferencesGraph.addVertex(className);
types.forEach(classReferencesGraph::addVertex);
types.forEach(type -> {
if (!classReferencesGraph.containsEdge(className, type)) {
classReferencesGraph.addEdge(className, type);
} else {
DefaultWeightedEdge edge = classReferencesGraph.getEdge(className, type);
classReferencesGraph.setEdgeWeight(
edge, classReferencesGraph.getEdgeWeight(edge) + 1);
}
});
}
});
} catch (FileNotFoundException e) {
e.printStackTrace();
}
processWithOpenRewrite(srcDirectory, classReferencesGraph);
}

return classReferencesGraph;
}

List<String> getClassDeclarations(Path javaSrcFile, Path srcDirectory) {
private void processWithOpenRewrite(String srcDir, Graph<String, DefaultWeightedEdge> classReferencesGraph)
throws IOException {
File srcDirectory = new File(srcDir);

org.openrewrite.java.JavaParser javaParser =
JavaParser.fromJavaVersion().build();
ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace);

JavaVisitor javaVisitor = new JavaVisitor();

List<String> fqdns = new ArrayList<>();

javaParser.parse(List.of(javaSrcFile), srcDirectory, ctx).forEach(cu -> javaVisitor.visit(cu, ctx));

return fqdns;
}

/**
* Get instance variables types of a java source file using java parser
* @param classNamesToFilterBy - only add instance variable types which have these class names as type
* @param file
* @return
*/
private List<String> getInstanceVarTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
CompilationUnit compilationUnit;
try {
compilationUnit = StaticJavaParser.parse(javaSrcFile);

return compilationUnit.findAll(FieldDeclaration.class).stream()
.map(f -> f.getVariables().get(0).getType())
.filter(v -> !v.isPrimitiveType())
.map(Object::toString)
.filter(classNamesToFilterBy::contains)
.collect(Collectors.toList());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return new ArrayList<>();
}
JavaVariableVisitor<ExecutionContext> javaVariableCapturingVisitor =
new JavaVariableVisitor<>(classReferencesGraph);

/**
* Get parameter types of methods declared in a java source file using java parser
* @param classNamesToFilterBy - only add types which have these class names as type
* @param file
* @return
*/
private List<String> getMethodArgumentTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
CompilationUnit compilationUnit;
try {
compilationUnit = StaticJavaParser.parse(javaSrcFile);
return compilationUnit.findAll(MethodDeclaration.class).stream()
.flatMap(f -> f.getParameters().stream()
.map(Parameter::getType)
.filter(type -> !type.isPrimitiveType())
.collect(Collectors.toList())
.stream())
.map(Object::toString)
.filter(classNamesToFilterBy::contains)
.collect(Collectors.toList());
} catch (FileNotFoundException e) {
e.printStackTrace();
try (Stream<Path> walk = Files.walk(Paths.get(srcDirectory.getAbsolutePath()))) {
List<Path> list = walk.collect(Collectors.toList());
javaParser
.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx)
.forEach(cu -> javaVariableCapturingVisitor.visit(cu, ctx));
}
return new ArrayList<>();
}

/**
* Get all java classes in a source directory
*
* @param srcDirectory
* @return
* @throws IOException
*/
private List<String> getClassNames(String srcDirectory) throws IOException {
try (Stream<Path> filesStream = Files.walk(Paths.get(srcDirectory))) {
return filesStream
.map(path -> path.getFileName().toString())
.filter(fileName -> fileName.endsWith(".java"))
.map(this::getClassName)
.collect(Collectors.toList());
}
}

/**
* Extract class name from java file name
* Example : MyJavaClass.java becomes MyJavaClass
*
* @param javaFileName
* @return
*/
private String getClassName(String javaFileName) {
return javaFileName.substring(0, javaFileName.indexOf('.'));
}
}
Loading

0 comments on commit f4a4384

Please sign in to comment.