1 /*
2 * Created on May 3, 2004
3 *
4 * Copyright (c) 2004, The JUNG Authors
5 *
6 * All rights reserved.
7 *
8 * This software is open-source under the BSD license; see either
9 * "license.txt" or
10 * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
11 */
12 package edu.uci.ics.jung.io;
13
14 import java.awt.geom.Point2D;
15 import java.io.BufferedReader;
16 import java.io.FileReader;
17 import java.io.IOException;
18 import java.io.Reader;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.StringTokenizer;
23
24 import com.google.common.base.Predicate;
25 import com.google.common.base.Predicates;
26 import com.google.common.base.Supplier;
27
28 import edu.uci.ics.jung.algorithms.util.MapSettableTransformer;
29 import edu.uci.ics.jung.algorithms.util.SettableTransformer;
30 import edu.uci.ics.jung.graph.DirectedGraph;
31 import edu.uci.ics.jung.graph.Graph;
32 import edu.uci.ics.jung.graph.UndirectedGraph;
33 import edu.uci.ics.jung.graph.util.EdgeType;
34
35
36 /**
37 * Reads a <code>Graph</code> from a Pajek NET formatted source.
38 *
39 * <p>If the edge constraints specify that the graph is strictly undirected,
40 * and an "*Arcs" section is encountered, or if the edge constraints specify that the
41 * graph is strictly directed, and an "*Edges" section is encountered,
42 * an <code>IllegalArgumentException</code> is thrown.
43 *
44 * <p>If the edge constraints do not permit parallel edges, only the first encountered
45 * of a set of parallel edges will be read; subsequent edges in that set will be ignored.
46 *
47 * <p>More restrictive edge constraints will cause vertices to be generated
48 * that are more time- and space-efficient.
49 *
50 * At the moment, only supports the
51 * part of the specification that defines:
52 * <ul>
53 * <li> vertex ids (each must have a value from 1 to n, where n is the number of vertices)
54 * <li> vertex labels (must be in quotes if interrupted by whitespace)
55 * <li> directed edge connections (single or list)
56 * <li> undirected edge connections (single or list)
57 * <li> edge weights (not compatible with edges specified in list form)
58 * <br><b>note</b>: this version of PajekNetReader does not support multiple edge
59 * weights, as PajekNetFile does; this behavior is consistent with the NET format.
60 * <li> vertex locations (x and y; z coordinate is ignored)
61 * </ul> <p>
62 *
63 * Here is an example format for a directed graph without edge weights
64 * and edges specified in list form: <br>
65 * <pre>
66 * *vertices [# of vertices]
67 * 1 "a"
68 * 2 "b"
69 * 3 "c"
70 * *arcslist
71 * 1 2 3
72 * 2 3
73 * </pre>
74 *
75 * Here is an example format for an undirected graph with edge weights
76 * and edges specified in non-list form: <br>
77 * <pre>
78 * *vertices [# of vertices]
79 * 1 "a"
80 * 2 "b"
81 * 3 "c"
82 * *edges
83 * 1 2 0.1
84 * 1 3 0.9
85 * 2 3 1.0
86 * </pre>
87 *
88 * @author Joshua O'Madadhain
89 * @see "'Pajek - Program for Analysis and Visualization of Large Networks', Vladimir Batagelj and Andrej Mrvar, http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/pajekman.pdf"
90 * @author Tom Nelson - converted to jung2
91 */
92 public class PajekNetReader<G extends Graph<V,E>,V,E>
93 {
94 protected Supplier<V> vertex_factory;
95 protected Supplier<E> edge_factory;
96
97 /**
98 * The map for vertex labels (if any) created by this class.
99 */
100 protected SettableTransformer<V, String> vertex_labels = new MapSettableTransformer<V,String>(new HashMap<V,String>());
101
102 /**
103 * The map for vertex locations (if any) defined by this class.
104 */
105 protected SettableTransformer<V, Point2D> vertex_locations = new MapSettableTransformer<V,Point2D>(new HashMap<V,Point2D>());
106
107 protected SettableTransformer<E, Number> edge_weights =
108 new MapSettableTransformer<E, Number>(new HashMap<E, Number>());
109
110 /**
111 * Used to specify whether the most recently read line is a
112 * Pajek-specific tag.
113 */
114 private static final Predicate<String> v_pred = new StartsWithPredicate("*vertices");
115 private static final Predicate<String> a_pred = new StartsWithPredicate("*arcs");
116 private static final Predicate<String> e_pred = new StartsWithPredicate("*edges");
117 private static final Predicate<String> t_pred = new StartsWithPredicate("*");
118 private static final Predicate<String> c_pred = Predicates.or(a_pred, e_pred);
119 protected static final Predicate<String> l_pred = ListTagPred.getInstance();
120
121 /**
122 * Creates a PajekNetReader instance with the specified vertex and edge factories.
123 * @param vertex_factory the Supplier to use to create vertex objects
124 * @param edge_factory the Supplier to use to create edge objects
125 */
126 public PajekNetReader(Supplier<V> vertex_factory, Supplier<E> edge_factory)
127 {
128 this.vertex_factory = vertex_factory;
129 this.edge_factory = edge_factory;
130 }
131
132 /**
133 * Creates a PajekNetReader instance with the specified edge Supplier,
134 * and whose vertex objects correspond to the integer IDs assigned in the file.
135 * Note that this requires <code>V</code> to be assignment-compatible with
136 * an <code>Integer</code> value.
137 * @param edge_factory the Supplier to use to create edge objects
138 */
139 public PajekNetReader(Supplier<E> edge_factory)
140 {
141 this(null, edge_factory);
142 }
143
144 /**
145 * Returns the graph created by parsing the specified file, as created
146 * by the specified Supplier.
147 * @param filename the file from which the graph is to be read
148 * @param graph_factory used to provide a graph instance
149 * @return a graph parsed from the specified file
150 * @throws IOException if the graph cannot be loaded
151 */
152 public G load(String filename, Supplier<? extends G> graph_factory) throws IOException
153 {
154 return load(new FileReader(filename), graph_factory.get());
155 }
156
157 /**
158 * Returns the graph created by parsing the specified reader, as created
159 * by the specified Supplier.
160 * @param reader the reader instance from which the graph is to be read
161 * @param graph_factory used to provide a graph instance
162 * @return a graph parsed from the specified reader
163 * @throws IOException if the graph cannot be loaded
164 */
165 public G load(Reader reader, Supplier<? extends G> graph_factory) throws IOException
166 {
167 return load(reader, graph_factory.get());
168 }
169
170 /**
171 * Returns the graph created by parsing the specified file, by populating the
172 * specified graph.
173 * @param filename the file from which the graph is to be read
174 * @param g the graph instance to populate
175 * @return a graph parsed from the specified file
176 * @throws IOException if the graph cannot be loaded
177 */
178 public G load(String filename, G g) throws IOException
179 {
180 if (g == null)
181 throw new IllegalArgumentException("Graph provided must be non-null");
182 return load(new FileReader(filename), g);
183 }
184
185 /**
186 * Populates the graph <code>g</code> with the graph represented by the
187 * Pajek-format data supplied by <code>reader</code>. Stores edge weights,
188 * if any, according to <code>nev</code> (if non-null).
189 *
190 * <p>Any existing vertices/edges of <code>g</code>, if any, are unaffected.
191 *
192 * <p>The edge data are filtered according to <code>g</code>'s constraints, if any; thus, if
193 * <code>g</code> only accepts directed edges, any undirected edges in the
194 * input are ignored.
195 *
196 * @param reader the reader from which the graph is to be read
197 * @param g the graph instance to populate
198 * @return a graph parsed from the specified reader
199 * @throws IOException if the graph cannot be loaded
200 */
201 public G load(Reader reader, G g) throws IOException
202 {
203 BufferedReader br = new BufferedReader(reader);
204
205 // ignore everything until we see '*Vertices'
206 String curLine = skip(br, v_pred);
207
208 if (curLine == null) // no vertices in the graph; return empty graph
209 return g;
210
211 // create appropriate number of vertices
212 StringTokenizer st = new StringTokenizer(curLine);
213 st.nextToken(); // skip past "*vertices";
214 int num_vertices = Integer.parseInt(st.nextToken());
215 List<V> id = null;
216 if (vertex_factory != null)
217 {
218 for (int i = 1; i <= num_vertices; i++)
219 g.addVertex(vertex_factory.get());
220 id = new ArrayList<V>(g.getVertices());
221 }
222
223 // read vertices until we see any Pajek format tag ('*...')
224 curLine = null;
225 while (br.ready())
226 {
227 curLine = br.readLine();
228 if (curLine == null || t_pred.apply(curLine))
229 break;
230 if (curLine == "") // skip blank lines
231 continue;
232
233 try
234 {
235 readVertex(curLine, id, num_vertices);
236 }
237 catch (IllegalArgumentException iae)
238 {
239 br.close();
240 reader.close();
241 throw iae;
242 }
243 }
244
245 // skip over the intermediate stuff (if any)
246 // and read the next arcs/edges section that we find
247 curLine = readArcsOrEdges(curLine, br, g, id, edge_factory);
248
249 // ditto
250 readArcsOrEdges(curLine, br, g, id, edge_factory);
251
252 br.close();
253 reader.close();
254
255 return g;
256 }
257
258 /**
259 * Parses <code>curLine</code> as a reference to a vertex, and optionally assigns
260 * label and location information.
261 */
262 @SuppressWarnings("unchecked")
263 private void readVertex(String curLine, List<V> id, int num_vertices)
264 {
265 V v;
266 String[] parts = null;
267 int coord_idx = -1; // index of first coordinate in parts; -1 indicates no coordinates found
268 String index;
269 String label = null;
270 // if there are quote marks on this line, split on them; label is surrounded by them
271 if (curLine.indexOf('"') != -1)
272 {
273 String[] initial_split = curLine.trim().split("\"");
274 // if there are any quote marks, there should be exactly 2
275 if (initial_split.length < 2 || initial_split.length > 3)
276 throw new IllegalArgumentException("Unbalanced (or too many) " +
277 "quote marks in " + curLine);
278 index = initial_split[0].trim();
279 label = initial_split[1].trim();
280 if (initial_split.length == 3)
281 parts = initial_split[2].trim().split("\\s+", -1);
282 coord_idx = 0;
283 }
284 else // no quote marks, but are there coordinates?
285 {
286 parts = curLine.trim().split("\\s+", -1);
287 index = parts[0];
288 switch (parts.length)
289 {
290 case 1: // just the ID; nothing to do, continue
291 break;
292 case 2: // just the ID and a label
293 label = parts[1];
294 break;
295 case 3: // ID, no label, coordinates
296 coord_idx = 1;
297 break;
298 default: // ID, label, (x,y) coordinates, maybe some other stuff
299 coord_idx = 2;
300 break;
301 }
302 }
303 int v_id = Integer.parseInt(index) - 1; // go from 1-based to 0-based index
304 if (v_id >= num_vertices || v_id < 0)
305 throw new IllegalArgumentException("Vertex number " + v_id +
306 "is not in the range [1," + num_vertices + "]");
307 if (id != null)
308 v = id.get(v_id);
309 else
310 v = (V)(new Integer(v_id));
311 // only attach the label if there's one to attach
312 if (label != null && label.length() > 0 && vertex_labels != null)
313 vertex_labels.set(v, label);
314
315 // parse the rest of the line
316 if (coord_idx != -1 && parts != null && parts.length >= coord_idx+2 && vertex_locations != null)
317 {
318 double x = Double.parseDouble(parts[coord_idx]);
319 double y = Double.parseDouble(parts[coord_idx+1]);
320 vertex_locations.set(v, new Point2D.Double(x,y));
321 }
322 }
323
324
325
326 @SuppressWarnings("unchecked")
327 private String readArcsOrEdges(String curLine, BufferedReader br, Graph<V,E> g, List<V> id, Supplier<E> edge_factory)
328 throws IOException
329 {
330 String nextLine = curLine;
331
332 // in case we're not there yet (i.e., format tag isn't arcs or edges)
333 if (! c_pred.apply(curLine))
334 nextLine = skip(br, c_pred);
335
336 boolean reading_arcs = false;
337 boolean reading_edges = false;
338 EdgeType directedness = null;
339 if (a_pred.apply(nextLine))
340 {
341 if (g instanceof UndirectedGraph) {
342 throw new IllegalArgumentException("Supplied undirected-only graph cannot be populated with directed edges");
343 } else {
344 reading_arcs = true;
345 directedness = EdgeType.DIRECTED;
346 }
347 }
348 if (e_pred.apply(nextLine))
349 {
350 if (g instanceof DirectedGraph)
351 throw new IllegalArgumentException("Supplied directed-only graph cannot be populated with undirected edges");
352 else
353 reading_edges = true;
354 directedness = EdgeType.UNDIRECTED;
355 }
356
357 if (!(reading_arcs || reading_edges))
358 return nextLine;
359
360 boolean is_list = l_pred.apply(nextLine);
361
362 while (br.ready())
363 {
364 nextLine = br.readLine();
365 if (nextLine == null || t_pred.apply(nextLine))
366 break;
367 if (curLine == "") // skip blank lines
368 continue;
369
370 StringTokenizer st = new StringTokenizer(nextLine.trim());
371
372 int vid1 = Integer.parseInt(st.nextToken()) - 1;
373 V v1;
374 if (id != null)
375 v1 = id.get(vid1);
376 else
377 v1 = (V)new Integer(vid1);
378
379
380 if (is_list) // one source, multiple destinations
381 {
382 do
383 {
384 createAddEdge(st, v1, directedness, g, id, edge_factory);
385 } while (st.hasMoreTokens());
386 }
387 else // one source, one destination, at most one weight
388 {
389 E e = createAddEdge(st, v1, directedness, g, id, edge_factory);
390 // get the edge weight if we care
391 if (edge_weights != null && st.hasMoreTokens())
392 edge_weights.set(e, new Float(st.nextToken()));
393 }
394 }
395 return nextLine;
396 }
397
398 @SuppressWarnings("unchecked")
399 protected E createAddEdge(StringTokenizer st, V v1,
400 EdgeType directed, Graph<V,E> g, List<V> id, Supplier<E> edge_factory)
401 {
402 int vid2 = Integer.parseInt(st.nextToken()) - 1;
403 V v2;
404 if (id != null)
405 v2 = id.get(vid2);
406 else
407 v2 = (V)new Integer(vid2);
408 E e = edge_factory.get();
409
410 // don't error-check this: let the graph implementation do whatever it's going to do
411 // (add the edge, replace the existing edge, throw an exception--depends on the graph implementation)
412 g.addEdge(e, v1, v2, directed);
413 return e;
414 }
415
416 /**
417 * Returns the first line read from <code>br</code> for which <code>p</code>
418 * returns <code>true</code>, or <code>null</code> if there is no
419 * such line.
420 * @param br the reader from which the graph is being read
421 * @param p predicate specifying what line to accept
422 * @return the first line from {@code br} that matches {@code p}, or null
423 * @throws IOException if an error is encountered while reading from {@code br}
424 */
425 protected String skip(BufferedReader br, Predicate<String> p) throws IOException
426 {
427 while (br.ready())
428 {
429 String curLine = br.readLine();
430 if (curLine == null)
431 break;
432 curLine = curLine.trim();
433 if (p.apply(curLine))
434 return curLine;
435 }
436 return null;
437 }
438
439 /**
440 * A Predicate which evaluates to <code>true</code> if the
441 * argument starts with the constructor-specified String.
442 *
443 * @author Joshua O'Madadhain
444 */
445 protected static class StartsWithPredicate implements Predicate<String> {
446 private String tag;
447
448 protected StartsWithPredicate(String s) {
449 this.tag = s;
450 }
451
452 public boolean apply(String str) {
453 return (str != null && str.toLowerCase().startsWith(tag));
454 }
455 }
456
457
458 /**
459 * A Predicate which evaluates to <code>true</code> if the
460 * argument ends with the string "list".
461 *
462 * @author Joshua O'Madadhain
463 */
464 protected static class ListTagPred implements Predicate<String>
465 {
466 protected static ListTagPred instance;
467
468 protected ListTagPred() {}
469
470 protected static ListTagPred getInstance()
471 {
472 if (instance == null)
473 instance = new ListTagPred();
474 return instance;
475 }
476
477 public boolean apply(String s)
478 {
479 return (s != null && s.toLowerCase().endsWith("list"));
480 }
481 }
482
483 /**
484 * @return the vertexLocationTransformer
485 */
486 public SettableTransformer<V, Point2D> getVertexLocationTransformer() {
487 return vertex_locations;
488 }
489
490 /**
491 * Provides a Function which will be used to write out the vertex locations.
492 * @param vertex_locations a container for the vertex locations
493 */
494 public void setVertexLocationTransformer(SettableTransformer<V, Point2D> vertex_locations)
495 {
496 this.vertex_locations = vertex_locations;
497 }
498
499 /**
500 * @return a mapping from vertices to their labels
501 */
502 public SettableTransformer<V, String> getVertexLabeller() {
503 return vertex_labels;
504 }
505
506 /**
507 * Provides a Function which will be used to write out the vertex labels.
508 * @param vertex_labels a container for the vertex labels
509 */
510 public void setVertexLabeller(SettableTransformer<V, String> vertex_labels)
511 {
512 this.vertex_labels = vertex_labels;
513 }
514
515 /**
516 * @return a mapping from edges to their weights
517 */
518 public SettableTransformer<E, Number> getEdgeWeightTransformer()
519 {
520 return edge_weights;
521 }
522
523 /**
524 * Provides a Function which will be used to write out edge weights.
525 * @param edge_weights a container for the edge weights
526 */
527 public void setEdgeWeightTransformer(SettableTransformer<E, Number> edge_weights)
528 {
529 this.edge_weights = edge_weights;
530 }
531
532 }