View Javadoc
1   /*
2    * Created on June 16, 2008
3    *
4    * Copyright (c) 2008, 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.BufferedWriter;
15  import java.io.IOException;
16  import java.io.Writer;
17  import java.util.Collection;
18  import java.util.Collections;
19  import java.util.HashMap;
20  import java.util.Map;
21  
22  import com.google.common.base.Function;
23  import com.google.common.base.Functions;
24  
25  import edu.uci.ics.jung.graph.Graph;
26  import edu.uci.ics.jung.graph.Hypergraph;
27  import edu.uci.ics.jung.graph.UndirectedGraph;
28  import edu.uci.ics.jung.graph.util.EdgeType;
29  import edu.uci.ics.jung.graph.util.Pair;
30  
31  /**
32   * Writes graphs out in GraphML format.
33   *
34   * Current known issues: 
35   * <ul>
36   * <li>Only supports one graph per output file.
37   * <li>Does not indent lines for text-format readability.
38   * </ul>
39   * 
40   */
41  public class GraphMLWriter<V,E> 
42  {
43      protected Function<? super V, String> vertex_ids;
44      protected Function<? super E, String> edge_ids;
45      protected Map<String, GraphMLMetadata<Hypergraph<V,E>>> graph_data;
46      protected Map<String, GraphMLMetadata<V>> vertex_data;
47      protected Map<String, GraphMLMetadata<E>> edge_data;
48      protected Function<? super V, String> vertex_desc;
49      protected Function<? super E, String> edge_desc;
50      protected Function<? super Hypergraph<V,E>, String> graph_desc;
51  	protected boolean directed;
52  	protected int nest_level;
53      
54  	public GraphMLWriter() 
55  	{
56  	    vertex_ids = new Function<V,String>()
57  	    { 
58  	        public String apply(V v) 
59  	        { 
60  	            return v.toString(); 
61  	        }
62  	    };
63  	    edge_ids = Functions.constant(null);
64  	    graph_data = Collections.emptyMap();
65          vertex_data = Collections.emptyMap();
66          edge_data = Collections.emptyMap();
67          vertex_desc = Functions.constant(null);
68          edge_desc = Functions.constant(null);
69          graph_desc = Functions.constant(null);
70          nest_level = 0;
71  	}
72  	
73  	
74  	/**
75  	 * Writes {@code graph} out using {@code w}.
76  	 * @param graph the graph to write out
77  	 * @param w the writer instance to which the graph data will be written out
78  	 * @throws IOException if writing the graph fails
79  	 */
80  	public void save(Hypergraph<V,E> graph, Writer w) throws IOException
81  	{
82  		BufferedWriter bw = new BufferedWriter(w);
83  
84  		// write out boilerplate header
85  		bw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
86  		bw.write("<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns/graphml\"\n" +
87  				"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"  \n");
88  		bw.write("xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns/graphml\">\n");
89  		
90  		// write out data specifiers, including defaults
91  		for (String key : graph_data.keySet())
92  			writeKeySpecification(key, "graph", graph_data.get(key), bw);
93  		for (String key : vertex_data.keySet())
94  			writeKeySpecification(key, "node", vertex_data.get(key), bw);
95  		for (String key : edge_data.keySet())
96  			writeKeySpecification(key, "edge", edge_data.get(key), bw);
97  
98  		// write out graph-level information
99  		// set edge default direction
100 		bw.write("<graph edgedefault=\"");
101 		directed = !(graph instanceof UndirectedGraph);
102         if (directed)
103             bw.write("directed\">\n");
104         else 
105             bw.write("undirected\">\n");
106 
107         // write graph description, if any
108 		String desc = graph_desc.apply(graph);
109 		if (desc != null)
110 			bw.write("<desc>" + desc + "</desc>\n");
111 		
112 		// write graph data out if any
113 		for (String key : graph_data.keySet())
114 		{
115 			Function<Hypergraph<V,E>, ?> t = graph_data.get(key).transformer;
116 			Object value = t.apply(graph);
117 			if (value != null)
118 				bw.write(format("data", "key", key, value.toString()) + "\n");
119 		}
120         
121 		// write vertex information
122         writeVertexData(graph, bw);
123 		
124 		// write edge information
125         writeEdgeData(graph, bw);
126 
127         // close graph
128         bw.write("</graph>\n");
129         bw.write("</graphml>\n");
130         bw.flush();
131         
132         bw.close();
133 	}
134 
135 //	public boolean save(Collection<Hypergraph<V,E>> graphs, Writer w)
136 //	{
137 //		return true;
138 //	}
139 
140 	protected void writeIndentedText(BufferedWriter w, String to_write) throws IOException
141 	{
142 	    for (int i = 0; i < nest_level; i++)
143 	        w.write("  ");
144 	    w.write(to_write);
145 	}
146 	
147 	protected void writeVertexData(Hypergraph<V,E> graph, BufferedWriter w) throws IOException
148 	{
149 		for (V v: graph.getVertices())
150 		{
151 			String v_string = String.format("<node id=\"%s\"", vertex_ids.apply(v));
152 			boolean closed = false;
153 			// write description out if any
154 			String desc = vertex_desc.apply(v);
155 			if (desc != null)
156 			{
157 				w.write(v_string + ">\n");
158 				closed = true;
159 				w.write("<desc>" + desc + "</desc>\n");
160 			}
161 			// write data out if any
162 			for (String key : vertex_data.keySet())
163 			{
164 				Function<V, ?> t = vertex_data.get(key).transformer;
165 				if (t != null)
166 				{
167     				Object value = t.apply(v);
168     				if (value != null)
169     				{
170     					if (!closed)
171     					{
172     						w.write(v_string + ">\n");
173     						closed = true;
174     					}
175     					w.write(format("data", "key", key, value.toString()) + "\n");
176     				}
177 				}
178 			}
179 			if (!closed)
180 				w.write(v_string + "/>\n"); // no contents; close the node with "/>"
181 			else
182 			    w.write("</node>\n");
183 		}
184 	}
185 
186 	protected void writeEdgeData(Hypergraph<V,E> g, Writer w) throws IOException
187 	{
188 		for (E e: g.getEdges())
189 		{
190 			Collection<V> vertices = g.getIncidentVertices(e);
191 			String id = edge_ids.apply(e);
192 			String e_string;
193 			boolean is_hyperedge = !(g instanceof Graph);
194             if (is_hyperedge)
195             {
196                 e_string = "<hyperedge ";
197                 // add ID if present
198                 if (id != null)
199                     e_string += "id=\"" + id + "\" ";
200             }
201             else
202 			{
203 				Pair<V> endpoints = new Pair<V>(vertices);
204 				V v1 = endpoints.getFirst();
205 				V v2 = endpoints.getSecond();
206 				e_string = "<edge ";
207 				// add ID if present
208 				if (id != null)
209 					e_string += "id=\"" + id + "\" ";
210 				// add edge type if doesn't match default
211 				EdgeType edge_type = g.getEdgeType(e);
212 				if (directed && edge_type == EdgeType.UNDIRECTED)
213 					e_string += "directed=\"false\" ";
214 				if (!directed && edge_type == EdgeType.DIRECTED)
215 					e_string += "directed=\"true\" ";
216 				e_string += "source=\"" + vertex_ids.apply(v1) + 
217 					"\" target=\"" + vertex_ids.apply(v2) + "\"";
218 			}
219 			
220 			boolean closed = false;
221 			// write description out if any
222 			String desc = edge_desc.apply(e);
223 			if (desc != null)
224 			{
225 				w.write(e_string + ">\n");
226 				closed = true;
227 				w.write("<desc>" + desc + "</desc>\n");
228 			}
229 			// write data out if any
230 			for (String key : edge_data.keySet())
231 			{
232 				Function<E, ?> t = edge_data.get(key).transformer;
233 				Object value = t.apply(e);
234 				if (value != null)
235 				{
236 					if (!closed)
237 					{
238 						w.write(e_string + ">\n");
239 						closed = true;
240 					}
241 					w.write(format("data", "key", key, value.toString()) + "\n");
242 				}
243 			}
244 			// if this is a hyperedge, write endpoints out if any
245 			if (is_hyperedge)
246 			{
247 				for (V v : vertices)
248 				{
249 					if (!closed)
250 					{
251 						w.write(e_string + ">\n");
252 						closed = true;
253 					}
254 					w.write("<endpoint node=\"" + vertex_ids.apply(v) + "\"/>\n");
255 				}
256 			}
257 			
258 			if (!closed)
259 				w.write(e_string + "/>\n"); // no contents; close the edge with "/>"
260 			else
261 			    if (is_hyperedge)
262 			        w.write("</hyperedge>\n");
263 			    else
264 			        w.write("</edge>\n");
265 		}
266 	}
267 
268 	protected void writeKeySpecification(String key, String type, 
269 			GraphMLMetadata<?> ds, BufferedWriter bw) throws IOException
270 	{
271 		bw.write("<key id=\"" + key + "\" for=\"" + type + "\"");
272 		boolean closed = false;
273 		// write out description if any
274 		String desc = ds.description;
275 		if (desc != null)
276 		{
277 			if (!closed)
278 			{
279 				bw.write(">\n");
280 				closed = true;
281 			}
282 			bw.write("<desc>" + desc + "</desc>\n");
283 		}
284 		// write out default if any
285 		Object def = ds.default_value;
286 		if (def != null)
287 		{
288 			if (!closed)
289 			{
290 				bw.write(">\n");
291 				closed = true;
292 			}
293 			bw.write("<default>" + def.toString() + "</default>\n");
294 		}
295 		if (!closed)
296 		    bw.write("/>\n");
297 		else
298 		    bw.write("</key>\n");
299 	}
300 	
301 	protected String format(String type, String attr, String value, String contents)
302 	{
303 		return String.format("<%s %s=\"%s\">%s</%s>", 
304 				type, attr, value, contents, type);
305 	}
306 	
307 	/**
308 	 * Provides an ID that will be used to identify a vertex in the output file.
309 	 * If the vertex IDs are not set, the ID for each vertex will default to
310 	 * the output of <code>toString</code> 
311 	 * (and thus not guaranteed to be unique).
312 	 * 
313 	 * @param vertex_ids a mapping from vertex to ID
314 	 */
315 	public void setVertexIDs(Function<V, String> vertex_ids) 
316 	{
317 		this.vertex_ids = vertex_ids;
318 	}
319 
320 
321 	/**
322 	 * Provides an ID that will be used to identify an edge in the output file.
323 	 * If any edge ID is missing, no ID will be written out for the
324 	 * corresponding edge.
325 	 * 
326 	 * @param edge_ids a mapping from edge to ID
327 	 */
328 	public void setEdgeIDs(Function<E, String> edge_ids) 
329 	{
330 		this.edge_ids = edge_ids;
331 	}
332 
333 	/**
334 	 * Provides a map from data type name to graph data.
335 	 * 
336 	 * @param graph_map map from data type name to graph data
337 	 */
338 	public void setGraphData(Map<String, GraphMLMetadata<Hypergraph<V,E>>> graph_map)
339 	{
340 		graph_data = graph_map;
341 	}
342 	
343     /**
344      * Provides a map from data type name to vertex data.
345      * 
346      * @param vertex_map map from data type name to vertex data
347      */
348 	public void setVertexData(Map<String, GraphMLMetadata<V>> vertex_map)
349 	{
350 		vertex_data = vertex_map;
351 	}
352 	
353     /**
354      * Provides a map from data type name to edge data.
355      * 
356      * @param edge_map map from data type name to edge data
357      */
358 	public void setEdgeData(Map<String, GraphMLMetadata<E>> edge_map)
359 	{
360 		edge_data = edge_map;
361 	}
362 	
363 	/**
364 	 * Adds a new graph data specification.
365 	 * 
366 	 * @param id the ID of the data to add
367 	 * @param description a description of the data to add
368 	 * @param default_value a default value for the data type
369 	 * @param graph_transformer a mapping from graphs to their string representations
370 	 */
371 	public void addGraphData(String id, String description, String default_value,
372 			Function<Hypergraph<V,E>, String> graph_transformer)
373 	{
374 		if (graph_data.equals(Collections.EMPTY_MAP))
375 			graph_data = new HashMap<String, GraphMLMetadata<Hypergraph<V,E>>>();
376 		graph_data.put(id, new GraphMLMetadata<Hypergraph<V,E>>(description, 
377 				default_value, graph_transformer));
378 	}
379 	
380 	/**
381 	 * Adds a new vertex data specification.
382 	 * 
383 	 * @param id the ID of the data to add
384 	 * @param description a description of the data to add
385 	 * @param default_value a default value for the data type
386 	 * @param vertex_transformer a mapping from vertices to their string representations
387 	 */
388 	public void addVertexData(String id, String description, String default_value,
389 			Function<V, String> vertex_transformer)
390 	{
391 		if (vertex_data.equals(Collections.EMPTY_MAP))
392 			vertex_data = new HashMap<String, GraphMLMetadata<V>>();
393 		vertex_data.put(id, new GraphMLMetadata<V>(description, default_value, 
394 				vertex_transformer));
395 	}
396 
397 	/**
398 	 * Adds a new edge data specification.
399 	 * 
400 	 * @param id the ID of the data to add
401 	 * @param description a description of the data to add
402 	 * @param default_value a default value for the data type
403 	 * @param edge_transformer a mapping from edges to their string representations
404 	 */
405 	public void addEdgeData(String id, String description, String default_value,
406 			Function<E, String> edge_transformer)
407 	{
408 		if (edge_data.equals(Collections.EMPTY_MAP))
409 			edge_data = new HashMap<String, GraphMLMetadata<E>>();
410 		edge_data.put(id, new GraphMLMetadata<E>(description, default_value, 
411 				edge_transformer));
412 	}
413 
414 	/**
415 	 * Provides vertex descriptions.
416 	 * @param vertex_desc a mapping from vertices to their descriptions
417 	 */
418 	public void setVertexDescriptions(Function<V, String> vertex_desc) 
419 	{
420 		this.vertex_desc = vertex_desc;
421 	}
422 
423     /**
424      * Provides edge descriptions.
425 	 * @param edge_desc a mapping from edges to their descriptions
426      */
427 	public void setEdgeDescriptions(Function<E, String> edge_desc) 
428 	{
429 		this.edge_desc = edge_desc;
430 	}
431 
432     /**
433      * Provides graph descriptions.
434 	 * @param graph_desc a mapping from graphs to their descriptions
435      */
436 	public void setGraphDescriptions(Function<Hypergraph<V,E>, String> graph_desc) 
437 	{
438 		this.graph_desc = graph_desc;
439 	}
440 }