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