001/*
002 * Copyright 2006 - 2013
003 *     Stefan Balev     <stefan.balev@graphstream-project.org>
004 *     Julien Baudry    <julien.baudry@graphstream-project.org>
005 *     Antoine Dutot    <antoine.dutot@graphstream-project.org>
006 *     Yoann Pigné      <yoann.pigne@graphstream-project.org>
007 *     Guilhelm Savin   <guilhelm.savin@graphstream-project.org>
008 * 
009 * This file is part of GraphStream <http://graphstream-project.org>.
010 * 
011 * GraphStream is a library whose purpose is to handle static or dynamic
012 * graph, create them from scratch, file or any source and display them.
013 * 
014 * This program is free software distributed under the terms of two licenses, the
015 * CeCILL-C license that fits European law, and the GNU Lesser General Public
016 * License. You can  use, modify and/ or redistribute the software under the terms
017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by
019 * the Free Software Foundation, either version 3 of the License, or (at your
020 * option) any later version.
021 * 
022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
024 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
025 * 
026 * You should have received a copy of the GNU Lesser General Public License
027 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
028 * 
029 * The fact that you are presently reading this means that you have had
030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
031 */
032package org.graphstream.stream.file;
033
034import org.graphstream.graph.Edge;
035import org.graphstream.graph.Element;
036import org.graphstream.graph.Graph;
037import org.graphstream.graph.Node;
038import org.graphstream.util.cumulative.CumulativeAttributes;
039import org.graphstream.util.cumulative.CumulativeSpells;
040import org.graphstream.util.cumulative.CumulativeSpells.Spell;
041import org.graphstream.util.cumulative.GraphSpells;
042
043import java.io.IOException;
044import java.net.URI;
045import java.net.URL;
046import java.text.DateFormat;
047import java.text.DecimalFormat;
048import java.text.DecimalFormatSymbols;
049import java.text.Format;
050import java.text.SimpleDateFormat;
051import java.util.Calendar;
052import java.util.Collection;
053import java.util.Date;
054import java.util.HashMap;
055import java.util.Locale;
056
057import javax.xml.stream.FactoryConfigurationError;
058import javax.xml.stream.XMLOutputFactory;
059import javax.xml.stream.XMLStreamException;
060import javax.xml.stream.XMLStreamWriter;
061
062public class FileSinkGEXF extends FileSinkBase {
063        public static enum TimeFormat {
064                INTEGER(new DecimalFormat("#", new DecimalFormatSymbols(Locale.ROOT))), DOUBLE(
065                                new DecimalFormat("#.0###################",
066                                                new DecimalFormatSymbols(Locale.ROOT))), DATE(
067                                new SimpleDateFormat("yyyy-MM-dd")), DATETIME(
068                                new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSZ"));
069                Format format;
070
071                TimeFormat(Format f) {
072                        this.format = f;
073                }
074        }
075
076        XMLStreamWriter stream;
077        boolean smart;
078        int depth;
079        int currentAttributeIndex = 0;
080        GraphSpells graphSpells;
081        TimeFormat timeFormat;
082
083        public FileSinkGEXF() {
084                smart = true;
085                depth = 0;
086                graphSpells = null;
087                timeFormat = TimeFormat.DOUBLE;
088        }
089
090        public void setTimeFormat(TimeFormat format) {
091                this.timeFormat = format;
092        }
093
094        protected void putSpellAttributes(Spell s) throws XMLStreamException {
095                if (s.isStarted()) {
096                        String start = s.isStartOpen() ? "startopen" : "start";
097                        String date = timeFormat.format.format(s.getStartDate());
098
099                        stream.writeAttribute(start, date);
100                }
101
102                if (s.isEnded()) {
103                        String end = s.isEndOpen() ? "endopen" : "end";
104                        String date = timeFormat.format.format(s.getEndDate());
105
106                        stream.writeAttribute(end, date);
107                }
108        }
109
110        protected void outputEndOfFile() throws IOException {
111                try {
112                        if (graphSpells != null) {
113                                exportGraphSpells();
114                                graphSpells = null;
115                        }
116
117                        endElement(stream, false);
118                        stream.writeEndDocument();
119                        stream.flush();
120                } catch (XMLStreamException e) {
121                        throw new IOException(e);
122                }
123        }
124
125        protected void outputHeader() throws IOException {
126                Calendar cal = Calendar.getInstance();
127                Date date = cal.getTime();
128                DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT,
129                                DateFormat.SHORT);
130
131                try {
132                        stream = XMLOutputFactory.newFactory()
133                                        .createXMLStreamWriter(output);
134                        stream.writeStartDocument("UTF-8", "1.0");
135
136                        startElement(stream, "gexf");
137                        stream.writeAttribute("xmlns", "http://www.gexf.net/1.2draft");
138                        stream.writeAttribute("xmlns:xsi",
139                                        "http://www.w3.org/2001/XMLSchema-instance");
140                        stream.writeAttribute("xsi:schemaLocation",
141                                        "http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd");
142                        stream.writeAttribute("version", "1.2");
143
144                        startElement(stream, "meta");
145                        stream.writeAttribute("lastmodifieddate", df.format(date));
146                        startElement(stream, "creator");
147                        stream.writeCharacters("GraphStream - " + getClass().getName());
148                        endElement(stream, true);
149                        endElement(stream, false);
150                } catch (XMLStreamException e) {
151                        throw new IOException(e);
152                } catch (FactoryConfigurationError e) {
153                        throw new IOException(e);
154                }
155        }
156
157        protected void startElement(XMLStreamWriter stream, String name)
158                        throws XMLStreamException {
159                if (smart) {
160                        stream.writeCharacters("\n");
161
162                        for (int i = 0; i < depth; i++)
163                                stream.writeCharacters(" ");
164                }
165
166                stream.writeStartElement(name);
167                depth++;
168        }
169
170        protected void endElement(XMLStreamWriter stream, boolean leaf)
171                        throws XMLStreamException {
172                depth--;
173
174                if (smart && !leaf) {
175                        stream.writeCharacters("\n");
176
177                        for (int i = 0; i < depth; i++)
178                                stream.writeCharacters(" ");
179                }
180
181                stream.writeEndElement();
182        }
183
184        @Override
185        protected void exportGraph(Graph g) {
186                GEXFAttributeMap nodeAttributes = new GEXFAttributeMap("node", g);
187                GEXFAttributeMap edgeAttributes = new GEXFAttributeMap("edge", g);
188
189                try {
190                        startElement(stream, "graph");
191                        stream.writeAttribute("defaultedgetype", "undirected");
192
193                        nodeAttributes.export(stream);
194                        edgeAttributes.export(stream);
195
196                        startElement(stream, "nodes");
197                        for (Node n : g.getEachNode()) {
198                                startElement(stream, "node");
199                                stream.writeAttribute("id", n.getId());
200
201                                if (n.hasAttribute("label"))
202                                        stream.writeAttribute("label", n.getAttribute("label")
203                                                        .toString());
204
205                                if (n.getAttributeCount() > 0) {
206                                        startElement(stream, "attvalues");
207                                        for (String key : n.getAttributeKeySet())
208                                                nodeAttributes.push(stream, n, key);
209                                        endElement(stream, false);
210                                }
211
212                                endElement(stream, n.getAttributeCount() == 0);
213                        }
214                        endElement(stream, false);
215
216                        startElement(stream, "edges");
217                        for (Edge e : g.getEachEdge()) {
218                                startElement(stream, "edge");
219
220                                stream.writeAttribute("id", e.getId());
221                                stream.writeAttribute("source", e.getSourceNode().getId());
222                                stream.writeAttribute("target", e.getTargetNode().getId());
223
224                                if (e.getAttributeCount() > 0) {
225                                        startElement(stream, "attvalues");
226                                        for (String key : e.getAttributeKeySet())
227                                                edgeAttributes.push(stream, e, key);
228                                        endElement(stream, false);
229                                }
230
231                                endElement(stream, e.getAttributeCount() == 0);
232                        }
233                        endElement(stream, false);
234
235                        endElement(stream, false);
236                } catch (XMLStreamException e1) {
237                        e1.printStackTrace();
238                }
239        }
240
241        protected void exportGraphSpells() {
242                GEXFAttributeMap nodeAttributes = new GEXFAttributeMap("node",
243                                graphSpells);
244                GEXFAttributeMap edgeAttributes = new GEXFAttributeMap("edge",
245                                graphSpells);
246
247                try {
248                        startElement(stream, "graph");
249                        stream.writeAttribute("mode", "dynamic");
250                        stream.writeAttribute("defaultedgetype", "undirected");
251                        stream.writeAttribute("timeformat", timeFormat.name().toLowerCase());
252
253                        nodeAttributes.export(stream);
254                        edgeAttributes.export(stream);
255
256                        startElement(stream, "nodes");
257                        for (String nodeId : graphSpells.getNodes()) {
258                                startElement(stream, "node");
259                                stream.writeAttribute("id", nodeId);
260
261                                CumulativeAttributes attr = graphSpells
262                                                .getNodeAttributes(nodeId);
263                                Object label = attr.getAny("label");
264
265                                if (label != null)
266                                        stream.writeAttribute("label", label.toString());
267
268                                CumulativeSpells spells = graphSpells.getNodeSpells(nodeId);
269
270                                if (!spells.isEternal()) {
271                                        startElement(stream, "spells");
272                                        for (int i = 0; i < spells.getSpellCount(); i++) {
273                                                Spell s = spells.getSpell(i);
274
275                                                startElement(stream, "spell");
276                                                putSpellAttributes(s);
277                                                endElement(stream, true);
278                                        }
279                                        endElement(stream, false);
280                                }
281
282                                if (attr.getAttributesCount() > 0) {
283                                        startElement(stream, "attvalues");
284                                        nodeAttributes.push(stream, nodeId, graphSpells);
285                                        endElement(stream, false);
286                                }
287
288                                endElement(stream,
289                                                spells.isEternal() && attr.getAttributesCount() == 0);
290                        }
291                        endElement(stream, false);
292
293                        startElement(stream, "edges");
294                        for (String edgeId : graphSpells.getEdges()) {
295                                startElement(stream, "edge");
296
297                                GraphSpells.EdgeData data = graphSpells.getEdgeData(edgeId);
298
299                                stream.writeAttribute("id", edgeId);
300                                stream.writeAttribute("source", data.getSource());
301                                stream.writeAttribute("target", data.getTarget());
302
303                                CumulativeAttributes attr = graphSpells
304                                                .getEdgeAttributes(edgeId);
305
306                                CumulativeSpells spells = graphSpells.getEdgeSpells(edgeId);
307
308                                if (!spells.isEternal()) {
309                                        startElement(stream, "spells");
310                                        for (int i = 0; i < spells.getSpellCount(); i++) {
311                                                Spell s = spells.getSpell(i);
312
313                                                startElement(stream, "spell");
314                                                putSpellAttributes(s);
315                                                endElement(stream, true);
316                                        }
317                                        endElement(stream, false);
318                                }
319
320                                if (attr.getAttributesCount() > 0) {
321                                        startElement(stream, "attvalues");
322                                        edgeAttributes.push(stream, edgeId, graphSpells);
323                                        endElement(stream, false);
324                                }
325
326                                endElement(stream,
327                                                spells.isEternal() && attr.getAttributesCount() == 0);
328                        }
329                        endElement(stream, false);
330
331                        endElement(stream, false);
332                } catch (XMLStreamException e1) {
333                        e1.printStackTrace();
334                }
335        }
336
337        protected void checkGraphSpells() {
338                if (graphSpells == null)
339                        graphSpells = new GraphSpells();
340        }
341
342        public void edgeAttributeAdded(String sourceId, long timeId, String edgeId,
343                        String attribute, Object value) {
344                checkGraphSpells();
345                graphSpells.edgeAttributeAdded(sourceId, timeId, edgeId, attribute,
346                                value);
347        }
348
349        public void edgeAttributeChanged(String sourceId, long timeId,
350                        String edgeId, String attribute, Object oldValue, Object newValue) {
351                checkGraphSpells();
352                graphSpells.edgeAttributeChanged(sourceId, timeId, edgeId, attribute,
353                                oldValue, newValue);
354        }
355
356        public void edgeAttributeRemoved(String sourceId, long timeId,
357                        String edgeId, String attribute) {
358                checkGraphSpells();
359                graphSpells.edgeAttributeRemoved(sourceId, timeId, edgeId, attribute);
360        }
361
362        public void graphAttributeAdded(String sourceId, long timeId,
363                        String attribute, Object value) {
364                checkGraphSpells();
365                graphSpells.graphAttributeAdded(sourceId, timeId, attribute, value);
366        }
367
368        public void graphAttributeChanged(String sourceId, long timeId,
369                        String attribute, Object oldValue, Object newValue) {
370                checkGraphSpells();
371                graphSpells.graphAttributeChanged(sourceId, timeId, attribute,
372                                oldValue, newValue);
373        }
374
375        public void graphAttributeRemoved(String sourceId, long timeId,
376                        String attribute) {
377                checkGraphSpells();
378                graphSpells.graphAttributeRemoved(sourceId, timeId, attribute);
379        }
380
381        public void nodeAttributeAdded(String sourceId, long timeId, String nodeId,
382                        String attribute, Object value) {
383                checkGraphSpells();
384                graphSpells.nodeAttributeAdded(sourceId, timeId, nodeId, attribute,
385                                value);
386        }
387
388        public void nodeAttributeChanged(String sourceId, long timeId,
389                        String nodeId, String attribute, Object oldValue, Object newValue) {
390                checkGraphSpells();
391                graphSpells.nodeAttributeChanged(sourceId, timeId, nodeId, attribute,
392                                oldValue, newValue);
393        }
394
395        public void nodeAttributeRemoved(String sourceId, long timeId,
396                        String nodeId, String attribute) {
397                checkGraphSpells();
398                graphSpells.nodeAttributeRemoved(sourceId, timeId, nodeId, attribute);
399        }
400
401        public void edgeAdded(String sourceId, long timeId, String edgeId,
402                        String fromNodeId, String toNodeId, boolean directed) {
403                checkGraphSpells();
404                graphSpells.edgeAdded(sourceId, timeId, edgeId, fromNodeId, toNodeId,
405                                directed);
406        }
407
408        public void edgeRemoved(String sourceId, long timeId, String edgeId) {
409                checkGraphSpells();
410                graphSpells.edgeRemoved(sourceId, timeId, edgeId);
411        }
412
413        public void graphCleared(String sourceId, long timeId) {
414                checkGraphSpells();
415                graphSpells.graphCleared(sourceId, timeId);
416        }
417
418        public void nodeAdded(String sourceId, long timeId, String nodeId) {
419                checkGraphSpells();
420                graphSpells.nodeAdded(sourceId, timeId, nodeId);
421        }
422
423        public void nodeRemoved(String sourceId, long timeId, String nodeId) {
424                checkGraphSpells();
425                graphSpells.nodeRemoved(sourceId, timeId, nodeId);
426        }
427
428        public void stepBegins(String sourceId, long timeId, double step) {
429                checkGraphSpells();
430                graphSpells.stepBegins(sourceId, timeId, step);
431        }
432
433        class GEXFAttribute {
434                int index;
435                String key;
436                String type;
437
438                GEXFAttribute(String key, String type) {
439                        this.index = currentAttributeIndex++;
440                        this.key = key;
441                        this.type = type;
442                }
443        }
444
445        class GEXFAttributeMap extends HashMap<String, GEXFAttribute> {
446                private static final long serialVersionUID = 6176508111522815024L;
447                protected String type;
448
449                GEXFAttributeMap(String type, Graph g) {
450                        this.type = type;
451
452                        Iterable<? extends Element> iterable;
453
454                        if (type.equals("node"))
455                                iterable = (Iterable<? extends Element>) g.getNodeSet();
456                        else
457                                iterable = (Iterable<? extends Element>) g.getEdgeSet();
458
459                        for (Element e : iterable) {
460                                for (String key : e.getAttributeKeySet()) {
461                                        Object value = e.getAttribute(key);
462                                        check(key, value);
463                                }
464                        }
465                }
466
467                GEXFAttributeMap(String type, GraphSpells spells) {
468                        this.type = type;
469
470                        if (type.equals("node")) {
471                                for (String nodeId : spells.getNodes()) {
472                                        CumulativeAttributes attr = spells
473                                                        .getNodeAttributes(nodeId);
474
475                                        for (String key : attr.getAttributes()) {
476                                                for (Spell s : attr.getAttributeSpells(key)) {
477                                                        Object value = s.getAttachedData();
478                                                        check(key, value);
479                                                }
480                                        }
481                                }
482                        } else {
483                                for (String edgeId : spells.getEdges()) {
484                                        CumulativeAttributes attr = spells
485                                                        .getEdgeAttributes(edgeId);
486
487                                        for (String key : attr.getAttributes()) {
488                                                for (Spell s : attr.getAttributeSpells(key)) {
489                                                        Object value = s.getAttachedData();
490                                                        check(key, value);
491                                                }
492                                        }
493                                }
494                        }
495                }
496
497                void check(String key, Object value) {
498                        String id = getID(key, value);
499                        String attType = "string";
500
501                        if (containsKey(id))
502                                return;
503
504                        if (value instanceof Integer || value instanceof Short)
505                                attType = "integer";
506                        else if (value instanceof Long)
507                                attType = "long";
508                        else if (value instanceof Float)
509                                attType = "float";
510                        else if (value instanceof Double)
511                                attType = "double";
512                        else if (value instanceof Boolean)
513                                attType = "boolean";
514                        else if (value instanceof URL || value instanceof URI)
515                                attType = "anyURI";
516                        else if (value.getClass().isArray() || value instanceof Collection)
517                                attType = "liststring";
518
519                        put(id, new GEXFAttribute(key, attType));
520                }
521
522                String getID(String key, Object value) {
523                        return String.format("%s@%s", key, value.getClass().getName());
524                }
525
526                void export(XMLStreamWriter stream) throws XMLStreamException {
527                        if (size() == 0)
528                                return;
529
530                        startElement(stream, "attributes");
531                        stream.writeAttribute("class", type);
532
533                        for (GEXFAttribute a : values()) {
534                                startElement(stream, "attribute");
535                                stream.writeAttribute("id", Integer.toString(a.index));
536                                stream.writeAttribute("title", a.key);
537                                stream.writeAttribute("type", a.type);
538                                endElement(stream, true);
539                        }
540
541                        endElement(stream, size() == 0);
542                }
543
544                void push(XMLStreamWriter stream, Element e, String key)
545                                throws XMLStreamException {
546                        String id = getID(key, e.getAttribute(key));
547                        GEXFAttribute a = get(id);
548
549                        if (a == null) {
550                                // TODO
551                                return;
552                        }
553
554                        startElement(stream, "attvalue");
555                        stream.writeAttribute("for", Integer.toString(a.index));
556                        stream.writeAttribute("value", e.getAttribute(key).toString());
557                        endElement(stream, true);
558                }
559
560                void push(XMLStreamWriter stream, String elementId, GraphSpells spells)
561                                throws XMLStreamException {
562                        CumulativeAttributes attr;
563
564                        if (type.equals("node"))
565                                attr = spells.getNodeAttributes(elementId);
566                        else
567                                attr = spells.getEdgeAttributes(elementId);
568
569                        for (String key : attr.getAttributes()) {
570                                for (Spell s : attr.getAttributeSpells(key)) {
571                                        Object value = s.getAttachedData();
572                                        String id = getID(key, value);
573                                        GEXFAttribute a = get(id);
574
575                                        if (a == null) {
576                                                // TODO
577                                                return;
578                                        }
579
580                                        startElement(stream, "attvalue");
581                                        stream.writeAttribute("for", Integer.toString(a.index));
582                                        stream.writeAttribute("value", value.toString());
583                                        putSpellAttributes(s);
584                                        endElement(stream, true);
585                                }
586                        }
587                }
588        }
589}