edu.uci.ics.jung.io.PajekNetReader Maven / Gradle / Ivy
Show all versions of jung-io Show documentation
/*
* Created on May 3, 2004
*
* Copyright (c) 2004, The JUNG Authors
*
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
*/
package edu.uci.ics.jung.io;
import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import edu.uci.ics.jung.algorithms.util.MapSettableTransformer;
import edu.uci.ics.jung.algorithms.util.SettableTransformer;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedGraph;
import edu.uci.ics.jung.graph.util.EdgeType;
/**
* Reads a Graph
from a Pajek NET formatted source.
*
* If the edge constraints specify that the graph is strictly undirected,
* and an "*Arcs" section is encountered, or if the edge constraints specify that the
* graph is strictly directed, and an "*Edges" section is encountered,
* an IllegalArgumentException
is thrown.
*
*
If the edge constraints do not permit parallel edges, only the first encountered
* of a set of parallel edges will be read; subsequent edges in that set will be ignored.
*
*
More restrictive edge constraints will cause vertices to be generated
* that are more time- and space-efficient.
*
* At the moment, only supports the
* part of the specification that defines:
*
* - vertex ids (each must have a value from 1 to n, where n is the number of vertices)
*
- vertex labels (must be in quotes if interrupted by whitespace)
*
- directed edge connections (single or list)
*
- undirected edge connections (single or list)
*
- edge weights (not compatible with edges specified in list form)
*
note: this version of PajekNetReader does not support multiple edge
* weights, as PajekNetFile does; this behavior is consistent with the NET format.
* - vertex locations (x and y; z coordinate is ignored)
*
*
* Here is an example format for a directed graph without edge weights
* and edges specified in list form:
*
* *vertices [# of vertices]
* 1 "a"
* 2 "b"
* 3 "c"
* *arcslist
* 1 2 3
* 2 3
*
*
* Here is an example format for an undirected graph with edge weights
* and edges specified in non-list form:
*
* *vertices [# of vertices]
* 1 "a"
* 2 "b"
* 3 "c"
* *edges
* 1 2 0.1
* 1 3 0.9
* 2 3 1.0
*
*
* @author Joshua O'Madadhain
* @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"
* @author Tom Nelson - converted to jung2
*/
public class PajekNetReader,V,E>
{
protected Supplier vertex_factory;
protected Supplier edge_factory;
/**
* The map for vertex labels (if any) created by this class.
*/
protected SettableTransformer vertex_labels = new MapSettableTransformer(new HashMap());
/**
* The map for vertex locations (if any) defined by this class.
*/
protected SettableTransformer vertex_locations = new MapSettableTransformer(new HashMap());
protected SettableTransformer edge_weights =
new MapSettableTransformer(new HashMap());
/**
* Used to specify whether the most recently read line is a
* Pajek-specific tag.
*/
private static final Predicate v_pred = new StartsWithPredicate("*vertices");
private static final Predicate a_pred = new StartsWithPredicate("*arcs");
private static final Predicate e_pred = new StartsWithPredicate("*edges");
private static final Predicate t_pred = new StartsWithPredicate("*");
private static final Predicate c_pred = Predicates.or(a_pred, e_pred);
protected static final Predicate l_pred = ListTagPred.getInstance();
/**
* Creates a PajekNetReader instance with the specified vertex and edge factories.
* @param vertex_factory the Supplier to use to create vertex objects
* @param edge_factory the Supplier to use to create edge objects
*/
public PajekNetReader(Supplier vertex_factory, Supplier edge_factory)
{
this.vertex_factory = vertex_factory;
this.edge_factory = edge_factory;
}
/**
* Creates a PajekNetReader instance with the specified edge Supplier,
* and whose vertex objects correspond to the integer IDs assigned in the file.
* Note that this requires V
to be assignment-compatible with
* an Integer
value.
* @param edge_factory the Supplier to use to create edge objects
*/
public PajekNetReader(Supplier edge_factory)
{
this(null, edge_factory);
}
/**
* Returns the graph created by parsing the specified file, as created
* by the specified Supplier.
* @param filename the file from which the graph is to be read
* @param graph_factory used to provide a graph instance
* @return a graph parsed from the specified file
* @throws IOException if the graph cannot be loaded
*/
public G load(String filename, Supplier extends G> graph_factory) throws IOException
{
return load(new FileReader(filename), graph_factory.get());
}
/**
* Returns the graph created by parsing the specified reader, as created
* by the specified Supplier.
* @param reader the reader instance from which the graph is to be read
* @param graph_factory used to provide a graph instance
* @return a graph parsed from the specified reader
* @throws IOException if the graph cannot be loaded
*/
public G load(Reader reader, Supplier extends G> graph_factory) throws IOException
{
return load(reader, graph_factory.get());
}
/**
* Returns the graph created by parsing the specified file, by populating the
* specified graph.
* @param filename the file from which the graph is to be read
* @param g the graph instance to populate
* @return a graph parsed from the specified file
* @throws IOException if the graph cannot be loaded
*/
public G load(String filename, G g) throws IOException
{
if (g == null)
throw new IllegalArgumentException("Graph provided must be non-null");
return load(new FileReader(filename), g);
}
/**
* Populates the graph g
with the graph represented by the
* Pajek-format data supplied by reader
. Stores edge weights,
* if any, according to nev
(if non-null).
*
* Any existing vertices/edges of g
, if any, are unaffected.
*
*
The edge data are filtered according to g
's constraints, if any; thus, if
* g
only accepts directed edges, any undirected edges in the
* input are ignored.
*
* @param reader the reader from which the graph is to be read
* @param g the graph instance to populate
* @return a graph parsed from the specified reader
* @throws IOException if the graph cannot be loaded
*/
public G load(Reader reader, G g) throws IOException
{
BufferedReader br = new BufferedReader(reader);
// ignore everything until we see '*Vertices'
String curLine = skip(br, v_pred);
if (curLine == null) // no vertices in the graph; return empty graph
return g;
// create appropriate number of vertices
StringTokenizer st = new StringTokenizer(curLine);
st.nextToken(); // skip past "*vertices";
int num_vertices = Integer.parseInt(st.nextToken());
List id = null;
if (vertex_factory != null)
{
for (int i = 1; i <= num_vertices; i++)
g.addVertex(vertex_factory.get());
id = new ArrayList(g.getVertices());
}
// read vertices until we see any Pajek format tag ('*...')
curLine = null;
while (br.ready())
{
curLine = br.readLine();
if (curLine == null || t_pred.apply(curLine))
break;
if (curLine == "") // skip blank lines
continue;
try
{
readVertex(curLine, id, num_vertices);
}
catch (IllegalArgumentException iae)
{
br.close();
reader.close();
throw iae;
}
}
// skip over the intermediate stuff (if any)
// and read the next arcs/edges section that we find
curLine = readArcsOrEdges(curLine, br, g, id, edge_factory);
// ditto
readArcsOrEdges(curLine, br, g, id, edge_factory);
br.close();
reader.close();
return g;
}
/**
* Parses curLine
as a reference to a vertex, and optionally assigns
* label and location information.
*/
@SuppressWarnings("unchecked")
private void readVertex(String curLine, List id, int num_vertices)
{
V v;
String[] parts = null;
int coord_idx = -1; // index of first coordinate in parts; -1 indicates no coordinates found
String index;
String label = null;
// if there are quote marks on this line, split on them; label is surrounded by them
if (curLine.indexOf('"') != -1)
{
String[] initial_split = curLine.trim().split("\"");
// if there are any quote marks, there should be exactly 2
if (initial_split.length < 2 || initial_split.length > 3)
throw new IllegalArgumentException("Unbalanced (or too many) " +
"quote marks in " + curLine);
index = initial_split[0].trim();
label = initial_split[1].trim();
if (initial_split.length == 3)
parts = initial_split[2].trim().split("\\s+", -1);
coord_idx = 0;
}
else // no quote marks, but are there coordinates?
{
parts = curLine.trim().split("\\s+", -1);
index = parts[0];
switch (parts.length)
{
case 1: // just the ID; nothing to do, continue
break;
case 2: // just the ID and a label
label = parts[1];
break;
case 3: // ID, no label, coordinates
coord_idx = 1;
break;
default: // ID, label, (x,y) coordinates, maybe some other stuff
coord_idx = 2;
break;
}
}
int v_id = Integer.parseInt(index) - 1; // go from 1-based to 0-based index
if (v_id >= num_vertices || v_id < 0)
throw new IllegalArgumentException("Vertex number " + v_id +
"is not in the range [1," + num_vertices + "]");
if (id != null)
v = id.get(v_id);
else
v = (V)(new Integer(v_id));
// only attach the label if there's one to attach
if (label != null && label.length() > 0 && vertex_labels != null)
vertex_labels.set(v, label);
// parse the rest of the line
if (coord_idx != -1 && parts != null && parts.length >= coord_idx+2 && vertex_locations != null)
{
double x = Double.parseDouble(parts[coord_idx]);
double y = Double.parseDouble(parts[coord_idx+1]);
vertex_locations.set(v, new Point2D.Double(x,y));
}
}
@SuppressWarnings("unchecked")
private String readArcsOrEdges(String curLine, BufferedReader br, Graph g, List id, Supplier edge_factory)
throws IOException
{
String nextLine = curLine;
// in case we're not there yet (i.e., format tag isn't arcs or edges)
if (! c_pred.apply(curLine))
nextLine = skip(br, c_pred);
boolean reading_arcs = false;
boolean reading_edges = false;
EdgeType directedness = null;
if (a_pred.apply(nextLine))
{
if (g instanceof UndirectedGraph) {
throw new IllegalArgumentException("Supplied undirected-only graph cannot be populated with directed edges");
} else {
reading_arcs = true;
directedness = EdgeType.DIRECTED;
}
}
if (e_pred.apply(nextLine))
{
if (g instanceof DirectedGraph)
throw new IllegalArgumentException("Supplied directed-only graph cannot be populated with undirected edges");
else
reading_edges = true;
directedness = EdgeType.UNDIRECTED;
}
if (!(reading_arcs || reading_edges))
return nextLine;
boolean is_list = l_pred.apply(nextLine);
while (br.ready())
{
nextLine = br.readLine();
if (nextLine == null || t_pred.apply(nextLine))
break;
if (curLine == "") // skip blank lines
continue;
StringTokenizer st = new StringTokenizer(nextLine.trim());
int vid1 = Integer.parseInt(st.nextToken()) - 1;
V v1;
if (id != null)
v1 = id.get(vid1);
else
v1 = (V)new Integer(vid1);
if (is_list) // one source, multiple destinations
{
do
{
createAddEdge(st, v1, directedness, g, id, edge_factory);
} while (st.hasMoreTokens());
}
else // one source, one destination, at most one weight
{
E e = createAddEdge(st, v1, directedness, g, id, edge_factory);
// get the edge weight if we care
if (edge_weights != null && st.hasMoreTokens())
edge_weights.set(e, new Float(st.nextToken()));
}
}
return nextLine;
}
@SuppressWarnings("unchecked")
protected E createAddEdge(StringTokenizer st, V v1,
EdgeType directed, Graph g, List id, Supplier edge_factory)
{
int vid2 = Integer.parseInt(st.nextToken()) - 1;
V v2;
if (id != null)
v2 = id.get(vid2);
else
v2 = (V)new Integer(vid2);
E e = edge_factory.get();
// don't error-check this: let the graph implementation do whatever it's going to do
// (add the edge, replace the existing edge, throw an exception--depends on the graph implementation)
g.addEdge(e, v1, v2, directed);
return e;
}
/**
* Returns the first line read from br
for which p
* returns true
, or null
if there is no
* such line.
* @param br the reader from which the graph is being read
* @param p predicate specifying what line to accept
* @return the first line from {@code br} that matches {@code p}, or null
* @throws IOException if an error is encountered while reading from {@code br}
*/
protected String skip(BufferedReader br, Predicate p) throws IOException
{
while (br.ready())
{
String curLine = br.readLine();
if (curLine == null)
break;
curLine = curLine.trim();
if (p.apply(curLine))
return curLine;
}
return null;
}
/**
* A Predicate which evaluates to true
if the
* argument starts with the constructor-specified String.
*
* @author Joshua O'Madadhain
*/
protected static class StartsWithPredicate implements Predicate {
private String tag;
protected StartsWithPredicate(String s) {
this.tag = s;
}
public boolean apply(String str) {
return (str != null && str.toLowerCase().startsWith(tag));
}
}
/**
* A Predicate which evaluates to true
if the
* argument ends with the string "list".
*
* @author Joshua O'Madadhain
*/
protected static class ListTagPred implements Predicate
{
protected static ListTagPred instance;
protected ListTagPred() {}
protected static ListTagPred getInstance()
{
if (instance == null)
instance = new ListTagPred();
return instance;
}
public boolean apply(String s)
{
return (s != null && s.toLowerCase().endsWith("list"));
}
}
/**
* @return the vertexLocationTransformer
*/
public SettableTransformer getVertexLocationTransformer() {
return vertex_locations;
}
/**
* Provides a Function which will be used to write out the vertex locations.
* @param vertex_locations a container for the vertex locations
*/
public void setVertexLocationTransformer(SettableTransformer vertex_locations)
{
this.vertex_locations = vertex_locations;
}
/**
* @return a mapping from vertices to their labels
*/
public SettableTransformer getVertexLabeller() {
return vertex_labels;
}
/**
* Provides a Function which will be used to write out the vertex labels.
* @param vertex_labels a container for the vertex labels
*/
public void setVertexLabeller(SettableTransformer vertex_labels)
{
this.vertex_labels = vertex_labels;
}
/**
* @return a mapping from edges to their weights
*/
public SettableTransformer getEdgeWeightTransformer()
{
return edge_weights;
}
/**
* Provides a Function which will be used to write out edge weights.
* @param edge_weights a container for the edge weights
*/
public void setEdgeWeightTransformer(SettableTransformer edge_weights)
{
this.edge_weights = edge_weights;
}
}