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
Expand Up @@ -22,8 +22,8 @@
@ToString
public class CodebaseGraphDTO {

private final Graph<String, DefaultWeightedEdge> classReferencesGraph;
private final Graph<String, DefaultWeightedEdge> packageReferencesGraph;
private final Graph<VertexInfo, DefaultWeightedEdge> classReferencesGraph;
private final Graph<VertexInfo, DefaultWeightedEdge> packageReferencesGraph;
private final Map<DefaultWeightedEdge, Set<DefaultWeightedEdge>> classRelationshipsInPackageRelationship;
// used for looking up files where classes reside
private final Map<String, String> classToSourceFilePathMapping;
Expand All @@ -35,15 +35,17 @@ public class CodebaseGraphDTO {
* Creates a new CodebaseGraphDTO.
*
* @param classReferencesGraph the class reference graph
* (vertices are {@link VertexInfo})
* @param packageReferencesGraph the package reference graph
* (vertices are {@link VertexInfo})
* @param classRelationshipsInPackageRelationship the class-to-package relationships
* @param classToSourceFilePathMapping the class to source file path mapping
* @param classDisharmonies the list of class disharmonies
* @param methodDisharmonies the list of method disharmonies
*/
public CodebaseGraphDTO(
Graph<String, DefaultWeightedEdge> classReferencesGraph,
Graph<String, DefaultWeightedEdge> packageReferencesGraph,
Graph<VertexInfo, DefaultWeightedEdge> classReferencesGraph,
Graph<VertexInfo, DefaultWeightedEdge> packageReferencesGraph,
Map<DefaultWeightedEdge, Set<DefaultWeightedEdge>> classRelationshipsInPackageRelationship,
Map<String, String> classToSourceFilePathMapping,
List<ClassDisharmony> classDisharmonies,
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
public class GraphDependencyCollector implements DependencyCollector {

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

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

@Getter
private final Set<String> packagesInCodebase = new HashSet<>();
Expand All @@ -25,27 +25,55 @@ public class GraphDependencyCollector implements DependencyCollector {
private final Map<DefaultWeightedEdge, Set<DefaultWeightedEdge>> classRelationshipsInPackageRelationship =
new HashMap<>();

/**
* Class locations (as reported by the visitors) captured during collection,
* keyed by class FQN. The repository-relative locations used for rendering
* are hydrated onto the {@link VertexInfo} vertices from the canonical
* class-to-source-file mapping after the walk (see {@link VertexInfoHydrator});
* this map keeps the raw information captured at collection time.
*/
@Getter
private final Map<String, String> recordedClassLocations = new HashMap<>();

/** Registry of the VertexInfo instances created for each vertex name. */
private final Map<String, VertexInfo> vertexRegistry = new HashMap<>();

/**
* Creates a dependency collector that writes into the supplied graphs.
*
* @param classReferencesGraph the graph receiving class dependencies
* @param packageReferencesGraph the graph receiving cross-package dependencies
*/
public GraphDependencyCollector(
Graph<String, DefaultWeightedEdge> classReferencesGraph,
Graph<String, DefaultWeightedEdge> packageReferencesGraph) {
Graph<VertexInfo, DefaultWeightedEdge> classReferencesGraph,
Graph<VertexInfo, DefaultWeightedEdge> packageReferencesGraph) {
this.classReferencesGraph = classReferencesGraph;
this.packageReferencesGraph = packageReferencesGraph;
}

/**
* Adds or increments a class dependency and records its corresponding package relationship.
* Dependencies from a class to itself are ignored.
*
* @param fromClassFqn the fully qualified source class name
* @param toClassFqn the fully qualified target class name
*/
@Override
public void addClassDependency(String fromClassFqn, String toClassFqn) {
if (fromClassFqn.equals(toClassFqn)) {
return;
}

classReferencesGraph.addVertex(fromClassFqn);
classReferencesGraph.addVertex(toClassFqn);
VertexInfo fromVertex = getOrCreateVertex(fromClassFqn);
VertexInfo toVertex = getOrCreateVertex(toClassFqn);
classReferencesGraph.addVertex(fromVertex);
classReferencesGraph.addVertex(toVertex);

DefaultWeightedEdge classRelationship;
if (!classReferencesGraph.containsEdge(fromClassFqn, toClassFqn)) {
classRelationship = classReferencesGraph.addEdge(fromClassFqn, toClassFqn);
if (!classReferencesGraph.containsEdge(fromVertex, toVertex)) {
classRelationship = classReferencesGraph.addEdge(fromVertex, toVertex);
} else {
classRelationship = classReferencesGraph.getEdge(fromClassFqn, toClassFqn);
classRelationship = classReferencesGraph.getEdge(fromVertex, toVertex);
classReferencesGraph.setEdgeWeight(
classRelationship, classReferencesGraph.getEdgeWeight(classRelationship) + 1);
}
Expand All @@ -63,6 +91,13 @@ public void addClassDependency(String fromClassFqn, String toClassFqn) {
}
}

/**
* Adds or increments the package dependency implied by two class names.
*
* @param fromClassFqn the fully qualified source class name
* @param toClassFqn the fully qualified target class name
* @return the package edge, or {@code null} for equal or empty endpoint packages
*/
@Override
public DefaultWeightedEdge addPackageDependency(String fromClassFqn, String toClassFqn) {
String fromPackageName = getPackageFromFqn(fromClassFqn);
Expand All @@ -80,14 +115,16 @@ public DefaultWeightedEdge addPackageDependency(String fromClassFqn, String toCl
return null;
}

packageReferencesGraph.addVertex(fromPackageName);
packageReferencesGraph.addVertex(toPackageName);
VertexInfo fromPackageVertex = getOrCreateVertex(fromPackageName);
VertexInfo toPackageVertex = getOrCreateVertex(toPackageName);
packageReferencesGraph.addVertex(fromPackageVertex);
packageReferencesGraph.addVertex(toPackageVertex);

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

Expand All @@ -102,18 +139,40 @@ protected String getPackageFromFqn(String fqn) {
return fqn.substring(0, lastIndex);
}

/**
* Returns the registered {@link VertexInfo} for the given class or package
* name, creating and registering one (with the parent namespace derived
* from the name) when absent.
*/
private VertexInfo getOrCreateVertex(String name) {
return vertexRegistry.computeIfAbsent(name, VertexInfo::forName);
}

/**
* Records the raw source location reported by a visitor for later inspection.
*
* @param classFqn the fully qualified class name
* @param sourceFilePath the visitor-reported source location
*/
@Override
public void recordClassLocation(String classFqn, String sourceFilePath) {
// This will be handled by JavaVisitor which maintains the mapping
// Capture the location reported during collection; repository-relative
// hydration of the VertexInfo vertices happens after the walk.
recordedClassLocations.put(classFqn, sourceFilePath);
}

@Override
public void registerPackage(String packageName) {
packagesInCodebase.add(packageName);
}

/**
* Registers a class vertex with its derived parent namespace, even if it has no dependencies.
*
* @param classFqn the fully qualified class name
*/
@Override
public void registerClassVertex(String classFqn) {
classReferencesGraph.addVertex(classFqn);
classReferencesGraph.addVertex(getOrCreateVertex(classFqn));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package org.hjug.graphbuilder;

/**
* Rich graph vertex captured when graph dependency information is collected.
*
* <p>Replaces the bare class/package name String that previously served as the
* graph vertex so the renderer can build a composed codebase map from the
* graph image. Carries:
*
* <ul>
* <li>{@link #getName()} — the vertex name: the fully qualified class name
* (class graph) or the package name (package graph).</li>
* <li>{@link #getLocationOnDisk()} — the location on disk of the vertex
* relative to the repository root (source file path for a class;
* member-file directory for a package). Populated by hydration after the
* visitor walk; may be {@code null} when no location is known.</li>
* <li>{@link #getParentNamespace()} — the parent namespace: the parent
* package if this vertex is a package, or the enclosing construct
* (package or class) if the vertex is not a package and contains code.
* Derived from the name at creation time.</li>
* </ul>
*
* <p>Identity is {@code name}-based ({@code equals}/{@code hashCode}) so
* JGraphT vertex deduplication behaves exactly like the previous String
* vertices and code can probe graphs with cheap {@link #forName(String)}
* lookups. {@code toString()} returns the name so edge rendering
* ({@code "(source : target)"}) is unchanged.
*/
public class VertexInfo {

private final String name;
private final String parentNamespace;
private String locationOnDisk;

/** Creates a vertex with the supplied identity and parent namespace, leaving its disk location unset. */
private VertexInfo(String name, String parentNamespace) {
this.name = name;
this.parentNamespace = parentNamespace;
}

/**
* Creates a vertex for the given class or package name with the parent
* namespace derived from the name.
*
* @param name the class FQN or package name; must not be null or empty
* @return a VertexInfo whose parent namespace is the enclosing package
* (for a top-level class or a package) or the enclosing class (for
* a nested, anonymous, or synthetic class); {@code null} when no
* enclosing construct can be derived
* @throws NullPointerException if {@code name} is null
* @throws IllegalArgumentException if {@code name} is empty
*/
public static VertexInfo forName(String name) {
if (name == null) {
throw new NullPointerException("name must not be null");
}
if (name.isEmpty()) {
throw new IllegalArgumentException("name must not be empty");
}
return new VertexInfo(name, deriveParentNamespace(name));
}

/**
* Derives the enclosing construct of a vertex from its name.
*
* <p>Rules:
* <ul>
* <li>Kotlin anonymous vertex ({@code pkg.Outer.<anonymous>}, trailing
* segment starts with {@code <}): the text before the last dot; a
* packageless literal {@code <anonymous>} has no parent.</li>
* <li>Nested/anonymous/synthetic class ({@code $} in the simple name):
* the enclosing class (text before the last {@code $}).</li>
* <li>Top-level class or package: the text before the last dot (the
* containing package / parent package); {@code null} when the name
* has no dot (default package).</li>
* </ul>
*
* @param name the class FQN or package name
* @return the parent namespace, or {@code null} when none can be derived
*/
static String deriveParentNamespace(String name) {
int lastDot = name.lastIndexOf('.');
String simpleName = lastDot >= 0 ? name.substring(lastDot + 1) : name;

if (simpleName.startsWith("<")) {
// Kotlin anonymous: "pkg.Outer.<anonymous>" -> "pkg.Outer";
// packageless "<anonymous>" has no enclosing construct
return lastDot > 0 ? name.substring(0, lastDot) : null;
}

int lastDollar = simpleName.lastIndexOf('$');
if (lastDollar >= 0) {
// nested / anonymous / synthetic: enclosing class before the last $
String enclosingSimple = simpleName.substring(0, lastDollar);
if (enclosingSimple.isEmpty()) {
return null;
}
return lastDot >= 0 ? name.substring(0, lastDot + 1) + enclosingSimple : enclosingSimple;
}

// top-level class or package: containing / parent package
return lastDot >= 0 ? name.substring(0, lastDot) : null;
}

/** Returns the vertex name (class FQN or package name). */
public String getName() {
return name;
}

/**
* Returns the location on disk of this vertex relative to the repository
* root, or {@code null} when not (yet) known.
*/
public String getLocationOnDisk() {
return locationOnDisk;
}

/** Sets the repository-relative location on disk (used during hydration). */
void setLocationOnDisk(String locationOnDisk) {
this.locationOnDisk = locationOnDisk;
}

/**
* Returns the parent namespace: the parent package if this vertex is a
* package, or the enclosing construct (package or class) if the vertex is
* not a package and contains code; {@code null} when none can be derived.
*/
public String getParentNamespace() {
return parentNamespace;
}

/**
* Compares vertex names; parent namespaces and hydrated disk locations do not affect identity.
*
* @param o the object to compare
* @return whether the other object is a vertex with the same name
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof VertexInfo)) {
return false;
}
return name.equals(((VertexInfo) o).name);
}

/**
* Returns a name-based hash that remains stable when the disk location is hydrated.
*
* @return the vertex name's hash code
*/
@Override
public int hashCode() {
return name.hashCode();
}

/**
* Returns the vertex name for graph edge rendering.
*
* @return the fully qualified class name or package name
*/
@Override
public String toString() {
return name;
}
}
Loading
Loading