DEV Community

nikhilpereira1793
nikhilpereira1793

Posted on

Detecting Circular References In A Java Project

As Java projects get bigger, with several developers working on them, it may lead to code having circular references. It is difficult to serialize objects with circular references. I have created a tool to detect circular references in a Java project : Circular Reference Detector Gitlab Source Code. Below are the steps taken to detect circular references in a Java project.

Program Goal

  • Input : Path to a Java project's source files.
  • Output : Folder containing the image files of cyclic graphs for each class having circular references.

Steps

  • Using Java Parser, parse every Java file in the given source directory and collect the references of a class in a directed graph using JgraphT
  • Detect cycles for each vertex in the graph using JgraphT's CycleDetector
  • Store graphs as PNG files using jgrapht-ext library

Code Snippets

Parse Java files in the source directory and add the class references to a graph.
Graph<String, DefaultEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultEdge.class);
try (Stream<Path> filesStream = Files.walk(Paths.get(srcDirectory))) {
    filesStream             
    .filter(path -> path.getFileName().toString().endsWith(".java"))
    .forEach(path ->{
        Set<String> instanceVarTypes = getInstanceVarTypes(path.toFile());
        if( ! instanceVarTypes.isEmpty()) {                     
            String className = getClassName(path.getFileName().toString());
            classReferencesGraph.addVertex(className);
            instanceVarTypes.forEach(classReferencesGraph::addVertex);
            instanceVarTypes.forEach( var -> classReferencesGraph.addEdge(className, var));
        }
    });
}

Collecting the Instance Variables of a Class using Java Parser
CompilationUnit compilationUnit = StaticJavaParser.parse(javaSrcFile);      
List<Node> instanceVarTypes = compilationUnit.findAll(FieldDeclaration.class)
    .stream()
    .map(f -> f.getVariables().get(0).getType())
    .filter(v -> !v.isPrimitiveType())
    .map( Object::toString)
    .collect(Collectors.toSet());

Create Directed Graph using Map
Graph<String, DefaultEdge> graph = new DefaultDirectedGraph<>(DefaultEdge.class);       
//add vertices
classNametoInstanceVarTypesMap.keySet().forEach(className ->{           
  graph.addVertex(className);
});
//add edges
classNametoInstanceVarTypesMap
  .forEach((className , instanceVariableTypes) -> {
      instanceVariableTypes.forEach( instVar -> {
        if (classNametoInstanceVarTypesMap.containsKey(instVar)){
          graph.addEdge(className, instVar);
        }           
    });
});

Detect cycles for each vertex in the graph using JGraphT CycleDetector and add the cycles to a map.
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap = new HashMap<>();
CycleDetector<String, DefaultEdge> cycleDetector = new CycleDetector<>(classReferencesGraph);
cycleDetector.findCycles().forEach(v -> {
    AsSubgraph<String, DefaultEdge> subGraph = new AsSubgraph<>(classReferencesGraph,
            cycleDetector.findCyclesContainingVertex(v));
    cyclesForEveryVertexMap.put(v,subGraph);
});
  • findCyclesContainingVertex(vertex) returns set of vertices participating in the cycle for a vertex. Create a subgraph of the main graph using these vertices.

Create PNG Image of a graph using jgrapht-ext library.
new File(outputDirectoryPath).mkdirs();
File imgFile = new File(outputDirectoryPath+"/graph" + imageName + ".png");
if(imgFile.createNewFile()) {
    JGraphXAdapter<String, DefaultEdge> graphAdapter = new JGraphXAdapter<>(subGraph);
    mxIGraphLayout layout = new mxCircleLayout(graphAdapter);
    layout.execute(graphAdapter.getDefaultParent());

    BufferedImage image = mxCellRenderer.createBufferedImage(graphAdapter, null, 2, Color.WHITE, true, null);
    if (image != null) {
        ImageIO.write(image, "PNG", imgFile);
    }
}

Sample Output Graph Image

Sample output graph image.


References

Top comments (0)