All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.uci.ics.jung.io.GraphMLReader Maven / Gradle / Ivy

The newest version!
/*
 * Created on Sep 21, 2007
 *
 * Copyright (c) 2007, 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.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.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 com.google.common.base.Supplier;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

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:
 * 
    *
  • graphs and hypergraphs *
  • directed and undirected edges *
  • graph, vertex, edge data *
  • graph, vertex, edge descriptions and data descriptions *
  • vertex and edge IDs *
* Each of these is exposed via appropriate get methods. * * Does not currently support nested graphs or ports. * *

Note that the user is responsible for supplying a graph * Factory that can support the edge types in the supplied * GraphML file. If the graph generated by the Factory 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, 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 current_states; protected BiMap tag_state; protected Supplier graph_factory; protected Supplier vertex_factory; protected Supplier edge_factory; protected BiMap vertex_ids; protected BiMap edge_ids; protected Map> graph_metadata; protected Map> vertex_metadata; protected Map> edge_metadata; protected Map vertex_desc; protected Map edge_desc; protected Map graph_desc; protected KeyType key_type; protected Collection hyperedge_vertices; protected List graphs; protected StringBuilder current_text = new StringBuilder(); /** * Creates a GraphMLReader instance with the specified * vertex and edge factories. * * @param vertex_factory the vertex supplier to use to create vertex objects * @param edge_factory the edge supplier to use to create edge objects * @throws ParserConfigurationException if a SAX parser cannot be constructed * @throws SAXException if the SAX parser factory cannot be constructed */ public GraphMLReader(Supplier vertex_factory, Supplier edge_factory) throws ParserConfigurationException, SAXException { current_vertex = null; current_edge = null; SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); saxp = saxParserFactory.newSAXParser(); current_states = new LinkedList(); tag_state = HashBiMap.create(); 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 GraphMLReader instance that assigns the vertex * and edge id 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 String. * @throws ParserConfigurationException if a SAX parser cannot be constructed * @throws SAXException if the SAX parser factory cannot be constructed */ public GraphMLReader() throws ParserConfigurationException, SAXException { this(null, null); } /** * Returns a list of the graphs parsed from the specified reader, as created by * the specified Supplier. * @param reader the source of the graph data in GraphML format * @param graph_factory used to build graph instances * @return the graphs parsed from the specified reader * @throws IOException if an error is encountered while parsing the graph */ public List loadMultiple(Reader reader, Supplier 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 Supplier. * @param filename the source of the graph data in GraphML format * @param graph_factory used to build graph instances * @return the graphs parsed from the specified file * @throws IOException if an error is encountered while parsing the graph */ public List loadMultiple(String filename, Supplier graph_factory) throws IOException { return loadMultiple(new FileReader(filename), graph_factory); } /** * Populates the specified graph with the data parsed from the reader. * @param reader the source of the graph data in GraphML format * @param g the graph instance to populate * @throws IOException if an error is encountered while parsing the graph */ 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. * @param filename the source of the graph data in GraphML format * @param g the graph instance to populate * @throws IOException if an error is encountered while parsing the graph */ 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 graphml rather than graph. */ protected void initializeData() { this.vertex_ids = HashBiMap.create(); this.vertex_desc = new HashMap(); this.vertex_metadata = new HashMap>(); this.edge_ids = HashBiMap.create(); this.edge_desc = new HashMap(); this.edge_metadata = new HashMap>(); this.graph_desc = new HashMap(); this.graph_metadata = new HashMap>(); this.hyperedge_vertices = new ArrayList(); } 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 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.inverse().get(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 Supplier is null if there's only one graph if (graph_factory != null) current_graph = graph_factory.get(); // reset all non-key data structures (to avoid collisions between different graphs) clearData(); // set up default direction of edges Map 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 * @param atts * @param metadata_map * @param current_elt */ private void addExtraData(Map atts, Map> 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> entry: metadata_map.entrySet()) { GraphMLMetadata gmlm = entry.getValue(); if (gmlm.default_value != null) { SettableTransformer st = (SettableTransformer)gmlm.transformer; st.set(current_elt, gmlm.default_value); } } // place remaining items in data for (Map.Entry entry : atts.entrySet()) { String key = entry.getKey(); GraphMLMetadata key_data = metadata_map.get(key); SettableTransformer st; if (key_data != null) { // if there's a default value, don't override it if (key_data.default_value != null) continue; st = (SettableTransformer)key_data.transformer; } else { st = new MapSettableTransformer( new HashMap()); key_data = new GraphMLMetadata(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 void addDatum(Map> metadata, T current_elt, String text) throws SAXNotSupportedException { if (metadata.containsKey(this.current_key)) { SettableTransformer st = (SettableTransformer)(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.inverse().get(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 getAttributeMap(Attributes atts) { Map att_map = new HashMap(); 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 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(null, null, new MapSettableTransformer(new HashMap()))); edge_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); graph_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); key_type = KeyType.ALL; } else { TagState type = tag_state.get(for_type); switch (type) { case VERTEX: vertex_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); key_type = KeyType.VERTEX; break; case EDGE: case HYPEREDGE: edge_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); key_type = KeyType.EDGE; break; case GRAPH: graph_metadata.put(id, new GraphMLMetadata(null, null, new MapSettableTransformer(new HashMap()))); 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 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.inverse().get(id); if (v == null) { if (vertex_factory != null) v = vertex_factory.get(); 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 edge_atts = getAttributeMap(atts); String id = edge_atts.remove("id"); E e; if (edge_factory != null) e = edge_factory.get(); else if (id != null) e = (E)id; else throw new IllegalArgumentException("If no edge Supplier 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 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.inverse().get(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.inverse().get(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)this.current_graph).addEdge(e, source, target, edge_type); else this.current_graph.addEdge(e, new Pair(source, target)); } /** * @return a bidirectional map relating vertices and IDs. */ public BiMap 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. * @return a bidirectional map relating edges and IDs. */ public BiMap getEdgeIDs() { return edge_ids; } /** * @return a map from graph type name to type metadata */ public Map> getGraphMetadata() { return graph_metadata; } /** * @return a map from vertex type name to type metadata */ public Map> getVertexMetadata() { return vertex_metadata; } /** * @return a map from edge type name to type metadata */ public Map> getEdgeMetadata() { return edge_metadata; } /** * @return a map from graphs to graph descriptions */ public Map getGraphDescriptions() { return graph_desc; } /** * @return a map from vertices to vertex descriptions */ public Map getVertexDescriptions() { return vertex_desc; } /** * @return a map from edges to edge descriptions */ public Map getEdgeDescriptions() { return edge_desc; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy