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

Java tutorial

Introduction

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

Source

/*
 * Created on Sep 21, 2007
 *
 * Copyright (c) 2007, 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.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.collections15.BidiMap;
import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.bidimap.DualHashBidiMap;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.helpers.DefaultHandler;

import edu.uci.ics.jung.algorithms.util.MapSettableTransformer;
import edu.uci.ics.jung.algorithms.util.SettableTransformer;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.Hypergraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;

/**
 * Reads in data from a GraphML-formatted file and generates graphs based on
 * that data.  Currently supports the following parts of the GraphML
 * specification:
 * <ul>
 * <li/>graphs and hypergraphs
 * <li/>directed and undirected edges
 * <li/>graph, vertex, edge <code>data</code>
 * <li/>graph, vertex, edge descriptions and <code>data</code> descriptions
 * <li/>vertex and edge IDs
 * </ul>
 * Each of these is exposed via appropriate <code>get</code> methods.
 *
 * Does not currently support nested graphs or ports.
 *
 * <p>Note that the user is responsible for supplying a graph
 * <code>Factory</code> that can support the edge types in the supplied
 * GraphML file.  If the graph generated by the <code>Factory</code> is
 * not compatible (for example: if the graph does not accept directed
 * edges, and the GraphML file contains a directed edge) then the results
 * are graph-implementation-dependent.
 *
 * @see "http://graphml.graphdrawing.org/specification.html"
 */
public class GraphMLReader<G extends Hypergraph<V, E>, V, E> extends DefaultHandler {
    protected enum TagState {
        NO_TAG, VERTEX, EDGE, HYPEREDGE, ENDPOINT, GRAPH, DATA, KEY, DESC, DEFAULT_KEY, GRAPHML, OTHER
    }

    protected enum KeyType {
        NONE, VERTEX, EDGE, GRAPH, ALL
    }

    protected SAXParser saxp;
    protected EdgeType default_edgetype;
    protected G current_graph;
    protected V current_vertex;
    protected E current_edge;
    protected String current_key;
    protected LinkedList<TagState> current_states;
    protected BidiMap<String, TagState> tag_state;
    protected Factory<G> graph_factory;
    protected Factory<V> vertex_factory;
    protected Factory<E> edge_factory;
    protected BidiMap<V, String> vertex_ids;
    protected BidiMap<E, String> edge_ids;
    protected Map<String, GraphMLMetadata<G>> graph_metadata;
    protected Map<String, GraphMLMetadata<V>> vertex_metadata;
    protected Map<String, GraphMLMetadata<E>> edge_metadata;
    protected Map<V, String> vertex_desc;
    protected Map<E, String> edge_desc;
    protected Map<G, String> graph_desc;
    protected KeyType key_type;
    protected Collection<V> hyperedge_vertices;

    protected List<G> graphs;

    protected StringBuilder current_text = new StringBuilder();

    /**
     * Creates a <code>GraphMLReader</code> instance with the specified
     * vertex and edge factories.
     *
     * @param vertex_factory the vertex factory to use to create vertex objects
     * @param edge_factory the edge factory to use to create edge objects
     * @throws ParserConfigurationException
     * @throws SAXException
     */
    public GraphMLReader(Factory<V> vertex_factory, Factory<E> edge_factory)
            throws ParserConfigurationException, SAXException {
        current_vertex = null;
        current_edge = null;

        SAXParserFactory factory = SAXParserFactory.newInstance();
        saxp = factory.newSAXParser();

        current_states = new LinkedList<TagState>();

        tag_state = new DualHashBidiMap<String, TagState>();
        tag_state.put("node", TagState.VERTEX);
        tag_state.put("edge", TagState.EDGE);
        tag_state.put("hyperedge", TagState.HYPEREDGE);
        tag_state.put("endpoint", TagState.ENDPOINT);
        tag_state.put("graph", TagState.GRAPH);
        tag_state.put("data", TagState.DATA);
        tag_state.put("key", TagState.KEY);
        tag_state.put("desc", TagState.DESC);
        tag_state.put("default", TagState.DEFAULT_KEY);
        tag_state.put("graphml", TagState.GRAPHML);

        this.key_type = KeyType.NONE;

        this.vertex_factory = vertex_factory;
        this.edge_factory = edge_factory;
    }

    /**
     * Creates a <code>GraphMLReader</code> instance that assigns the vertex
     * and edge <code>id</code> strings to be the vertex and edge objects,
     * as well as their IDs.
     * Note that this requires that (a) each edge have a valid ID, which is not
     * normally a requirement for edges in GraphML, and (b) that the vertex
     * and edge types be assignment-compatible with <code>String</code>.
     * @throws ParserConfigurationException
     * @throws SAXException
     */
    public GraphMLReader() throws ParserConfigurationException, SAXException {
        this(null, null);
    }

    /**
     * Returns a list of the graphs parsed from the specified reader, as created by
     * the specified factory.
     * @throws IOException
     */
    public List<G> loadMultiple(Reader reader, Factory<G> graph_factory) throws IOException {
        this.graph_factory = graph_factory;
        initializeData();
        clearData();
        parse(reader);

        return graphs;
    }

    /**
     * Returns a list of the graphs parsed from the specified file, as created by
     * the specified factory.
     * @throws IOException
     */
    public List<G> loadMultiple(String filename, Factory<G> graph_factory) throws IOException {
        return loadMultiple(new FileReader(filename), graph_factory);
    }

    /**
     * Populates the specified graph with the data parsed from the reader.
     * @throws IOException
     */
    public void load(Reader reader, G g) throws IOException {
        this.current_graph = g;
        this.graph_factory = null;
        initializeData();
        clearData();

        parse(reader);
    }

    /**
     * Populates the specified graph with the data parsed from the specified file.
     * @throws IOException
     */
    public void load(String filename, G g) throws IOException {
        load(new FileReader(filename), g);
    }

    protected void clearData() {
        this.vertex_ids.clear();
        this.vertex_desc.clear();

        this.edge_ids.clear();
        this.edge_desc.clear();

        this.graph_desc.clear();

        this.hyperedge_vertices.clear();
    }

    /**
     * This is separate from initialize() because these data structures are shared among all
     * graphs loaded (i.e., they're defined inside <code>graphml</code> rather than <code>graph</code>.
     */
    protected void initializeData() {
        this.vertex_ids = new DualHashBidiMap<V, String>();
        this.vertex_desc = new HashMap<V, String>();
        this.vertex_metadata = new HashMap<String, GraphMLMetadata<V>>();

        this.edge_ids = new DualHashBidiMap<E, String>();
        this.edge_desc = new HashMap<E, String>();
        this.edge_metadata = new HashMap<String, GraphMLMetadata<E>>();

        this.graph_desc = new HashMap<G, String>();
        this.graph_metadata = new HashMap<String, GraphMLMetadata<G>>();

        this.hyperedge_vertices = new ArrayList<V>();
    }

    protected void parse(Reader reader) throws IOException {
        try {
            saxp.parse(new InputSource(reader), this);
            reader.close();
        } catch (SAXException saxe) {
            throw new IOException(saxe.getMessage());
        }
    }

    @Override
    public void startElement(String uri, String name, String qName, Attributes atts)
            throws SAXNotSupportedException {
        String tag = qName.toLowerCase();
        TagState state = tag_state.get(tag);
        if (state == null)
            state = TagState.OTHER;

        switch (state) {
        case GRAPHML:
            break;

        case VERTEX:
            if (this.current_graph == null)
                throw new SAXNotSupportedException("Graph must be defined prior to elements");
            if (this.current_edge != null || this.current_vertex != null)
                throw new SAXNotSupportedException("Nesting elements not supported");

            createVertex(atts);

            break;

        case ENDPOINT:
            if (this.current_graph == null)
                throw new SAXNotSupportedException("Graph must be defined prior to elements");
            if (this.current_edge == null)
                throw new SAXNotSupportedException("No edge defined for endpoint");
            if (this.current_states.getFirst() != TagState.HYPEREDGE)
                throw new SAXNotSupportedException("Endpoints must be defined inside hyperedge");
            Map<String, String> endpoint_atts = getAttributeMap(atts);
            String node = endpoint_atts.remove("node");
            if (node == null)
                throw new SAXNotSupportedException("Endpoint must include an 'id' attribute");
            V v = vertex_ids.getKey(node);
            if (v == null)
                throw new SAXNotSupportedException("Endpoint refers to nonexistent node ID: " + node);

            this.current_vertex = v;
            hyperedge_vertices.add(v);
            break;

        case EDGE:
        case HYPEREDGE:
            if (this.current_graph == null)
                throw new SAXNotSupportedException("Graph must be defined prior to elements");
            if (this.current_edge != null || this.current_vertex != null)
                throw new SAXNotSupportedException("Nesting elements not supported");

            createEdge(atts, state);
            break;

        case GRAPH:
            if (this.current_graph != null && graph_factory != null)
                throw new SAXNotSupportedException("Nesting graphs not currently supported");

            // graph factory is null if there's only one graph
            if (graph_factory != null)
                current_graph = graph_factory.create();

            // reset all non-key data structures (to avoid collisions between different graphs)
            clearData();

            // set up default direction of edges
            Map<String, String> graph_atts = getAttributeMap(atts);
            String default_direction = graph_atts.remove("edgedefault");
            if (default_direction == null)
                throw new SAXNotSupportedException("All graphs must specify a default edge direction");
            if (default_direction.equals("directed"))
                this.default_edgetype = EdgeType.DIRECTED;
            else if (default_direction.equals("undirected"))
                this.default_edgetype = EdgeType.UNDIRECTED;
            else
                throw new SAXNotSupportedException(
                        "Invalid or unrecognized default edge direction: " + default_direction);

            // put remaining attribute/value pairs in graph_data
            addExtraData(graph_atts, graph_metadata, current_graph);

            break;

        case DATA:
            if (this.current_states.contains(TagState.DATA))
                throw new SAXNotSupportedException("Nested data not supported");
            handleData(atts);
            break;

        case KEY:
            createKey(atts);
            break;

        default:
            break;
        }

        current_states.addFirst(state);
    }

    /**
     *
     * @param <T>
     * @param atts
     * @param metadata_map
     * @param current_elt
     */
    protected <T> void addExtraData(Map<String, String> atts, Map<String, GraphMLMetadata<T>> metadata_map,
            T current_elt) {
        // load in the default values; these override anything that might
        // be in the attribute map (because that's not really a proper
        // way to associate data)
        for (Map.Entry<String, GraphMLMetadata<T>> entry : metadata_map.entrySet()) {
            GraphMLMetadata<T> gmlm = entry.getValue();
            if (gmlm.default_value != null) {
                SettableTransformer<T, String> st = (SettableTransformer<T, String>) gmlm.transformer;
                st.set(current_elt, gmlm.default_value);
            }
        }

        // place remaining items in data
        for (Map.Entry<String, String> entry : atts.entrySet()) {
            String key = entry.getKey();
            GraphMLMetadata<T> key_data = metadata_map.get(key);
            SettableTransformer<T, String> st;
            if (key_data != null) {
                // if there's a default value, don't override it
                if (key_data.default_value != null)
                    continue;
                st = (SettableTransformer<T, String>) key_data.transformer;
            } else {
                st = new MapSettableTransformer<T, String>(new HashMap<T, String>());
                key_data = new GraphMLMetadata<T>(null, null, st);
                metadata_map.put(key, key_data);
            }
            st.set(current_elt, entry.getValue());
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXNotSupportedException {
        this.current_text.append(new String(ch, start, length));
    }

    protected <T> void addDatum(Map<String, GraphMLMetadata<T>> metadata, T current_elt, String text)
            throws SAXNotSupportedException {
        if (metadata.containsKey(this.current_key)) {
            SettableTransformer<T, String> st = (SettableTransformer<T, String>) (metadata
                    .get(this.current_key).transformer);
            st.set(current_elt, text);
        } else
            throw new SAXNotSupportedException("key " + this.current_key + " not valid for element " + current_elt);
    }

    @Override
    public void endElement(String uri, String name, String qName) throws SAXNotSupportedException {
        String text = current_text.toString().trim();
        current_text.setLength(0);

        String tag = qName.toLowerCase();
        TagState state = tag_state.get(tag);
        if (state == null)
            state = TagState.OTHER;
        if (state == TagState.OTHER)
            return;

        if (state != current_states.getFirst())
            throw new SAXNotSupportedException(
                    "Unbalanced tags: opened " + tag_state.getKey(current_states.getFirst()) + ", closed " + tag);

        switch (state) {
        case VERTEX:
        case ENDPOINT:
            current_vertex = null;
            break;

        case EDGE:
            current_edge = null;
            break;

        case HYPEREDGE:
            current_graph.addEdge(current_edge, hyperedge_vertices);
            hyperedge_vertices.clear();
            current_edge = null;
            break;

        case GRAPH:
            current_graph = null;
            break;

        case KEY:
            current_key = null;
            break;

        case DESC:
            switch (this.current_states.get(1)) // go back one
            {
            case GRAPH:
                graph_desc.put(current_graph, text);
                break;
            case VERTEX:
            case ENDPOINT:
                vertex_desc.put(current_vertex, text);
                break;
            case EDGE:
            case HYPEREDGE:
                edge_desc.put(current_edge, text);
                break;
            case DATA:
                switch (key_type) {
                case GRAPH:
                    graph_metadata.get(current_key).description = text;
                    break;
                case VERTEX:
                    vertex_metadata.get(current_key).description = text;
                    break;
                case EDGE:
                    edge_metadata.get(current_key).description = text;
                    break;
                case ALL:
                    graph_metadata.get(current_key).description = text;
                    vertex_metadata.get(current_key).description = text;
                    edge_metadata.get(current_key).description = text;
                    break;
                default:
                    throw new SAXNotSupportedException("Invalid key type" + " specified for default: " + key_type);
                }

                break;
            default:
                break;
            }
            break;
        case DATA:
            this.key_type = KeyType.NONE;
            switch (this.current_states.get(1)) {
            case GRAPH:
                addDatum(graph_metadata, current_graph, text);
                break;
            case VERTEX:
            case ENDPOINT:
                addDatum(vertex_metadata, current_vertex, text);
                break;
            case EDGE:
            case HYPEREDGE:
                addDatum(edge_metadata, current_edge, text);
                break;
            default:
                break;
            }
            break;
        case DEFAULT_KEY:
            if (this.current_states.get(1) != TagState.KEY)
                throw new SAXNotSupportedException(
                        "'default' only defined in context of 'key' tag: " + "stack: " + current_states.toString());

            switch (key_type) {
            case GRAPH:
                graph_metadata.get(current_key).default_value = text;
                break;
            case VERTEX:
                vertex_metadata.get(current_key).default_value = text;
                break;
            case EDGE:
                edge_metadata.get(current_key).default_value = text;
                break;
            case ALL:
                graph_metadata.get(current_key).default_value = text;
                vertex_metadata.get(current_key).default_value = text;
                edge_metadata.get(current_key).default_value = text;
                break;
            default:
                throw new SAXNotSupportedException("Invalid key type" + " specified for default: " + key_type);
            }

            break;
        default:
            break;
        }

        current_states.removeFirst();
    }

    protected Map<String, String> getAttributeMap(Attributes atts) {
        Map<String, String> att_map = new HashMap<String, String>();
        for (int i = 0; i < atts.getLength(); i++)
            att_map.put(atts.getQName(i), atts.getValue(i));

        return att_map;
    }

    protected void handleData(Attributes atts) throws SAXNotSupportedException {
        switch (this.current_states.getFirst()) {
        case GRAPH:
            break;
        case VERTEX:
        case ENDPOINT:
            break;
        case EDGE:
            break;
        case HYPEREDGE:
            break;
        default:
            throw new SAXNotSupportedException("'data' tag only defined "
                    + "if immediately containing tag is 'graph', 'node', " + "'edge', or 'hyperedge'");
        }
        this.current_key = getAttributeMap(atts).get("key");
        if (this.current_key == null)
            throw new SAXNotSupportedException("'data' tag requires a key specification");
        if (this.current_key.equals(""))
            throw new SAXNotSupportedException("'data' tag requires a non-empty key");
        if (!getGraphMetadata().containsKey(this.current_key) && !getVertexMetadata().containsKey(this.current_key)
                && !getEdgeMetadata().containsKey(this.current_key)) {
            throw new SAXNotSupportedException("'data' tag's key specification must reference a defined key");
        }

    }

    protected void createKey(Attributes atts) throws SAXNotSupportedException {
        Map<String, String> key_atts = getAttributeMap(atts);
        String id = key_atts.remove("id");
        String for_type = key_atts.remove("for");

        if (for_type == null || for_type.equals("") || for_type.equals("all")) {
            vertex_metadata.put(id, new GraphMLMetadata<V>(null, null,
                    new MapSettableTransformer<V, String>(new HashMap<V, String>())));
            edge_metadata.put(id, new GraphMLMetadata<E>(null, null,
                    new MapSettableTransformer<E, String>(new HashMap<E, String>())));
            graph_metadata.put(id, new GraphMLMetadata<G>(null, null,
                    new MapSettableTransformer<G, String>(new HashMap<G, String>())));
            key_type = KeyType.ALL;
        } else {
            TagState type = tag_state.get(for_type);
            switch (type) {
            case VERTEX:
                vertex_metadata.put(id, new GraphMLMetadata<V>(null, null,
                        new MapSettableTransformer<V, String>(new HashMap<V, String>())));
                key_type = KeyType.VERTEX;
                break;
            case EDGE:
            case HYPEREDGE:
                edge_metadata.put(id, new GraphMLMetadata<E>(null, null,
                        new MapSettableTransformer<E, String>(new HashMap<E, String>())));
                key_type = KeyType.EDGE;
                break;
            case GRAPH:
                graph_metadata.put(id, new GraphMLMetadata<G>(null, null,
                        new MapSettableTransformer<G, String>(new HashMap<G, String>())));
                key_type = KeyType.GRAPH;
                break;
            default:
                throw new SAXNotSupportedException("Invalid metadata target type: " + for_type);
            }
        }

        this.current_key = id;

    }

    @SuppressWarnings("unchecked")
    protected void createVertex(Attributes atts) throws SAXNotSupportedException {
        Map<String, String> vertex_atts = getAttributeMap(atts);
        String id = vertex_atts.remove("id");
        if (id == null)
            throw new SAXNotSupportedException("node attribute list missing " + "'id': " + atts.toString());
        V v = vertex_ids.getKey(id);

        if (v == null) {
            if (vertex_factory != null)
                v = vertex_factory.create();
            else
                v = (V) id;
            vertex_ids.put(v, id);
            this.current_graph.addVertex(v);

            // put remaining attribute/value pairs in vertex_data
            addExtraData(vertex_atts, vertex_metadata, v);
        } else
            throw new SAXNotSupportedException("Node id \"" + id + " is a duplicate of an existing node ID");

        this.current_vertex = v;
    }

    @SuppressWarnings("unchecked")
    protected void createEdge(Attributes atts, TagState state) throws SAXNotSupportedException {
        Map<String, String> edge_atts = getAttributeMap(atts);

        String id = edge_atts.remove("id");
        E e;
        if (edge_factory != null)
            e = edge_factory.create();
        else if (id != null)
            e = (E) id;
        else
            throw new IllegalArgumentException(
                    "If no edge factory is supplied, " + "edge id may not be null: " + edge_atts);

        if (id != null) {
            if (edge_ids.containsKey(e))
                throw new SAXNotSupportedException("Edge id \"" + id + "\" is a duplicate of an existing edge ID");
            edge_ids.put(e, id);
        }

        if (state == TagState.EDGE)
            assignEdgeSourceTarget(e, atts, edge_atts); //, id);

        // put remaining attribute/value pairs in edge_data
        addExtraData(edge_atts, edge_metadata, e);

        this.current_edge = e;
    }

    protected void assignEdgeSourceTarget(E e, Attributes atts, Map<String, String> edge_atts)//, String id)
            throws SAXNotSupportedException {
        String source_id = edge_atts.remove("source");
        if (source_id == null)
            throw new SAXNotSupportedException("edge attribute list missing " + "'source': " + atts.toString());
        V source = vertex_ids.getKey(source_id);
        if (source == null)
            throw new SAXNotSupportedException(
                    "specified 'source' attribute " + "\"" + source_id + "\" does not match any node ID");

        String target_id = edge_atts.remove("target");
        if (target_id == null)
            throw new SAXNotSupportedException("edge attribute list missing " + "'target': " + atts.toString());
        V target = vertex_ids.getKey(target_id);
        if (target == null)
            throw new SAXNotSupportedException(
                    "specified 'target' attribute " + "\"" + target_id + "\" does not match any node ID");

        String directed = edge_atts.remove("directed");
        EdgeType edge_type;
        if (directed == null)
            edge_type = default_edgetype;
        else if (directed.equals("true"))
            edge_type = EdgeType.DIRECTED;
        else if (directed.equals("false"))
            edge_type = EdgeType.UNDIRECTED;
        else
            throw new SAXNotSupportedException("Unrecognized edge direction specifier 'direction=\"" + directed
                    + "\"': " + "source: " + source_id + ", target: " + target_id);

        if (current_graph instanceof Graph)
            ((Graph<V, E>) this.current_graph).addEdge(e, source, target, edge_type);
        else
            this.current_graph.addEdge(e, new Pair<V>(source, target));
    }

    /**
     * Returns a bidirectional map relating vertices and IDs.
     */
    public BidiMap<V, String> getVertexIDs() {
        return vertex_ids;
    }

    /**
     * Returns a bidirectional map relating edges and IDs.
     * This is not guaranteed to always be populated (edge IDs are not
     * required in GraphML files.
     */
    public BidiMap<E, String> getEdgeIDs() {
        return edge_ids;
    }

    /**
     * Returns a map from graph type name to type metadata.
     */
    public Map<String, GraphMLMetadata<G>> getGraphMetadata() {
        return graph_metadata;
    }

    /**
     * Returns a map from vertex type name to type metadata.
     */
    public Map<String, GraphMLMetadata<V>> getVertexMetadata() {
        return vertex_metadata;
    }

    /**
     * Returns a map from edge type name to type metadata.
     */
    public Map<String, GraphMLMetadata<E>> getEdgeMetadata() {
        return edge_metadata;
    }

    /**
     * Returns a map from graphs to graph descriptions.
     */
    public Map<G, String> getGraphDescriptions() {
        return graph_desc;
    }

    /**
     * Returns a map from vertices to vertex descriptions.
     */
    public Map<V, String> getVertexDescriptions() {
        return vertex_desc;
    }

    /**
     * Returns a map from edges to edge descriptions.
     */
    public Map<E, String> getEdgeDescriptions() {
        return edge_desc;
    }
}