Java tutorial
/* * Created on May 3, 2004 * * Copyright (c) 2004, the JUNG Project and the Regents of the University * of California * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * http://jung.sourceforge.net/license.txt 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 org.apache.commons.collections15.Factory; import org.apache.commons.collections15.Predicate; import org.apache.commons.collections15.functors.OrPredicate; 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 <code>Graph</code> from a Pajek NET formatted source. * * <p>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 <code>IllegalArgumentException</code> is thrown.</p> * * <p>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.</p> * * <p>More restrictive edge constraints will cause vertices to be generated * that are more time- and space-efficient.</p> * * At the moment, only supports the * part of the specification that defines: * <ul> * <li> vertex ids (each must have a value from 1 to n, where n is the number of vertices) * <li> vertex labels (must be in quotes if interrupted by whitespace) * <li> directed edge connections (single or list) * <li> undirected edge connections (single or list) * <li> edge weights (not compatible with edges specified in list form) * <br><b>note</b>: this version of PajekNetReader does not support multiple edge * weights, as PajekNetFile does; this behavior is consistent with the NET format. * <li/> vertex locations (x and y; z coordinate is ignored) * </ul> <p> * * Here is an example format for a directed graph without edge weights * and edges specified in list form: <br> * <pre> * *vertices <# of vertices> * 1 "a" * 2 "b" * 3 "c" * *arcslist * 1 2 3 * 2 3 * </pre> * * Here is an example format for an undirected graph with edge weights * and edges specified in non-list form: <br> * <pre> * *vertices <# of vertices> * 1 "a" * 2 "b" * 3 "c" * *edges * 1 2 0.1 * 1 3 0.9 * 2 3 1.0 * </pre> * * @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<G extends Graph<V, E>, V, E> { protected Factory<V> vertex_factory; protected Factory<E> edge_factory; /** * The map for vertex labels (if any) created by this class. */ protected SettableTransformer<V, String> vertex_labels = new MapSettableTransformer<V, String>( new HashMap<V, String>()); /** * The map for vertex locations (if any) defined by this class. */ protected SettableTransformer<V, Point2D> vertex_locations = new MapSettableTransformer<V, Point2D>( new HashMap<V, Point2D>()); protected SettableTransformer<E, Number> edge_weights = new MapSettableTransformer<E, Number>( new HashMap<E, Number>()); /** * Used to specify whether the most recently read line is a * Pajek-specific tag. */ private static final Predicate<String> v_pred = new StartsWithPredicate("*vertices"); private static final Predicate<String> a_pred = new StartsWithPredicate("*arcs"); private static final Predicate<String> e_pred = new StartsWithPredicate("*edges"); private static final Predicate<String> t_pred = new StartsWithPredicate("*"); private static final Predicate<String> c_pred = OrPredicate.getInstance(a_pred, e_pred); protected static final Predicate<String> l_pred = ListTagPred.getInstance(); /** * Creates a PajekNetReader instance with the specified vertex and edge factories. * @param vertex_factory the factory to use to create vertex objects * @param edge_factory the factory to use to create edge objects */ public PajekNetReader(Factory<V> vertex_factory, Factory<E> edge_factory) { this.vertex_factory = vertex_factory; this.edge_factory = edge_factory; } /** * Creates a PajekNetReader instance with the specified edge factory, * and whose vertex objects correspond to the integer IDs assigned in the file. * Note that this requires <code>V</code> to be assignment-compatible with * an <code>Integer</code> value. * @param edge_factory the factory to use to create edge objects */ public PajekNetReader(Factory<E> edge_factory) { this(null, edge_factory); } /** * Returns the graph created by parsing the specified file, as created * by the specified factory. * @throws IOException */ public G load(String filename, Factory<? extends G> graph_factory) throws IOException { return load(new FileReader(filename), graph_factory.create()); } /** * Returns the graph created by parsing the specified reader, as created * by the specified factory. * @throws IOException */ public G load(Reader reader, Factory<? extends G> graph_factory) throws IOException { return load(reader, graph_factory.create()); } /** * Returns the graph created by parsing the specified file, by populating the * specified graph. * @throws IOException */ 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 <code>g</code> with the graph represented by the * Pajek-format data supplied by <code>reader</code>. Stores edge weights, * if any, according to <code>nev</code> (if non-null). * * <p>Any existing vertices/edges of <code>g</code>, if any, are unaffected. * * <p>The edge data are filtered according to <code>g</code>'s constraints, if any; thus, if * <code>g</code> only accepts directed edges, any undirected edges in the * input are ignored. * * @throws IOException */ 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<V> id = null; if (vertex_factory != null) { for (int i = 1; i <= num_vertices; i++) g.addVertex(vertex_factory.create()); id = new ArrayList<V>(g.getVertices()); } // read vertices until we see any Pajek format tag ('*...') curLine = null; while (br.ready()) { curLine = br.readLine(); if (curLine == null || t_pred.evaluate(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 <code>curLine</code> as a reference to a vertex, and optionally assigns * label and location information. */ @SuppressWarnings("unchecked") private void readVertex(String curLine, List<V> 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<V, E> g, List<V> id, Factory<E> 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.evaluate(curLine)) nextLine = skip(br, c_pred); boolean reading_arcs = false; boolean reading_edges = false; EdgeType directedness = null; if (a_pred.evaluate(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.evaluate(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.evaluate(nextLine); while (br.ready()) { nextLine = br.readLine(); if (nextLine == null || t_pred.evaluate(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<V, E> g, List<V> id, Factory<E> 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.create(); // 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 <code>br</code> for which <code>p</code> * returns <code>true</code>, or <code>null</code> if there is no * such line. * @throws IOException */ protected String skip(BufferedReader br, Predicate<String> p) throws IOException { while (br.ready()) { String curLine = br.readLine(); if (curLine == null) break; curLine = curLine.trim(); if (p.evaluate(curLine)) return curLine; } return null; } /** * A Predicate which evaluates to <code>true</code> if the * argument starts with the constructor-specified String. * * @author Joshua O'Madadhain */ protected static class StartsWithPredicate implements Predicate<String> { private String tag; protected StartsWithPredicate(String s) { this.tag = s; } public boolean evaluate(String str) { return (str != null && str.toLowerCase().startsWith(tag)); } } /** * A Predicate which evaluates to <code>true</code> if the * argument ends with the string "list". * * @author Joshua O'Madadhain */ protected static class ListTagPred implements Predicate<String> { protected static ListTagPred instance; protected ListTagPred() { } protected static ListTagPred getInstance() { if (instance == null) instance = new ListTagPred(); return instance; } public boolean evaluate(String s) { return (s != null && s.toLowerCase().endsWith("list")); } } /** * @return the vertexLocationTransformer */ public SettableTransformer<V, Point2D> getVertexLocationTransformer() { return vertex_locations; } /** * Provides a transformer which will be used to write out the vertex locations. */ public void setVertexLocationTransformer(SettableTransformer<V, Point2D> vertex_locations) { this.vertex_locations = vertex_locations; } /** * Returns a transformer from vertices to their labels. */ public SettableTransformer<V, String> getVertexLabeller() { return vertex_labels; } /** * Provides a transformer which will be used to write out the vertex labels. */ public void setVertexLabeller(SettableTransformer<V, String> vertex_labels) { this.vertex_labels = vertex_labels; } /** * Returns a transformer from edges to their weights. */ public SettableTransformer<E, Number> getEdgeWeightTransformer() { return edge_weights; } /** * Provides a transformer which will be used to write out edge weights. */ public void setEdgeWeightTransformer(SettableTransformer<E, Number> edge_weights) { this.edge_weights = edge_weights; } }