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
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.hjug.graphbuilder;

public interface DependencyCollector {

/**
* Records a dependency from one class to another
*
* @param fromClassFqn The fully qualified name of the class that depends on another
* @param toClassFqn The fully qualified name of the class being depended upon
*/
void addClassDependency(String fromClassFqn, String toClassFqn);

/**
* Records a dependency from one package to another
*
* @param fromPackageName The package that depends on another
* @param toPackageName The package being depended upon
*/
void addPackageDependency(String fromPackageName, String toPackageName);

/**
* Records the source file location for a class
*
* @param classFqn The fully qualified name of the class
* @param sourceFilePath The path to the source file containing the class
*/
void recordClassLocation(String classFqn, String sourceFilePath);

/**
* Registers a package as being part of the codebase
*
* @param packageName The package name to register
*/
void registerPackage(String packageName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.hjug.graphbuilder;

import lombok.Builder;
import lombok.Value;

@Value
@Builder
public class GraphBuilderConfig {

@Builder.Default
boolean excludeTests = true;

@Builder.Default
String testSourceDirectory = "src/test";

public static GraphBuilderConfig defaultConfig() {
return GraphBuilderConfig.builder().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.hjug.graphbuilder;

import java.util.HashSet;
import java.util.Set;
import lombok.Getter;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultWeightedEdge;

public class GraphDependencyCollector implements DependencyCollector {

@Getter
private final Graph<String, DefaultWeightedEdge> classReferencesGraph;

@Getter
private final Graph<String, DefaultWeightedEdge> packageReferencesGraph;

@Getter
private final Set<String> packagesInCodebase = new HashSet<>();

public GraphDependencyCollector(
Graph<String, DefaultWeightedEdge> classReferencesGraph,
Graph<String, DefaultWeightedEdge> packageReferencesGraph) {
this.classReferencesGraph = classReferencesGraph;
this.packageReferencesGraph = packageReferencesGraph;
}

@Override
public void addClassDependency(String fromClassFqn, String toClassFqn) {
if (fromClassFqn.equals(toClassFqn)) {
return;
}

classReferencesGraph.addVertex(fromClassFqn);
classReferencesGraph.addVertex(toClassFqn);

if (!classReferencesGraph.containsEdge(fromClassFqn, toClassFqn)) {
classReferencesGraph.addEdge(fromClassFqn, toClassFqn);
} else {
DefaultWeightedEdge edge = classReferencesGraph.getEdge(fromClassFqn, toClassFqn);
classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1);
}
}

@Override
public void addPackageDependency(String fromPackageName, String toPackageName) {
if (fromPackageName.equals(toPackageName)) {
return;
}

packageReferencesGraph.addVertex(fromPackageName);
packageReferencesGraph.addVertex(toPackageName);

if (!packageReferencesGraph.containsEdge(fromPackageName, toPackageName)) {
packageReferencesGraph.addEdge(fromPackageName, toPackageName);
} else {
DefaultWeightedEdge edge = packageReferencesGraph.getEdge(fromPackageName, toPackageName);
packageReferencesGraph.setEdgeWeight(edge, packageReferencesGraph.getEdgeWeight(edge) + 1);
}
}

@Override
public void recordClassLocation(String classFqn, String sourceFilePath) {
// This will be handled by JavaVisitor which maintains the mapping
}

@Override
public void registerPackage(String packageName) {
packagesInCodebase.add(packageName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,49 +23,63 @@
public class JavaGraphBuilder {

/**
* Given a java source directory, return a CodebaseGraphDTO
* Given a java source directory, return a CodebaseGraphDTO using default configuration
*
* @param srcDirectory
* @param srcDirectory The source directory to analyze
* @param excludeTests Whether to exclude test files
* @param testSourceDirectory The test source directory pattern to exclude
* @return CodebaseGraphDTO
* @throws IOException
*/
public CodebaseGraphDTO getCodebaseGraphDTO(String srcDirectory, boolean excludeTests, String testSourceDirectory)
throws IOException {
CodebaseGraphDTO codebaseGraphDTO;
GraphBuilderConfig config = GraphBuilderConfig.builder()
.excludeTests(excludeTests)
.testSourceDirectory(testSourceDirectory)
.build();
return getCodebaseGraphDTO(srcDirectory, config);
}

/**
* Given a java source directory and configuration, return a CodebaseGraphDTO
*
* @param srcDirectory The source directory to analyze
* @param config The configuration for the graph builder
* @return CodebaseGraphDTO
* @throws IOException
*/
public CodebaseGraphDTO getCodebaseGraphDTO(String srcDirectory, GraphBuilderConfig config) throws IOException {
if (srcDirectory == null || srcDirectory.isEmpty()) {
throw new IllegalArgumentException();
} else {
codebaseGraphDTO = processWithOpenRewrite(srcDirectory, excludeTests, testSourceDirectory);
throw new IllegalArgumentException("Source directory cannot be null or empty");
}

return codebaseGraphDTO;
return processWithOpenRewrite(srcDirectory, config);
}

private CodebaseGraphDTO processWithOpenRewrite(String srcDir, boolean excludeTests, String testSourceDirectory)
throws IOException {
private CodebaseGraphDTO processWithOpenRewrite(String srcDir, GraphBuilderConfig config) throws IOException {
File srcDirectory = new File(srcDir);

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

final Graph<String, DefaultWeightedEdge> classReferencesGraph =
new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class);

final Graph<String, DefaultWeightedEdge> packageReferencesGraph =
new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class);

final JavaVisitor<ExecutionContext> javaVisitor =
new JavaVisitor<>(classReferencesGraph, packageReferencesGraph);
final GraphDependencyCollector dependencyCollector =
new GraphDependencyCollector(classReferencesGraph, packageReferencesGraph);

final JavaVisitor<ExecutionContext> javaVisitor = new JavaVisitor<>(dependencyCollector);
final JavaVariableTypeVisitor<ExecutionContext> javaVariableTypeVisitor =
new JavaVariableTypeVisitor<>(classReferencesGraph, packageReferencesGraph);
new JavaVariableTypeVisitor<>(dependencyCollector);
final JavaMethodDeclarationVisitor<ExecutionContext> javaMethodDeclarationVisitor =
new JavaMethodDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph);
new JavaMethodDeclarationVisitor<>(dependencyCollector);

try (Stream<Path> pathStream = Files.walk(Paths.get(srcDirectory.getAbsolutePath()))) {
List<Path> list;
if (excludeTests) {
if (config.isExcludeTests()) {
list = pathStream
.filter(file -> !file.toString().contains(testSourceDirectory))
.filter(file -> !file.toString().contains(config.getTestSourceDirectory()))
.collect(Collectors.toList());
} else {
list = pathStream.collect(Collectors.toList());
Expand All @@ -80,7 +94,7 @@ private CodebaseGraphDTO processWithOpenRewrite(String srcDir, boolean excludeTe
});
}

removeClassesNotInCodebase(javaVisitor.getPackagesInCodebase(), classReferencesGraph);
removeClassesNotInCodebase(dependencyCollector.getPackagesInCodebase(), classReferencesGraph);

return new CodebaseGraphDTO(
classReferencesGraph, packageReferencesGraph, javaVisitor.getClassToSourceFilePathMapping());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.hjug.graphbuilder.visitor;

import lombok.Getter;
import org.hjug.graphbuilder.DependencyCollector;
import org.openrewrite.java.JavaIsoVisitor;

@Getter
public abstract class BaseCodebaseVisitor<P> extends JavaIsoVisitor<P> {

protected final DependencyCollector dependencyCollector;

protected BaseCodebaseVisitor(DependencyCollector dependencyCollector) {
this.dependencyCollector = dependencyCollector;
}

protected abstract String getCurrentOwnerFqn();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.hjug.graphbuilder.visitor;

import lombok.extern.slf4j.Slf4j;
import org.hjug.graphbuilder.DependencyCollector;
import org.openrewrite.Cursor;
import org.openrewrite.java.service.AnnotationService;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeTree;

@Slf4j
public abstract class BaseTypeProcessor {

private final TypeDependencyExtractor typeDependencyExtractor = new TypeDependencyExtractor();

protected abstract DependencyCollector getDependencyCollector();

protected void processType(String ownerFqn, JavaType javaType) {
if (javaType == null || javaType instanceof JavaType.Unknown) {
return;
}

for (String dependency : typeDependencyExtractor.extractDependencies(javaType)) {
getDependencyCollector().addClassDependency(ownerFqn, dependency);
}
}

protected void processAnnotation(String ownerFqn, J.Annotation annotation, Cursor cursor) {
if (annotation.getType() instanceof JavaType.Unknown) {
return;
}

JavaType.Class type = (JavaType.Class) annotation.getType();
if (null != type) {
String annotationFqn = type.getFullyQualifiedName();
log.debug("Variable Annotation FQN: {}", annotationFqn);
getDependencyCollector().addClassDependency(ownerFqn, annotationFqn);

if (null != annotation.getArguments()) {
for (Expression argument : annotation.getArguments()) {
processType(ownerFqn, argument.getType());
}
}
}
}

protected void processTypeParameter(String ownerFqn, J.TypeParameter typeParameter, Cursor cursor) {
if (null != typeParameter.getBounds()) {
for (TypeTree bound : typeParameter.getBounds()) {
processType(ownerFqn, bound.getType());
}
}

if (!typeParameter.getAnnotations().isEmpty()) {
for (J.Annotation annotation : typeParameter.getAnnotations()) {
processAnnotation(ownerFqn, annotation, cursor);
}
}
}

protected void processAnnotations(String ownerFqn, Cursor cursor) {
AnnotationService annotationService = new AnnotationService();
for (J.Annotation annotation : annotationService.getAllAnnotations(cursor)) {
processAnnotation(ownerFqn, annotation, cursor);
}
}

protected String getPackageFromFqn(String fqn) {
if (!fqn.contains(".")) {
return "";
}
int lastIndex = fqn.lastIndexOf(".");
return fqn.substring(0, lastIndex);
}
}
Loading
Loading