Skip to content

Commit d693d64

Browse files
Merge pull request #229 from refactorfirst/capture-source-hierarchy
Capturing location and parent namespace
2 parents d798d64 + e8460e8 commit d693d64

49 files changed

Lines changed: 2387 additions & 921 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎codebase-graph-builder/src/main/java/org/hjug/graphbuilder/CodebaseGraphDTO.java‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
@ToString
2323
public class CodebaseGraphDTO {
2424

25-
private final Graph<String, DefaultWeightedEdge> classReferencesGraph;
26-
private final Graph<String, DefaultWeightedEdge> packageReferencesGraph;
25+
private final Graph<VertexInfo, DefaultWeightedEdge> classReferencesGraph;
26+
private final Graph<VertexInfo, DefaultWeightedEdge> packageReferencesGraph;
2727
private final Map<DefaultWeightedEdge, Set<DefaultWeightedEdge>> classRelationshipsInPackageRelationship;
2828
// used for looking up files where classes reside
2929
private final Map<String, String> classToSourceFilePathMapping;
@@ -35,15 +35,17 @@ public class CodebaseGraphDTO {
3535
* Creates a new CodebaseGraphDTO.
3636
*
3737
* @param classReferencesGraph the class reference graph
38+
* (vertices are {@link VertexInfo})
3839
* @param packageReferencesGraph the package reference graph
40+
* (vertices are {@link VertexInfo})
3941
* @param classRelationshipsInPackageRelationship the class-to-package relationships
4042
* @param classToSourceFilePathMapping the class to source file path mapping
4143
* @param classDisharmonies the list of class disharmonies
4244
* @param methodDisharmonies the list of method disharmonies
4345
*/
4446
public CodebaseGraphDTO(
45-
Graph<String, DefaultWeightedEdge> classReferencesGraph,
46-
Graph<String, DefaultWeightedEdge> packageReferencesGraph,
47+
Graph<VertexInfo, DefaultWeightedEdge> classReferencesGraph,
48+
Graph<VertexInfo, DefaultWeightedEdge> packageReferencesGraph,
4749
Map<DefaultWeightedEdge, Set<DefaultWeightedEdge>> classRelationshipsInPackageRelationship,
4850
Map<String, String> classToSourceFilePathMapping,
4951
List<ClassDisharmony> classDisharmonies,

‎codebase-graph-builder/src/main/java/org/hjug/graphbuilder/CompositeGraphBuilder.java‎

Lines changed: 96 additions & 63 deletions
Large diffs are not rendered by default.

‎codebase-graph-builder/src/main/java/org/hjug/graphbuilder/GraphDependencyCollector.java‎

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
public class GraphDependencyCollector implements DependencyCollector {
1414

1515
@Getter
16-
private final Graph<String, DefaultWeightedEdge> classReferencesGraph;
16+
private final Graph<VertexInfo, DefaultWeightedEdge> classReferencesGraph;
1717

1818
@Getter
19-
private final Graph<String, DefaultWeightedEdge> packageReferencesGraph;
19+
private final Graph<VertexInfo, DefaultWeightedEdge> packageReferencesGraph;
2020

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

28+
/**
29+
* Class locations (as reported by the visitors) captured during collection,
30+
* keyed by class FQN. The repository-relative locations used for rendering
31+
* are hydrated onto the {@link VertexInfo} vertices from the canonical
32+
* class-to-source-file mapping after the walk (see {@link VertexInfoHydrator});
33+
* this map keeps the raw information captured at collection time.
34+
*/
35+
@Getter
36+
private final Map<String, String> recordedClassLocations = new HashMap<>();
37+
38+
/** Registry of the VertexInfo instances created for each vertex name. */
39+
private final Map<String, VertexInfo> vertexRegistry = new HashMap<>();
40+
41+
/**
42+
* Creates a dependency collector that writes into the supplied graphs.
43+
*
44+
* @param classReferencesGraph the graph receiving class dependencies
45+
* @param packageReferencesGraph the graph receiving cross-package dependencies
46+
*/
2847
public GraphDependencyCollector(
29-
Graph<String, DefaultWeightedEdge> classReferencesGraph,
30-
Graph<String, DefaultWeightedEdge> packageReferencesGraph) {
48+
Graph<VertexInfo, DefaultWeightedEdge> classReferencesGraph,
49+
Graph<VertexInfo, DefaultWeightedEdge> packageReferencesGraph) {
3150
this.classReferencesGraph = classReferencesGraph;
3251
this.packageReferencesGraph = packageReferencesGraph;
3352
}
3453

54+
/**
55+
* Adds or increments a class dependency and records its corresponding package relationship.
56+
* Dependencies from a class to itself are ignored.
57+
*
58+
* @param fromClassFqn the fully qualified source class name
59+
* @param toClassFqn the fully qualified target class name
60+
*/
3561
@Override
3662
public void addClassDependency(String fromClassFqn, String toClassFqn) {
3763
if (fromClassFqn.equals(toClassFqn)) {
3864
return;
3965
}
4066

41-
classReferencesGraph.addVertex(fromClassFqn);
42-
classReferencesGraph.addVertex(toClassFqn);
67+
VertexInfo fromVertex = getOrCreateVertex(fromClassFqn);
68+
VertexInfo toVertex = getOrCreateVertex(toClassFqn);
69+
classReferencesGraph.addVertex(fromVertex);
70+
classReferencesGraph.addVertex(toVertex);
4371

4472
DefaultWeightedEdge classRelationship;
45-
if (!classReferencesGraph.containsEdge(fromClassFqn, toClassFqn)) {
46-
classRelationship = classReferencesGraph.addEdge(fromClassFqn, toClassFqn);
73+
if (!classReferencesGraph.containsEdge(fromVertex, toVertex)) {
74+
classRelationship = classReferencesGraph.addEdge(fromVertex, toVertex);
4775
} else {
48-
classRelationship = classReferencesGraph.getEdge(fromClassFqn, toClassFqn);
76+
classRelationship = classReferencesGraph.getEdge(fromVertex, toVertex);
4977
classReferencesGraph.setEdgeWeight(
5078
classRelationship, classReferencesGraph.getEdgeWeight(classRelationship) + 1);
5179
}
@@ -63,6 +91,13 @@ public void addClassDependency(String fromClassFqn, String toClassFqn) {
6391
}
6492
}
6593

94+
/**
95+
* Adds or increments the package dependency implied by two class names.
96+
*
97+
* @param fromClassFqn the fully qualified source class name
98+
* @param toClassFqn the fully qualified target class name
99+
* @return the package edge, or {@code null} for equal or empty endpoint packages
100+
*/
66101
@Override
67102
public DefaultWeightedEdge addPackageDependency(String fromClassFqn, String toClassFqn) {
68103
String fromPackageName = getPackageFromFqn(fromClassFqn);
@@ -80,14 +115,16 @@ public DefaultWeightedEdge addPackageDependency(String fromClassFqn, String toCl
80115
return null;
81116
}
82117

83-
packageReferencesGraph.addVertex(fromPackageName);
84-
packageReferencesGraph.addVertex(toPackageName);
118+
VertexInfo fromPackageVertex = getOrCreateVertex(fromPackageName);
119+
VertexInfo toPackageVertex = getOrCreateVertex(toPackageName);
120+
packageReferencesGraph.addVertex(fromPackageVertex);
121+
packageReferencesGraph.addVertex(toPackageVertex);
85122

86123
DefaultWeightedEdge packageEdge;
87-
if (!packageReferencesGraph.containsEdge(fromPackageName, toPackageName)) {
88-
packageEdge = packageReferencesGraph.addEdge(fromPackageName, toPackageName);
124+
if (!packageReferencesGraph.containsEdge(fromPackageVertex, toPackageVertex)) {
125+
packageEdge = packageReferencesGraph.addEdge(fromPackageVertex, toPackageVertex);
89126
} else {
90-
packageEdge = packageReferencesGraph.getEdge(fromPackageName, toPackageName);
127+
packageEdge = packageReferencesGraph.getEdge(fromPackageVertex, toPackageVertex);
91128
packageReferencesGraph.setEdgeWeight(packageEdge, packageReferencesGraph.getEdgeWeight(packageEdge) + 1);
92129
}
93130

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

142+
/**
143+
* Returns the registered {@link VertexInfo} for the given class or package
144+
* name, creating and registering one (with the parent namespace derived
145+
* from the name) when absent.
146+
*/
147+
private VertexInfo getOrCreateVertex(String name) {
148+
return vertexRegistry.computeIfAbsent(name, VertexInfo::forName);
149+
}
150+
151+
/**
152+
* Records the raw source location reported by a visitor for later inspection.
153+
*
154+
* @param classFqn the fully qualified class name
155+
* @param sourceFilePath the visitor-reported source location
156+
*/
105157
@Override
106158
public void recordClassLocation(String classFqn, String sourceFilePath) {
107-
// This will be handled by JavaVisitor which maintains the mapping
159+
// Capture the location reported during collection; repository-relative
160+
// hydration of the VertexInfo vertices happens after the walk.
161+
recordedClassLocations.put(classFqn, sourceFilePath);
108162
}
109163

110164
@Override
111165
public void registerPackage(String packageName) {
112166
packagesInCodebase.add(packageName);
113167
}
114168

169+
/**
170+
* Registers a class vertex with its derived parent namespace, even if it has no dependencies.
171+
*
172+
* @param classFqn the fully qualified class name
173+
*/
115174
@Override
116175
public void registerClassVertex(String classFqn) {
117-
classReferencesGraph.addVertex(classFqn);
176+
classReferencesGraph.addVertex(getOrCreateVertex(classFqn));
118177
}
119178
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package org.hjug.graphbuilder;
2+
3+
/**
4+
* Rich graph vertex captured when graph dependency information is collected.
5+
*
6+
* <p>Replaces the bare class/package name String that previously served as the
7+
* graph vertex so the renderer can build a composed codebase map from the
8+
* graph image. Carries:
9+
*
10+
* <ul>
11+
* <li>{@link #getName()} — the vertex name: the fully qualified class name
12+
* (class graph) or the package name (package graph).</li>
13+
* <li>{@link #getLocationOnDisk()} — the location on disk of the vertex
14+
* relative to the repository root (source file path for a class;
15+
* member-file directory for a package). Populated by hydration after the
16+
* visitor walk; may be {@code null} when no location is known.</li>
17+
* <li>{@link #getParentNamespace()} — the parent namespace: the parent
18+
* package if this vertex is a package, or the enclosing construct
19+
* (package or class) if the vertex is not a package and contains code.
20+
* Derived from the name at creation time.</li>
21+
* </ul>
22+
*
23+
* <p>Identity is {@code name}-based ({@code equals}/{@code hashCode}) so
24+
* JGraphT vertex deduplication behaves exactly like the previous String
25+
* vertices and code can probe graphs with cheap {@link #forName(String)}
26+
* lookups. {@code toString()} returns the name so edge rendering
27+
* ({@code "(source : target)"}) is unchanged.
28+
*/
29+
public class VertexInfo {
30+
31+
private final String name;
32+
private final String parentNamespace;
33+
private String locationOnDisk;
34+
35+
/** Creates a vertex with the supplied identity and parent namespace, leaving its disk location unset. */
36+
private VertexInfo(String name, String parentNamespace) {
37+
this.name = name;
38+
this.parentNamespace = parentNamespace;
39+
}
40+
41+
/**
42+
* Creates a vertex for the given class or package name with the parent
43+
* namespace derived from the name.
44+
*
45+
* @param name the class FQN or package name; must not be null or empty
46+
* @return a VertexInfo whose parent namespace is the enclosing package
47+
* (for a top-level class or a package) or the enclosing class (for
48+
* a nested, anonymous, or synthetic class); {@code null} when no
49+
* enclosing construct can be derived
50+
* @throws NullPointerException if {@code name} is null
51+
* @throws IllegalArgumentException if {@code name} is empty
52+
*/
53+
public static VertexInfo forName(String name) {
54+
if (name == null) {
55+
throw new NullPointerException("name must not be null");
56+
}
57+
if (name.isEmpty()) {
58+
throw new IllegalArgumentException("name must not be empty");
59+
}
60+
return new VertexInfo(name, deriveParentNamespace(name));
61+
}
62+
63+
/**
64+
* Derives the enclosing construct of a vertex from its name.
65+
*
66+
* <p>Rules:
67+
* <ul>
68+
* <li>Kotlin anonymous vertex ({@code pkg.Outer.<anonymous>}, trailing
69+
* segment starts with {@code <}): the text before the last dot; a
70+
* packageless literal {@code <anonymous>} has no parent.</li>
71+
* <li>Nested/anonymous/synthetic class ({@code $} in the simple name):
72+
* the enclosing class (text before the last {@code $}).</li>
73+
* <li>Top-level class or package: the text before the last dot (the
74+
* containing package / parent package); {@code null} when the name
75+
* has no dot (default package).</li>
76+
* </ul>
77+
*
78+
* @param name the class FQN or package name
79+
* @return the parent namespace, or {@code null} when none can be derived
80+
*/
81+
static String deriveParentNamespace(String name) {
82+
int lastDot = name.lastIndexOf('.');
83+
String simpleName = lastDot >= 0 ? name.substring(lastDot + 1) : name;
84+
85+
if (simpleName.startsWith("<")) {
86+
// Kotlin anonymous: "pkg.Outer.<anonymous>" -> "pkg.Outer";
87+
// packageless "<anonymous>" has no enclosing construct
88+
return lastDot > 0 ? name.substring(0, lastDot) : null;
89+
}
90+
91+
int lastDollar = simpleName.lastIndexOf('$');
92+
if (lastDollar >= 0) {
93+
// nested / anonymous / synthetic: enclosing class before the last $
94+
String enclosingSimple = simpleName.substring(0, lastDollar);
95+
if (enclosingSimple.isEmpty()) {
96+
return null;
97+
}
98+
return lastDot >= 0 ? name.substring(0, lastDot + 1) + enclosingSimple : enclosingSimple;
99+
}
100+
101+
// top-level class or package: containing / parent package
102+
return lastDot >= 0 ? name.substring(0, lastDot) : null;
103+
}
104+
105+
/** Returns the vertex name (class FQN or package name). */
106+
public String getName() {
107+
return name;
108+
}
109+
110+
/**
111+
* Returns the location on disk of this vertex relative to the repository
112+
* root, or {@code null} when not (yet) known.
113+
*/
114+
public String getLocationOnDisk() {
115+
return locationOnDisk;
116+
}
117+
118+
/** Sets the repository-relative location on disk (used during hydration). */
119+
void setLocationOnDisk(String locationOnDisk) {
120+
this.locationOnDisk = locationOnDisk;
121+
}
122+
123+
/**
124+
* Returns the parent namespace: the parent package if this vertex is a
125+
* package, or the enclosing construct (package or class) if the vertex is
126+
* not a package and contains code; {@code null} when none can be derived.
127+
*/
128+
public String getParentNamespace() {
129+
return parentNamespace;
130+
}
131+
132+
/**
133+
* Compares vertex names; parent namespaces and hydrated disk locations do not affect identity.
134+
*
135+
* @param o the object to compare
136+
* @return whether the other object is a vertex with the same name
137+
*/
138+
@Override
139+
public boolean equals(Object o) {
140+
if (this == o) {
141+
return true;
142+
}
143+
if (!(o instanceof VertexInfo)) {
144+
return false;
145+
}
146+
return name.equals(((VertexInfo) o).name);
147+
}
148+
149+
/**
150+
* Returns a name-based hash that remains stable when the disk location is hydrated.
151+
*
152+
* @return the vertex name's hash code
153+
*/
154+
@Override
155+
public int hashCode() {
156+
return name.hashCode();
157+
}
158+
159+
/**
160+
* Returns the vertex name for graph edge rendering.
161+
*
162+
* @return the fully qualified class name or package name
163+
*/
164+
@Override
165+
public String toString() {
166+
return name;
167+
}
168+
}

0 commit comments

Comments
 (0)