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.util;
033
034import java.io.PrintStream;
035import java.lang.reflect.Array;
036import java.util.EnumMap;
037import java.util.HashMap;
038import java.util.Stack;
039
040import org.graphstream.stream.Sink;
041
042/**
043 * A sink that can be used to display event in a PrintStream like System.out.
044 * Format of messages can be customized, inserting keywords quoted with '%' in
045 * the format.
046 * 
047 * '%sourceId%' and '%timeId%' keywords are defined for each event. Following
048 * defines keywords available for each event types:
049 * <dl>
050 * <dt>ADD_NODE</dt>
051 * <dd>
052 * <ul>
053 * <li>%nodeId%</li>
054 * </ul>
055 * </dd>
056 * <dt>ADD_NODE_ATTRIBUTE</dt>
057 * <dd>
058 * <ul>
059 * <li>%nodeId%</li>
060 * <li>%attributeId%</li>
061 * <li>%value%</li>
062 * </ul>
063 * </dd>
064 * <dt>SET_NODE_ATTRIBUTE</dt>
065 * <dd>
066 * <ul>
067 * <li>%nodeId%</li>
068 * <li>%attributeId%</li>
069 * <li>%value%</li>
070 * </ul>
071 * </dd>
072 * <dt>DEL_NODE_ATTRIBUTE</dt>
073 * <dd>
074 * <ul>
075 * <li>%nodeId%</li>
076 * <li>%attributeId%</li>
077 * </ul>
078 * </dd>
079 * <dt>DEL_NODE</dt>
080 * <dd>
081 * <ul>
082 * <li>%nodeId%</li>
083 * </ul>
084 * </dd>
085 * <dt>ADD_EDGE</dt>
086 * <dd>
087 * <ul>
088 * <li>%edgeId%</li>
089 * <li>%source%</li>
090 * <li>%target%</li>
091 * <li>%directed%</li>
092 * </ul>
093 * </dd>
094 * <dt>ADD_EDGE_ATTRIBUTE</dt>
095 * <dd>
096 * <ul>
097 * <li>%edgeId%</li>
098 * <li>%attributeId%</li>
099 * <li>%value%</li>
100 * </ul>
101 * </dd>
102 * <dt>SET_EDGE_ATTRIBUTE</dt>
103 * <dd>
104 * <ul>
105 * <li>%edgeId%</li>
106 * <li>%attributeId%</li>
107 * <li>%value%</li>
108 * </ul>
109 * </dd>
110 * <dt>DEL_EDGE_ATTRIBUTE</dt>
111 * <dd>
112 * <ul>
113 * <li>%edgeId%</li>
114 * <li>%attributeId%</li>
115 * </ul>
116 * </dd>
117 * <dt>DEL_EDGE</dt>
118 * <dd>
119 * <ul>
120 * <li>%edgeId%</li>
121 * </ul>
122 * </dd>
123 * <dt>ADD_GRAPH_ATTRIBUTE</dt>
124 * <dd>
125 * <ul>
126 * <li>%attributeId%</li>
127 * <li>%value%</li>
128 * </ul>
129 * </dd>
130 * <dt>SET_GRAPH_ATTRIBUTE</dt>
131 * <dd>
132 * <ul>
133 * <li>%attributeId%</li>
134 * <li>%value%</li>
135 * </ul>
136 * </dd>
137 * <dt>DEL_GRAPH_ATTRIBUTE</dt>
138 * <dd>
139 * <ul>
140 * <li>%attributeId%</li>
141 * </ul>
142 * </dd>
143 * <dt>CLEAR</dt>
144 * <dd></dd>
145 * <dt>STEP_BEGINS</dt>
146 * <dd>
147 * <ul>
148 * <li>%step%</li>
149 * </ul>
150 * </dd>
151 * </dl>
152 */
153public class VerboseSink implements Sink {
154        public static final String DEFAULT_AN_FORMAT = "%prefix%[%sourceId%:%timeId%] add node \"%nodeId%\"%suffix%";
155        public static final String DEFAULT_CNA_FORMAT = "%prefix%[%sourceId%:%timeId%] set node \"%nodeId%\" +\"%attributeId%\"=%value%%suffix%";
156        public static final String DEFAULT_CNC_FORMAT = "%prefix%[%sourceId%:%timeId%] set node \"%nodeId%\" \"%attributeId%\"=%value%%suffix%";
157        public static final String DEFAULT_CNR_FORMAT = "%prefix%[%sourceId%:%timeId%] set node \"%nodeId%\" -\"%attributeId%\"%suffix%";
158        public static final String DEFAULT_DN_FORMAT = "%prefix%[%sourceId%:%timeId%] remove node \"%nodeId%\"%suffix%";
159
160        public static final String DEFAULT_AE_FORMAT = "%prefix%[%sourceId%:%timeId%] add edge \"%edgeId%\" : \"%source%\" -- \"%target%\"%suffix%";
161        public static final String DEFAULT_CEA_FORMAT = "%prefix%[%sourceId%:%timeId%] set edge \"%edgeId%\" +\"%attributeId%\"=%value%%suffix%";
162        public static final String DEFAULT_CEC_FORMAT = "%prefix%[%sourceId%:%timeId%] set edge \"%edgeId%\" \"%attributeId%\"=%value%%suffix%";
163        public static final String DEFAULT_CER_FORMAT = "%prefix%[%sourceId%:%timeId%] set edge \"%edgeId%\" -\"%attributeId%\"%suffix%";
164        public static final String DEFAULT_DE_FORMAT = "%prefix%[%sourceId%:%timeId%] remove edge \"%edgeId%\"%suffix%";
165
166        public static final String DEFAULT_CGA_FORMAT = "%prefix%[%sourceId%:%timeId%] set +\"%attributeId%\"=%value%%suffix%";
167        public static final String DEFAULT_CGC_FORMAT = "%prefix%[%sourceId%:%timeId%] set \"%attributeId%\"=%value%%suffix%";
168        public static final String DEFAULT_CGR_FORMAT = "%prefix%[%sourceId%:%timeId%] set -\"%attributeId%\"%suffix%";
169
170        public static final String DEFAULT_CL_FORMAT = "%prefix%[%sourceId%:%timeId%] clear%suffix%";
171        public static final String DEFAULT_ST_FORMAT = "%prefix%[%sourceId%:%timeId%] step %step% begins%suffix%";
172
173        /*
174         * Shortcut to use HashMap<String, Object>.
175         */
176        private static class Args extends HashMap<String, Object> {
177                private static final long serialVersionUID = 3064164898156692557L;
178        }
179
180        /**
181         * Enumeration defining type of events.
182         * 
183         */
184        public static enum EventType {
185                ADD_NODE, ADD_NODE_ATTRIBUTE, SET_NODE_ATTRIBUTE, DEL_NODE_ATTRIBUTE, DEL_NODE, ADD_EDGE, ADD_EDGE_ATTRIBUTE, SET_EDGE_ATTRIBUTE, DEL_EDGE_ATTRIBUTE, DEL_EDGE, ADD_GRAPH_ATTRIBUTE, SET_GRAPH_ATTRIBUTE, DEL_GRAPH_ATTRIBUTE, CLEAR, STEP_BEGINS
186        }
187
188        /**
189         * Flag used to indicate if the sink has to flush the output when writting a
190         * message.
191         */
192        protected boolean autoflush;
193        /**
194         * Stream used to write message.
195         */
196        protected final PrintStream out;
197        /**
198         * Format of messages associated with each event.
199         */
200        protected final EnumMap<EventType, String> formats;
201        /**
202         * Flag used to indicate if an event has to be written or note.
203         */
204        protected final EnumMap<EventType, Boolean> enable;
205        /*
206         * Used to avoid to create a lot of hashmap when passing event arguments.
207         */
208        private final Stack<Args> argsStack;
209
210        protected String prefix;
211
212        protected String suffix;
213
214        /**
215         * Create a new verbose sink using System.out.
216         */
217        public VerboseSink() {
218                this(System.out);
219        }
220
221        /**
222         * Create a new verbose sink.
223         * 
224         * @param out
225         *            stream used to output message
226         */
227        public VerboseSink(PrintStream out) {
228                this.out = out;
229                argsStack = new Stack<Args>();
230                enable = new EnumMap<EventType, Boolean>(EventType.class);
231                formats = new EnumMap<EventType, String>(EventType.class);
232
233                formats.put(EventType.ADD_NODE, DEFAULT_AN_FORMAT);
234                formats.put(EventType.ADD_NODE_ATTRIBUTE, DEFAULT_CNA_FORMAT);
235                formats.put(EventType.SET_NODE_ATTRIBUTE, DEFAULT_CNC_FORMAT);
236                formats.put(EventType.DEL_NODE_ATTRIBUTE, DEFAULT_CNR_FORMAT);
237                formats.put(EventType.DEL_NODE, DEFAULT_DN_FORMAT);
238
239                formats.put(EventType.ADD_EDGE, DEFAULT_AE_FORMAT);
240                formats.put(EventType.ADD_EDGE_ATTRIBUTE, DEFAULT_CEA_FORMAT);
241                formats.put(EventType.SET_EDGE_ATTRIBUTE, DEFAULT_CEC_FORMAT);
242                formats.put(EventType.DEL_EDGE_ATTRIBUTE, DEFAULT_CER_FORMAT);
243                formats.put(EventType.DEL_EDGE, DEFAULT_DE_FORMAT);
244
245                formats.put(EventType.ADD_GRAPH_ATTRIBUTE, DEFAULT_CGA_FORMAT);
246                formats.put(EventType.SET_GRAPH_ATTRIBUTE, DEFAULT_CGC_FORMAT);
247                formats.put(EventType.DEL_GRAPH_ATTRIBUTE, DEFAULT_CGR_FORMAT);
248
249                formats.put(EventType.CLEAR, DEFAULT_CL_FORMAT);
250                formats.put(EventType.STEP_BEGINS, DEFAULT_ST_FORMAT);
251
252                for (EventType t : EventType.values())
253                        enable.put(t, Boolean.TRUE);
254
255                suffix = "";
256                prefix = "";
257        }
258
259        /**
260         * Enable or disable autoflush.
261         * 
262         * @param on
263         *            true to enable autoflush
264         */
265        public void setAutoFlush(boolean on) {
266                this.autoflush = on;
267        }
268
269        /**
270         * Redefines message format of an event.
271         * 
272         * @param type
273         *            type of the event
274         * @param format
275         *            new format of the message attached with the event
276         */
277        public void setEventFormat(EventType type, String format) {
278                formats.put(type, format);
279        }
280
281        /**
282         * Enable or disable an event.
283         * 
284         * @param type
285         *            type of the event
286         * @param on
287         *            true to enable message for this event
288         */
289        public void setEventEnabled(EventType type, boolean on) {
290                enable.put(type, on);
291        }
292
293        /**
294         * Enable or disable all messages associated with attribute events.
295         * 
296         * @param on
297         *            true to enable events
298         */
299        public void setElementEventEnabled(boolean on) {
300                enable.put(EventType.ADD_EDGE_ATTRIBUTE, on);
301                enable.put(EventType.SET_EDGE_ATTRIBUTE, on);
302                enable.put(EventType.DEL_EDGE_ATTRIBUTE, on);
303                enable.put(EventType.ADD_NODE_ATTRIBUTE, on);
304                enable.put(EventType.SET_NODE_ATTRIBUTE, on);
305                enable.put(EventType.DEL_NODE_ATTRIBUTE, on);
306                enable.put(EventType.ADD_GRAPH_ATTRIBUTE, on);
307                enable.put(EventType.SET_GRAPH_ATTRIBUTE, on);
308                enable.put(EventType.DEL_GRAPH_ATTRIBUTE, on);
309        }
310
311        /**
312         * Enable or disable all messages associated with element events.
313         * 
314         * @param on
315         *            true to enable events
316         */
317        public void setAttributeEventEnabled(boolean on) {
318                enable.put(EventType.ADD_EDGE, on);
319                enable.put(EventType.DEL_EDGE, on);
320                enable.put(EventType.ADD_NODE, on);
321                enable.put(EventType.DEL_NODE, on);
322                enable.put(EventType.CLEAR, on);
323        }
324
325        /**
326         * Set prefix used in messages.
327         * 
328         * @param prefix
329         *            new prefix
330         */
331        public void setPrefix(String prefix) {
332                this.prefix = prefix;
333        }
334
335        /**
336         * Set suffix used in messages.
337         * 
338         * @param suffix
339         *            new suffix
340         */
341        public void setSuffix(String suffix) {
342                this.suffix = suffix;
343        }
344
345        private void print(EventType type, Args args) {
346                if (!enable.get(type))
347                        return;
348
349                String out = formats.get(type);
350
351                for (String k : args.keySet()) {
352                        Object o = args.get(k);
353                        out = out.replace(String.format("%%%s%%", k), o == null ? "null"
354                                        : o.toString());
355                }
356
357                this.out.print(out);
358                this.out.printf("\n");
359
360                if (autoflush)
361                        this.out.flush();
362
363                argsPnP(args);
364        }
365
366        private Args argsPnP(Args args) {
367                if (args == null) {
368                        if (argsStack.size() > 0)
369                                args = argsStack.pop();
370                        else
371                                args = new Args();
372
373                        args.put("prefix", prefix);
374                        args.put("suffix", suffix);
375
376                        return args;
377                } else {
378                        args.clear();
379                        argsStack.push(args);
380
381                        return null;
382                }
383        }
384
385        private String toStringValue(Object o) {
386                if (o == null)
387                        return "<null>";
388
389                if (o instanceof String)
390                        return "\"" + ((String) o).replace("\"", "\\\"");
391                else if (o.getClass().isArray()) {
392                        StringBuilder buffer = new StringBuilder();
393                        buffer.append("{");
394
395                        for (int i = 0; i < Array.getLength(o); i++) {
396                                if (i > 0)
397                                        buffer.append(", ");
398                                buffer.append(toStringValue(Array.get(o, i)));
399                        }
400
401                        buffer.append("}");
402                        return buffer.toString();
403                }
404
405                return o.toString();
406        }
407
408        /*
409         * (non-Javadoc)
410         * 
411         * @see
412         * org.graphstream.stream.AttributeSink#edgeAttributeAdded(java.lang.String,
413         * long, java.lang.String, java.lang.String, java.lang.Object)
414         */
415        public void edgeAttributeAdded(String sourceId, long timeId, String edgeId,
416                        String attribute, Object value) {
417                Args args = argsPnP(null);
418
419                args.put("sourceId", sourceId);
420                args.put("timeId", timeId);
421                args.put("edgeId", edgeId);
422                args.put("attributeId", attribute);
423                args.put("value", toStringValue(value));
424
425                print(EventType.ADD_EDGE_ATTRIBUTE, args);
426        }
427
428        /*
429         * (non-Javadoc)
430         * 
431         * @see
432         * org.graphstream.stream.AttributeSink#edgeAttributeChanged(java.lang.String
433         * , long, java.lang.String, java.lang.String, java.lang.Object,
434         * java.lang.Object)
435         */
436        public void edgeAttributeChanged(String sourceId, long timeId,
437                        String edgeId, String attribute, Object oldValue, Object newValue) {
438                Args args = argsPnP(null);
439
440                args.put("sourceId", sourceId);
441                args.put("timeId", timeId);
442                args.put("edgeId", edgeId);
443                args.put("attributeId", attribute);
444                args.put("value", toStringValue(newValue));
445
446                print(EventType.SET_EDGE_ATTRIBUTE, args);
447        }
448
449        /*
450         * (non-Javadoc)
451         * 
452         * @see
453         * org.graphstream.stream.AttributeSink#edgeAttributeRemoved(java.lang.String
454         * , long, java.lang.String, java.lang.String)
455         */
456        public void edgeAttributeRemoved(String sourceId, long timeId,
457                        String edgeId, String attribute) {
458                Args args = argsPnP(null);
459
460                args.put("sourceId", sourceId);
461                args.put("timeId", timeId);
462                args.put("edgeId", edgeId);
463                args.put("attributeId", attribute);
464
465                print(EventType.DEL_EDGE_ATTRIBUTE, args);
466        }
467
468        /*
469         * (non-Javadoc)
470         * 
471         * @see
472         * org.graphstream.stream.AttributeSink#graphAttributeAdded(java.lang.String
473         * , long, java.lang.String, java.lang.Object)
474         */
475        public void graphAttributeAdded(String sourceId, long timeId,
476                        String attribute, Object value) {
477                Args args = argsPnP(null);
478
479                args.put("sourceId", sourceId);
480                args.put("timeId", timeId);
481                args.put("attributeId", attribute);
482                args.put("value", toStringValue(value));
483
484                print(EventType.ADD_GRAPH_ATTRIBUTE, args);
485        }
486
487        /*
488         * (non-Javadoc)
489         * 
490         * @see
491         * org.graphstream.stream.AttributeSink#graphAttributeChanged(java.lang.
492         * String, long, java.lang.String, java.lang.Object, java.lang.Object)
493         */
494        public void graphAttributeChanged(String sourceId, long timeId,
495                        String attribute, Object oldValue, Object newValue) {
496                Args args = argsPnP(null);
497
498                args.put("sourceId", sourceId);
499                args.put("timeId", timeId);
500                args.put("attributeId", attribute);
501                args.put("value", toStringValue(newValue));
502
503                print(EventType.SET_GRAPH_ATTRIBUTE, args);
504        }
505
506        /*
507         * (non-Javadoc)
508         * 
509         * @see
510         * org.graphstream.stream.AttributeSink#graphAttributeRemoved(java.lang.
511         * String, long, java.lang.String)
512         */
513        public void graphAttributeRemoved(String sourceId, long timeId,
514                        String attribute) {
515                Args args = argsPnP(null);
516
517                args.put("sourceId", sourceId);
518                args.put("timeId", timeId);
519                args.put("attributeId", attribute);
520
521                print(EventType.DEL_GRAPH_ATTRIBUTE, args);
522        }
523
524        /*
525         * (non-Javadoc)
526         * 
527         * @see
528         * org.graphstream.stream.AttributeSink#nodeAttributeAdded(java.lang.String,
529         * long, java.lang.String, java.lang.String, java.lang.Object)
530         */
531        public void nodeAttributeAdded(String sourceId, long timeId, String nodeId,
532                        String attribute, Object value) {
533                Args args = argsPnP(null);
534
535                args.put("sourceId", sourceId);
536                args.put("timeId", timeId);
537                args.put("nodeId", nodeId);
538                args.put("attributeId", attribute);
539                args.put("value", toStringValue(value));
540
541                print(EventType.ADD_NODE_ATTRIBUTE, args);
542        }
543
544        /*
545         * (non-Javadoc)
546         * 
547         * @see
548         * org.graphstream.stream.AttributeSink#nodeAttributeChanged(java.lang.String
549         * , long, java.lang.String, java.lang.String, java.lang.Object,
550         * java.lang.Object)
551         */
552        public void nodeAttributeChanged(String sourceId, long timeId,
553                        String nodeId, String attribute, Object oldValue, Object newValue) {
554                Args args = argsPnP(null);
555
556                args.put("sourceId", sourceId);
557                args.put("timeId", timeId);
558                args.put("nodeId", nodeId);
559                args.put("attributeId", attribute);
560                args.put("value", toStringValue(newValue));
561
562                print(EventType.SET_NODE_ATTRIBUTE, args);
563        }
564
565        /*
566         * (non-Javadoc)
567         * 
568         * @see
569         * org.graphstream.stream.AttributeSink#nodeAttributeRemoved(java.lang.String
570         * , long, java.lang.String, java.lang.String)
571         */
572        public void nodeAttributeRemoved(String sourceId, long timeId,
573                        String nodeId, String attribute) {
574                Args args = argsPnP(null);
575
576                args.put("sourceId", sourceId);
577                args.put("timeId", timeId);
578                args.put("nodeId", nodeId);
579                args.put("attributeId", attribute);
580
581                print(EventType.DEL_NODE_ATTRIBUTE, args);
582        }
583
584        /*
585         * (non-Javadoc)
586         * 
587         * @see org.graphstream.stream.ElementSink#edgeAdded(java.lang.String, long,
588         * java.lang.String, java.lang.String, java.lang.String, boolean)
589         */
590        public void edgeAdded(String sourceId, long timeId, String edgeId,
591                        String fromNodeId, String toNodeId, boolean directed) {
592                Args args = argsPnP(null);
593
594                args.put("sourceId", sourceId);
595                args.put("timeId", timeId);
596                args.put("edgeId", edgeId);
597                args.put("source", fromNodeId);
598                args.put("target", toNodeId);
599
600                print(EventType.ADD_EDGE, args);
601        }
602
603        /*
604         * (non-Javadoc)
605         * 
606         * @see org.graphstream.stream.ElementSink#edgeRemoved(java.lang.String,
607         * long, java.lang.String)
608         */
609        public void edgeRemoved(String sourceId, long timeId, String edgeId) {
610                Args args = argsPnP(null);
611
612                args.put("sourceId", sourceId);
613                args.put("timeId", timeId);
614                args.put("edgeId", edgeId);
615
616                print(EventType.DEL_EDGE, args);
617        }
618
619        /*
620         * (non-Javadoc)
621         * 
622         * @see org.graphstream.stream.ElementSink#graphCleared(java.lang.String,
623         * long)
624         */
625        public void graphCleared(String sourceId, long timeId) {
626                Args args = argsPnP(null);
627
628                args.put("sourceId", sourceId);
629                args.put("timeId", timeId);
630
631                print(EventType.CLEAR, args);
632        }
633
634        /*
635         * (non-Javadoc)
636         * 
637         * @see org.graphstream.stream.ElementSink#nodeAdded(java.lang.String, long,
638         * java.lang.String)
639         */
640        public void nodeAdded(String sourceId, long timeId, String nodeId) {
641                Args args = argsPnP(null);
642
643                args.put("sourceId", sourceId);
644                args.put("timeId", timeId);
645                args.put("nodeId", nodeId);
646
647                print(EventType.ADD_NODE, args);
648        }
649
650        /*
651         * (non-Javadoc)
652         * 
653         * @see org.graphstream.stream.ElementSink#nodeRemoved(java.lang.String,
654         * long, java.lang.String)
655         */
656        public void nodeRemoved(String sourceId, long timeId, String nodeId) {
657                Args args = argsPnP(null);
658
659                args.put("sourceId", sourceId);
660                args.put("timeId", timeId);
661                args.put("nodeId", nodeId);
662
663                print(EventType.DEL_NODE, args);
664        }
665
666        /*
667         * (non-Javadoc)
668         * 
669         * @see org.graphstream.stream.ElementSink#stepBegins(java.lang.String,
670         * long, double)
671         */
672        public void stepBegins(String sourceId, long timeId, double step) {
673                Args args = argsPnP(null);
674
675                args.put("sourceId", sourceId);
676                args.put("timeId", timeId);
677                args.put("step", step);
678
679                print(EventType.STEP_BEGINS, args);
680        }
681}