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.graph.implementations;
033
034import java.io.IOException;
035import java.util.AbstractCollection;
036import java.util.Collection;
037import java.util.Iterator;
038
039import org.graphstream.graph.Edge;
040import org.graphstream.graph.EdgeFactory;
041import org.graphstream.graph.EdgeRejectedException;
042import org.graphstream.graph.ElementNotFoundException;
043import org.graphstream.graph.Graph;
044import org.graphstream.graph.IdAlreadyInUseException;
045import org.graphstream.graph.Node;
046import org.graphstream.graph.NodeFactory;
047import org.graphstream.stream.AttributeSink;
048import org.graphstream.stream.ElementSink;
049import org.graphstream.stream.GraphParseException;
050import org.graphstream.stream.Replayable;
051import org.graphstream.stream.Sink;
052import org.graphstream.stream.SourceBase;
053import org.graphstream.stream.file.FileSink;
054import org.graphstream.stream.file.FileSinkFactory;
055import org.graphstream.stream.file.FileSource;
056import org.graphstream.stream.file.FileSourceFactory;
057import org.graphstream.ui.layout.Layout;
058import org.graphstream.ui.layout.Layouts;
059import org.graphstream.ui.swingViewer.GraphRenderer;
060import org.graphstream.ui.swingViewer.Viewer;
061import org.graphstream.util.GraphListeners;
062
063/**
064 * <p>
065 * This class provides a basic implementation of
066 * {@link org.graphstream.graph.Graph} interface, to minimize the effort
067 * required to implement this interface. It provides event management
068 * implementing all the methods of {@link org.graphstream.stream.Pipe}. It also
069 * manages strict checking and auto-creation policies, as well as other services
070 * as displaying, reading and writing.
071 * </p>
072 * 
073 * <p>
074 * Subclasses have to maintain data structures allowing to efficiently access
075 * graph elements by their id or index and iterating on them. They also have to
076 * maintain coherent indices of the graph elements. When AbstractGraph decides
077 * to add or remove elements, it calls one of the "callbacks"
078 * {@link #addNodeCallback(AbstractNode)},
079 * {@link #addEdgeCallback(AbstractEdge)},
080 * {@link #removeNodeCallback(AbstractNode)},
081 * {@link #removeEdgeCallback(AbstractEdge)}, {@link #clearCallback()}. The role
082 * of these callbacks is to update the data structures and to re-index elements
083 * if necessary.
084 * </p>
085 */
086public abstract class AbstractGraph extends AbstractElement implements Graph,
087                Replayable {
088        // *** Fields ***
089
090        private boolean strictChecking;
091        private boolean autoCreate;
092        GraphListeners listeners;
093        private NodeFactory<? extends AbstractNode> nodeFactory;
094        private EdgeFactory<? extends AbstractEdge> edgeFactory;
095
096        private double step = 0;
097
098        private boolean nullAttributesAreErrors;
099
100        private long replayId = 0;
101
102        // *** Constructors ***
103
104        /**
105         * The same as {@code AbstractGraph(id, true, false)}
106         * 
107         * @param id
108         *            Identifier of the graph
109         * @see #AbstractGraph(String, boolean, boolean)
110         */
111        public AbstractGraph(String id) {
112                this(id, true, false);
113        }
114
115        /**
116         * Creates a new graph. Subclasses must create their node and edge factories
117         * and initialize their data structures in their constructors.
118         * 
119         * @param id
120         * @param strictChecking
121         * @param autoCreate
122         */
123        public AbstractGraph(String id, boolean strictChecking, boolean autoCreate) {
124                super(id);
125
126                this.strictChecking = strictChecking;
127                this.autoCreate = autoCreate;
128                this.listeners = new GraphListeners(this);
129        }
130
131        // *** Inherited from abstract element
132
133        @Override
134        protected void attributeChanged(AttributeChangeEvent event,
135                        String attribute, Object oldValue, Object newValue) {
136                listeners.sendAttributeChangedEvent(id, SourceBase.ElementType.GRAPH,
137                                attribute, event, oldValue, newValue);
138        }
139
140        @Override
141        public boolean nullAttributesAreErrors() {
142                return nullAttributesAreErrors;
143        }
144
145        // *** Inherited from graph ***
146
147        // some helpers
148
149        // get node / edge by its id/index
150
151        public abstract <T extends Node> T getNode(String id);
152
153        public abstract <T extends Node> T getNode(int index);
154
155        public abstract <T extends Edge> T getEdge(String id);
156
157        public abstract <T extends Edge> T getEdge(int index);
158
159        // node and edge count, iterators and views
160
161        public abstract int getNodeCount();
162
163        public abstract int getEdgeCount();
164
165        public abstract <T extends Node> Iterator<T> getNodeIterator();
166
167        public abstract <T extends Edge> Iterator<T> getEdgeIterator();
168
169        /**
170         * This implementation uses {@link #getNodeIterator()}
171         * 
172         * @see org.graphstream.graph.Graph#getEachNode()
173         */
174        public <T extends Node> Iterable<? extends T> getEachNode() {
175                return new Iterable<T>() {
176                        public Iterator<T> iterator() {
177                                return getNodeIterator();
178                        }
179                };
180        }
181
182        /**
183         * This implementation uses {@link #getEdgeIterator()}
184         * 
185         * @see org.graphstream.graph.Graph#getEachEdge()
186         */
187        public <T extends Edge> Iterable<? extends T> getEachEdge() {
188                return new Iterable<T>() {
189                        public Iterator<T> iterator() {
190                                return getEdgeIterator();
191                        }
192                };
193        }
194
195        /**
196         * This implementation uses {@link #getNodeIterator()} and
197         * {@link #getNodeCount()}
198         * 
199         * @see org.graphstream.graph.Graph#getNodeSet()
200         */
201        public <T extends Node> Collection<T> getNodeSet() {
202                return new AbstractCollection<T>() {
203                        public Iterator<T> iterator() {
204                                return getNodeIterator();
205                        }
206
207                        public int size() {
208                                return getNodeCount();
209                        }
210                };
211        }
212
213        /**
214         * This implementation uses {@link #getEdgeIterator()} and
215         * {@link #getEdgeCount()}
216         * 
217         * @see org.graphstream.graph.Graph#getNodeSet()
218         */
219        public <T extends Edge> Collection<T> getEdgeSet() {
220                return new AbstractCollection<T>() {
221                        public Iterator<T> iterator() {
222                                return getEdgeIterator();
223                        }
224
225                        public int size() {
226                                return getEdgeCount();
227                        }
228                };
229        }
230
231        /**
232         * This implementation returns {@link #getNodeIterator()}
233         * 
234         * @see java.lang.Iterable#iterator()
235         */
236        public Iterator<Node> iterator() {
237                return getNodeIterator();
238        }
239
240        // Factories
241
242        public NodeFactory<? extends Node> nodeFactory() {
243                return nodeFactory;
244        }
245
246        public EdgeFactory<? extends Edge> edgeFactory() {
247                return edgeFactory;
248        }
249
250        @SuppressWarnings("unchecked")
251        public void setNodeFactory(NodeFactory<? extends Node> nf) {
252                nodeFactory = (NodeFactory<? extends AbstractNode>) nf;
253        }
254
255        @SuppressWarnings("unchecked")
256        public void setEdgeFactory(EdgeFactory<? extends Edge> ef) {
257                edgeFactory = (EdgeFactory<? extends AbstractEdge>) ef;
258        }
259
260        // strict checking, autocreation, etc
261
262        public boolean isStrict() {
263                return strictChecking;
264        }
265
266        public boolean isAutoCreationEnabled() {
267                return autoCreate;
268        }
269
270        public double getStep() {
271                return step;
272        }
273
274        public void setNullAttributesAreErrors(boolean on) {
275                nullAttributesAreErrors = on;
276        }
277
278        public void setStrict(boolean on) {
279                strictChecking = on;
280        }
281
282        public void setAutoCreate(boolean on) {
283                autoCreate = on;
284        }
285
286        public void stepBegins(double time) {
287                listeners.sendStepBegins(time);
288                this.step = time;
289        }
290
291        // adding and removing elements
292
293        /*
294         * (non-Javadoc)
295         * 
296         * @see org.graphstream.graph.Graph#clear()
297         */
298        public void clear() {
299                listeners.sendGraphCleared();
300
301                Iterator<AbstractNode> it = getNodeIterator();
302
303                while (it.hasNext())
304                        it.next().clearCallback();
305
306                clearCallback();
307                clearAttributesWithNoEvent();
308        }
309
310        /*
311         * (non-Javadoc)
312         * 
313         * @see org.graphstream.graph.Graph#addNode(java.lang.String)
314         */
315        @SuppressWarnings("unchecked")
316        public <T extends Node> T addNode(String id) {
317                AbstractNode node = getNode(id);
318
319                if (node != null) {
320                        if (strictChecking)
321                                throw new IdAlreadyInUseException("id \"" + id
322                                                + "\" already in use. Cannot create a node.");
323                        return (T) node;
324                }
325
326                node = nodeFactory.newInstance(id, this);
327                addNodeCallback(node);
328
329                listeners.sendNodeAdded(id);
330
331                return (T) node;
332        }
333
334        /*
335         * (non-Javadoc)
336         * 
337         * @see org.graphstream.graph.Graph#addEdge(java.lang.String,
338         * java.lang.String, java.lang.String)
339         */
340        public <T extends Edge> T addEdge(String id, String node1, String node2) {
341                return addEdge(id, node1, node2, false);
342        }
343
344        /*
345         * (non-Javadoc)
346         * 
347         * @see org.graphstream.graph.Graph#addEdge(java.lang.String,
348         * java.lang.String, java.lang.String, boolean)
349         */
350        public <T extends Edge> T addEdge(String id, String from, String to,
351                        boolean directed) {
352                return addEdge(id, (AbstractNode) getNode(from), from,
353                                (AbstractNode) getNode(to), to, directed);
354        }
355
356        /*
357         * (non-Javadoc)
358         * 
359         * @see org.graphstream.graph.Graph#addEdge(java.lang.String, int, int)
360         */
361        public <T extends Edge> T addEdge(String id, int index1, int index2) {
362                return addEdge(id, index1, index2, false);
363        }
364
365        /*
366         * (non-Javadoc)
367         * 
368         * @see org.graphstream.graph.Graph#addEdge(java.lang.String, int, int,
369         * boolean)
370         */
371        public <T extends Edge> T addEdge(String id, int fromIndex, int toIndex,
372                        boolean directed) {
373                return addEdge(id, getNode(fromIndex), getNode(toIndex), directed);
374        }
375
376        /*
377         * (non-Javadoc)
378         * 
379         * @see org.graphstream.graph.Graph#addEdge(java.lang.String,
380         * org.graphstream.graph.Node, org.graphstream.graph.Node)
381         */
382        public <T extends Edge> T addEdge(String id, Node node1, Node node2) {
383                return addEdge(id, node1, node2, false);
384        }
385
386        /*
387         * (non-Javadoc)
388         * 
389         * @see org.graphstream.graph.Graph#addEdge(java.lang.String,
390         * org.graphstream.graph.Node, org.graphstream.graph.Node, boolean)
391         */
392        public <T extends Edge> T addEdge(String id, Node from, Node to,
393                        boolean directed) {
394                return addEdge(id, (AbstractNode) from, from.getId(),
395                                (AbstractNode) to, to.getId(), directed);
396        }
397
398        /*
399         * (non-Javadoc)
400         * 
401         * @see org.graphstream.graph.Graph#removeNode(java.lang.String)
402         */
403        public <T extends Node> T removeNode(String id) {
404                AbstractNode node = getNode(id);
405
406                if (node == null) {
407                        if (strictChecking)
408                                throw new ElementNotFoundException("Node \"" + id
409                                                + "\" not found. Cannot remove it.");
410                        return null;
411                }
412
413                return removeNode(node);
414        }
415
416        /*
417         * (non-Javadoc)
418         * 
419         * @see org.graphstream.graph.Graph#removeNode(int)
420         */
421        public <T extends Node> T removeNode(int index) {
422                Node node = getNode(index);
423
424                if (node == null) {
425                        if (strictChecking)
426                                throw new ElementNotFoundException("Node #" + index
427                                                + " not found. Cannot remove it.");
428                        return null;
429                }
430
431                return removeNode(node);
432        }
433
434        /*
435         * (non-Javadoc)
436         * 
437         * @see org.graphstream.graph.Graph#removeNode(org.graphstream.graph.Node)
438         */
439        @SuppressWarnings("unchecked")
440        public <T extends Node> T removeNode(Node node) {
441                if (node == null)
442                        return null;
443
444                removeNode((AbstractNode) node, true);
445                return (T) node;
446        }
447
448        /*
449         * (non-Javadoc)
450         * 
451         * @see org.graphstream.graph.Graph#removeEdge(java.lang.String)
452         */
453        public <T extends Edge> T removeEdge(String id) {
454                Edge edge = getEdge(id);
455
456                if (edge == null) {
457                        if (strictChecking)
458                                throw new ElementNotFoundException("Edge \"" + id
459                                                + "\" not found. Cannot remove it.");
460                        return null;
461                }
462
463                return removeEdge(edge);
464        }
465
466        /*
467         * (non-Javadoc)
468         * 
469         * @see org.graphstream.graph.Graph#removeEdge(int)
470         */
471        public <T extends Edge> T removeEdge(int index) {
472                Edge edge = getEdge(index);
473
474                if (edge == null) {
475                        if (strictChecking)
476                                throw new ElementNotFoundException("Edge #" + index
477                                                + " not found. Cannot remove it.");
478                        return null;
479                }
480
481                return removeEdge(edge);
482        }
483
484        /*
485         * (non-Javadoc)
486         * 
487         * @see org.graphstream.graph.Graph#removeEdge(org.graphstream.graph.Edge)
488         */
489        @SuppressWarnings("unchecked")
490        public <T extends Edge> T removeEdge(Edge edge) {
491                if (edge == null)
492                        return null;
493
494                removeEdge((AbstractEdge) edge, true, true, true);
495                return (T) edge;
496        }
497
498        /*
499         * (non-Javadoc)
500         * 
501         * @see org.graphstream.graph.Graph#removeEdge(java.lang.String,
502         * java.lang.String)
503         */
504        public <T extends Edge> T removeEdge(String from, String to) {
505                Node fromNode = getNode(from);
506                Node toNode = getNode(to);
507
508                if (fromNode == null || toNode == null) {
509                        if (strictChecking)
510                                throw new ElementNotFoundException(
511                                                "Cannot remove the edge. The node \"%s\" does not exist",
512                                                fromNode == null ? from : to);
513                        return null;
514                }
515
516                return removeEdge(fromNode, toNode);
517        }
518
519        /*
520         * (non-Javadoc)
521         * 
522         * @see org.graphstream.graph.Graph#removeEdge(int, int)
523         */
524        public <T extends Edge> T removeEdge(int fromIndex, int toIndex) {
525                Node fromNode = getNode(fromIndex);
526                Node toNode = getNode(toIndex);
527
528                if (fromNode == null || toNode == null) {
529                        if (strictChecking)
530                                throw new ElementNotFoundException(
531                                                "Cannot remove the edge. The node #%d does not exist",
532                                                fromNode == null ? fromIndex : toIndex);
533                        return null;
534                }
535
536                return removeEdge(fromNode, toNode);
537        }
538
539        /*
540         * (non-Javadoc)
541         * 
542         * @see org.graphstream.graph.Graph#removeEdge(org.graphstream.graph.Node,
543         * org.graphstream.graph.Node)
544         */
545        public <T extends Edge> T removeEdge(Node node1, Node node2) {
546                AbstractEdge edge = node1.getEdgeToward(node2);
547
548                if (edge == null) {
549                        if (strictChecking)
550                                throw new ElementNotFoundException(
551                                                "There is no edge from \"%s\" to \"%s\". Cannot remove it.",
552                                                node1.getId(), node2.getId());
553                        return null;
554                }
555
556                return removeEdge(edge);
557        }
558
559        // *** Sinks, sources etc. ***
560
561        /*
562         * (non-Javadoc)
563         * 
564         * @see org.graphstream.graph.Graph#attributeSinks()
565         */
566        public Iterable<AttributeSink> attributeSinks() {
567                return listeners.attributeSinks();
568        }
569
570        /*
571         * *(non-Javadoc)
572         * 
573         * @see org.graphstream.graph.Graph#elementSinks()
574         */
575        public Iterable<ElementSink> elementSinks() {
576                return listeners.elementSinks();
577        }
578
579        /*
580         * *(non-Javadoc)
581         * 
582         * @see
583         * org.graphstream.stream.Source#addAttributeSink(org.graphstream.stream
584         * .AttributeSink)
585         */
586        public void addAttributeSink(AttributeSink sink) {
587                listeners.addAttributeSink(sink);
588        }
589
590        /*
591         * (non-Javadoc)
592         * 
593         * @see org.graphstream.stream.Source#addElementSink(org.graphstream.stream.
594         * ElementSink)
595         */
596        public void addElementSink(ElementSink sink) {
597                listeners.addElementSink(sink);
598        }
599
600        /*
601         * (non-Javadoc)
602         * 
603         * @see org.graphstream.stream.Source#addSink(org.graphstream.stream.Sink)
604         */
605        public void addSink(Sink sink) {
606                listeners.addSink(sink);
607        }
608
609        /*
610         * (non-Javadoc)
611         * 
612         * @see org.graphstream.stream.Source#clearAttributeSinks()
613         */
614        public void clearAttributeSinks() {
615                listeners.clearAttributeSinks();
616        }
617
618        /*
619         * *(non-Javadoc)
620         * 
621         * @see org.graphstream.stream.Source#clearElementSinks()
622         */
623        public void clearElementSinks() {
624                listeners.clearElementSinks();
625        }
626
627        /*
628         * *(non-Javadoc)
629         * 
630         * @see org.graphstream.stream.Source#clearSinks()
631         */
632        public void clearSinks() {
633                listeners.clearSinks();
634        }
635
636        /*
637         * (non-Javadoc)
638         * 
639         * @see
640         * org.graphstream.stream.Source#removeAttributeSink(org.graphstream.stream
641         * .AttributeSink)
642         */
643        public void removeAttributeSink(AttributeSink sink) {
644                listeners.removeAttributeSink(sink);
645        }
646
647        /*
648         * (non-Javadoc)
649         * 
650         * @see
651         * org.graphstream.stream.Source#removeElementSink(org.graphstream.stream
652         * .ElementSink)
653         */
654        public void removeElementSink(ElementSink sink) {
655                listeners.removeElementSink(sink);
656        }
657
658        /*
659         * (non-Javadoc)
660         * 
661         * @see
662         * org.graphstream.stream.Source#removeSink(org.graphstream.stream.Sink)
663         */
664        public void removeSink(Sink sink) {
665                listeners.removeSink(sink);
666        }
667
668        public void edgeAttributeAdded(String sourceId, long timeId, String edgeId,
669                        String attribute, Object value) {
670                listeners
671                                .edgeAttributeAdded(sourceId, timeId, edgeId, attribute, value);
672        }
673
674        public void edgeAttributeChanged(String sourceId, long timeId,
675                        String edgeId, String attribute, Object oldValue, Object newValue) {
676                listeners.edgeAttributeChanged(sourceId, timeId, edgeId, attribute,
677                                oldValue, newValue);
678        }
679
680        public void edgeAttributeRemoved(String sourceId, long timeId,
681                        String edgeId, String attribute) {
682                listeners.edgeAttributeRemoved(sourceId, timeId, edgeId, attribute);
683        }
684
685        public void graphAttributeAdded(String sourceId, long timeId,
686                        String attribute, Object value) {
687                listeners.graphAttributeAdded(sourceId, timeId, attribute, value);
688        }
689
690        public void graphAttributeChanged(String sourceId, long timeId,
691                        String attribute, Object oldValue, Object newValue) {
692                listeners.graphAttributeChanged(sourceId, timeId, attribute, oldValue,
693                                newValue);
694        }
695
696        public void graphAttributeRemoved(String sourceId, long timeId,
697                        String attribute) {
698                listeners.graphAttributeRemoved(sourceId, timeId, attribute);
699        }
700
701        public void nodeAttributeAdded(String sourceId, long timeId, String nodeId,
702                        String attribute, Object value) {
703                listeners
704                                .nodeAttributeAdded(sourceId, timeId, nodeId, attribute, value);
705        }
706
707        public void nodeAttributeChanged(String sourceId, long timeId,
708                        String nodeId, String attribute, Object oldValue, Object newValue) {
709                listeners.nodeAttributeChanged(sourceId, timeId, nodeId, attribute,
710                                oldValue, newValue);
711        }
712
713        public void nodeAttributeRemoved(String sourceId, long timeId,
714                        String nodeId, String attribute) {
715                listeners.nodeAttributeRemoved(sourceId, timeId, nodeId, attribute);
716        }
717
718        public void edgeAdded(String sourceId, long timeId, String edgeId,
719                        String fromNodeId, String toNodeId, boolean directed) {
720                listeners.edgeAdded(sourceId, timeId, edgeId, fromNodeId, toNodeId,
721                                directed);
722        }
723
724        public void edgeRemoved(String sourceId, long timeId, String edgeId) {
725                listeners.edgeRemoved(sourceId, timeId, edgeId);
726        }
727
728        public void graphCleared(String sourceId, long timeId) {
729                listeners.graphCleared(sourceId, timeId);
730        }
731
732        public void nodeAdded(String sourceId, long timeId, String nodeId) {
733                listeners.nodeAdded(sourceId, timeId, nodeId);
734        }
735
736        public void nodeRemoved(String sourceId, long timeId, String nodeId) {
737                listeners.nodeRemoved(sourceId, timeId, nodeId);
738        }
739
740        public void stepBegins(String sourceId, long timeId, double step) {
741                listeners.stepBegins(sourceId, timeId, step);
742        }
743
744        // display, read, write
745
746        public Viewer display() {
747                return display(true);
748        }
749
750        public Viewer display(boolean autoLayout) {
751                Viewer viewer = new Viewer(this,
752                                Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD);
753                GraphRenderer renderer = Viewer.newGraphRenderer();
754                viewer.addView(Viewer.DEFAULT_VIEW_ID, renderer);
755                if (autoLayout) {
756                        Layout layout = Layouts.newLayoutAlgorithm();
757                        viewer.enableAutoLayout(layout);
758                }
759                return viewer;
760        }
761
762        public void read(FileSource input, String filename) throws IOException,
763                        GraphParseException {
764                input.readAll(filename);
765        }
766
767        public void read(String filename) throws IOException, GraphParseException,
768                        ElementNotFoundException {
769                FileSource input = FileSourceFactory.sourceFor(filename);
770                if (input != null) {
771                        input.addSink(this);
772                        read(input, filename);
773                        input.removeSink(this);
774                } else {
775                        throw new IOException("No source reader for " + filename);
776                }
777        }
778
779        public void write(FileSink output, String filename) throws IOException {
780                output.writeAll(this, filename);
781        }
782
783        public void write(String filename) throws IOException {
784                FileSink output = FileSinkFactory.sinkFor(filename);
785                if (output != null) {
786                        write(output, filename);
787                } else {
788                        throw new IOException("No sink writer for " + filename);
789                }
790        }
791
792        /*
793         * (non-Javadoc)
794         * 
795         * @see org.graphstream.stream.Replayable#getReplayController()
796         */
797        public Replayable.Controller getReplayController() {
798                return new GraphReplayController();
799        }
800
801        // *** callbacks maintaining user's data structure
802
803        /**
804         * This method is automatically called when a new node is created.
805         * Subclasses must add the new node to their data structure and to set its
806         * index correctly.
807         * 
808         * @param node
809         *            the node to be added
810         */
811        protected abstract void addNodeCallback(AbstractNode node);
812
813        /**
814         * This method is automatically called when a new edge is created.
815         * Subclasses must add the new edge to their data structure and to set its
816         * index correctly.
817         * 
818         * @param edge
819         *            the edge to be added
820         */
821        protected abstract void addEdgeCallback(AbstractEdge edge);
822
823        /**
824         * This method is automatically called when a node is removed. Subclasses
825         * must remove the node from their data structures and to re-index other
826         * node(s) so that node indices remain coherent.
827         * 
828         * @param node
829         *            the node to be removed
830         */
831        protected abstract void removeNodeCallback(AbstractNode node);
832
833        /**
834         * This method is automatically called when an edge is removed. Subclasses
835         * must remove the edge from their data structures and re-index other
836         * edge(s) so that edge indices remain coherent.
837         * 
838         * @param edge
839         *            the edge to be removed
840         */
841        protected abstract void removeEdgeCallback(AbstractEdge edge);
842
843        /**
844         * This method is automatically called when the graph is cleared. Subclasses
845         * must remove all the nodes and all the edges from their data structures.
846         */
847        protected abstract void clearCallback();
848
849        // *** _ methods ***
850
851        // Why do we pass both the ids and the references of the endpoints here?
852        // When the caller knows the references it's stupid to call getNode(id)
853        // here. If the node does not exist the reference will be null.
854        // And if autoCreate is on, we need also the id. Sad but true!
855        @SuppressWarnings("unchecked")
856        protected <T extends Edge> T addEdge(String edgeId, AbstractNode src,
857                        String srcId, AbstractNode dst, String dstId, boolean directed) {
858                AbstractEdge edge = getEdge(edgeId);
859                if (edge != null) {
860                        if (strictChecking)
861                                throw new IdAlreadyInUseException("id \"" + edgeId
862                                                + "\" already in use. Cannot create an edge.");
863                        if ((edge.getSourceNode() == src && edge.getTargetNode() == dst)
864                                        || (!directed && edge.getTargetNode() == src && edge
865                                                        .getSourceNode() == dst))
866                                return (T) edge;
867                        return null;
868                }
869
870                if (src == null || dst == null) {
871                        if (strictChecking)
872                                throw new ElementNotFoundException(
873                                                String.format(
874                                                                "Cannot create edge %s[%s-%s%s]. Node '%s' does not exist.",
875                                                                edgeId, srcId, directed ? ">" : "-", dstId,
876                                                                src == null ? srcId : dstId));
877                        if (!autoCreate)
878                                return null;
879                        if (src == null)
880                                src = addNode(srcId);
881                        if (dst == null)
882                                dst = addNode(dstId);
883                }
884                // at this point edgeId is not in use and both src and dst are not null
885                edge = edgeFactory.newInstance(edgeId, src, dst, directed);
886                // see if the endpoints accept the edge
887                if (!src.addEdgeCallback(edge)) {
888                        if (strictChecking)
889                                throw new EdgeRejectedException("Edge " + edge
890                                                + " was rejected by node " + src);
891                        return null;
892                }
893                // note that for loop edges the callback is called only once
894                if (src != dst && !dst.addEdgeCallback(edge)) {
895                        // the edge is accepted by src but rejected by dst
896                        // so we have to remove it from src
897                        src.removeEdgeCallback(edge);
898                        if (strictChecking)
899                                throw new EdgeRejectedException("Edge " + edge
900                                                + " was rejected by node " + dst);
901                        return null;
902                }
903
904                // now we can finally add it
905                addEdgeCallback(edge);
906
907                listeners.sendEdgeAdded(edgeId, srcId, dstId, directed);
908
909                return (T) edge;
910        }
911
912        // helper for removeNode_
913        private void removeAllEdges(AbstractNode node) {
914                // first check if the EdgeIterator of node supports remove
915                // if this is the case, we will use it, generally it will be much more
916                // efficient
917                Iterator<AbstractEdge> edgeIt = node.getEdgeIterator();
918                boolean supportsRemove = true;
919                if (!edgeIt.hasNext())
920                        return;
921                try {
922                        edgeIt.next();
923                        edgeIt.remove();
924                } catch (UnsupportedOperationException e) {
925                        supportsRemove = false;
926                }
927                if (supportsRemove)
928                        while (edgeIt.hasNext()) {
929                                edgeIt.next();
930                                edgeIt.remove();
931                        }
932                else
933                        while (node.getDegree() > 0)
934                                removeEdge(node.getEdge(0));
935        }
936
937        // *** Methods for iterators ***
938
939        /**
940         * This method is similar to {@link #removeNode(Node)} but allows to control
941         * if {@link #removeNodeCallback(AbstractNode)} is called or not. It is
942         * useful for iterators supporting {@link java.util.Iterator#remove()} who
943         * want to update the data structures by their owns.
944         * 
945         * @param node
946         *            the node to be removed
947         * @param graphCallback
948         *            if {@code false}, {@code removeNodeCallback(node)} is not
949         *            called
950         */
951        protected void removeNode(AbstractNode node, boolean graphCallback) {
952                if (node == null)
953                        return;
954
955                removeAllEdges(node);
956                listeners.sendNodeRemoved(node.getId());
957
958                if (graphCallback)
959                        removeNodeCallback(node);
960        }
961
962        /**
963         * This method is similar to {@link #removeEdge(Edge)} but allows to control
964         * if different callbacks are called or not. It is useful for iterators
965         * supporting {@link java.util.Iterator#remove()} who want to update the
966         * data structures by their owns.
967         * 
968         * @param edge
969         *            the edge to be removed
970         * @param graphCallback
971         *            if {@code false}, {@link #removeEdgeCallback(AbstractEdge)} of
972         *            the graph is not called
973         * @param sourceCallback
974         *            if {@code false},
975         *            {@link AbstractNode#removeEdgeCallback(AbstractEdge)} is not
976         *            called for the source node of the edge
977         * @param targetCallback
978         *            if {@code false},
979         *            {@link AbstractNode#removeEdgeCallback(AbstractEdge)} is not
980         *            called for the target node of the edge
981         */
982        protected void removeEdge(AbstractEdge edge, boolean graphCallback,
983                        boolean sourceCallback, boolean targetCallback) {
984                if (edge == null)
985                        return;
986
987                AbstractNode src = edge.getSourceNode();
988                AbstractNode dst = edge.getTargetNode();
989
990                listeners.sendEdgeRemoved(edge.getId());
991
992                if (sourceCallback)
993                        src.removeEdgeCallback(edge);
994
995                if (src != dst && targetCallback)
996                        dst.removeEdgeCallback(edge);
997
998                if (graphCallback)
999                        removeEdgeCallback(edge);
1000        }
1001
1002        class GraphReplayController extends SourceBase implements
1003                        Replayable.Controller {
1004                GraphReplayController() {
1005                        super(AbstractGraph.this.id + "replay");
1006                }
1007
1008                /*
1009                 * (non-Javadoc)
1010                 * 
1011                 * @see org.graphstream.stream.Replayable.Controller#replay()
1012                 */
1013                public void replay() {
1014                        String sourceId = String.format("%s-replay-%x", id, replayId++);
1015                        replay(sourceId);
1016                }
1017
1018                /*
1019                 * (non-Javadoc)
1020                 * 
1021                 * @see
1022                 * org.graphstream.stream.Replayable.Controller#replay(java.lang.String)
1023                 */
1024                public void replay(String sourceId) {
1025                        for (String key : getAttributeKeySet())
1026                                sendGraphAttributeAdded(sourceId, key, getAttribute(key));
1027
1028                        for (int i = 0; i < getNodeCount(); i++) {
1029                                Node node = getNode(i);
1030                                String nodeId = node.getId();
1031
1032                                sendNodeAdded(sourceId, nodeId);
1033
1034                                if (node.getAttributeCount() > 0)
1035                                        for (String key : node.getAttributeKeySet())
1036                                                sendNodeAttributeAdded(sourceId, nodeId, key,
1037                                                                node.getAttribute(key));
1038                        }
1039
1040                        for (int i = 0; i < getEdgeCount(); i++) {
1041                                Edge edge = getEdge(i);
1042                                String edgeId = edge.getId();
1043
1044                                sendEdgeAdded(sourceId, edgeId, edge.getNode0().getId(), edge
1045                                                .getNode1().getId(), edge.isDirected());
1046
1047                                if (edge.getAttributeCount() > 0)
1048                                        for (String key : edge.getAttributeKeySet())
1049                                                sendEdgeAttributeAdded(sourceId, edgeId, key,
1050                                                                edge.getAttribute(key));
1051                        }
1052                }
1053        }
1054}