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;
033
034import java.util.ArrayList;
035import java.util.LinkedList;
036import java.util.List;
037
038import org.graphstream.graph.implementations.AbstractElement.AttributeChangeEvent;
039import org.graphstream.stream.sync.SourceTime;
040
041/**
042 * Base implementation of an input that provide basic sink handling.
043 * 
044 * <p>
045 * This implementation can register a set of graph sinks (or separate sets of
046 * attributes or elements sinks) and provides protected methods to easily
047 * broadcast events to all the sinks (beginning with "send").
048 * </p>
049 * 
050 * <p>
051 * Each time you want to produce an event toward all registered sinks, you call
052 * one of the "send*" methods with correct parameters. The parameters of the
053 * "send*" methods maps to the usual GraphStream events.
054 * </p>
055 * 
056 * <p>
057 * This class is "reentrant". This means that if a send*() method is called
058 * during the execution of another or the same send*() method, the event is
059 * deferred until the first send*() method is finished. This avoid recursive
060 * loops if a sink modifies the input during event handling.
061 * </p>
062 */
063public abstract class SourceBase implements Source {
064        // Attribute
065
066        public enum ElementType {
067                NODE, EDGE, GRAPH
068        };
069
070        /**
071         * Set of graph attributes sinks.
072         */
073        protected ArrayList<AttributeSink> attrSinks = new ArrayList<AttributeSink>();
074
075        /**
076         * Set of graph elements sinks.
077         */
078        protected ArrayList<ElementSink> eltsSinks = new ArrayList<ElementSink>();
079
080        /**
081         * A queue that allow the management of events (nodes/edge
082         * add/delete/change) in the right order.
083         */
084        protected LinkedList<GraphEvent> eventQueue = new LinkedList<GraphEvent>();
085
086        /**
087         * A boolean that indicates whether or not an Sink event is being sent
088         * during another one.
089         */
090        protected boolean eventProcessing = false;
091
092        /**
093         * Id of this source.
094         */
095        protected String sourceId;
096        
097        /**
098         * Time of this source.
099         */
100        protected SourceTime sourceTime;
101
102        // Construction
103
104        protected SourceBase() {
105                this(String.format("sourceOnThread#%d_%d", Thread.currentThread()
106                                .getId(), System.currentTimeMillis()
107                                + ((int) (Math.random() * 1000))));
108        }
109
110        protected SourceBase(String sourceId) {
111                this.sourceId = sourceId;
112                this.sourceTime = new SourceTime(sourceId);
113        }
114
115        // Access
116
117        public Iterable<AttributeSink> attributeSinks() {
118                return attrSinks;
119        }
120
121        public Iterable<ElementSink> elementSinks() {
122                return eltsSinks;
123        }
124
125        // Command
126
127        public void addSink(Sink sink) {
128                addAttributeSink(sink);
129                addElementSink(sink);
130        }
131
132        public void addAttributeSink(AttributeSink sink) {
133                if (!eventProcessing) {
134                        eventProcessing = true;
135                        manageEvents();
136
137                        attrSinks.add(sink);
138
139                        manageEvents();
140                        eventProcessing = false;
141                } else {
142                        eventQueue.add(new AddToListEvent<AttributeSink>(attrSinks, sink));
143                }
144        }
145
146        public void addElementSink(ElementSink sink) {
147                if (!eventProcessing) {
148                        eventProcessing = true;
149                        manageEvents();
150
151                        eltsSinks.add(sink);
152
153                        manageEvents();
154                        eventProcessing = false;
155                } else {
156                        eventQueue.add(new AddToListEvent<ElementSink>(eltsSinks, sink));
157                }
158        }
159
160        public void clearSinks() {
161                clearElementSinks();
162                clearAttributeSinks();
163        }
164
165        public void clearElementSinks() {
166                if (!eventProcessing) {
167                        eventProcessing = true;
168                        manageEvents();
169
170                        eltsSinks.clear();
171
172                        manageEvents();
173                        eventProcessing = false;
174                } else {
175                        eventQueue.add(new ClearListEvent<ElementSink>(eltsSinks));
176                }
177        }
178
179        public void clearAttributeSinks() {
180                if (!eventProcessing) {
181                        eventProcessing = true;
182                        manageEvents();
183
184                        attrSinks.clear();
185
186                        manageEvents();
187                        eventProcessing = false;
188                } else {
189                        eventQueue.add(new ClearListEvent<AttributeSink>(attrSinks));
190                }
191        }
192
193        public void removeSink(Sink sink) {
194                removeAttributeSink(sink);
195                removeElementSink(sink);
196        }
197
198        public void removeAttributeSink(AttributeSink sink) {
199                if (!eventProcessing) {
200                        eventProcessing = true;
201                        manageEvents();
202
203                        attrSinks.remove(sink);
204
205                        manageEvents();
206                        eventProcessing = false;
207                } else {
208                        eventQueue.add(new RemoveFromListEvent<AttributeSink>(attrSinks,
209                                        sink));
210                }
211        }
212
213        public void removeElementSink(ElementSink sink) {
214                if (!eventProcessing) {
215                        eventProcessing = true;
216                        manageEvents();
217
218                        eltsSinks.remove(sink);
219
220                        manageEvents();
221                        eventProcessing = false;
222                } else {
223                        eventQueue
224                                        .add(new RemoveFromListEvent<ElementSink>(eltsSinks, sink));
225                }
226        }
227
228        /**
229         * Send a "graph cleared" event to all element sinks.
230         * 
231         * @param sourceId
232         *            The source identifier.
233         */
234        public void sendGraphCleared(String sourceId) {
235                sendGraphCleared(sourceId, sourceTime.newEvent());
236        }
237
238        /**
239         * Send a "graph cleared" event to all element sinks.
240         * 
241         * @param sourceId
242         *            The source identifier.
243         * @param timeId
244         */
245        public void sendGraphCleared(String sourceId, long timeId) {
246                if (!eventProcessing) {
247                        eventProcessing = true;
248                        manageEvents();
249
250                        for (int i = 0; i < eltsSinks.size(); i++)
251                                eltsSinks.get(i).graphCleared(sourceId, timeId);
252
253                        manageEvents();
254                        eventProcessing = false;
255                } else {
256                        eventQueue.add(new BeforeGraphClearEvent(sourceId, timeId));
257                }
258        }
259
260        /**
261         * Send a "step begins" event to all element sinks.
262         * 
263         * @param sourceId
264         *            The graph identifier.
265         * @param step
266         *            The step time stamp.
267         */
268        public void sendStepBegins(String sourceId, double step) {
269                sendStepBegins(sourceId, sourceTime.newEvent(), step);
270        }
271
272        /**
273         * Send a "step begins" event to all element sinks.
274         * 
275         * @param sourceId
276         *            The graph identifier.
277         * @param timeId
278         * @param step
279         *            The step time stamp.
280         */
281        public void sendStepBegins(String sourceId, long timeId, double step) {
282                if (!eventProcessing) {
283                        eventProcessing = true;
284                        manageEvents();
285
286                        for (int i = 0; i < eltsSinks.size(); i++)
287                                eltsSinks.get(i).stepBegins(sourceId, timeId, step);
288
289                        manageEvents();
290                        eventProcessing = false;
291                } else {
292                        eventQueue.add(new StepBeginsEvent(sourceId, timeId, step));
293                }
294        }
295
296        /**
297         * Send a "node added" event to all element sinks.
298         * 
299         * @param sourceId
300         *            The source identifier.
301         * @param nodeId
302         *            The node identifier.
303         */
304        public void sendNodeAdded(String sourceId, String nodeId) {
305                sendNodeAdded(sourceId, sourceTime.newEvent(), nodeId);
306        }
307
308        /**
309         * Send a "node added" event to all element sinks.
310         * 
311         * @param sourceId
312         *            The source identifier.
313         * @param timeId
314         * @param nodeId
315         *            The node identifier.
316         */
317        public void sendNodeAdded(String sourceId, long timeId, String nodeId) {
318                if (!eventProcessing) {
319                        eventProcessing = true;
320                        manageEvents();
321
322                        for (int i = 0; i < eltsSinks.size(); i++)
323                                eltsSinks.get(i).nodeAdded(sourceId, timeId, nodeId);
324
325                        manageEvents();
326                        eventProcessing = false;
327                } else {
328                        eventQueue.add(new AfterNodeAddEvent(sourceId, timeId, nodeId));
329                }
330        }
331
332        /**
333         * Send a "node removed" event to all element sinks.
334         * 
335         * @param sourceId
336         *            The graph identifier.
337         * @param nodeId
338         *            The node identifier.
339         */
340        public void sendNodeRemoved(String sourceId, String nodeId) {
341                sendNodeRemoved(sourceId, sourceTime.newEvent(), nodeId);
342        }
343
344        /**
345         * Send a "node removed" event to all element sinks.
346         * 
347         * @param sourceId
348         *            The graph identifier.
349         * @param timeId
350         * @param nodeId
351         *            The node identifier.
352         */
353        public void sendNodeRemoved(String sourceId, long timeId, String nodeId) {
354                if (!eventProcessing) {
355                        eventProcessing = true;
356                        manageEvents();
357
358                        for (int i = 0; i < eltsSinks.size(); i++)
359                                eltsSinks.get(i).nodeRemoved(sourceId, timeId, nodeId);
360
361                        manageEvents();
362                        eventProcessing = false;
363                } else {
364                        eventQueue.add(new BeforeNodeRemoveEvent(sourceId, timeId, nodeId));
365                }
366        }
367
368        /**
369         * Send an "edge added" event to all element sinks.
370         * 
371         * @param sourceId
372         *            The source identifier.
373         * @param edgeId
374         *            The edge identifier.
375         * @param fromNodeId
376         *            The edge start node.
377         * @param toNodeId
378         *            The edge end node.
379         * @param directed
380         *            Is the edge directed?.
381         */
382        public void sendEdgeAdded(String sourceId, String edgeId,
383                        String fromNodeId, String toNodeId, boolean directed) {
384                sendEdgeAdded(sourceId, sourceTime.newEvent(), edgeId, fromNodeId,
385                                toNodeId, directed);
386        }
387
388        /**
389         * Send an "edge added" event to all element sinks.
390         * 
391         * @param sourceId
392         *            The source identifier.
393         * @param timeId
394         * @param edgeId
395         *            The edge identifier.
396         * @param fromNodeId
397         *            The edge start node.
398         * @param toNodeId
399         *            The edge end node.
400         * @param directed
401         *            Is the edge directed?.
402         */
403        public void sendEdgeAdded(String sourceId, long timeId, String edgeId,
404                        String fromNodeId, String toNodeId, boolean directed) {
405                if (!eventProcessing) {
406                        eventProcessing = true;
407                        manageEvents();
408
409                        for (int i = 0; i < eltsSinks.size(); i++)
410                                eltsSinks.get(i).edgeAdded(sourceId, timeId, edgeId,
411                                                fromNodeId, toNodeId, directed);
412
413                        manageEvents();
414                        eventProcessing = false;
415                } else {
416                        eventQueue.add(new AfterEdgeAddEvent(sourceId, timeId, edgeId,
417                                        fromNodeId, toNodeId, directed));
418                }
419        }
420
421        /**
422         * Send a "edge removed" event to all element sinks.
423         * 
424         * @param sourceId
425         *            The source identifier.
426         * @param edgeId
427         *            The edge identifier.
428         */
429        public void sendEdgeRemoved(String sourceId, String edgeId) {
430                sendEdgeRemoved(sourceId, sourceTime.newEvent(), edgeId);
431        }
432
433        /**
434         * Send a "edge removed" event to all element sinks.
435         * 
436         * @param sourceId
437         *            The source identifier.
438         * @param timeId
439         * @param edgeId
440         *            The edge identifier.
441         */
442        public void sendEdgeRemoved(String sourceId, long timeId, String edgeId) {
443                if (!eventProcessing) {
444                        eventProcessing = true;
445                        manageEvents();
446
447                        for (int i = 0; i < eltsSinks.size(); i++)
448                                eltsSinks.get(i).edgeRemoved(sourceId, timeId, edgeId);
449
450                        manageEvents();
451                        eventProcessing = false;
452                } else {
453                        eventQueue.add(new BeforeEdgeRemoveEvent(sourceId, timeId, edgeId));
454                }
455        }
456
457        /**
458         * Send a "edge attribute added" event to all attribute sinks.
459         * 
460         * @param sourceId
461         *            The source identifier.
462         * @param edgeId
463         *            The edge identifier.
464         * @param attribute
465         *            The attribute name.
466         * @param value
467         *            The attribute value.
468         */
469        public void sendEdgeAttributeAdded(String sourceId, String edgeId,
470                        String attribute, Object value) {
471                sendAttributeChangedEvent(sourceId, edgeId, ElementType.EDGE,
472                                attribute, AttributeChangeEvent.ADD, null, value);
473        }
474
475        /**
476         * Send a "edge attribute added" event to all attribute sinks.
477         * 
478         * @param sourceId
479         *            The source identifier.
480         * @param timeId
481         * @param edgeId
482         *            The edge identifier.
483         * @param attribute
484         *            The attribute name.
485         * @param value
486         *            The attribute value.
487         */
488        public void sendEdgeAttributeAdded(String sourceId, long timeId,
489                        String edgeId, String attribute, Object value) {
490                sendAttributeChangedEvent(sourceId, timeId, edgeId, ElementType.EDGE,
491                                attribute, AttributeChangeEvent.ADD, null, value);
492        }
493
494        /**
495         * Send a "edge attribute changed" event to all attribute sinks.
496         * 
497         * @param sourceId
498         *            The source identifier.
499         * @param edgeId
500         *            The edge identifier.
501         * @param attribute
502         *            The attribute name.
503         * @param oldValue
504         *            The old attribute value.
505         * @param newValue
506         *            The new attribute value.
507         */
508        public void sendEdgeAttributeChanged(String sourceId, String edgeId,
509                        String attribute, Object oldValue, Object newValue) {
510                sendAttributeChangedEvent(sourceId, edgeId, ElementType.EDGE,
511                                attribute, AttributeChangeEvent.CHANGE, oldValue, newValue);
512        }
513
514        /**
515         * Send a "edge attribute changed" event to all attribute sinks.
516         * 
517         * @param sourceId
518         *            The source identifier.
519         * @param timeId
520         * @param edgeId
521         *            The edge identifier.
522         * @param attribute
523         *            The attribute name.
524         * @param oldValue
525         *            The old attribute value.
526         * @param newValue
527         *            The new attribute value.
528         */
529        public void sendEdgeAttributeChanged(String sourceId, long timeId,
530                        String edgeId, String attribute, Object oldValue, Object newValue) {
531                sendAttributeChangedEvent(sourceId, timeId, edgeId, ElementType.EDGE,
532                                attribute, AttributeChangeEvent.CHANGE, oldValue, newValue);
533        }
534
535        /**
536         * Send a "edge attribute removed" event to all attribute sinks.
537         * 
538         * @param sourceId
539         *            The source identifier.
540         * @param edgeId
541         *            The edge identifier.
542         * @param attribute
543         *            The attribute name.
544         */
545        public void sendEdgeAttributeRemoved(String sourceId, String edgeId,
546                        String attribute) {
547                sendAttributeChangedEvent(sourceId, edgeId, ElementType.EDGE,
548                                attribute, AttributeChangeEvent.REMOVE, null, null);
549        }
550
551        /**
552         * Send a "edge attribute removed" event to all attribute sinks.
553         * 
554         * @param sourceId
555         *            The source identifier.
556         * @param timeId
557         * @param edgeId
558         *            The edge identifier.
559         * @param attribute
560         *            The attribute name.
561         */
562        public void sendEdgeAttributeRemoved(String sourceId, long timeId,
563                        String edgeId, String attribute) {
564                sendAttributeChangedEvent(sourceId, timeId, edgeId, ElementType.EDGE,
565                                attribute, AttributeChangeEvent.REMOVE, null, null);
566        }
567
568        /**
569         * Send a "graph attribute added" event to all attribute sinks.
570         * 
571         * @param sourceId
572         *            The source identifier.
573         * @param attribute
574         *            The attribute name.
575         * @param value
576         *            The attribute value.
577         */
578        public void sendGraphAttributeAdded(String sourceId, String attribute,
579                        Object value) {
580                sendAttributeChangedEvent(sourceId, null, ElementType.GRAPH, attribute,
581                                AttributeChangeEvent.ADD, null, value);
582        }
583
584        /**
585         * Send a "graph attribute added" event to all attribute sinks.
586         * 
587         * @param sourceId
588         *            The source identifier.
589         * @param timeId
590         * @param attribute
591         *            The attribute name.
592         * @param value
593         *            The attribute value.
594         */
595        public void sendGraphAttributeAdded(String sourceId, long timeId,
596                        String attribute, Object value) {
597                sendAttributeChangedEvent(sourceId, timeId, null, ElementType.GRAPH,
598                                attribute, AttributeChangeEvent.ADD, null, value);
599        }
600
601        /**
602         * Send a "graph attribute changed" event to all attribute sinks.
603         * 
604         * @param sourceId
605         *            The source identifier.
606         * @param attribute
607         *            The attribute name.
608         * @param oldValue
609         *            The attribute old value.
610         * @param newValue
611         *            The attribute new value.
612         */
613        public void sendGraphAttributeChanged(String sourceId, String attribute,
614                        Object oldValue, Object newValue) {
615                sendAttributeChangedEvent(sourceId, null, ElementType.GRAPH, attribute,
616                                AttributeChangeEvent.CHANGE, oldValue, newValue);
617        }
618
619        /**
620         * Send a "graph attribute changed" event to all attribute sinks.
621         * 
622         * @param sourceId
623         *            The source identifier.
624         * @param timeId
625         * @param attribute
626         *            The attribute name.
627         * @param oldValue
628         *            The attribute old value.
629         * @param newValue
630         *            The attribute new value.
631         */
632        public void sendGraphAttributeChanged(String sourceId, long timeId,
633                        String attribute, Object oldValue, Object newValue) {
634                sendAttributeChangedEvent(sourceId, timeId, null, ElementType.GRAPH,
635                                attribute, AttributeChangeEvent.CHANGE, oldValue, newValue);
636        }
637
638        /**
639         * Send a "graph attribute removed" event to all attribute sinks.
640         * 
641         * @param sourceId
642         *            The source identifier.
643         * @param attribute
644         *            The attribute name.
645         */
646        public void sendGraphAttributeRemoved(String sourceId, String attribute) {
647                sendAttributeChangedEvent(sourceId, null, ElementType.GRAPH, attribute,
648                                AttributeChangeEvent.REMOVE, null, null);
649        }
650
651        /**
652         * Send a "graph attribute removed" event to all attribute sinks.
653         * 
654         * @param sourceId
655         *            The source identifier.
656         * @param timeId
657         * @param attribute
658         *            The attribute name.
659         */
660        public void sendGraphAttributeRemoved(String sourceId, long timeId,
661                        String attribute) {
662                sendAttributeChangedEvent(sourceId, timeId, null, ElementType.GRAPH,
663                                attribute, AttributeChangeEvent.REMOVE, null, null);
664        }
665
666        /**
667         * Send a "node attribute added" event to all attribute sinks.
668         * 
669         * @param sourceId
670         *            The source identifier.
671         * @param nodeId
672         *            The node identifier.
673         * @param attribute
674         *            The attribute name.
675         * @param value
676         *            The attribute value.
677         */
678        public void sendNodeAttributeAdded(String sourceId, String nodeId,
679                        String attribute, Object value) {
680                sendAttributeChangedEvent(sourceId, nodeId, ElementType.NODE,
681                                attribute, AttributeChangeEvent.ADD, null, value);
682        }
683
684        /**
685         * Send a "node attribute added" event to all attribute sinks.
686         * 
687         * @param sourceId
688         *            The source identifier.
689         * @param timeId
690         * @param nodeId
691         *            The node identifier.
692         * @param attribute
693         *            The attribute name.
694         * @param value
695         *            The attribute value.
696         */
697        public void sendNodeAttributeAdded(String sourceId, long timeId,
698                        String nodeId, String attribute, Object value) {
699                sendAttributeChangedEvent(sourceId, timeId, nodeId, ElementType.NODE,
700                                attribute, AttributeChangeEvent.ADD, null, value);
701        }
702
703        /**
704         * Send a "node attribute changed" event to all attribute sinks.
705         * 
706         * @param sourceId
707         *            The source identifier.
708         * @param nodeId
709         *            The node identifier.
710         * @param attribute
711         *            The attribute name.
712         * @param oldValue
713         *            The attribute old value.
714         * @param newValue
715         *            The attribute new value.
716         */
717        public void sendNodeAttributeChanged(String sourceId, String nodeId,
718                        String attribute, Object oldValue, Object newValue) {
719                sendAttributeChangedEvent(sourceId, nodeId, ElementType.NODE,
720                                attribute, AttributeChangeEvent.CHANGE, oldValue, newValue);
721        }
722
723        /**
724         * Send a "node attribute changed" event to all attribute sinks.
725         * 
726         * @param sourceId
727         *            The source identifier.
728         * @param timeId
729         * @param nodeId
730         *            The node identifier.
731         * @param attribute
732         *            The attribute name.
733         * @param oldValue
734         *            The attribute old value.
735         * @param newValue
736         *            The attribute new value.
737         */
738        public void sendNodeAttributeChanged(String sourceId, long timeId,
739                        String nodeId, String attribute, Object oldValue, Object newValue) {
740                sendAttributeChangedEvent(sourceId, timeId, nodeId, ElementType.NODE,
741                                attribute, AttributeChangeEvent.CHANGE, oldValue, newValue);
742        }
743
744        /**
745         * Send a "node attribute removed" event to all attribute sinks.
746         * 
747         * @param sourceId
748         *            The source identifier.
749         * @param nodeId
750         *            The node identifier.
751         * @param attribute
752         *            The attribute name.
753         */
754        public void sendNodeAttributeRemoved(String sourceId, String nodeId,
755                        String attribute) {
756                sendAttributeChangedEvent(sourceId, nodeId, ElementType.NODE,
757                                attribute, AttributeChangeEvent.REMOVE, null, null);
758        }
759
760        /**
761         * Send a "node attribute removed" event to all attribute sinks.
762         * 
763         * @param sourceId
764         *            The source identifier.
765         * @param timeId
766         * @param nodeId
767         *            The node identifier.
768         * @param attribute
769         *            The attribute name.
770         */
771        public void sendNodeAttributeRemoved(String sourceId, long timeId,
772                        String nodeId, String attribute) {
773                sendAttributeChangedEvent(sourceId, timeId, nodeId, ElementType.NODE,
774                                attribute, AttributeChangeEvent.REMOVE, null, null);
775        }
776
777        /**
778         * Send a add/change/remove attribute event on an element. This method is a
779         * generic way of notifying of an attribute change and is equivalent to
780         * individual send*Attribute*() methods.
781         * 
782         * @param sourceId
783         *            The source identifier.
784         * @param eltId
785         *            The changed element identifier.
786         * @param eltType
787         *            The changed element type.
788         * @param attribute
789         *            The changed attribute.
790         * @param event
791         *            The add/change/remove action.
792         * @param oldValue
793         *            The old attribute value (null if the attribute is removed or
794         *            added).
795         * @param newValue
796         *            The new attribute value (null if removed).
797         */
798        public void sendAttributeChangedEvent(String sourceId, String eltId,
799                        ElementType eltType, String attribute, AttributeChangeEvent event,
800                        Object oldValue, Object newValue) {
801                sendAttributeChangedEvent(sourceId, sourceTime.newEvent(), eltId,
802                                eltType, attribute, event, oldValue, newValue);
803        }
804
805        public void sendAttributeChangedEvent(String sourceId, long timeId,
806                        String eltId, ElementType eltType, String attribute,
807                        AttributeChangeEvent event, Object oldValue, Object newValue) {
808                if (!eventProcessing) {
809                        eventProcessing = true;
810                        manageEvents();
811
812                        if (event == AttributeChangeEvent.ADD) {
813                                if (eltType == ElementType.NODE) {
814                                        for (int i = 0; i < attrSinks.size(); i++)
815                                                attrSinks.get(i).nodeAttributeAdded(sourceId, timeId,
816                                                                eltId, attribute, newValue);
817                                } else if (eltType == ElementType.EDGE) {
818                                        for (int i = 0; i < attrSinks.size(); i++)
819                                                attrSinks.get(i).edgeAttributeAdded(sourceId, timeId,
820                                                                eltId, attribute, newValue);
821                                } else {
822                                        for (int i = 0; i < attrSinks.size(); i++)
823                                                attrSinks.get(i).graphAttributeAdded(sourceId, timeId,
824                                                                attribute, newValue);
825                                }
826                        } else if (event == AttributeChangeEvent.REMOVE) {
827                                if (eltType == ElementType.NODE) {
828                                        for (int i = 0; i < attrSinks.size(); i++)
829                                                attrSinks.get(i).nodeAttributeRemoved(sourceId, timeId,
830                                                                eltId, attribute);
831                                } else if (eltType == ElementType.EDGE) {
832                                        for (int i = 0; i < attrSinks.size(); i++)
833                                                attrSinks.get(i).edgeAttributeRemoved(sourceId, timeId,
834                                                                eltId, attribute);
835                                } else {
836                                        for (int i = 0; i < attrSinks.size(); i++)
837                                                attrSinks.get(i).graphAttributeRemoved(sourceId,
838                                                                timeId, attribute);
839                                }
840                        } else {
841                                if (eltType == ElementType.NODE) {
842                                        for (int i = 0; i < attrSinks.size(); i++)
843                                                attrSinks.get(i).nodeAttributeChanged(sourceId, timeId,
844                                                                eltId, attribute, oldValue, newValue);
845                                } else if (eltType == ElementType.EDGE) {
846                                        for (int i = 0; i < attrSinks.size(); i++)
847                                                attrSinks.get(i).edgeAttributeChanged(sourceId, timeId,
848                                                                eltId, attribute, oldValue, newValue);
849                                } else {
850                                        for (int i = 0; i < attrSinks.size(); i++)
851                                                attrSinks.get(i).graphAttributeChanged(sourceId,
852                                                                timeId, attribute, oldValue, newValue);
853                                }
854                        }
855
856                        manageEvents();
857                        eventProcessing = false;
858                } else {
859                        eventQueue.add(new AttributeChangedEvent(sourceId, timeId, eltId,
860                                        eltType, attribute, event, oldValue, newValue));
861                }
862        }
863
864        // Deferred event management
865
866        /**
867         * If in "event processing mode", ensure all pending events are processed.
868         */
869        protected void manageEvents() {
870                if (eventProcessing) {
871                        while (!eventQueue.isEmpty())
872                                eventQueue.remove().trigger();
873                }
874        }
875
876        // Events Management
877
878        /**
879         * Interface that provide general purpose classification for evens involved
880         * in graph modifications
881         */
882        abstract class GraphEvent {
883                String sourceId;
884                long timeId;
885
886                GraphEvent(String sourceId, long timeId) {
887                        this.sourceId = sourceId;
888                        this.timeId = timeId;
889                }
890
891                abstract void trigger();
892        }
893
894        class AfterEdgeAddEvent extends GraphEvent {
895                String edgeId;
896                String fromNodeId;
897                String toNodeId;
898                boolean directed;
899
900                AfterEdgeAddEvent(String sourceId, long timeId, String edgeId,
901                                String fromNodeId, String toNodeId, boolean directed) {
902                        super(sourceId, timeId);
903                        this.edgeId = edgeId;
904                        this.fromNodeId = fromNodeId;
905                        this.toNodeId = toNodeId;
906                        this.directed = directed;
907                }
908
909                void trigger() {
910                        for (int i = 0; i < eltsSinks.size(); i++)
911                                eltsSinks.get(i).edgeAdded(sourceId, timeId, edgeId,
912                                                fromNodeId, toNodeId, directed);
913                }
914        }
915
916        class BeforeEdgeRemoveEvent extends GraphEvent {
917                String edgeId;
918
919                BeforeEdgeRemoveEvent(String sourceId, long timeId, String edgeId) {
920                        super(sourceId, timeId);
921                        this.edgeId = edgeId;
922                }
923
924                void trigger() {
925                        for (int i = 0; i < eltsSinks.size(); i++)
926                                eltsSinks.get(i).edgeRemoved(sourceId, timeId, edgeId);
927                }
928        }
929
930        class AfterNodeAddEvent extends GraphEvent {
931                String nodeId;
932
933                AfterNodeAddEvent(String sourceId, long timeId, String nodeId) {
934                        super(sourceId, timeId);
935                        this.nodeId = nodeId;
936                }
937
938                void trigger() {
939                        for (int i = 0; i < eltsSinks.size(); i++)
940                                eltsSinks.get(i).nodeAdded(sourceId, timeId, nodeId);
941                }
942        }
943
944        class BeforeNodeRemoveEvent extends GraphEvent {
945                String nodeId;
946
947                BeforeNodeRemoveEvent(String sourceId, long timeId, String nodeId) {
948                        super(sourceId, timeId);
949                        this.nodeId = nodeId;
950                }
951
952                void trigger() {
953                        for (int i = 0; i < eltsSinks.size(); i++)
954                                eltsSinks.get(i).nodeRemoved(sourceId, timeId, nodeId);
955                }
956        }
957
958        class BeforeGraphClearEvent extends GraphEvent {
959                BeforeGraphClearEvent(String sourceId, long timeId) {
960                        super(sourceId, timeId);
961                }
962
963                void trigger() {
964                        for (int i = 0; i < eltsSinks.size(); i++)
965                                eltsSinks.get(i).graphCleared(sourceId, timeId);
966                }
967        }
968
969        class StepBeginsEvent extends GraphEvent {
970                double step;
971
972                StepBeginsEvent(String sourceId, long timeId, double step) {
973                        super(sourceId, timeId);
974                        this.step = step;
975                }
976
977                void trigger() {
978                        for (int i = 0; i < eltsSinks.size(); i++)
979                                eltsSinks.get(i).stepBegins(sourceId, timeId, step);
980                }
981        }
982
983        class AttributeChangedEvent extends GraphEvent {
984                ElementType eltType;
985
986                String eltId;
987
988                String attribute;
989
990                AttributeChangeEvent event;
991
992                Object oldValue;
993
994                Object newValue;
995
996                AttributeChangedEvent(String sourceId, long timeId, String eltId,
997                                ElementType eltType, String attribute,
998                                AttributeChangeEvent event, Object oldValue, Object newValue) {
999                        super(sourceId, timeId);
1000                        this.eltType = eltType;
1001                        this.eltId = eltId;
1002                        this.attribute = attribute;
1003                        this.event = event;
1004                        this.oldValue = oldValue;
1005                        this.newValue = newValue;
1006                }
1007
1008                void trigger() {
1009                        switch (event) {
1010                        case ADD:
1011                                switch (eltType) {
1012                                case NODE:
1013                                        for (int i = 0; i < attrSinks.size(); i++)
1014                                                attrSinks.get(i).nodeAttributeAdded(sourceId, timeId,
1015                                                                eltId, attribute, newValue);
1016                                        break;
1017                                case EDGE:
1018                                        for (int i = 0; i < attrSinks.size(); i++)
1019                                                attrSinks.get(i).edgeAttributeAdded(sourceId, timeId,
1020                                                                eltId, attribute, newValue);
1021                                        break;
1022                                default:
1023                                        for (int i = 0; i < attrSinks.size(); i++)
1024                                                attrSinks.get(i).graphAttributeAdded(sourceId, timeId,
1025                                                                attribute, newValue);
1026                                }
1027                                break;
1028                        case REMOVE:
1029                                switch (eltType) {
1030                                case NODE:
1031                                        for (int i = 0; i < attrSinks.size(); i++)
1032                                                attrSinks.get(i).nodeAttributeRemoved(sourceId, timeId,
1033                                                                eltId, attribute);
1034                                        break;
1035                                case EDGE:
1036                                        for (int i = 0; i < attrSinks.size(); i++)
1037                                                attrSinks.get(i).edgeAttributeRemoved(sourceId, timeId,
1038                                                                eltId, attribute);
1039                                        break;
1040                                default:
1041                                        for (int i = 0; i < attrSinks.size(); i++)
1042                                                attrSinks.get(i).graphAttributeRemoved(sourceId,
1043                                                                timeId, attribute);
1044                                }
1045                                break;
1046                        default:
1047                                switch (eltType) {
1048                                case NODE:
1049                                        for (int i = 0; i < attrSinks.size(); i++)
1050                                                attrSinks.get(i).nodeAttributeChanged(sourceId, timeId,
1051                                                                eltId, attribute, oldValue, newValue);
1052                                        break;
1053                                case EDGE:
1054                                        for (int i = 0; i < attrSinks.size(); i++)
1055                                                attrSinks.get(i).edgeAttributeChanged(sourceId, timeId,
1056                                                                eltId, attribute, oldValue, newValue);
1057                                        break;
1058                                default:
1059                                        for (int i = 0; i < attrSinks.size(); i++)
1060                                                attrSinks.get(i).graphAttributeChanged(sourceId,
1061                                                                timeId, attribute, oldValue, newValue);
1062                                }
1063                        }
1064                }
1065        }
1066
1067        class AddToListEvent<T> extends GraphEvent {
1068                List<T> l;
1069                T obj;
1070
1071                AddToListEvent(List<T> l, T obj) {
1072                        super(null, -1);
1073                        this.l = l;
1074                        this.obj = obj;
1075                }
1076
1077                void trigger() {
1078                        l.add(obj);
1079                }
1080        }
1081
1082        class RemoveFromListEvent<T> extends GraphEvent {
1083                List<T> l;
1084                T obj;
1085
1086                RemoveFromListEvent(List<T> l, T obj) {
1087                        super(null, -1);
1088                        this.l = l;
1089                        this.obj = obj;
1090                }
1091
1092                void trigger() {
1093                        l.remove(obj);
1094                }
1095        }
1096
1097        class ClearListEvent<T> extends GraphEvent {
1098                List<T> l;
1099
1100                ClearListEvent(List<T> l) {
1101                        super(null, -1);
1102                        this.l = l;
1103                }
1104
1105                void trigger() {
1106                        l.clear();
1107                }
1108        }
1109}