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.ui.graphicGraph;
033
034import java.io.IOException;
035import java.util.AbstractCollection;
036import java.util.ArrayList;
037import java.util.Arrays;
038import java.util.Collection;
039import java.util.HashMap;
040import java.util.Iterator;
041
042import org.graphstream.graph.Edge;
043import org.graphstream.graph.EdgeFactory;
044import org.graphstream.graph.Element;
045import org.graphstream.graph.Graph;
046import org.graphstream.graph.Node;
047import org.graphstream.graph.NodeFactory;
048import org.graphstream.graph.ElementNotFoundException;
049import org.graphstream.graph.IdAlreadyInUseException;
050import org.graphstream.graph.implementations.AbstractElement;
051import org.graphstream.stream.AttributeSink;
052import org.graphstream.stream.ElementSink;
053import org.graphstream.stream.Sink;
054import org.graphstream.stream.SourceBase.ElementType;
055import org.graphstream.stream.file.FileSink;
056import org.graphstream.stream.file.FileSource;
057import org.graphstream.ui.geom.Point3;
058import org.graphstream.ui.graphicGraph.stylesheet.Style;
059import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants;
060import org.graphstream.ui.graphicGraph.stylesheet.StyleSheet;
061import org.graphstream.ui.graphicGraph.stylesheet.Value;
062import org.graphstream.ui.graphicGraph.stylesheet.Values;
063import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.Units;
064import org.graphstream.util.GraphListeners;
065
066/**
067 * Graph representation used in display classes.
068 * 
069 * <p>
070 * Warning: This class is NOT a general graph class, and it should NOT be used
071 * as it. This class is particularly dedicated to fast drawing of the graph and
072 * is internally arranged to be fast for this task only. It implements graph
073 * solely to be easily susceptible to be used as a sink and source for graph
074 * events. Some of the common methods of the Graph interface are not functional
075 * and will throw an exception if used (as documented in their respective
076 * JavaDoc).
077 * </p>
078 * 
079 * <p>
080 * The purpose of the graphic graph is to represent a graph with some often used
081 * graphic attributes (like position, label, etc.) stored as fields in the nodes
082 * and edges and most of the style stored in styles pertaining to a style sheet
083 * that tries to imitate the way CSS works. For example, the GraphicNode class
084 * defines a label, a position (x,y,z) and a style that is taken from the style
085 * sheet.
086 * </p>
087 * 
088 * <p>
089 * The style sheet is uploaded on the graph using an attribute correspondingly
090 * named "ui.stylesheet" or "ui.stylesheet" (the second one is better). It can
091 * be a string that contains the whole style sheet, or an URL of the form :
092 * </p>
093 * 
094 * <pre>
095 * url(name)
096 * </pre>
097 * 
098 * <p>
099 * The graphic graph does not completely duplicate a graph, it only store things
100 * that are useful for drawing it. Although it implements "Graph", some methods
101 * are not implemented and will throw a runtime exception. These methods are
102 * mostly utility methods like write(), read(), and naturally display().
103 * </p>
104 * 
105 * <p>
106 * The graphic graph has the ability to store attributes like any other graph
107 * element, however the attributes stored by the graphic graph are restricted.
108 * There is a filter on the attribute adding methods that let pass only:
109 * <ul>
110 * <li>All attributes starting with "ui.".</li>
111 * <li>The "x", "y", "z", "xy" and "xyz" attributes.</li>
112 * <li>The "stylesheet" attribute (although "ui.stylesheet" is preferred).</li>
113 * <li>The "label" attribute.</li>
114 * </ul>
115 * All other attributes are filtered and not stored. The result is that if the
116 * graphic graph is used as an input (a source of graph events) some attributes
117 * will not pass through the filter.
118 * </p>
119 * 
120 * <p>
121 * The implementation of this graph relies on the StyleGroupSet class and this
122 * is indeed its way to store its elements (grouped by style and Z level).
123 * </p>
124 * 
125 * <p>
126 * In addition to this, it provides, as all graphs do, the relational
127 * information for edges.
128 * </p>
129 * 
130 * TODO : this graph cannot handle modification inside event listener methods !!
131 */
132public class GraphicGraph extends AbstractElement implements Graph,
133                StyleGroupListener {
134        /**
135         * Set of styles.
136         */
137        protected StyleSheet styleSheet;
138
139        /**
140         * Associate graphic elements with styles.
141         */
142        protected StyleGroupSet styleGroups;
143
144        /**
145         * Connectivity. The way nodes are connected one with another via edges. The
146         * map is sorted by node. For each node an array of edges lists the
147         * connectivity.
148         */
149        protected HashMap<GraphicNode, ArrayList<GraphicEdge>> connectivity;
150
151        /**
152         * The style of this graph. This is a shortcut to avoid searching it in the
153         * style sheet.
154         */
155        public StyleGroup style;
156
157        /**
158         * Memorize the step events.
159         */
160        public double step = 0;
161
162        /**
163         * Set to true each time the graph was modified internally and a redraw is
164         * needed.
165         */
166        public boolean graphChanged;
167
168        /**
169         * Set to true each time a sprite or node moved.
170         */
171        protected boolean boundsChanged = true;
172
173        /**
174         * Maximum position of a node or sprite in the graphic graph. Computed by
175         * {@link #computeBounds()}.
176         */
177        protected Point3 hi = new Point3();
178
179        /**
180         * Minimum position of a node or sprite in the graphic graph. Computed by
181         * {@link #computeBounds()}.
182         */
183        protected Point3 lo = new Point3();
184
185        /**
186         * Set of listeners of this graph.
187         */
188        protected GraphListeners listeners;
189
190        /**
191         * Time of other known sources.
192         */
193        // protected SinkTime sinkTime = new SinkTime();
194
195        /**
196         * Are null attributes access an error ?
197         */
198        protected boolean nullAttrError = false;
199
200        /**
201         * Report back the XYZ events on nodes and sprites? If enabled, each change
202         * in the position of nodes and sprites will be sent to potential listeners
203         * of the graph. By default this is disabled as long there are no listeners.
204         */
205        protected boolean feedbackXYZ = true;
206
207        /**
208         * New empty graphic graph.
209         * 
210         * A default style sheet is created, it then can be "cascaded" with other
211         * style sheets.
212         */
213        public GraphicGraph(String id) {
214                super(id);
215
216                listeners = new GraphListeners(this);
217                styleSheet = new StyleSheet();
218                styleGroups = new StyleGroupSet(styleSheet);
219                connectivity = new HashMap<GraphicNode, ArrayList<GraphicEdge>>();
220
221                styleGroups.addListener(this);
222                styleGroups.addElement(this); // Add style to this graph.
223
224                style = styleGroups.getStyleFor(this);
225        }
226
227        // Access
228
229        /**
230         * True if the graph was edited or changed in any way since the last reset
231         * of the "changed" flag.
232         * 
233         * @return true if the graph was changed.
234         */
235        public boolean graphChangedFlag() {
236                return graphChanged;
237        }
238
239        /**
240         * Reset the "changed" flag.
241         * 
242         * @see #graphChangedFlag()
243         */
244        public void resetGraphChangedFlag() {
245                graphChanged = false;
246        }
247
248        /**
249         * The style sheet. This style sheet is the result of the "cascade" or
250         * accumulation of styles added via attributes of the graph.
251         * 
252         * @return A style sheet.
253         */
254        public StyleSheet getStyleSheet() {
255                return styleSheet;
256        }
257
258        /**
259         * The graph style group.
260         * 
261         * @return A style group.
262         */
263        public StyleGroup getStyle() {
264                return style;
265        }
266
267        /**
268         * The complete set of style groups.
269         * 
270         * @return The style groups.
271         */
272        public StyleGroupSet getStyleGroups() {
273                return styleGroups;
274        }
275
276        @Override
277        public String toString() {
278                return String.format("[%s %d nodes %d edges]", getId(), getNodeCount(),
279                                getEdgeCount());
280        }
281
282        public double getStep() {
283                return step;
284        }
285
286        /**
287         * The maximum position of a node or sprite. Notice that this is updated
288         * only each time the {@link #computeBounds()} method is called.
289         * 
290         * @return The maximum node or sprite position.
291         */
292        public Point3 getMaxPos() {
293                return hi;
294        }
295
296        /**
297         * The minimum position of a node or sprite. Notice that this is updated
298         * only each time the {@link #computeBounds()} method is called.
299         * 
300         * @return The minimum node or sprite position.
301         */
302        public Point3 getMinPos() {
303                return lo;
304        }
305
306        /**
307         * Does the graphic graph publish via attribute changes the XYZ changes on
308         * nodes and sprites when changed ?. This is disabled by default, and
309         * enabled as soon as there is at least one listener.
310         */
311        public boolean feedbackXYZ() {
312                return feedbackXYZ;
313        }
314
315        // Command
316
317        /**
318         * Should the graphic graph publish via attribute changes the XYZ changes on
319         * nodes and sprites when changed ?.
320         */
321        public void feedbackXYZ(boolean on) {
322                feedbackXYZ = on;
323        }
324
325        /**
326         * Compute the overall bounds of the graphic graph according to the nodes
327         * and sprites positions. We can only compute the graph bounds from the
328         * nodes and sprites centres since the node and graph bounds may in certain
329         * circumstances be computed according to the graph bounds. The bounds are
330         * stored in the graph metrics.
331         * 
332         * This operation will process each node and sprite and is therefore costly.
333         * However it does this computation again only when a node or sprite moved.
334         * Therefore it can be called several times, if nothing moved in the graph,
335         * the computation will not be redone.
336         * 
337         * @see #getMaxPos()
338         * @see #getMinPos()
339         */
340        public void computeBounds() {
341                if (boundsChanged) {
342                        lo.x = lo.y = lo.z = Double.POSITIVE_INFINITY;// 1000000000; // A
343                                                                                                                        // bug with
344                                                                                                                        // Double.MAX_VALUE
345                                                                                                                        // during
346                        // comparisons ?
347                        hi.x = hi.y = hi.z = Double.NEGATIVE_INFINITY;// -1000000000; // A
348                                                                                                                        // bug with
349                                                                                                                        // Double.MIN_VALUE
350                                                                                                                        // during
351                        // comparisons ?
352
353                        for (Node n : getEachNode()) {
354                                GraphicNode node = (GraphicNode) n;
355
356                                if (!node.hidden && node.positionned) {
357                                        if (node.x < lo.x)
358                                                lo.x = node.x;
359                                        if (node.x > hi.x)
360                                                hi.x = node.x;
361                                        if (node.y < lo.y)
362                                                lo.y = node.y;
363                                        if (node.y > hi.y)
364                                                hi.y = node.y;
365                                        if (node.z < lo.z)
366                                                lo.z = node.z;
367                                        if (node.z > hi.z)
368                                                hi.z = node.z;
369                                }
370                        }
371
372                        for (GraphicSprite sprite : spriteSet()) {
373                                if (!sprite.isAttached()
374                                                && sprite.getUnits() == StyleConstants.Units.GU) {
375                                        double x = sprite.getX();
376                                        double y = sprite.getY();
377                                        double z = sprite.getZ();
378
379                                        if (!sprite.hidden) {
380                                                if (x < lo.x)
381                                                        lo.x = x;
382                                                if (x > hi.x)
383                                                        hi.x = x;
384                                                if (y < lo.y)
385                                                        lo.y = y;
386                                                if (y > hi.y)
387                                                        hi.y = y;
388                                                if (z < lo.z)
389                                                        lo.z = z;
390                                                if (z > hi.z)
391                                                        hi.z = z;
392                                        }
393                                }
394                        }
395
396                        if ((hi.x - lo.x < 0.000001)) {
397                                hi.x = 1;
398                                lo.x = -1;
399                        }
400                        if ((hi.y - lo.y < 0.000001)) {
401                                hi.y = 1;
402                                lo.y = -1;
403                        }
404                        if ((hi.z - lo.z < 0.000001)) {
405                                hi.z = 1;
406                                lo.z = -1;
407                        }
408
409                        boundsChanged = false;
410                }
411        }
412
413        protected void moveNode(String id, double x, double y, double z) {
414                GraphicNode node = (GraphicNode) styleGroups.getNode(id);
415
416                if (node != null) {
417                        node.x = x;
418                        node.y = y;
419                        node.z = z;
420                        node.addAttribute("x", x);
421                        node.addAttribute("y", y);
422                        node.addAttribute("z", z);
423
424                        graphChanged = true;
425                }
426        }
427
428        @SuppressWarnings("unchecked")
429        public <T extends Node> T getNode(String id) {
430                return (T) styleGroups.getNode(id);
431        }
432
433        @SuppressWarnings("unchecked")
434        public <T extends Edge> T getEdge(String id) {
435                return (T) styleGroups.getEdge(id);
436        }
437
438        public GraphicSprite getSprite(String id) {
439                return styleGroups.getSprite(id);
440        }
441
442        @Override
443        protected void attributeChanged(AttributeChangeEvent event,
444                        String attribute, Object oldValue, Object newValue) {
445
446                // One of the most important method. Most of the communication comes
447                // from attributes.
448
449                if (attribute.equals("ui.repaint")) {
450                        graphChanged = true;
451                } else if (attribute.equals("ui.stylesheet")
452                                || attribute.equals("stylesheet")) {
453                        if (event == AttributeChangeEvent.ADD
454                                        || event == AttributeChangeEvent.CHANGE) {
455                                if (newValue instanceof String) {
456                                        try {
457                                                styleSheet.load((String) newValue);
458                                                graphChanged = true;
459                                        } catch (IOException e) {
460                                                System.err
461                                                                .printf("Error while parsing style sheet for graph '%s' : %n",
462                                                                                getId());
463                                                if (((String) newValue).startsWith("url"))
464                                                        System.err.printf("    %s%n", ((String) newValue));
465                                                System.err.printf("    %s%n", e.getMessage());
466                                        }
467                                } else {
468                                        System.err
469                                                        .printf("Error with stylesheet specification what to do with '%s' ?%n",
470                                                                        newValue);
471                                }
472                        } else // Remove the style.
473                        {
474                                styleSheet.clear();
475                                graphChanged = true;
476                        }
477                } else if (attribute.startsWith("ui.sprite.")) {
478                        // Defers the sprite handling to the sprite API.
479                        spriteAttribute(event, null, attribute, newValue);
480                        graphChanged = true;
481                }
482
483                listeners.sendAttributeChangedEvent(getId(), ElementType.GRAPH,
484                                attribute, event, oldValue, newValue);
485        }
486
487        /**
488         * Display the node/edge relations.
489         */
490        public void printConnectivity() {
491                Iterator<GraphicNode> keys = connectivity.keySet().iterator();
492
493                System.err.printf("Graphic graph connectivity:%n");
494
495                while (keys.hasNext()) {
496                        GraphicNode node = keys.next();
497                        System.err.printf("    [%s] -> ", node.getId());
498                        ArrayList<GraphicEdge> edges = connectivity.get(node);
499                        for (GraphicEdge edge : edges)
500                                System.err.printf(" (%s %d)", edge.getId(),
501                                                edge.getMultiIndex());
502                        System.err.printf("%n");
503                }
504        }
505
506        // Style group listener interface
507
508        public void elementStyleChanged(Element element, StyleGroup oldStyle,
509                        StyleGroup style) {
510                if (element instanceof GraphicElement) {
511                        GraphicElement ge = (GraphicElement) element;
512                        ge.style = style;
513                        graphChanged = true;
514                } else if (element instanceof GraphicGraph) {
515                        GraphicGraph gg = (GraphicGraph) element;
516                        gg.style = style;
517                        graphChanged = true;
518                } else {
519                        throw new RuntimeException("WTF ?");
520                }
521        }
522
523        public void styleChanged(StyleGroup style) {
524
525        }
526
527        // Graph interface
528
529        public Iterable<? extends Edge> getEachEdge() {
530                return styleGroups.edges();
531        }
532
533        public Iterable<? extends Node> getEachNode() {
534                return styleGroups.nodes();
535        }
536
537        @SuppressWarnings("all")
538        public <T extends Node> Collection<T> getNodeSet() {
539                return new AbstractCollection<T>() {
540                        public Iterator<T> iterator() {
541                                return getNodeIterator();
542                        }
543
544                        public int size() {
545                                return getNodeCount();
546                        }
547                };
548        }
549
550        @SuppressWarnings("all")
551        public <T extends Edge> Collection<T> getEdgeSet() {
552                return new AbstractCollection<T>() {
553                        public Iterator<T> iterator() {
554                                return getEdgeIterator();
555                        }
556
557                        public int size() {
558                                return getEdgeCount();
559                        }
560                };
561        }
562
563        @SuppressWarnings("unchecked")
564        public Iterator<Node> iterator() {
565                return (Iterator<Node>) styleGroups.getNodeIterator();
566        }
567
568        /*
569         * (non-Javadoc)
570         * 
571         * @see org.graphstream.stream.Source#addSink(org.graphstream.stream.Sink)
572         */
573        public void addSink(Sink listener) {
574                listeners.addSink(listener);
575        }
576
577        /*
578         * (non-Javadoc)
579         * 
580         * @see
581         * org.graphstream.stream.Source#removeSink(org.graphstream.stream.Sink)
582         */
583        public void removeSink(Sink listener) {
584                listeners.removeSink(listener);
585        }
586
587        /*
588         * *(non-Javadoc)
589         * 
590         * @see
591         * org.graphstream.stream.Source#addAttributeSink(org.graphstream.stream
592         * .AttributeSink)
593         */
594        public void addAttributeSink(AttributeSink listener) {
595                listeners.addAttributeSink(listener);
596        }
597
598        /*
599         * *(non-Javadoc)
600         * 
601         * @see
602         * org.graphstream.stream.Source#removeAttributeSink(org.graphstream.stream
603         * .AttributeSink)
604         */
605        public void removeAttributeSink(AttributeSink listener) {
606                listeners.removeAttributeSink(listener);
607        }
608
609        /*
610         * *(non-Javadoc)
611         * 
612         * @see org.graphstream.stream.Source#addElementSink(org.graphstream.stream.
613         * ElementSink)
614         */
615        public void addElementSink(ElementSink listener) {
616                listeners.addElementSink(listener);
617        }
618
619        /*
620         * *(non-Javadoc)
621         * 
622         * @see
623         * org.graphstream.stream.Source#removeElementSink(org.graphstream.stream
624         * .ElementSink)
625         */
626        public void removeElementSink(ElementSink listener) {
627                listeners.removeElementSink(listener);
628        }
629
630        /*
631         * *(non-Javadoc)
632         * 
633         * @see org.graphstream.graph.Graph#attributeSinks()
634         */
635        public Iterable<AttributeSink> attributeSinks() {
636                return listeners.attributeSinks();
637        }
638
639        /*
640         * *(non-Javadoc)
641         * 
642         * @see org.graphstream.graph.Graph#elementSinks()
643         */
644        public Iterable<ElementSink> elementSinks() {
645                return listeners.elementSinks();
646        }
647
648        /*
649         * (non-Javadoc)
650         * 
651         * @see org.graphstream.graph.Graph#addEdge(java.lang.String,
652         * java.lang.String, java.lang.String)
653         */
654        @SuppressWarnings("unchecked")
655        public <T extends Edge> T addEdge(String id, String from, String to)
656                        throws IdAlreadyInUseException, ElementNotFoundException {
657                return (T) addEdge(id, from, to, false);
658        }
659
660        /*
661         * (non-Javadoc)
662         * 
663         * @see org.graphstream.graph.Graph#addEdge(java.lang.String,
664         * java.lang.String, java.lang.String, boolean)
665         */
666        @SuppressWarnings("unchecked")
667        public <T extends Edge> T addEdge(String id, String from, String to,
668                        boolean directed) throws IdAlreadyInUseException,
669                        ElementNotFoundException {
670                GraphicEdge edge = (GraphicEdge) styleGroups.getEdge(id);
671
672                if (edge == null) {
673                        GraphicNode n1 = (GraphicNode) styleGroups.getNode(from);
674                        GraphicNode n2 = (GraphicNode) styleGroups.getNode(to);
675
676                        if (n1 == null)
677                                throw new ElementNotFoundException("node \"%s\"", from);
678
679                        if (n2 == null)
680                                throw new ElementNotFoundException("node \"%s\"", to);
681
682                        edge = new GraphicEdge(id, n1, n2, directed, null);//, attributes);
683
684                        styleGroups.addElement(edge);
685
686                        ArrayList<GraphicEdge> l1 = connectivity.get(n1);
687                        ArrayList<GraphicEdge> l2 = connectivity.get(n2);
688
689                        if (l1 == null) {
690                                l1 = new ArrayList<GraphicEdge>();
691                                connectivity.put(n1, l1);
692                        }
693
694                        if (l2 == null) {
695                                l2 = new ArrayList<GraphicEdge>();
696                                connectivity.put(n2, l2);
697                        }
698
699                        l1.add(edge);
700                        l2.add(edge);
701                        edge.countSameEdges(l1);
702
703                        graphChanged = true;
704
705                        listeners.sendEdgeAdded(id, from, to, directed);
706                }
707
708                return (T) edge;
709        }
710
711        /*
712         * (non-Javadoc)
713         * 
714         * @see org.graphstream.graph.Graph#addNode(java.lang.String)
715         */
716        @SuppressWarnings("unchecked")
717        public <T extends Node> T addNode(String id) throws IdAlreadyInUseException {
718                GraphicNode node = (GraphicNode) styleGroups.getNode(id);
719
720                if (node == null) {
721                        node = new GraphicNode(this, id, null);//, attributes);
722
723                        styleGroups.addElement(node);
724
725                        graphChanged = true;
726
727                        listeners.sendNodeAdded(id);
728                }
729
730                return (T) node;
731        }
732
733        /*
734         * (non-Javadoc)
735         * 
736         * @see org.graphstream.graph.Graph#clear()
737         */
738        public void clear() {
739                listeners.sendGraphCleared();
740
741                clearAttributesWithNoEvent();
742
743                connectivity.clear();
744                styleGroups.clear();
745                styleSheet.clear();
746
747                step = 0;
748                graphChanged = true;
749
750                styleGroups.addElement(this);
751                style = styleGroups.getStyleFor(this);
752        }
753
754        /*
755         * (non-Javadoc)
756         * 
757         * @see org.graphstream.graph.Graph#removeEdge(java.lang.String)
758         */
759        @SuppressWarnings("unchecked")
760        public <T extends Edge> T removeEdge(String id)
761                        throws ElementNotFoundException {
762                GraphicEdge edge = (GraphicEdge) styleGroups.getEdge(id);
763
764                if (edge != null) {
765                        listeners.sendEdgeRemoved(id);
766
767                        if (connectivity.get(edge.from) != null)
768                                connectivity.get(edge.from).remove(edge);
769                        if (connectivity.get(edge.to) != null)
770                                connectivity.get(edge.to).remove(edge);
771
772                        styleGroups.removeElement(edge);
773                        edge.removed();
774
775                        graphChanged = true;
776                }
777
778                return (T) edge;
779        }
780
781        /*
782         * (non-Javadoc)
783         * 
784         * @see org.graphstream.graph.Graph#removeEdge(java.lang.String,
785         * java.lang.String)
786         */
787        @SuppressWarnings("unchecked")
788        public <T extends Edge> T removeEdge(String from, String to)
789                        throws ElementNotFoundException {
790                GraphicNode node0 = (GraphicNode) styleGroups.getNode(from);
791                GraphicNode node1 = (GraphicNode) styleGroups.getNode(to);
792
793                if (node0 != null && node1 != null) {
794                        ArrayList<GraphicEdge> edges0 = connectivity.get(node0);
795                        ArrayList<GraphicEdge> edges1 = connectivity.get(node1);
796
797                        for (GraphicEdge edge0 : edges0) {
798                                for (GraphicEdge edge1 : edges1) {
799                                        if (edge0 == edge1) {
800                                                removeEdge(edge0.getId());
801                                                return (T) edge0;
802                                        }
803                                }
804                        }
805                }
806
807                return null;
808        }
809
810        /*
811         * *(non-Javadoc)
812         * 
813         * @see org.graphstream.graph.Graph#removeNode(java.lang.String)
814         */
815        @SuppressWarnings("unchecked")
816        public <T extends Node> T removeNode(String id)
817                        throws ElementNotFoundException {
818                GraphicNode node = (GraphicNode) styleGroups.getNode(id);
819
820                if (node != null) {
821                        listeners.sendNodeRemoved(id);
822
823                        if (connectivity.get(node) != null) {
824                                // We must do a copy of the connectivity set for the node
825                                // since we will be modifying the connectivity as we process
826                                // edges.
827                                ArrayList<GraphicEdge> l = new ArrayList<GraphicEdge>(
828                                                connectivity.get(node));
829
830                                for (GraphicEdge edge : l)
831                                        removeEdge(edge.getId());
832
833                                connectivity.remove(node);
834                        }
835
836                        styleGroups.removeElement(node);
837                        node.removed();
838
839                        graphChanged = true;
840                }
841
842                return (T) node;
843        }
844
845        public org.graphstream.ui.swingViewer.Viewer display() {
846                throw new RuntimeException(
847                                "GraphicGraph is used by display() and cannot recursively define display()");
848        }
849
850        public org.graphstream.ui.swingViewer.Viewer display(boolean autoLayout) {
851                throw new RuntimeException(
852                                "GraphicGraph is used by display() and cannot recursively define display()");
853        }
854
855        /*
856         * (non-Javadoc)
857         * 
858         * @see org.graphstream.graph.Graph#stepBegins(double)
859         */
860        public void stepBegins(double step) {
861                listeners.sendStepBegins(step);
862                this.step = step;
863        }
864
865        public EdgeFactory<? extends Edge> edgeFactory() {
866                throw new RuntimeException("GraphicGraph does not support EdgeFactory");
867        }
868
869        public int getEdgeCount() {
870                return styleGroups.getEdgeCount();
871        }
872
873        @SuppressWarnings("unchecked")
874        public <T extends Edge> Iterator<T> getEdgeIterator() {
875                return (Iterator<T>) styleGroups.getEdgeIterator();
876        }
877
878        public int getNodeCount() {
879                return styleGroups.getNodeCount();
880        }
881
882        public int getSpriteCount() {
883                return styleGroups.getSpriteCount();
884        }
885
886        @SuppressWarnings("unchecked")
887        public <T extends Node> Iterator<T> getNodeIterator() {
888                return (Iterator<T>) styleGroups.getNodeIterator();
889        }
890
891        public Iterator<? extends GraphicSprite> getSpriteIterator() {
892                return styleGroups.getSpriteIterator();
893        }
894
895        public Iterable<? extends GraphicSprite> spriteSet() {
896                return styleGroups.sprites();
897        }
898
899        public boolean isAutoCreationEnabled() {
900                return false;
901        }
902
903        public NodeFactory<? extends Node> nodeFactory() {
904                throw new RuntimeException("GraphicGraph does not support NodeFactory");
905        }
906
907        public void setAutoCreate(boolean on) {
908                throw new RuntimeException(
909                                "GraphicGraph does not support auto-creation");
910        }
911
912        public boolean isStrict() {
913                return false;
914        }
915
916        public void setStrict(boolean on) {
917                throw new RuntimeException(
918                                "GraphicGraph does not support strict checking");
919        }
920
921        @Override
922        public boolean nullAttributesAreErrors() {
923                return nullAttrError;
924        }
925
926        public void setNullAttributesAreErrors(boolean on) {
927                nullAttrError = on;
928        }
929
930        public void setEdgeFactory(EdgeFactory<? extends Edge> ef) {
931                throw new RuntimeException(
932                                "you cannot change the edge factory for graphic graphs !");
933        }
934
935        public void setNodeFactory(NodeFactory<? extends Node> nf) {
936                throw new RuntimeException(
937                                "you cannot change the node factory for graphic graphs !");
938        }
939
940        public void read(String filename) throws IOException {
941                throw new RuntimeException("GraphicGraph does not support I/O");
942        }
943
944        public void read(FileSource input, String filename) throws IOException {
945                throw new RuntimeException("GraphicGraph does not support I/O");
946        }
947
948        public void write(FileSink output, String filename) throws IOException {
949                throw new RuntimeException("GraphicGraph does not support I/O");
950        }
951
952        public void write(String filename) throws IOException {
953                throw new RuntimeException("GraphicGraph does not support I/O");
954        }
955
956        // Output interface
957
958        /*
959         * (non-Javadoc)
960         * 
961         * @see
962         * org.graphstream.stream.AttributeSink#edgeAttributeAdded(java.lang.String,
963         * long, java.lang.String, java.lang.String, java.lang.Object)
964         */
965        public void edgeAttributeAdded(String sourceId, long timeId, String edgeId,
966                        String attribute, Object value) {
967                listeners
968                                .edgeAttributeAdded(sourceId, timeId, edgeId, attribute, value);
969        }
970
971        /*
972         * (non-Javadoc)
973         * 
974         * @see
975         * org.graphstream.stream.AttributeSink#edgeAttributeChanged(java.lang.String
976         * , long, java.lang.String, java.lang.String, java.lang.Object,
977         * java.lang.Object)
978         */
979        public void edgeAttributeChanged(String sourceId, long timeId,
980                        String edgeId, String attribute, Object oldValue, Object newValue) {
981                listeners.edgeAttributeChanged(sourceId, timeId, edgeId, attribute,
982                                oldValue, newValue);
983        }
984
985        /*
986         * (non-Javadoc)
987         * 
988         * @see
989         * org.graphstream.stream.AttributeSink#edgeAttributeRemoved(java.lang.String
990         * , long, java.lang.String, java.lang.String)
991         */
992        public void edgeAttributeRemoved(String sourceId, long timeId,
993                        String edgeId, String attribute) {
994                listeners.edgeAttributeRemoved(sourceId, timeId, edgeId, attribute);
995        }
996
997        /*
998         * (non-Javadoc)
999         * 
1000         * @see
1001         * org.graphstream.stream.AttributeSink#graphAttributeAdded(java.lang.String
1002         * , long, java.lang.String, java.lang.Object)
1003         */
1004        public void graphAttributeAdded(String sourceId, long timeId,
1005                        String attribute, Object value) {
1006                listeners.graphAttributeAdded(sourceId, timeId, attribute, value);
1007        }
1008
1009        /*
1010         * (non-Javadoc)
1011         * 
1012         * @see
1013         * org.graphstream.stream.AttributeSink#graphAttributeChanged(java.lang.
1014         * String, long, java.lang.String, java.lang.Object, java.lang.Object)
1015         */
1016        public void graphAttributeChanged(String sourceId, long timeId,
1017                        String attribute, Object oldValue, Object newValue) {
1018                listeners.graphAttributeChanged(sourceId, timeId, attribute, oldValue,
1019                                newValue);
1020        }
1021
1022        /*
1023         * (non-Javadoc)
1024         * 
1025         * @see
1026         * org.graphstream.stream.AttributeSink#graphAttributeRemoved(java.lang.
1027         * String, long, java.lang.String)
1028         */
1029        public void graphAttributeRemoved(String sourceId, long timeId,
1030                        String attribute) {
1031                listeners.graphAttributeRemoved(sourceId, timeId, attribute);
1032        }
1033
1034        /*
1035         * (non-Javadoc)
1036         * 
1037         * @see
1038         * org.graphstream.stream.AttributeSink#nodeAttributeAdded(java.lang.String,
1039         * long, java.lang.String, java.lang.String, java.lang.Object)
1040         */
1041        public void nodeAttributeAdded(String sourceId, long timeId, String nodeId,
1042                        String attribute, Object value) {
1043                listeners
1044                                .nodeAttributeAdded(sourceId, timeId, nodeId, attribute, value);
1045        }
1046
1047        /*
1048         * (non-Javadoc)
1049         * 
1050         * @see
1051         * org.graphstream.stream.AttributeSink#nodeAttributeChanged(java.lang.String
1052         * , long, java.lang.String, java.lang.String, java.lang.Object,
1053         * java.lang.Object)
1054         */
1055        public void nodeAttributeChanged(String sourceId, long timeId,
1056                        String nodeId, String attribute, Object oldValue, Object newValue) {
1057                listeners.nodeAttributeChanged(sourceId, timeId, nodeId, attribute,
1058                                oldValue, newValue);
1059        }
1060
1061        /*
1062         * (non-Javadoc)
1063         * 
1064         * @see
1065         * org.graphstream.stream.AttributeSink#nodeAttributeRemoved(java.lang.String
1066         * , long, java.lang.String, java.lang.String)
1067         */
1068        public void nodeAttributeRemoved(String sourceId, long timeId,
1069                        String nodeId, String attribute) {
1070                listeners.nodeAttributeRemoved(sourceId, timeId, nodeId, attribute);
1071        }
1072
1073        /*
1074         * (non-Javadoc)
1075         * 
1076         * @see org.graphstream.stream.ElementSink#edgeAdded(java.lang.String, long,
1077         * java.lang.String, java.lang.String, java.lang.String, boolean)
1078         */
1079        public void edgeAdded(String sourceId, long timeId, String edgeId,
1080                        String fromNodeId, String toNodeId, boolean directed) {
1081                listeners.edgeAdded(sourceId, timeId, edgeId, fromNodeId, toNodeId,
1082                                directed);
1083        }
1084
1085        /*
1086         * *(non-Javadoc)
1087         * 
1088         * @see org.graphstream.stream.ElementSink#edgeRemoved(java.lang.String,
1089         * long, java.lang.String)
1090         */
1091        public void edgeRemoved(String sourceId, long timeId, String edgeId) {
1092                listeners.edgeRemoved(sourceId, timeId, edgeId);
1093        }
1094
1095        /*
1096         * *(non-Javadoc)
1097         * 
1098         * @see org.graphstream.stream.ElementSink#graphCleared(java.lang.String,
1099         * long)
1100         */
1101        public void graphCleared(String sourceId, long timeId) {
1102                listeners.graphCleared(sourceId, timeId);
1103        }
1104
1105        /*
1106         * (non-Javadoc)
1107         * 
1108         * @see org.graphstream.stream.ElementSink#nodeAdded(java.lang.String, long,
1109         * java.lang.String)
1110         */
1111        public void nodeAdded(String sourceId, long timeId, String nodeId) {
1112                listeners.nodeAdded(sourceId, timeId, nodeId);
1113        }
1114
1115        /*
1116         * (non-Javadoc)
1117         * 
1118         * @see org.graphstream.stream.ElementSink#nodeRemoved(java.lang.String,
1119         * long, java.lang.String)
1120         */
1121        public void nodeRemoved(String sourceId, long timeId, String nodeId) {
1122                listeners.nodeRemoved(sourceId, timeId, nodeId);
1123        }
1124
1125        /*
1126         * (non-Javadoc)
1127         * 
1128         * @see org.graphstream.stream.ElementSink#stepBegins(java.lang.String,
1129         * long, double)
1130         */
1131        public void stepBegins(String sourceId, long timeId, double time) {
1132                listeners.sendStepBegins(sourceId, timeId, time);
1133                stepBegins(time);
1134        }
1135
1136        // Sprite interface
1137
1138        protected void spriteAttribute(AttributeChangeEvent event, Element element,
1139                        String attribute, Object value) {
1140                String spriteId = attribute.substring(10); // Remove the "ui.sprite."
1141                                                                                                        // prefix.
1142                int pos = spriteId.indexOf('.'); // Look if there is something after the
1143                                                                                        // sprite id.
1144                String attr = null;
1145
1146                if (pos > 0) {
1147                        attr = spriteId.substring(pos + 1); // Cut the sprite id.
1148                        spriteId = spriteId.substring(0, pos); // Cut the sprite attribute
1149                                                                                                        // name.
1150                }
1151
1152                if (attr == null) {
1153                        addOrChangeSprite(event, element, spriteId, value);
1154                } else {
1155                        if (event == AttributeChangeEvent.ADD) {
1156                                GraphicSprite sprite = styleGroups.getSprite(spriteId);
1157
1158                                // We add the sprite, in case of a replay, some attributes of
1159                                // the sprite can be
1160                                // changed before the sprite is declared.
1161                                if (sprite == null) {
1162                                        addOrChangeSprite(AttributeChangeEvent.ADD, element,
1163                                                        spriteId, null);
1164                                        sprite = styleGroups.getSprite(spriteId);
1165                                }
1166
1167                                sprite.addAttribute(attr, value);
1168                        } else if (event == AttributeChangeEvent.CHANGE) {
1169                                GraphicSprite sprite = styleGroups.getSprite(spriteId);
1170
1171                                if (sprite == null) {
1172                                        addOrChangeSprite(AttributeChangeEvent.ADD, element,
1173                                                        spriteId, null);
1174                                        sprite = styleGroups.getSprite(spriteId);
1175                                }
1176
1177                                sprite.changeAttribute(attr, value);
1178                        } else if (event == AttributeChangeEvent.REMOVE) {
1179                                GraphicSprite sprite = styleGroups.getSprite(spriteId);
1180
1181                                if (sprite != null)
1182                                        sprite.removeAttribute(attr);
1183                        }
1184                }
1185        }
1186
1187        protected void addOrChangeSprite(AttributeChangeEvent event,
1188                        Element element, String spriteId, Object value) {
1189
1190                if (event == AttributeChangeEvent.ADD
1191                                || event == AttributeChangeEvent.CHANGE) {
1192                        GraphicSprite sprite = styleGroups.getSprite(spriteId);
1193
1194                        if (sprite == null)
1195                                sprite = addSprite_(spriteId);
1196
1197                        if (element != null) {
1198                                if (element instanceof GraphicNode)
1199                                        sprite.attachToNode((GraphicNode) element);
1200                                else if (element instanceof GraphicEdge)
1201                                        sprite.attachToEdge((GraphicEdge) element);
1202                        }
1203
1204                        if (value != null && (!(value instanceof Boolean)))
1205                                positionSprite(sprite, value);
1206                } else if (event == AttributeChangeEvent.REMOVE) {
1207                        if (element == null) {
1208                                if (styleGroups.getSprite(spriteId) != null) {
1209                                        removeSprite_(spriteId);
1210                                }
1211                        } else {
1212                                GraphicSprite sprite = styleGroups.getSprite(spriteId);
1213
1214                                if (sprite != null)
1215                                        sprite.detach();
1216                        }
1217                }
1218        }
1219
1220        public GraphicSprite addSprite(String id) {
1221                String prefix = String.format("ui.sprite.%s", id);
1222                System.out.printf("add sprite %s\n", id);
1223                addAttribute(prefix, 0, 0, 0);
1224                GraphicSprite s = styleGroups.getSprite(id);
1225                assert (s != null);
1226                return s;
1227        }
1228
1229        protected GraphicSprite addSprite_(String id) {
1230                GraphicSprite s = new GraphicSprite(id, this);
1231                styleGroups.addElement(s);
1232                graphChanged = true;
1233
1234                return s;
1235        }
1236
1237        public void removeSprite(String id) {
1238                String prefix = String.format("ui.sprite.%s", id);
1239                removeAttribute(prefix);
1240        }
1241
1242        protected GraphicSprite removeSprite_(String id) {
1243                GraphicSprite sprite = (GraphicSprite) styleGroups.getSprite(id);
1244
1245                if (sprite != null) {
1246                        sprite.detach();
1247                        styleGroups.removeElement(sprite);
1248                        sprite.removed();
1249
1250                        graphChanged = true;
1251                }
1252
1253                return sprite;
1254        }
1255
1256        protected void positionSprite(GraphicSprite sprite, Object value) {
1257                if (value instanceof Object[]) {
1258                        Object[] values = (Object[]) value;
1259
1260                        if (values.length == 4) {
1261                                if (values[0] instanceof Number && values[1] instanceof Number
1262                                                && values[2] instanceof Number
1263                                                && values[3] instanceof Style.Units) {
1264                                        sprite.setPosition(((Number) values[0]).doubleValue(),
1265                                                        ((Number) values[1]).doubleValue(),
1266                                                        ((Number) values[2]).doubleValue(),
1267                                                        (Style.Units) values[3]);
1268                                } else {
1269                                        System.err
1270                                                        .printf("GraphicGraph : cannot parse values[4] for sprite position.%n");
1271                                }
1272                        } else if (values.length == 3) {
1273                                if (values[0] instanceof Number && values[1] instanceof Number
1274                                                && values[2] instanceof Number) {
1275                                        sprite.setPosition(((Number) values[0]).doubleValue(),
1276                                                        ((Number) values[1]).doubleValue(),
1277                                                        ((Number) values[2]).doubleValue(), Units.GU);
1278                                } else {
1279                                        System.err
1280                                                        .printf("GraphicGraph : cannot parse values[3] for sprite position.%n");
1281                                }
1282                        } else if (values.length == 1) {
1283                                if (values[0] instanceof Number) {
1284                                        sprite.setPosition(((Number) values[0]).doubleValue());
1285                                } else {
1286                                        System.err
1287                                                        .printf("GraphicGraph : sprite position percent is not a number.%n");
1288                                }
1289                        } else {
1290                                System.err
1291                                                .printf("GraphicGraph : cannot transform value '%s' (length=%d) into a position%n",
1292                                                                Arrays.toString(values), values.length);
1293                        }
1294                } else if (value instanceof Number) {
1295                        sprite.setPosition(((Number) value).doubleValue());
1296                } else if (value instanceof Value) {
1297                        sprite.setPosition(((Value) value).value);
1298                } else if (value instanceof Values) {
1299                        sprite.setPosition((Values) value);
1300                } else if (value == null) {
1301                        throw new RuntimeException("What do you expect with a null value ?");
1302                } else {
1303                        System.err
1304                                        .printf("GraphicGraph : cannot place sprite with posiiton '%s' (instance of %s)%n",
1305                                                        value, value.getClass().getName());
1306                }
1307        }
1308
1309        /*
1310         * (non-Javadoc)
1311         * 
1312         * @see org.graphstream.stream.Source#clearAttributeSinks()
1313         */
1314        public void clearAttributeSinks() {
1315                listeners.clearAttributeSinks();
1316        }
1317
1318        /*
1319         * *(non-Javadoc)
1320         * 
1321         * @see org.graphstream.stream.Source#clearElementSinks()
1322         */
1323        public void clearElementSinks() {
1324                listeners.clearElementSinks();
1325        }
1326
1327        /*
1328         * (non-Javadoc)
1329         * 
1330         * @see org.graphstream.stream.Source#clearSinks()
1331         */
1332        public void clearSinks() {
1333                listeners.clearSinks();
1334        }
1335
1336        // stubs for the new methods
1337
1338        public <T extends Edge> T addEdge(String id, int index1, int index2) {
1339                throw new RuntimeException("not implemented !");
1340        }
1341
1342        public <T extends Edge> T addEdge(String id, int fromIndex, int toIndex,
1343                        boolean directed) {
1344                throw new RuntimeException("not implemented !");
1345        }
1346
1347        public <T extends Edge> T addEdge(String id, Node node1, Node node2) {
1348                throw new RuntimeException("not implemented !");
1349        }
1350
1351        public <T extends Edge> T addEdge(String id, Node from, Node to,
1352                        boolean directed) {
1353                throw new RuntimeException("not implemented !");
1354        }
1355
1356        public <T extends Edge> T getEdge(int index)
1357                        throws IndexOutOfBoundsException {
1358                throw new RuntimeException("not implemented !");
1359        }
1360
1361        public <T extends Node> T getNode(int index)
1362                        throws IndexOutOfBoundsException {
1363                throw new RuntimeException("not implemented !");
1364        }
1365
1366        public <T extends Edge> T removeEdge(int index) {
1367                throw new RuntimeException("not implemented !");
1368        }
1369
1370        public <T extends Edge> T removeEdge(int fromIndex, int toIndex) {
1371                throw new RuntimeException("not implemented !");
1372        }
1373
1374        public <T extends Edge> T removeEdge(Node node1, Node node2) {
1375                throw new RuntimeException("not implemented !");
1376        }
1377
1378        public <T extends Edge> T removeEdge(Edge edge) {
1379                throw new RuntimeException("not implemented !");
1380        }
1381
1382        public <T extends Node> T removeNode(int index) {
1383                throw new RuntimeException("not implemented !");
1384        }
1385
1386        public <T extends Node> T removeNode(Node node) {
1387                throw new RuntimeException("not implemented !");
1388        }
1389
1390        /**
1391         * Replay all the elements of the graph and all attributes as new events to
1392         * all connected sinks.
1393         * 
1394         * Be very careful with this method, it introduces new events in the event
1395         * stream and some sinks may therefore receive them twice !! Graph replay is
1396         * always dangerous !
1397         */
1398        public void replay() {
1399                // Replay all graph attributes.
1400
1401                if (getAttributeKeySet() != null)
1402                        for (String key : getAttributeKeySet()) {
1403                                listeners.sendGraphAttributeAdded(id, key, getAttribute(key));
1404                        }
1405
1406                // Replay all nodes and their attributes.
1407
1408                for (Node node : this) {
1409                        listeners.sendNodeAdded(id, node.getId());
1410
1411                        if (node.getAttributeKeySet() != null) {
1412                                for (String key : node.getAttributeKeySet()) {
1413                                        listeners.sendNodeAttributeAdded(id, node.getId(), key,
1414                                                        node.getAttribute(key));
1415                                }
1416                        }
1417                }
1418
1419                // Replay all edges and their attributes.
1420
1421                for (Edge edge : getEachEdge()) {
1422                        listeners.sendEdgeAdded(id, edge.getId(), edge.getSourceNode()
1423                                        .getId(), edge.getTargetNode().getId(), edge.isDirected());
1424
1425                        if (edge.getAttributeKeySet() != null) {
1426                                for (String key : edge.getAttributeKeySet()) {
1427                                        listeners.sendEdgeAttributeAdded(id, edge.getId(), key,
1428                                                        edge.getAttribute(key));
1429                                }
1430                        }
1431                }
1432        }
1433}