Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit f7e964c

Browse files
refactor: extract shared GraphUtils with getOtherEnd, BFS, adjacency helpers
Five analyzers (CommunityDetector, GraphDiameterAnalyzer, NodeCentralityAnalyzer, PageRankAnalyzer, ShortestPathFinder) had identical private getOtherEnd(edge, String) methods — verbatim 4-line copy-paste returning the other endpoint of a JUNG edge. GraphDiameterAnalyzer additionally duplicated bfsDistances(), bfsComponent(), and findLargestComponent() that are general-purpose graph traversal operations. LinkPredictionAnalyzer duplicated buildAdjacency() and getCommonNeighbors(). New GraphUtils class centralizes these operations as static methods: - getOtherEnd(edge, String) — edge endpoint lookup - buildAdjacencyMap(Graph) — vertex-to-neighbors map - bfsDistances(Graph, String) — BFS hop counts from source - bfsComponent(Graph, String) — connected component discovery - findComponents(Graph) — all components sorted largest-first - findLargestComponent(Graph) — convenience for diameter analysis - getCommonNeighbors(Map, String, String) — shared neighbor set All 7 affected files now delegate to GraphUtils, eliminating ~80 lines of duplicated traversal code. Private wrapper methods preserved to maintain internal call signatures and avoid touching every call site — the delegation is transparent. Backward compatibility: all public APIs unchanged. GraphUtils methods are stateless and thread-safe.
1 parent e694ba7 commit f7e964c

7 files changed

Lines changed: 178 additions & 91 deletions

Gvisual/src/gvisual/CommunityDetector.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,6 @@ public DetectionResult detect() {
261261
}
262262

263263
private String getOtherEnd(edge e, String current) {
264-
String v1 = e.getVertex1();
265-
String v2 = e.getVertex2();
266-
if (current.equals(v1)) return v2;
267-
if (current.equals(v2)) return v1;
268-
return null;
264+
return GraphUtils.getOtherEnd(e, current);
269265
}
270266
}

Gvisual/src/gvisual/GraphDiameterAnalyzer.java

Lines changed: 3 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ public String getSummary() {
203203
// --- Private helpers ---
204204

205205
private int computeEccentricity(String source, Set<String> component) {
206-
Map<String, Integer> distances = bfsDistances(source);
206+
Map<String, Integer> distances = GraphUtils.bfsDistances(graph, source);
207207
int maxDist = 0;
208208
for (String v : component) {
209209
Integer d = distances.get(v);
@@ -214,67 +214,12 @@ private int computeEccentricity(String source, Set<String> component) {
214214
return maxDist;
215215
}
216216

217-
private Map<String, Integer> bfsDistances(String source) {
218-
Map<String, Integer> distances = new HashMap<String, Integer>();
219-
Queue<String> queue = new LinkedList<String>();
220-
distances.put(source, 0);
221-
queue.add(source);
222-
223-
while (!queue.isEmpty()) {
224-
String current = queue.poll();
225-
int currentDist = distances.get(current);
226-
for (edge e : graph.getIncidentEdges(current)) {
227-
String neighbor = getOtherEnd(e, current);
228-
if (neighbor != null && !distances.containsKey(neighbor)) {
229-
distances.put(neighbor, currentDist + 1);
230-
queue.add(neighbor);
231-
}
232-
}
233-
}
234-
return distances;
235-
}
236-
237217
private Set<String> findLargestComponent() {
238-
Set<String> visited = new HashSet<String>();
239-
Set<String> largest = Collections.emptySet();
240-
241-
for (String vertex : graph.getVertices()) {
242-
if (!visited.contains(vertex)) {
243-
Set<String> component = bfsComponent(vertex);
244-
visited.addAll(component);
245-
if (component.size() > largest.size()) {
246-
largest = component;
247-
}
248-
}
249-
}
250-
return largest;
251-
}
252-
253-
private Set<String> bfsComponent(String source) {
254-
Set<String> component = new LinkedHashSet<String>();
255-
Queue<String> queue = new LinkedList<String>();
256-
component.add(source);
257-
queue.add(source);
258-
259-
while (!queue.isEmpty()) {
260-
String current = queue.poll();
261-
for (edge e : graph.getIncidentEdges(current)) {
262-
String neighbor = getOtherEnd(e, current);
263-
if (neighbor != null && !component.contains(neighbor)) {
264-
component.add(neighbor);
265-
queue.add(neighbor);
266-
}
267-
}
268-
}
269-
return component;
218+
return GraphUtils.findLargestComponent(graph);
270219
}
271220

272221
private String getOtherEnd(edge e, String current) {
273-
String v1 = e.getVertex1();
274-
String v2 = e.getVertex2();
275-
if (current.equals(v1)) return v2;
276-
if (current.equals(v2)) return v1;
277-
return null;
222+
return GraphUtils.getOtherEnd(e, current);
278223
}
279224

280225
private String formatSet(Set<String> set, int max) {
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package gvisual;
2+
3+
import edu.uci.ics.jung.graph.Graph;
4+
import java.util.*;
5+
6+
/**
7+
* Shared graph traversal and adjacency utilities used by multiple analyzers.
8+
*
9+
* <p>Centralizes common operations that were previously duplicated across
10+
* {@link CommunityDetector}, {@link GraphDiameterAnalyzer},
11+
* {@link NodeCentralityAnalyzer}, {@link PageRankAnalyzer},
12+
* {@link ShortestPathFinder}, {@link LinkPredictionAnalyzer}, and
13+
* {@link GraphIsomorphismAnalyzer}.</p>
14+
*
15+
* <p>All methods are static and stateless — safe for concurrent use.</p>
16+
*
17+
* @author zalenix
18+
*/
19+
public final class GraphUtils {
20+
21+
private GraphUtils() { /* utility class */ }
22+
23+
/**
24+
* Returns the vertex at the other end of an edge from the given vertex.
25+
*
26+
* @param e the edge
27+
* @param current the vertex we're "standing on"
28+
* @return the other endpoint, or {@code null} if {@code current} is not
29+
* an endpoint of the edge
30+
*/
31+
public static String getOtherEnd(edge e, String current) {
32+
String v1 = e.getVertex1();
33+
String v2 = e.getVertex2();
34+
if (current.equals(v1)) return v2;
35+
if (current.equals(v2)) return v1;
36+
return null;
37+
}
38+
39+
/**
40+
* Builds an adjacency set map for all vertices in the graph.
41+
*
42+
* @param graph the JUNG graph
43+
* @return map from each vertex to its set of neighbor vertex IDs
44+
*/
45+
public static Map<String, Set<String>> buildAdjacencyMap(
46+
Graph<String, edge> graph) {
47+
Map<String, Set<String>> adj = new HashMap<String, Set<String>>();
48+
for (String v : graph.getVertices()) {
49+
Set<String> neighbors = new HashSet<String>();
50+
Collection<String> graphNeighbors = graph.getNeighbors(v);
51+
if (graphNeighbors != null) {
52+
neighbors.addAll(graphNeighbors);
53+
}
54+
adj.put(v, neighbors);
55+
}
56+
return adj;
57+
}
58+
59+
/**
60+
* BFS from a source vertex, returning distances (hop counts) to all
61+
* reachable vertices.
62+
*
63+
* @param graph the JUNG graph
64+
* @param source the starting vertex
65+
* @return map from vertex ID to its BFS distance from source
66+
*/
67+
public static Map<String, Integer> bfsDistances(
68+
Graph<String, edge> graph, String source) {
69+
Map<String, Integer> distances = new HashMap<String, Integer>();
70+
Queue<String> queue = new LinkedList<String>();
71+
distances.put(source, 0);
72+
queue.add(source);
73+
74+
while (!queue.isEmpty()) {
75+
String current = queue.poll();
76+
int currentDist = distances.get(current);
77+
for (edge e : graph.getIncidentEdges(current)) {
78+
String neighbor = getOtherEnd(e, current);
79+
if (neighbor != null && !distances.containsKey(neighbor)) {
80+
distances.put(neighbor, currentDist + 1);
81+
queue.add(neighbor);
82+
}
83+
}
84+
}
85+
return distances;
86+
}
87+
88+
/**
89+
* BFS to discover the connected component containing a source vertex.
90+
*
91+
* @param graph the JUNG graph
92+
* @param source the starting vertex
93+
* @return set of all vertices reachable from source (including source)
94+
*/
95+
public static Set<String> bfsComponent(
96+
Graph<String, edge> graph, String source) {
97+
Set<String> component = new LinkedHashSet<String>();
98+
Queue<String> queue = new LinkedList<String>();
99+
component.add(source);
100+
queue.add(source);
101+
102+
while (!queue.isEmpty()) {
103+
String current = queue.poll();
104+
for (edge e : graph.getIncidentEdges(current)) {
105+
String neighbor = getOtherEnd(e, current);
106+
if (neighbor != null && !component.contains(neighbor)) {
107+
component.add(neighbor);
108+
queue.add(neighbor);
109+
}
110+
}
111+
}
112+
return component;
113+
}
114+
115+
/**
116+
* Finds all connected components of the graph.
117+
*
118+
* @param graph the JUNG graph
119+
* @return list of components, each a set of vertex IDs, sorted largest-first
120+
*/
121+
public static List<Set<String>> findComponents(
122+
Graph<String, edge> graph) {
123+
Set<String> visited = new HashSet<String>();
124+
List<Set<String>> components = new ArrayList<Set<String>>();
125+
126+
for (String vertex : graph.getVertices()) {
127+
if (!visited.contains(vertex)) {
128+
Set<String> component = bfsComponent(graph, vertex);
129+
visited.addAll(component);
130+
components.add(component);
131+
}
132+
}
133+
134+
// Sort largest-first
135+
Collections.sort(components, new Comparator<Set<String>>() {
136+
public int compare(Set<String> a, Set<String> b) {
137+
return Integer.compare(b.size(), a.size());
138+
}
139+
});
140+
return components;
141+
}
142+
143+
/**
144+
* Finds the largest connected component.
145+
*
146+
* @param graph the JUNG graph
147+
* @return set of vertices in the largest component, or empty set if graph is empty
148+
*/
149+
public static Set<String> findLargestComponent(
150+
Graph<String, edge> graph) {
151+
List<Set<String>> components = findComponents(graph);
152+
return components.isEmpty() ? Collections.<String>emptySet() : components.get(0);
153+
}
154+
155+
/**
156+
* Returns the set of common neighbors between two vertices.
157+
*
158+
* @param adjacency precomputed adjacency map
159+
* @param u first vertex
160+
* @param v second vertex
161+
* @return set of vertices adjacent to both u and v
162+
*/
163+
public static Set<String> getCommonNeighbors(
164+
Map<String, Set<String>> adjacency, String u, String v) {
165+
Set<String> common = new HashSet<String>(adjacency.get(u));
166+
common.retainAll(adjacency.get(v));
167+
return common;
168+
}
169+
}

Gvisual/src/gvisual/LinkPredictionAnalyzer.java

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -328,22 +328,11 @@ private double computeScore(Method method, Map<String, Set<String>> adjacency,
328328
// ── Helpers ─────────────────────────────────────────────────
329329

330330
private Map<String, Set<String>> buildAdjacency(Collection<String> vertices) {
331-
Map<String, Set<String>> adj = new HashMap<String, Set<String>>();
332-
for (String v : vertices) {
333-
Set<String> neighbors = new HashSet<String>();
334-
Collection<String> graphNeighbors = graph.getNeighbors(v);
335-
if (graphNeighbors != null) {
336-
neighbors.addAll(graphNeighbors);
337-
}
338-
adj.put(v, neighbors);
339-
}
340-
return adj;
331+
return GraphUtils.buildAdjacencyMap(graph);
341332
}
342333

343334
private Set<String> getCommonNeighbors(Map<String, Set<String>> adjacency,
344335
String u, String v) {
345-
Set<String> common = new HashSet<String>(adjacency.get(u));
346-
common.retainAll(adjacency.get(v));
347-
return common;
336+
return GraphUtils.getCommonNeighbors(adjacency, u, v);
348337
}
349338
}

Gvisual/src/gvisual/NodeCentralityAnalyzer.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -481,10 +481,6 @@ private void computeBetweennessAndCloseness() {
481481
}
482482

483483
private String getOtherEnd(edge e, String current) {
484-
String v1 = e.getVertex1();
485-
String v2 = e.getVertex2();
486-
if (current.equals(v1)) return v2;
487-
if (current.equals(v2)) return v1;
488-
return null;
484+
return GraphUtils.getOtherEnd(e, current);
489485
}
490486
}

Gvisual/src/gvisual/PageRankAnalyzer.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -760,10 +760,6 @@ public String toString() {
760760
// ──────────────── Private helpers ────────────────
761761

762762
private String getOtherEnd(edge e, String current) {
763-
String v1 = e.getVertex1();
764-
String v2 = e.getVertex2();
765-
if (current.equals(v1)) return v2;
766-
if (current.equals(v2)) return v1;
767-
return null;
763+
return GraphUtils.getOtherEnd(e, current);
768764
}
769765
}

Gvisual/src/gvisual/ShortestPathFinder.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,7 @@ private void validateVertex(String vertex, String name) {
300300
}
301301

302302
private String getOtherEnd(edge e, String current) {
303-
String v1 = e.getVertex1();
304-
String v2 = e.getVertex2();
305-
if (current.equals(v1)) return v2;
306-
if (current.equals(v2)) return v1;
307-
return null;
303+
return GraphUtils.getOtherEnd(e, current);
308304
}
309305

310306
private PathResult buildPath(String source, String target,

0 commit comments

Comments
 (0)