edu.uci.ics.jung.io.PajekNetReader.java Source code

Java tutorial

Introduction

Here is the source code for edu.uci.ics.jung.io.PajekNetReader.java

Source

/*
 * 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;
    }

}