This repository was archived by the owner on Jun 18, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGraphFileParser.java
More file actions
186 lines (161 loc) · 6.62 KB
/
Copy pathGraphFileParser.java
File metadata and controls
186 lines (161 loc) · 6.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package gvisual;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
import java.util.logging.Logger;
/**
* Parses a graph definition file (nodes + edges) into a JUNG graph and
* classified edge lists.
*
* <p>Extracted from {@link Main#addGraph()} to separate file I/O and
* parsing logic from Swing UI construction. This makes the parsing
* independently testable and reusable (e.g. for headless analysis or
* batch processing).
*
* <h3>File format</h3>
* <pre>
* nodes
* A
* B
* C
* edges
* FR A B 1.5
* CL B C 2.0
* </pre>
*
* Each edge line: {@code <type_code> <vertex1> <vertex2> <weight>}
*/
public class GraphFileParser {
private static final Logger LOGGER = Logger.getLogger(GraphFileParser.class.getName());
/**
* Result of parsing a graph file. Holds the graph, classified edge
* lists, and the set of all vertices found.
*/
public static class ParseResult {
private final Graph<String, edge> graph;
private final Map<EdgeType, List<edge>> edgesByType;
private final Set<String> vertices;
private final int skippedLines;
ParseResult(Graph<String, edge> graph,
Map<EdgeType, List<edge>> edgesByType,
Set<String> vertices,
int skippedLines) {
this.graph = graph;
this.edgesByType = Collections.unmodifiableMap(edgesByType);
this.vertices = Collections.unmodifiableSet(vertices);
this.skippedLines = skippedLines;
}
/** The parsed JUNG graph (undirected, sparse). */
public Graph<String, edge> getGraph() { return graph; }
/** Edges grouped by {@link EdgeType}. */
public Map<EdgeType, List<edge>> getEdgesByType() { return edgesByType; }
/** Convenience accessor for a single edge type's list (never null). */
public List<edge> getEdges(EdgeType type) {
return edgesByType.getOrDefault(type, Collections.emptyList());
}
/** All vertex identifiers found in the file. */
public Set<String> getVertices() { return vertices; }
/** Number of lines skipped due to parse errors. */
public int getSkippedLines() { return skippedLines; }
}
/**
* Parse a graph file into a {@link ParseResult}.
*
* @param filePath path to the graph definition file
* @param visibleFilter predicate that returns {@code true} for edge type
* codes that should be added to the graph (not just
* classified). Pass {@code code -> true} to include all.
* @return parsed result containing graph, edge lists, and vertices
* @throws IOException if the file cannot be read
*/
public static ParseResult parse(String filePath, Predicate<String> visibleFilter)
throws IOException {
Graph<String, edge> g = new UndirectedSparseGraph<>();
Map<EdgeType, List<edge>> edgesByType = new EnumMap<>(EdgeType.class);
for (EdgeType t : EdgeType.values()) {
edgesByType.put(t, new ArrayList<>());
}
Set<String> vertices = new LinkedHashSet<>();
int skipped = 0;
File database = new File(filePath);
LineIterator lineIterator = null;
try {
lineIterator = FileUtils.lineIterator(database);
int section = -1; // 0 = nodes, 1 = edges
while (lineIterator.hasNext()) {
String line = lineIterator.nextLine().trim();
if (line.isEmpty()) continue;
if (line.equalsIgnoreCase("nodes")) {
section = 0;
continue;
}
if (line.equalsIgnoreCase("edges")) {
section = 1;
continue;
}
if (section == 0) {
// Node line
String[] parts = line.split("\\s+");
if (parts.length < 1 || parts[0].isEmpty()) {
skipped++;
continue;
}
String vertex = parts[0];
g.addVertex(vertex);
vertices.add(vertex);
} else if (section == 1) {
// Edge line: <type> <v1> <v2> <weight>
String[] parts = line.split("\\s+");
if (parts.length < 4) {
LOGGER.warning("Skipping malformed edge line: " + line);
skipped++;
continue;
}
double weight;
try {
weight = Double.parseDouble(parts[3]);
} catch (NumberFormatException e) {
LOGGER.warning("Skipping edge with invalid weight: " + line);
skipped++;
continue;
}
if (Double.isNaN(weight) || Double.isInfinite(weight)) {
LOGGER.warning("Skipping edge with non-finite weight: " + line);
skipped++;
continue;
}
edge curEdge = new edge(parts[0], parts[1], parts[2]);
curEdge.setWeight(weight);
// Classify by type
EdgeType edgeType = EdgeType.fromCode(parts[0]);
if (edgeType != null) {
List<edge> typeList = edgesByType.get(edgeType);
// Set label on first edge of each type for the legend
if (typeList.stream().noneMatch(e -> e.getLabel() != null)) {
curEdge.setLabel(edgeType.getDisplayLabel());
}
typeList.add(curEdge);
}
// Only add to graph if this type is visible
if (visibleFilter.test(parts[0])) {
g.addEdge(curEdge, parts[1], parts[2]);
}
}
}
} finally {
LineIterator.closeQuietly(lineIterator);
}
return new ParseResult(g, edgesByType, vertices, skipped);
}
/**
* Convenience overload that includes all edge types.
*/
public static ParseResult parse(String filePath) throws IOException {
return parse(filePath, code -> true);
}
}