View Javadoc
1   /*
2    * Created on Sep 21, 2007
3    *
4    * Copyright (c) 2007, The JUNG Authors
5    *
6    * All rights reserved.
7    *
8    * This software is open-source under the BSD license; see either
9    * "license.txt" or
10   * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
11   */
12  package edu.uci.ics.jung.io;
13  
14  import java.io.FileReader;
15  import java.io.IOException;
16  import java.io.Reader;
17  import java.util.ArrayList;
18  import java.util.Collection;
19  import java.util.HashMap;
20  import java.util.LinkedList;
21  import java.util.List;
22  import java.util.Map;
23  
24  import javax.xml.parsers.ParserConfigurationException;
25  import javax.xml.parsers.SAXParser;
26  import javax.xml.parsers.SAXParserFactory;
27  
28  import org.xml.sax.Attributes;
29  import org.xml.sax.InputSource;
30  import org.xml.sax.SAXException;
31  import org.xml.sax.SAXNotSupportedException;
32  import org.xml.sax.helpers.DefaultHandler;
33  
34  import com.google.common.base.Supplier;
35  import com.google.common.collect.BiMap;
36  import com.google.common.collect.HashBiMap;
37  
38  import edu.uci.ics.jung.algorithms.util.MapSettableTransformer;
39  import edu.uci.ics.jung.algorithms.util.SettableTransformer;
40  import edu.uci.ics.jung.graph.Graph;
41  import edu.uci.ics.jung.graph.Hypergraph;
42  import edu.uci.ics.jung.graph.util.EdgeType;
43  import edu.uci.ics.jung.graph.util.Pair;
44  
45  /**
46   * Reads in data from a GraphML-formatted file and generates graphs based on
47   * that data.  Currently supports the following parts of the GraphML
48   * specification:
49   * <ul>
50   * <li>graphs and hypergraphs
51   * <li>directed and undirected edges
52   * <li>graph, vertex, edge <code>data</code>
53   * <li>graph, vertex, edge descriptions and <code>data</code> descriptions
54   * <li>vertex and edge IDs
55   * </ul>
56   * Each of these is exposed via appropriate <code>get</code> methods.
57   *
58   * Does not currently support nested graphs or ports.
59   *
60   * <p>Note that the user is responsible for supplying a graph
61   * <code>Factory</code> that can support the edge types in the supplied
62   * GraphML file.  If the graph generated by the <code>Factory</code> is
63   * not compatible (for example: if the graph does not accept directed
64   * edges, and the GraphML file contains a directed edge) then the results
65   * are graph-implementation-dependent.
66   *
67   * @see "http://graphml.graphdrawing.org/specification.html"
68   */
69  public class GraphMLReader<G extends Hypergraph<V,E>, V, E> extends DefaultHandler
70  {
71      protected enum TagState {NO_TAG, VERTEX, EDGE, HYPEREDGE, ENDPOINT, GRAPH,
72        DATA, KEY, DESC, DEFAULT_KEY, GRAPHML, OTHER}
73  
74      protected enum KeyType {NONE, VERTEX, EDGE, GRAPH, ALL}
75  
76      protected SAXParser saxp;
77      protected EdgeType default_edgetype;
78      protected G current_graph;
79      protected V current_vertex;
80      protected E current_edge;
81      protected String current_key;
82      protected LinkedList<TagState> current_states;
83      protected BiMap<String, TagState> tag_state;
84      protected Supplier<G> graph_factory;
85      protected Supplier<V> vertex_factory;
86      protected Supplier<E> edge_factory;
87      protected BiMap<V, String> vertex_ids;
88      protected BiMap<E, String> edge_ids;
89      protected Map<String, GraphMLMetadata<G>> graph_metadata;
90      protected Map<String, GraphMLMetadata<V>> vertex_metadata;
91      protected Map<String, GraphMLMetadata<E>> edge_metadata;
92      protected Map<V, String> vertex_desc;
93      protected Map<E, String> edge_desc;
94      protected Map<G, String> graph_desc;
95      protected KeyType key_type;
96      protected Collection<V> hyperedge_vertices;
97  
98      protected List<G> graphs;
99  
100     protected StringBuilder current_text = new StringBuilder();
101     
102     /**
103      * Creates a <code>GraphMLReader</code> instance with the specified
104      * vertex and edge factories.
105      *
106      * @param vertex_factory the vertex supplier to use to create vertex objects
107      * @param edge_factory the edge supplier to use to create edge objects
108      * @throws ParserConfigurationException if a SAX parser cannot be constructed
109      * @throws SAXException if the SAX parser factory cannot be constructed
110      */
111     public GraphMLReader(Supplier<V> vertex_factory,
112     		Supplier<E> edge_factory)
113         throws ParserConfigurationException, SAXException
114     {
115         current_vertex = null;
116         current_edge = null;
117 
118         SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
119         saxp = saxParserFactory.newSAXParser();
120 
121         current_states = new LinkedList<TagState>();
122 
123         tag_state = HashBiMap.<String, TagState>create();
124         tag_state.put("node", TagState.VERTEX);
125         tag_state.put("edge", TagState.EDGE);
126         tag_state.put("hyperedge", TagState.HYPEREDGE);
127         tag_state.put("endpoint", TagState.ENDPOINT);
128         tag_state.put("graph", TagState.GRAPH);
129         tag_state.put("data", TagState.DATA);
130         tag_state.put("key", TagState.KEY);
131         tag_state.put("desc", TagState.DESC);
132         tag_state.put("default", TagState.DEFAULT_KEY);
133         tag_state.put("graphml", TagState.GRAPHML);
134 
135         this.key_type = KeyType.NONE;
136 
137         this.vertex_factory = vertex_factory;
138         this.edge_factory = edge_factory;
139     }
140 
141     /**
142      * Creates a <code>GraphMLReader</code> instance that assigns the vertex
143      * and edge <code>id</code> strings to be the vertex and edge objects,
144      * as well as their IDs.
145      * Note that this requires that (a) each edge have a valid ID, which is not
146      * normally a requirement for edges in GraphML, and (b) that the vertex
147      * and edge types be assignment-compatible with <code>String</code>.
148      * @throws ParserConfigurationException if a SAX parser cannot be constructed
149      * @throws SAXException if the SAX parser factory cannot be constructed
150      */
151     public GraphMLReader() throws ParserConfigurationException, SAXException
152     {
153     	this(null, null);
154     }
155 
156     /**
157      * Returns a list of the graphs parsed from the specified reader, as created by
158      * the specified Supplier.
159      * @param reader the source of the graph data in GraphML format
160      * @param graph_factory used to build graph instances
161      * @return the graphs parsed from the specified reader
162      * @throws IOException if an error is encountered while parsing the graph
163      */
164     public List<G> loadMultiple(Reader reader, Supplier<G> graph_factory)
165         throws IOException
166     {
167         this.graph_factory = graph_factory;
168         initializeData();
169         clearData();
170         parse(reader);
171 
172         return graphs;
173     }
174 
175     /**
176      * Returns a list of the graphs parsed from the specified file, as created by
177      * the specified Supplier.
178      * @param filename the source of the graph data in GraphML format
179      * @param graph_factory used to build graph instances
180      * @return the graphs parsed from the specified file
181      * @throws IOException if an error is encountered while parsing the graph
182      */
183     public List<G> loadMultiple(String filename, Supplier<G> graph_factory) throws IOException
184     {
185         return loadMultiple(new FileReader(filename), graph_factory);
186     }
187 
188     /**
189      * Populates the specified graph with the data parsed from the reader.
190      * @param reader the source of the graph data in GraphML format
191      * @param g the graph instance to populate
192      * @throws IOException if an error is encountered while parsing the graph
193      */
194     public void load(Reader reader, G g) throws IOException
195     {
196         this.current_graph = g;
197         this.graph_factory = null;
198         initializeData();
199         clearData();
200 
201         parse(reader);
202     }
203 
204     /**
205      * Populates the specified graph with the data parsed from the specified file.
206      * @param filename the source of the graph data in GraphML format
207      * @param g the graph instance to populate
208      * @throws IOException if an error is encountered while parsing the graph
209      */
210     public void load(String filename, G g) throws IOException
211     {
212         load(new FileReader(filename), g);
213     }
214 
215     protected void clearData()
216     {
217         this.vertex_ids.clear();
218         this.vertex_desc.clear();
219 
220         this.edge_ids.clear();
221         this.edge_desc.clear();
222 
223         this.graph_desc.clear();
224 
225         this.hyperedge_vertices.clear();
226     }
227 
228     /**
229      * This is separate from initialize() because these data structures are shared among all
230      * graphs loaded (i.e., they're defined inside <code>graphml</code> rather than <code>graph</code>.
231      */
232     protected void initializeData()
233     {
234         this.vertex_ids = HashBiMap.<V, String>create();
235         this.vertex_desc = new HashMap<V, String>();
236         this.vertex_metadata = new HashMap<String, GraphMLMetadata<V>>();
237 
238         this.edge_ids = HashBiMap.<E, String>create();
239         this.edge_desc = new HashMap<E, String>();
240         this.edge_metadata = new HashMap<String, GraphMLMetadata<E>>();
241 
242         this.graph_desc = new HashMap<G, String>();
243         this.graph_metadata = new HashMap<String, GraphMLMetadata<G>>();
244 
245         this.hyperedge_vertices = new ArrayList<V>();
246     }
247 
248     protected void parse(Reader reader) throws IOException
249     {
250         try
251         {
252             saxp.parse(new InputSource(reader), this);
253             reader.close();
254         }
255         catch (SAXException saxe)
256         {
257             throw new IOException(saxe.getMessage());
258         }
259     }
260 
261     @Override
262     public void startElement(String uri, String name, String qName, Attributes atts) throws SAXNotSupportedException
263     {
264         String tag = qName.toLowerCase();
265         TagState state = tag_state.get(tag);
266         if (state == null)
267             state = TagState.OTHER;
268 
269         switch (state)
270         {
271             case GRAPHML:
272                 break;
273 
274             case VERTEX:
275                 if (this.current_graph == null)
276                     throw new SAXNotSupportedException("Graph must be defined prior to elements");
277                 if (this.current_edge != null || this.current_vertex != null)
278                     throw new SAXNotSupportedException("Nesting elements not supported");
279 
280                 createVertex(atts);
281 
282                 break;
283 
284             case ENDPOINT:
285                 if (this.current_graph == null)
286                     throw new SAXNotSupportedException("Graph must be defined prior to elements");
287                 if (this.current_edge == null)
288                     throw new SAXNotSupportedException("No edge defined for endpoint");
289                 if (this.current_states.getFirst() != TagState.HYPEREDGE)
290                     throw new SAXNotSupportedException("Endpoints must be defined inside hyperedge");
291                 Map<String, String> endpoint_atts = getAttributeMap(atts);
292                 String node = endpoint_atts.remove("node");
293                 if (node == null)
294                     throw new SAXNotSupportedException("Endpoint must include an 'id' attribute");
295                 V v = vertex_ids.inverse().get(node);
296                 if (v == null)
297                     throw new SAXNotSupportedException("Endpoint refers to nonexistent node ID: " + node);
298 
299                 this.current_vertex = v;
300                 hyperedge_vertices.add(v);
301                 break;
302 
303             case EDGE:
304             case HYPEREDGE:
305                 if (this.current_graph == null)
306                     throw new SAXNotSupportedException("Graph must be defined prior to elements");
307                 if (this.current_edge != null || this.current_vertex != null)
308                     throw new SAXNotSupportedException("Nesting elements not supported");
309 
310                 createEdge(atts, state);
311                 break;
312 
313             case GRAPH:
314                 if (this.current_graph != null && graph_factory != null)
315                     throw new SAXNotSupportedException("Nesting graphs not currently supported");
316 
317                 // graph Supplier is null if there's only one graph
318                 if (graph_factory != null)
319                     current_graph = graph_factory.get();
320 
321                 // reset all non-key data structures (to avoid collisions between different graphs)
322                 clearData();
323 
324                 // set up default direction of edges
325                 Map<String, String> graph_atts = getAttributeMap(atts);
326                 String default_direction = graph_atts.remove("edgedefault");
327                 if (default_direction == null)
328                     throw new SAXNotSupportedException("All graphs must specify a default edge direction");
329                 if (default_direction.equals("directed"))
330                     this.default_edgetype = EdgeType.DIRECTED;
331                 else if (default_direction.equals("undirected"))
332                     this.default_edgetype = EdgeType.UNDIRECTED;
333                 else
334                     throw new SAXNotSupportedException("Invalid or unrecognized default edge direction: " + default_direction);
335 
336                 // put remaining attribute/value pairs in graph_data
337             	addExtraData(graph_atts, graph_metadata, current_graph);
338 
339                 break;
340 
341             case DATA:
342                 if (this.current_states.contains(TagState.DATA))
343                     throw new SAXNotSupportedException("Nested data not supported");
344                 handleData(atts);
345                 break;
346 
347             case KEY:
348                 createKey(atts);
349                 break;
350 
351 
352             default:
353                 break;
354         }
355 
356         current_states.addFirst(state);
357     }
358 
359     /**
360      * 
361      * @param <T>
362      * @param atts
363      * @param metadata_map
364      * @param current_elt
365      */
366 	private <T> void addExtraData(Map<String, String> atts,
367 			Map<String, GraphMLMetadata<T>> metadata_map, T current_elt)
368 	{
369 		// load in the default values; these override anything that might
370 		// be in the attribute map (because that's not really a proper
371 		// way to associate data)
372         for (Map.Entry<String, GraphMLMetadata<T>> entry: metadata_map.entrySet())
373         {
374         	GraphMLMetadata<T> gmlm = entry.getValue();
375         	if (gmlm.default_value != null)
376         	{
377             	SettableTransformer<T, String> st =
378             		(SettableTransformer<T, String>)gmlm.transformer;
379         		st.set(current_elt, gmlm.default_value);
380         	}
381         }
382 
383         // place remaining items in data
384         for (Map.Entry<String, String> entry : atts.entrySet())
385         {
386 			String key = entry.getKey();
387 			GraphMLMetadata<T> key_data = metadata_map.get(key);
388 			SettableTransformer<T, String> st;
389 			if (key_data != null)
390 			{
391 				// if there's a default value, don't override it
392 				if (key_data.default_value != null)
393 					continue;
394 				st = (SettableTransformer<T, String>)key_data.transformer;
395 			}
396 			else
397 			{
398 				st = new MapSettableTransformer<T, String>(
399 							new HashMap<T, String>());
400 				key_data = new GraphMLMetadata<T>(null, null, st);
401 				metadata_map.put(key, key_data);
402 			}
403 			st.set(current_elt, entry.getValue());
404         }
405 	}
406 
407 
408     @Override
409     public void characters(char[] ch, int start, int length) throws SAXNotSupportedException
410     {
411         this.current_text.append(new String(ch, start, length));
412     }
413 
414 
415     protected <T>void addDatum(Map<String, GraphMLMetadata<T>> metadata,
416     		T current_elt, String text) throws SAXNotSupportedException
417     {
418         if (metadata.containsKey(this.current_key))
419         {
420         	SettableTransformer<T, String> st =
421         		(SettableTransformer<T, String>)(metadata.get(this.current_key).transformer);
422             st.set(current_elt, text);
423         }
424         else
425             throw new SAXNotSupportedException("key " + this.current_key +
426             		" not valid for element " + current_elt);
427     }
428 
429     @Override
430     public void endElement(String uri, String name, String qName) throws SAXNotSupportedException
431     {
432         String text = current_text.toString().trim();
433         current_text.setLength(0);
434         
435         String tag = qName.toLowerCase();
436         TagState state = tag_state.get(tag);
437         if (state == null)
438             state = TagState.OTHER;
439         if (state == TagState.OTHER)
440             return;
441 
442         if (state != current_states.getFirst())
443             throw new SAXNotSupportedException("Unbalanced tags: opened " +
444             		tag_state.inverse().get(current_states.getFirst()) +
445                     ", closed " + tag);
446 
447         switch(state)
448         {
449             case VERTEX:
450             case ENDPOINT:
451                 current_vertex = null;
452                 break;
453 
454             case EDGE:
455                 current_edge = null;
456                 break;
457 
458             case HYPEREDGE:
459                 current_graph.addEdge(current_edge, hyperedge_vertices);
460                 hyperedge_vertices.clear();
461                 current_edge = null;
462                 break;
463 
464             case GRAPH:
465                 current_graph = null;
466                 break;
467 
468             case KEY:
469                 current_key = null;
470                 break;
471 
472             case DESC:
473                 switch (this.current_states.get(1)) // go back one
474                 {
475                     case GRAPH:
476                         graph_desc.put(current_graph, text);
477                         break;
478                     case VERTEX:
479                     case ENDPOINT:
480                         vertex_desc.put(current_vertex, text);
481                         break;
482                     case EDGE:
483                     case HYPEREDGE:
484                         edge_desc.put(current_edge, text);
485                         break;
486                     case DATA:
487                         switch (key_type)
488                         {
489                             case GRAPH:
490                                 graph_metadata.get(current_key).description = text;
491                                 break;
492                             case VERTEX:
493                                 vertex_metadata.get(current_key).description = text;
494                                 break;
495                             case EDGE:
496                                 edge_metadata.get(current_key).description = text;
497                                 break;
498                             case ALL:
499                                 graph_metadata.get(current_key).description = text;
500                                 vertex_metadata.get(current_key).description = text;
501                                 edge_metadata.get(current_key).description = text;
502                                 break;
503                             default:
504                                 throw new SAXNotSupportedException("Invalid key type" +
505                                         " specified for default: " + key_type);
506                         }
507 
508                         break;
509                     default:
510                         break;
511                 }
512                 break;
513             case DATA:
514                 this.key_type = KeyType.NONE;
515                 switch (this.current_states.get(1))
516                 {
517                     case GRAPH:
518                         addDatum(graph_metadata, current_graph, text);
519                         break;
520                     case VERTEX:
521                     case ENDPOINT:
522                         addDatum(vertex_metadata, current_vertex, text);
523                         break;
524                     case EDGE:
525                     case HYPEREDGE:
526                         addDatum(edge_metadata, current_edge, text);
527                         break;
528                     default:
529                         break;
530                 }
531                 break;
532             case DEFAULT_KEY:
533                 if (this.current_states.get(1) != TagState.KEY)
534                     throw new SAXNotSupportedException("'default' only defined in context of 'key' tag: " +
535                             "stack: " + current_states.toString());
536 
537                 switch (key_type)
538                 {
539                     case GRAPH:
540                         graph_metadata.get(current_key).default_value = text;
541                         break;
542                     case VERTEX:
543                         vertex_metadata.get(current_key).default_value = text;
544                         break;
545                     case EDGE:
546                         edge_metadata.get(current_key).default_value = text;
547                         break;
548                     case ALL:
549                         graph_metadata.get(current_key).default_value = text;
550                         vertex_metadata.get(current_key).default_value = text;
551                         edge_metadata.get(current_key).default_value = text;
552                         break;
553                     default:
554                         throw new SAXNotSupportedException("Invalid key type" +
555                                 " specified for default: " + key_type);
556                 }
557 
558                 break;
559             default:
560                 break;
561         }
562 
563         current_states.removeFirst();
564     }
565 
566     protected Map<String, String> getAttributeMap(Attributes atts)
567     {
568         Map<String,String> att_map = new HashMap<String,String>();
569         for (int i = 0; i < atts.getLength(); i++)
570             att_map.put(atts.getQName(i), atts.getValue(i));
571 
572         return att_map;
573     }
574 
575     protected void handleData(Attributes atts) throws SAXNotSupportedException
576     {
577         switch (this.current_states.getFirst())
578         {
579             case GRAPH:
580                 break;
581             case VERTEX:
582             case ENDPOINT:
583                 break;
584             case EDGE:
585                 break;
586             case HYPEREDGE:
587                 break;
588             default:
589                 throw new SAXNotSupportedException("'data' tag only defined " +
590                 		"if immediately containing tag is 'graph', 'node', " +
591                         "'edge', or 'hyperedge'");
592         }
593         this.current_key = getAttributeMap(atts).get("key");
594         if (this.current_key == null)
595             throw new SAXNotSupportedException("'data' tag requires a key specification");
596         if (this.current_key.equals(""))
597             throw new SAXNotSupportedException("'data' tag requires a non-empty key");
598         if (!getGraphMetadata().containsKey(this.current_key) &&
599             !getVertexMetadata().containsKey(this.current_key) &&
600             !getEdgeMetadata().containsKey(this.current_key))
601         {
602             throw new SAXNotSupportedException("'data' tag's key specification must reference a defined key");
603         }
604 
605     }
606 
607     protected void createKey(Attributes atts) throws SAXNotSupportedException
608     {
609         Map<String, String> key_atts = getAttributeMap(atts);
610         String id = key_atts.remove("id");
611         String for_type = key_atts.remove("for");
612 
613         if (for_type == null || for_type.equals("") || for_type.equals("all"))
614         {
615             vertex_metadata.put(id,
616             		new GraphMLMetadata<V>(null, null,
617             				new MapSettableTransformer<V, String>(new HashMap<V, String>())));
618             edge_metadata.put(id,
619             		new GraphMLMetadata<E>(null, null,
620             				new MapSettableTransformer<E, String>(new HashMap<E, String>())));
621             graph_metadata.put(id,
622             		new GraphMLMetadata<G>(null, null,
623             				new MapSettableTransformer<G, String>(new HashMap<G, String>())));
624             key_type = KeyType.ALL;
625         }
626         else
627         {
628             TagState type = tag_state.get(for_type);
629             switch (type)
630             {
631                 case VERTEX:
632                     vertex_metadata.put(id,
633                     		new GraphMLMetadata<V>(null, null,
634                     				new MapSettableTransformer<V, String>(new HashMap<V, String>())));
635                     key_type = KeyType.VERTEX;
636                     break;
637                 case EDGE:
638                 case HYPEREDGE:
639                     edge_metadata.put(id,
640                     		new GraphMLMetadata<E>(null, null,
641                     				new MapSettableTransformer<E, String>(new HashMap<E, String>())));
642                     key_type = KeyType.EDGE;
643                     break;
644                 case GRAPH:
645                     graph_metadata.put(id,
646                     		new GraphMLMetadata<G>(null, null,
647                     				new MapSettableTransformer<G, String>(new HashMap<G, String>())));
648                     key_type = KeyType.GRAPH;
649                     break;
650                 default:
651                 	throw new SAXNotSupportedException(
652                 			"Invalid metadata target type: " + for_type);
653             }
654         }
655 
656         this.current_key = id;
657 
658     }
659 
660     @SuppressWarnings("unchecked")
661 	protected void createVertex(Attributes atts) throws SAXNotSupportedException
662     {
663         Map<String, String> vertex_atts = getAttributeMap(atts);
664         String id = vertex_atts.remove("id");
665         if (id == null)
666             throw new SAXNotSupportedException("node attribute list missing " +
667             		"'id': " + atts.toString());
668         V v = vertex_ids.inverse().get(id);
669 
670         if (v == null)
671         {
672         	if (vertex_factory != null)
673         		v = vertex_factory.get();
674         	else
675         		v = (V)id;
676             vertex_ids.put(v, id);
677             this.current_graph.addVertex(v);
678 
679             // put remaining attribute/value pairs in vertex_data
680            	addExtraData(vertex_atts, vertex_metadata, v);
681         }
682         else
683             throw new SAXNotSupportedException("Node id \"" + id +
684             		" is a duplicate of an existing node ID");
685 
686         this.current_vertex = v;
687     }
688 
689 
690     @SuppressWarnings("unchecked")
691 	protected void createEdge(Attributes atts, TagState state)
692     	throws SAXNotSupportedException
693     {
694         Map<String,String> edge_atts = getAttributeMap(atts);
695 
696         String id = edge_atts.remove("id");
697         E e;
698         if (edge_factory != null)
699         	e = edge_factory.get();
700         else
701             if (id != null)
702                 e = (E)id;
703             else
704                 throw new IllegalArgumentException("If no edge Supplier is supplied, " +
705                 		"edge id may not be null: " + edge_atts);
706 
707         if (id != null)
708         {
709             if (edge_ids.containsKey(e))
710                 throw new SAXNotSupportedException("Edge id \"" + id +
711                 		"\" is a duplicate of an existing edge ID");
712             edge_ids.put(e, id);
713         }
714 
715         if (state == TagState.EDGE)
716         	assignEdgeSourceTarget(e, atts, edge_atts); //, id);
717 
718         // put remaining attribute/value pairs in edge_data
719         addExtraData(edge_atts, edge_metadata, e);
720 
721         this.current_edge = e;
722     }
723 
724     protected void assignEdgeSourceTarget(E e, Attributes atts,
725     		Map<String, String> edge_atts)//, String id)
726     	throws SAXNotSupportedException
727     {
728         String source_id = edge_atts.remove("source");
729         if (source_id == null)
730             throw new SAXNotSupportedException("edge attribute list missing " +
731             		"'source': " + atts.toString());
732         V source = vertex_ids.inverse().get(source_id);
733         if (source == null)
734             throw new SAXNotSupportedException("specified 'source' attribute " +
735             		"\"" + source_id + "\" does not match any node ID");
736 
737         String target_id = edge_atts.remove("target");
738         if (target_id == null)
739             throw new SAXNotSupportedException("edge attribute list missing " +
740             		"'target': " + atts.toString());
741         V target = vertex_ids.inverse().get(target_id);
742         if (target == null)
743             throw new SAXNotSupportedException("specified 'target' attribute " +
744             		"\"" + target_id + "\" does not match any node ID");
745 
746         String directed = edge_atts.remove("directed");
747         EdgeType edge_type;
748         if (directed == null)
749             edge_type = default_edgetype;
750         else if (directed.equals("true"))
751             edge_type = EdgeType.DIRECTED;
752         else if (directed.equals("false"))
753             edge_type = EdgeType.UNDIRECTED;
754         else
755             throw new SAXNotSupportedException("Unrecognized edge direction specifier 'direction=\"" +
756             		directed + "\"': " + "source: " + source_id + ", target: " + target_id);
757 
758         if (current_graph instanceof Graph)
759             ((Graph<V,E>)this.current_graph).addEdge(e, source, target,
760             		edge_type);
761         else
762             this.current_graph.addEdge(e, new Pair<V>(source, target));
763     }
764 
765     /**
766      * @return a bidirectional map relating vertices and IDs.
767      */
768     public BiMap<V, String> getVertexIDs()
769     {
770         return vertex_ids;
771     }
772 
773     /**
774      * Returns a bidirectional map relating edges and IDs.
775      * This is not guaranteed to always be populated (edge IDs are not
776      * required in GraphML files.
777      * @return a bidirectional map relating edges and IDs.
778      */
779     public BiMap<E, String> getEdgeIDs()
780     {
781         return edge_ids;
782     }
783 
784     /**
785      * @return a map from graph type name to type metadata
786      */
787     public Map<String, GraphMLMetadata<G>> getGraphMetadata()
788     {
789     	return graph_metadata;
790     }
791 
792     /**
793      * @return a map from vertex type name to type metadata
794      */
795     public Map<String, GraphMLMetadata<V>> getVertexMetadata()
796     {
797     	return vertex_metadata;
798     }
799 
800     /**
801      * @return a map from edge type name to type metadata
802      */
803     public Map<String, GraphMLMetadata<E>> getEdgeMetadata()
804     {
805     	return edge_metadata;
806     }
807 
808     /**
809      * @return a map from graphs to graph descriptions
810      */
811     public Map<G, String> getGraphDescriptions()
812     {
813         return graph_desc;
814     }
815 
816     /**
817      * @return a map from vertices to vertex descriptions
818      */
819     public Map<V, String> getVertexDescriptions()
820     {
821         return vertex_desc;
822     }
823 
824     /**
825      * @return a map from edges to edge descriptions
826      */
827     public Map<E, String> getEdgeDescriptions()
828     {
829         return edge_desc;
830     }
831 }