/*
* Copyright (c) 2008, 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.graphml;
import java.io.IOException;
import java.io.Reader;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.apache.commons.collections15.Transformer;
import edu.uci.ics.jung.graph.Hypergraph;
import edu.uci.ics.jung.io.GraphReader;
import edu.uci.ics.jung.io.GraphIOException;
import edu.uci.ics.jung.io.graphml.parser.ElementParserRegistry;
import edu.uci.ics.jung.io.graphml.parser.GraphMLEventFilter;
/**
* Reads in data from a GraphML-formatted file and generates graphs based on
* that data. Does not currently support nested graphs.
* <p/>
* <p/>
* Note that the user is responsible for supplying a graph
* <code>Transformer</code> that will create graphs capable of supporting 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.
*
* @author Nathan Mittler - nathan.mittler@gmail.com
*
* @param <G>
* The graph type to be read from the GraphML file
* @param <V>
* The vertex type used by the graph
* @param <E>
* The edge type used by the graph
* @see "http://graphml.graphdrawing.org/specification.html"
*/
public class GraphMLReader2<G extends Hypergraph<V, E>, V, E> implements
GraphReader<G, V, E> {
protected XMLEventReader xmlEventReader;
protected Reader fileReader;
protected Transformer<GraphMetadata, G> graphTransformer;
protected Transformer<NodeMetadata, V> vertexTransformer;
protected Transformer<EdgeMetadata, E> edgeTransformer;
protected Transformer<HyperEdgeMetadata, E> hyperEdgeTransformer;
protected boolean initialized;
final protected GraphMLDocument document = new GraphMLDocument();
final protected ElementParserRegistry<G,V,E> parserRegistry;
/**
* Constructs a GraphML reader around the given reader. This constructor
* requires the user to supply transformation functions to convert from the
* GraphML metadata to Graph, Vertex, Edge instances. These transformer
* functions can be used as purely factories (i.e. the metadata is
* disregarded) or can use the metadata to set particular fields in the
* objects.
*
* @param fileReader the reader for the input GraphML document.
* @param graphTransformer Transformation function to convert from GraphML GraphMetadata
* to graph objects. This must be non-null.
* @param vertexTransformer Transformation function to convert from GraphML NodeMetadata
* to vertex objects. This must be non-null.
* @param edgeTransformer Transformation function to convert from GraphML EdgeMetadata
* to edge objects. This must be non-null.
* @param hyperEdgeTransformer Transformation function to convert from GraphML
* HyperEdgeMetadata to edge objects. This must be non-null.
* @throws IllegalArgumentException thrown if any of the arguments are null.
*/
public GraphMLReader2(Reader fileReader,
Transformer<GraphMetadata, G> graphTransformer,
Transformer<NodeMetadata, V> vertexTransformer,
Transformer<EdgeMetadata, E> edgeTransformer,
Transformer<HyperEdgeMetadata, E> hyperEdgeTransformer) {
if (fileReader == null) {
throw new IllegalArgumentException(
"Argument fileReader must be non-null");
}
if (graphTransformer == null) {
throw new IllegalArgumentException(
"Argument graphTransformer must be non-null");
}
if (vertexTransformer == null) {
throw new IllegalArgumentException(
"Argument vertexTransformer must be non-null");
}
if (edgeTransformer == null) {
throw new IllegalArgumentException(
"Argument edgeTransformer must be non-null");
}
if (hyperEdgeTransformer == null) {
throw new IllegalArgumentException(
"Argument hyperEdgeTransformer must be non-null");
}
this.fileReader = fileReader;
this.graphTransformer = graphTransformer;
this.vertexTransformer = vertexTransformer;
this.edgeTransformer = edgeTransformer;
this.hyperEdgeTransformer = hyperEdgeTransformer;
// Create the parser registry.
this.parserRegistry = new ElementParserRegistry<G,V,E>(document.getKeyMap(),
graphTransformer, vertexTransformer, edgeTransformer, hyperEdgeTransformer);
}
/**
* Gets the current transformer that is being used for graph objects.
*
* @return the current transformer.
*/
public Transformer<GraphMetadata, G> getGraphTransformer() {
return graphTransformer;
}
/**
* Gets the current transformer that is being used for vertex objects.
*
* @return the current transformer.
*/
public Transformer<NodeMetadata, V> getVertexTransformer() {
return vertexTransformer;
}
/**
* Gets the current transformer that is being used for edge objects.
*
* @return the current transformer.
*/
public Transformer<EdgeMetadata, E> getEdgeTransformer() {
return edgeTransformer;
}
/**
* Gets the current transformer that is being used for hyperedge objects.
*
* @return the current transformer.
*/
public Transformer<HyperEdgeMetadata, E> getHyperEdgeTransformer() {
return hyperEdgeTransformer;
}
/**
* Verifies the object state and initializes this reader. All transformer
* properties must be set and be non-null or a <code>GraphReaderException
* </code> will be thrown. This method may be called more than once.
* Successive calls will have no effect.
*
* @throws edu.uci.ics.jung.io.GraphIOException thrown if an error occurred.
*/
public void init() throws GraphIOException {
try {
if (!initialized) {
// Create the event reader.
XMLInputFactory factory = XMLInputFactory.newInstance();
xmlEventReader = factory.createXMLEventReader(fileReader);
xmlEventReader = factory.createFilteredReader(xmlEventReader,
new GraphMLEventFilter());
initialized = true;
}
} catch( Exception e ) {
ExceptionConverter.convert(e);
}
}
/**
* Closes the GraphML reader and disposes of any resources.
*
* @throws edu.uci.ics.jung.io.GraphIOException thrown if an error occurs.
*/
public void close() throws GraphIOException {
try {
// Clear the contents of the document.
document.clear();
if (xmlEventReader != null) {
xmlEventReader.close();
}
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
throw new GraphIOException(e);
} catch (XMLStreamException e) {
throw new GraphIOException(e);
} finally {
fileReader = null;
xmlEventReader = null;
graphTransformer = null;
vertexTransformer = null;
edgeTransformer = null;
hyperEdgeTransformer = null;
}
}
/**
* Returns the object that contains the metadata read in from the GraphML
* document
*
* @return the GraphML document
*/
public GraphMLDocument getGraphMLDocument() {
return document;
}
/**
* Reads a single graph object from the GraphML document. Automatically
* calls <code>init</code> to initialize the state of the reader.
*
* @return the graph that was read if one was found, otherwise null.
*/
@SuppressWarnings("unchecked")
public G readGraph() throws GraphIOException {
try {
// Initialize if not already.
init();
while (xmlEventReader.hasNext()) {
XMLEvent event = xmlEventReader.nextEvent();
if (event.isStartElement()) {
StartElement element = (StartElement) event;
String name = element.getName().getLocalPart();
// The element should be one of: key, graph, graphml
if (GraphMLConstants.KEY_NAME.equals(name)) {
// Parse the key object.
Key key = (Key) parserRegistry.getParser(name).parse(
xmlEventReader, element);
// Add the key to the key map.
document.getKeyMap().addKey(key);
} else if (GraphMLConstants.GRAPH_NAME.equals(name)) {
// Parse the graph.
GraphMetadata graph = (GraphMetadata) parserRegistry
.getParser(name).parse(xmlEventReader, element);
// Add it to the graph metadata list.
document.getGraphMetadata().add(graph);
// Return the graph object.
return (G)graph.getGraph();
} else if (GraphMLConstants.GRAPHML_NAME.equals(name)) {
// Ignore the graphML object.
} else {
// Encounted an unknown element - just skip by it.
parserRegistry.getUnknownElementParser().parse(
xmlEventReader, element);
}
} else if (event.isEndDocument()) {
break;
}
}
} catch (Exception e) {
ExceptionConverter.convert(e);
}
// We didn't read anything from the document.
throw new GraphIOException("Unable to read Graph from document - the document could be empty");
}
}