View Javadoc
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 }