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.util.ArrayList;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.Iterator;
038
039import org.graphstream.graph.Edge;
040import org.graphstream.graph.Element;
041import org.graphstream.graph.Graph;
042import org.graphstream.graph.Node;
043import org.graphstream.ui.graphicGraph.stylesheet.Rule;
044import org.graphstream.ui.graphicGraph.stylesheet.Selector;
045import org.graphstream.ui.graphicGraph.stylesheet.StyleSheet;
046import org.graphstream.ui.graphicGraph.stylesheet.StyleSheetListener;
047import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.ShadowMode;
048
049/**
050 * A set of style groups.
051 * 
052 * <p>
053 * This class is in charge or storing all the style groups and to update them.
054 * Each time an element is added or removed the groups are updated. Each time
055 * the style sheet changes the groups are updated.
056 * </p>
057 * 
058 * @author Antoine Dutot
059 */
060public class StyleGroupSet implements StyleSheetListener {
061        // Attribute
062
063        /**
064         * The style sheet.
065         */
066        protected StyleSheet stylesheet;
067
068        /**
069         * All the groups indexed by their unique identifier.
070         */
071        protected HashMap<String, StyleGroup> groups = new HashMap<String, StyleGroup>();
072
073        /**
074         * Allows to retrieve the group containing a node knowing the node id.
075         */
076        protected HashMap<String, String> byNodeIdGroups = new HashMap<String, String>();
077
078        /**
079         * Allows to retrieve the group containing an edge knowing the node id.
080         */
081        protected HashMap<String, String> byEdgeIdGroups = new HashMap<String, String>();
082
083        /**
084         * Allows to retrieve the group containing a sprite knowing the node id.
085         */
086        protected HashMap<String, String> bySpriteIdGroups = new HashMap<String, String>();
087
088        /**
089         * Allows to retrieve the group containing a graph knowing the node id.
090         */
091        protected HashMap<String, String> byGraphIdGroups = new HashMap<String, String>();
092
093        /**
094         * Virtual set of nodes. This set provides fake methods to make it appear as
095         * a set of nodes whereas it only maps on the node style groups.
096         */
097        protected NodeSet nodeSet = new NodeSet();
098
099        /**
100         * Virtual set of edges. This set provides fake methods to make it appear as
101         * a set of edges whereas it only maps on the edge style groups.
102         */
103        protected EdgeSet edgeSet = new EdgeSet();
104
105        /**
106         * Virtual set of sprites. This set provides fake methods to make it appear
107         * as a set of sprites whereas it only maps on the sprite style groups.
108         */
109        protected SpriteSet spriteSet = new SpriteSet();
110
111        /**
112         * Virtual set of graphs. This set provides fake methods to make it appear
113         * as a set of graphs whereas it only maps on the graph style groups.
114         */
115        protected GraphSet graphSet = new GraphSet();
116
117        /**
118         * The set of events actually occurring.
119         */
120        protected EventSet eventSet = new EventSet();
121
122        /**
123         * The groups sorted by their Z index.
124         */
125        protected ZIndex zIndex = new ZIndex();
126
127        /**
128         * Set of groups that cast shadow.
129         */
130        protected ShadowSet shadow = new ShadowSet();
131
132        /**
133         * Remove groups if they become empty?.
134         */
135        protected boolean removeEmptyGroups = true;
136
137        /**
138         * Set of listeners.
139         */
140        protected ArrayList<StyleGroupListener> listeners = new ArrayList<StyleGroupListener>();
141
142        // Construction
143
144        /**
145         * New empty style group set, using the given style sheet to create style
146         * groups. The group set installs itself as a listener of the style sheet.
147         * So in order to completely stop using such a group, you must call
148         * {@link #release()}.
149         * 
150         * @param stylesheet
151         *            The style sheet to use to create groups.
152         */
153        public StyleGroupSet(StyleSheet stylesheet) {
154                this.stylesheet = stylesheet;
155
156                stylesheet.addListener(this);
157        }
158
159        // Access
160
161        /**
162         * Number of groups.
163         * 
164         * @return The number of groups.
165         */
166        public int getGroupCount() {
167                return groups.size();
168        }
169
170        /**
171         * Return a group by its unique identifier. The way group identifier are
172         * constructed reflects their contents.
173         * 
174         * @param groupId
175         *            The group identifier.
176         * @return The corresponding group or null if not found.
177         */
178        public StyleGroup getGroup(String groupId) {
179                return groups.get(groupId);
180        }
181
182        /**
183         * Iterator on the set of groups in no particular order.
184         * 
185         * @return An iterator on the group set.
186         */
187        public Iterator<? extends StyleGroup> getGroupIterator() {
188                return groups.values().iterator();
189        }
190
191        /**
192         * Iterable set of groups elements, in no particular order.
193         * 
194         * @return An iterable on the set of groups.
195         */
196        public Iterable<? extends StyleGroup> groups() {
197                return groups.values();
198        }
199
200        /**
201         * Iterator on the Z index.
202         * 
203         * @return The z index iterator.
204         */
205        public Iterator<HashSet<StyleGroup>> getZIterator() {
206                return zIndex.getIterator();
207        }
208
209        /**
210         * Iterable set of "subsets of groups" sorted by Z level. Each subset of
211         * groups is at the same Z level.
212         * 
213         * @return The z levels.
214         */
215        public Iterable<HashSet<StyleGroup>> zIndex() {
216                return zIndex;
217        }
218
219        /**
220         * Iterator on the style groups that cast a shadow.
221         * 
222         * @return The shadow groups iterator.
223         */
224        public Iterator<StyleGroup> getShadowIterator() {
225                return shadow.getIterator();
226        }
227
228        /**
229         * Iterable set of groups that cast shadow.
230         * 
231         * @return All the groups that cast a shadow.
232         */
233        public Iterable<StyleGroup> shadows() {
234                return shadow;
235        }
236
237        /**
238         * True if the set contains and styles the node whose identifier is given.
239         * 
240         * @param id
241         *            The node identifier.
242         * @return True if the node is in this set.
243         */
244        public boolean containsNode(String id) {
245                return byNodeIdGroups.containsKey(id);
246        }
247
248        /**
249         * True if the set contains and styles the edge whose identifier is given.
250         * 
251         * @param id
252         *            The edge identifier.
253         * @return True if the edge is in this set.
254         */
255        public boolean containsEdge(String id) {
256                return byEdgeIdGroups.containsKey(id);
257        }
258
259        /**
260         * True if the set contains and styles the sprite whose identifier is given.
261         * 
262         * @param id
263         *            The sprite identifier.
264         * @return True if the sprite is in this set.
265         */
266        public boolean containsSprite(String id) {
267                return bySpriteIdGroups.containsKey(id);
268        }
269
270        /**
271         * True if the set contains and styles the graph whose identifier is given.
272         * 
273         * @param id
274         *            The graph identifier.
275         * @return True if the graph is in this set.
276         */
277        public boolean containsGraph(String id) {
278                return byGraphIdGroups.containsKey(id);
279        }
280
281        /**
282         * Get an element.
283         * 
284         * @param id
285         *            The element id.
286         * @param elt2grp
287         *            The kind of element.
288         * @return The element or null if not found.
289         */
290        protected Element getElement(String id, HashMap<String, String> elt2grp) {
291                String gid = elt2grp.get(id);
292
293                if (gid != null) {
294                        StyleGroup group = groups.get(gid);
295                        return group.getElement(id);
296                }
297
298                return null;
299        }
300
301        /**
302         * Get a node element knowing its identifier.
303         * 
304         * @param id
305         *            The node identifier.
306         * @return The node if it is in this set, else null.
307         */
308        public Node getNode(String id) {
309                return (Node) getElement(id, byNodeIdGroups);
310        }
311
312        /**
313         * Get an edge element knowing its identifier.
314         * 
315         * @param id
316         *            The edge identifier.
317         * @return The edge if it is in this set, else null.
318         */
319        public Edge getEdge(String id) {
320                return (Edge) getElement(id, byEdgeIdGroups);
321        }
322
323        /**
324         * Get a sprite element knowing its identifier.
325         * 
326         * @param id
327         *            The sprite identifier.
328         * @return The sprite if it is in this set, else null.
329         */
330        public GraphicSprite getSprite(String id) {
331                return (GraphicSprite) getElement(id, bySpriteIdGroups);
332        }
333
334        /**
335         * Get a graph element knowing its identifier.
336         * 
337         * @param id
338         *            The graph identifier.
339         * @return The graph if it is in this set, else null.
340         */
341        public Graph getGraph(String id) {
342                return (Graph) getElement(id, byGraphIdGroups);
343        }
344
345        /**
346         * The number of nodes referenced.
347         * 
348         * @return The node count.
349         */
350        public int getNodeCount() {
351                return byNodeIdGroups.size();
352        }
353
354        /**
355         * The number of edges referenced.
356         * 
357         * @return The edge count.
358         */
359        public int getEdgeCount() {
360                return byEdgeIdGroups.size();
361        }
362
363        /**
364         * The number of sprites referenced.
365         * 
366         * @return The sprite count.
367         */
368        public int getSpriteCount() {
369                return bySpriteIdGroups.size();
370        }
371
372        /**
373         * Iterator on the set of nodes.
374         * 
375         * @return An iterator on all node elements contained in style groups.
376         */
377        public Iterator<? extends Node> getNodeIterator() {
378                return new ElementIterator<Node>(byNodeIdGroups);
379        }
380
381        /**
382         * Iterator on the set of graphs.
383         * 
384         * @return An iterator on all graph elements contained in style groups.
385         */
386        public Iterator<? extends Graph> getGraphIterator() {
387                return new ElementIterator<Graph>(byGraphIdGroups);
388        }
389
390        /**
391         * Iterable set of nodes.
392         * 
393         * @return The set of all nodes.
394         */
395        public Iterable<? extends Node> nodes() {
396                return nodeSet;
397        }
398
399        /**
400         * Iterable set of graphs.
401         * 
402         * @return The set of all graphs.
403         */
404        public Iterable<? extends Graph> graphs() {
405                return graphSet;
406        }
407
408        /**
409         * Iterator on the set of edges.
410         * 
411         * @return An iterator on all edge elements contained in style groups.
412         */
413        public Iterator<? extends Edge> getEdgeIterator() {
414                return new ElementIterator<Edge>(byEdgeIdGroups);
415        }
416
417        /**
418         * Iterable set of edges.
419         * 
420         * @return The set of all edges.
421         */
422        public Iterable<? extends Edge> edges() {
423                return edgeSet;
424        }
425
426        /**
427         * Iterator on the set of sprite.
428         * 
429         * @return An iterator on all sprite elements contained in style groups.
430         */
431        public Iterator<? extends GraphicSprite> getSpriteIterator() {
432                return new ElementIterator<GraphicSprite>(bySpriteIdGroups);
433        }
434
435        /**
436         * Iterable set of sprites.
437         * 
438         * @return The set of all sprites.
439         */
440        public Iterable<? extends GraphicSprite> sprites() {
441                return spriteSet;
442        }
443
444        /**
445         * Retrieve the group identifier of an element knowing the element
446         * identifier.
447         * 
448         * @param element
449         *            The element to search for.
450         * @return Identifier of the group containing the element.
451         */
452        public String getElementGroup(Element element) {
453                if (element instanceof Node) {
454                        return byNodeIdGroups.get(element.getId());
455                } else if (element instanceof Edge) {
456                        return byEdgeIdGroups.get(element.getId());
457                } else if (element instanceof GraphicSprite) {
458                        return bySpriteIdGroups.get(element.getId());
459                } else if (element instanceof Graph) {
460                        return byGraphIdGroups.get(element.getId());
461                } else {
462                        throw new RuntimeException("What ?");
463                }
464        }
465
466        /**
467         * Get the style of an element.
468         * 
469         * @param element
470         *            The element to search for.
471         * @return The style group of the element (which is also a style).
472         */
473        public StyleGroup getStyleForElement(Element element) {
474                String gid = getElementGroup(element);
475
476                return groups.get(gid);
477        }
478
479        /**
480         * Get the style of a given node.
481         * 
482         * @param node
483         *            The node to search for.
484         * @return The node style.
485         */
486        public StyleGroup getStyleFor(Node node) {
487                String gid = byNodeIdGroups.get(node.getId());
488                return groups.get(gid);
489        }
490
491        /**
492         * Get the style of a given edge.
493         * 
494         * @param edge
495         *            The edge to search for.
496         * @return The edge style.
497         */
498        public StyleGroup getStyleFor(Edge edge) {
499                String gid = byEdgeIdGroups.get(edge.getId());
500                return groups.get(gid);
501        }
502
503        /**
504         * Get the style of a given sprite.
505         * 
506         * @param sprite
507         *            The node to search for.
508         * @return The sprite style.
509         */
510        public StyleGroup getStyleFor(GraphicSprite sprite) {
511                String gid = bySpriteIdGroups.get(sprite.getId());
512                return groups.get(gid);
513        }
514
515        /**
516         * Get the style of a given graph.
517         * 
518         * @param graph
519         *            The node to search for.
520         * @return The graph style.
521         */
522        public StyleGroup getStyleFor(Graph graph) {
523                String gid = byGraphIdGroups.get(graph.getId());
524                return groups.get(gid);
525        }
526
527        /**
528         * True if groups are removed when becoming empty. This setting allows to
529         * keep empty group when the set of elements is quite dynamic. This allows
530         * to avoid recreting groups when an element appears and disappears
531         * regularly.
532         * 
533         * @return True if the groups are removed when empty.
534         */
535        public boolean areEmptyGroupRemoved() {
536                return removeEmptyGroups;
537        }
538
539        /**
540         * The Z index object.
541         * 
542         * @return The Z index.
543         */
544        public ZIndex getZIndex() {
545                return zIndex;
546        }
547
548        /**
549         * The set of style groups that cast a shadow.
550         * 
551         * @return The set of shadowed style groups.
552         */
553        public ShadowSet getShadowSet() {
554                return shadow;
555        }
556
557        // Command
558
559        /**
560         * Release any dependency to the style sheet.
561         */
562        public void release() {
563                stylesheet.removeListener(this);
564        }
565
566        /**
567         * Empties this style group set. The style sheet is listener is not removed,
568         * use {@link #release()} to do that.
569         */
570        public void clear() {
571                byEdgeIdGroups.clear();
572                byNodeIdGroups.clear();
573                bySpriteIdGroups.clear();
574                byGraphIdGroups.clear();
575                groups.clear();
576                zIndex.clear();
577                shadow.clear();
578        }
579
580        /**
581         * Remove or keep groups that becomes empty, if true the groups are removed.
582         * If this setting was set to false, and is now true, the group set is
583         * purged of the empty groups.
584         * 
585         * @param on
586         *            If true the groups will be removed.
587         */
588        public void setRemoveEmptyGroups(boolean on) {
589                if (removeEmptyGroups == false && on == true) {
590                        Iterator<? extends StyleGroup> i = groups.values().iterator();
591
592                        while (i.hasNext()) {
593                                StyleGroup g = i.next();
594
595                                if (g.isEmpty())
596                                        i.remove();
597                        }
598                }
599
600                removeEmptyGroups = on;
601        }
602
603        protected StyleGroup addGroup(String id, ArrayList<Rule> rules,
604                        Element firstElement) {
605                StyleGroup group = new StyleGroup(id, rules, firstElement, eventSet);
606
607                groups.put(id, group);
608                zIndex.groupAdded(group);
609                shadow.groupAdded(group);
610
611                return group;
612        }
613
614        protected void removeGroup(StyleGroup group) {
615                zIndex.groupRemoved(group);
616                shadow.groupRemoved(group);
617                groups.remove(group.getId());
618                group.release();
619        }
620
621        /**
622         * Add an element and bind it to its style group. The group is created if
623         * needed.
624         * 
625         * @param element
626         *            The element to add.
627         * @return The style group where the element was added.
628         */
629        public StyleGroup addElement(Element element) {
630                StyleGroup group = addElement_(element);
631
632                for (StyleGroupListener listener : listeners)
633                        listener.elementStyleChanged(element, null, group);
634
635                return group;
636        }
637
638        protected StyleGroup addElement_(Element element) {
639                ArrayList<Rule> rules = stylesheet.getRulesFor(element);
640                String gid = stylesheet.getStyleGroupIdFor(element, rules);
641                StyleGroup group = groups.get(gid);
642
643                if (group == null)
644                        group = addGroup(gid, rules, element);
645                else
646                        group.addElement(element);
647
648                addElementToReverseSearch(element, gid);
649
650                return group;
651        }
652
653        /**
654         * Remove an element from the group set. If the group becomes empty after
655         * the element removal, depending on the setting of
656         * {@link #areEmptyGroupRemoved()}, the group is deleted or kept. Keeping
657         * groups allows to handle faster elements that constantly appear and
658         * disappear.
659         * 
660         * @param element
661         *            The element to remove.
662         */
663        public void removeElement(Element element) {
664                String gid = getElementGroup(element);
665                StyleGroup group = groups.get(gid);
666
667                if (group != null) {
668                        group.removeElement(element);
669                        removeElementFromReverseSearch(element);
670
671                        if (removeEmptyGroups && group.isEmpty())
672                                removeGroup(group);
673                }
674        }
675
676        /**
677         * Check if an element need to change from a style group to another.
678         * 
679         * <p>
680         * When an element can have potentially changed style due to some of its
681         * attributes (ui.class for example), instead of removing it then reading
682         * it, use this method to move the element from its current style group to a
683         * potentially different style group.
684         * </p>
685         * 
686         * <p>
687         * Explanation of this method : checking the style of an element may be done
688         * by removing it ({@link #removeElement(Element)}) and then re-adding it (
689         * {@link #addElement(Element)}). This must be done by the element since it
690         * knows when to check this. However you cannot only remove and add, since
691         * the style group inside which the element is can have events occurring on
692         * it, and these events must be passed from its old style to its new style.
693         * This method does all this information passing.
694         * </p>
695         * 
696         * @param element
697         *            The element to move.
698         */
699        public void checkElementStyleGroup(Element element) {
700                StyleGroup oldGroup = getGroup(getElementGroup(element));
701
702                // Get the old element "dynamic" status.
703
704                boolean isDyn = false;
705
706                // Get the old event set for the given element.
707
708                StyleGroup.ElementEvents events = null;
709
710                if (oldGroup != null) {
711                        isDyn = oldGroup.isElementDynamic(element);
712                        events = oldGroup.getEventsFor(element);
713                }
714
715                // Remove the element from its old style and add it to insert it in the
716                // correct style.
717
718                removeElement(element);
719                addElement_(element);
720
721                // Eventually push the events on the new style group.
722
723                StyleGroup newGroup = getGroup(getElementGroup(element));
724
725                if (newGroup != null && events != null) {
726                        for (String event : events.events)
727                                pushEventFor(element, event);
728                }
729
730                for (StyleGroupListener listener : listeners)
731                        listener.elementStyleChanged(element, oldGroup, newGroup);
732
733                // Eventually set the element as dynamic, if it was.
734
735                if (newGroup != null && isDyn)
736                        newGroup.pushElementAsDynamic(element);
737        }
738
739        protected void addElementToReverseSearch(Element element, String groupId) {
740                if (element instanceof Node) {
741                        byNodeIdGroups.put(element.getId(), groupId);
742                } else if (element instanceof Edge) {
743                        byEdgeIdGroups.put(element.getId(), groupId);
744                } else if (element instanceof GraphicSprite) {
745                        bySpriteIdGroups.put(element.getId(), groupId);
746                } else if (element instanceof Graph) {
747                        byGraphIdGroups.put(element.getId(), groupId);
748                } else {
749                        throw new RuntimeException("What ?");
750                }
751        }
752
753        protected void removeElementFromReverseSearch(Element element) {
754                if (element instanceof Node) {
755                        byNodeIdGroups.remove(element.getId());
756                } else if (element instanceof Edge) {
757                        byEdgeIdGroups.remove(element.getId());
758                } else if (element instanceof GraphicSprite) {
759                        bySpriteIdGroups.remove(element.getId());
760                } else if (element instanceof Graph) {
761                        byGraphIdGroups.remove(element.getId());
762                } else {
763                        throw new RuntimeException("What ?");
764                }
765        }
766
767        /**
768         * Push a global event on the event stack. Events trigger the replacement of
769         * a style by an alternative style (or meta-class) when possible. If an
770         * event is on the event stack, each time a style has an alternative
771         * corresponding to the event, the alternative is used instead of the style.
772         * 
773         * @param event
774         *            The event to push.
775         */
776        public void pushEvent(String event) {
777                eventSet.pushEvent(event);
778        }
779
780        /**
781         * Push an event specifically for a given element. This is normally done
782         * automatically by the graphic element.
783         * 
784         * @param element
785         *            The element considered.
786         * @param event
787         *            The event to push.
788         */
789        public void pushEventFor(Element element, String event) {
790                StyleGroup group = getGroup(getElementGroup(element));
791
792                if (group != null)
793                        group.pushEventFor(element, event);
794        }
795
796        /**
797         * Pop a global event from the event set.
798         * 
799         * @param event
800         *            The event to remove.
801         */
802        public void popEvent(String event) {
803                eventSet.popEvent(event);
804        }
805
806        /**
807         * Pop an event specifically for a given element. This is normally done
808         * automatically by the graphic element.
809         * 
810         * @param element
811         *            The element considered.
812         * @param event
813         *            The event to pop.
814         */
815        public void popEventFor(Element element, String event) {
816                StyleGroup group = getGroup(getElementGroup(element));
817
818                if (group != null)
819                        group.popEventFor(element, event);
820        }
821
822        /**
823         * Specify the given element has dynamic style attribute values. This is
824         * normally done automatically by the graphic element.
825         * 
826         * @param element
827         *            The element to add to the dynamic subset.
828         */
829        public void pushElementAsDynamic(Element element) {
830                StyleGroup group = getGroup(getElementGroup(element));
831
832                if (group != null)
833                        group.pushElementAsDynamic(element);
834        }
835
836        /**
837         * Remove the given element from the subset of elements having dynamic style
838         * attribute values. This is normally done automatically by the graphic
839         * element.
840         * 
841         * @param element
842         *            The element to remove from the dynamic subset.
843         */
844        public void popElementAsDynamic(Element element) {
845                StyleGroup group = getGroup(getElementGroup(element));
846
847                if (group != null)
848                        group.popElementAsDynamic(element);
849        }
850
851        /**
852         * Add a listener for element style changes.
853         * 
854         * @param listener
855         *            The listener to add.
856         */
857        public void addListener(StyleGroupListener listener) {
858                listeners.add(listener);
859        }
860
861        /**
862         * Remove a style change listener.
863         * 
864         * @param listener
865         *            The listener to remove.
866         */
867        public void removeListener(StyleGroupListener listener) {
868                int index = listeners.lastIndexOf(listener);
869
870                if (index >= 0) {
871                        listeners.remove(index);
872                }
873        }
874
875        // Listener -- What to do when a change occurs in the style sheet.
876
877        public void styleAdded(Rule oldRule, Rule newRule) {
878                // When a style change, we need to update groups.
879                // Several cases :
880                // 1. The style already exists
881                // * Nothing to do in fact. All the elements are still in place.
882                // No style rule (selectors) changed, and therefore we do not have
883                // to change the groups since they are built using the selectors.
884                // 2. The style is new
885                // * we need to check all the groups concerning this kind of element (we
886                // can
887                // restrict our search to these groups, since other will not be
888                // impacted),
889                // and check all elements of these groups.
890
891                if (oldRule == null)
892                        checkForNewStyle(newRule); // no need to check Z and shadow, done
893                                                                                // when adding/changing group.
894                else
895                        checkZIndexAndShadow(oldRule, newRule);
896        }
897
898        public void styleSheetCleared() {
899                ArrayList<Element> elements = new ArrayList<Element>();
900
901                for (Element element : graphs())
902                        elements.add(element);
903
904                for (Element element : nodes())
905                        elements.add(element);
906
907                for (Element element : edges())
908                        elements.add(element);
909
910                for (Element element : sprites())
911                        elements.add(element);
912
913                clear();
914
915                for (Element element : elements)
916                        removeElement(element);
917
918                for (Element element : elements)
919                        addElement(element);
920        }
921
922        /**
923         * Check each group that may have changed, for example to rebuild the Z
924         * index and the shadow set.
925         * 
926         * @param oldRule
927         *            The old rule that changed.
928         * @param newRule
929         *            The new rule that participated in the change.
930         */
931        protected void checkZIndexAndShadow(Rule oldRule, Rule newRule) {
932                if (oldRule != null) {
933                        if (oldRule.selector.getId() != null
934                                        || oldRule.selector.getClazz() != null) {
935                                // We may accelerate things a bit when a class or id style is
936                                // modified,
937                                // since only the groups listed in the style are concerned (we
938                                // are at the
939                                // bottom of the inheritance tree).
940                                if (oldRule.getGroups() != null)
941                                        for (String s : oldRule.getGroups()) {
942                                                StyleGroup group = groups.get(s);
943                                                if (group != null) {
944                                                        zIndex.groupChanged(group);
945                                                        shadow.groupChanged(group);
946                                                }
947                                        }
948                        } else {
949                                // For kind styles "NODE", "EDGE", "GRAPH", "SPRITE", we must
950                                // reset
951                                // the whole Z and shadows for the kind, since several styles
952                                // may
953                                // have changed.
954
955                                Selector.Type type = oldRule.selector.type;
956
957                                for (StyleGroup group : groups.values()) {
958                                        if (group.getType() == type) {
959                                                zIndex.groupChanged(group);
960                                                shadow.groupChanged(group);
961                                        }
962                                }
963                        }
964                }
965        }
966
967        /**
968         * We try to avoid at most to affect anew styles to elements and to recreate
969         * groups, which is time consuming.
970         * 
971         * Two cases :
972         * <ol>
973         * <li>The style is an specific (id) style. In this case a new group may be
974         * added.
975         * <ul>
976         * <li>check an element matches the style and in this case create the group
977         * by adding the element.</li>
978         * <li>else do nothing.</li>
979         * </ul>
980         * </li>
981         * <li>The style is a kind or class style.
982         * <ul>
983         * <li>check all the groups in the kind of the style (graph, node, edge,
984         * sprite) and only in this kind (since other will never be affected).</li>
985         * <li>remove all groups of this kind.</li>
986         * <li>add all elements of this kind anew to recreate the group.</li>
987         * </ul>
988         * </li>
989         * </ol>
990         */
991        protected void checkForNewStyle(Rule newRule) {
992                switch (newRule.selector.type) {
993                case GRAPH:
994                        if (newRule.selector.getId() != null)
995                                checkForNewIdStyle(newRule, byGraphIdGroups);
996                        else
997                                checkForNewStyle(newRule, byGraphIdGroups);
998                        break;
999                case NODE:
1000                        if (newRule.selector.getId() != null)
1001                                checkForNewIdStyle(newRule, byNodeIdGroups);
1002                        else
1003                                checkForNewStyle(newRule, byNodeIdGroups);
1004                        break;
1005                case EDGE:
1006                        if (newRule.selector.getId() != null)
1007                                checkForNewIdStyle(newRule, byEdgeIdGroups);
1008                        else
1009                                checkForNewStyle(newRule, byEdgeIdGroups);
1010                        break;
1011                case SPRITE:
1012                        if (newRule.selector.getId() != null)
1013                                checkForNewIdStyle(newRule, bySpriteIdGroups);
1014                        else
1015                                checkForNewStyle(newRule, bySpriteIdGroups);
1016                        break;
1017                case ANY:
1018                default:
1019                        throw new RuntimeException("What ?");
1020                }
1021        }
1022
1023        /**
1024         * Check for a new specific style (applies only to one element).
1025         * 
1026         * @param newRule
1027         *            The new style rule.
1028         * @param elt2grp
1029         *            The name space.
1030         */
1031        protected void checkForNewIdStyle(Rule newRule,
1032                        HashMap<String, String> elt2grp) {
1033                // There is only one element that matches the identifier.
1034
1035                Element element = getElement(newRule.selector.getId(), elt2grp);
1036
1037                if (element != null) {
1038                        checkElementStyleGroup(element);
1039                        // removeElement( element ); // Remove the element from its old
1040                        // group. Potentially delete a group.
1041                        // addElement( element ); // Add the element to its new own group
1042                        // (since this is an ID style).
1043                }
1044        }
1045
1046        /**
1047         * Check for a new kind or class style in a given name space (node, edge,
1048         * sprite, graph).
1049         * 
1050         * @param newRule
1051         *            The new style rule.
1052         * @param elt2grp
1053         *            The name space.
1054         */
1055        protected void checkForNewStyle(Rule newRule,
1056                        HashMap<String, String> elt2grp) {
1057                ArrayList<Element> elementsToCheck = new ArrayList<Element>();
1058
1059                for (String eltId : elt2grp.keySet())
1060                        elementsToCheck.add(getElement(eltId, elt2grp));
1061
1062                for (Element element : elementsToCheck) {
1063                        checkElementStyleGroup(element);
1064                        // removeElement( element );
1065                        // addElement( element );
1066                }
1067        }
1068
1069        // Utility
1070
1071        @Override
1072        public String toString() {
1073                StringBuilder builder = new StringBuilder();
1074
1075                builder.append(String.format("Style groups (%d) :%n", groups.size()));
1076
1077                for (StyleGroup group : groups.values()) {
1078                        builder.append(group.toString(1));
1079                        builder.append(String.format("%n"));
1080                }
1081
1082                return builder.toString();
1083        }
1084
1085        // Inner classes
1086
1087        /**
1088         * Set of events (meta-classes) actually active.
1089         * 
1090         * <p>
1091         * The event set contains the set of events actually occurring. This is used
1092         * to select alternate styles. The events actually occurring are in
1093         * precedence order. The last one is the most important.
1094         * </p>
1095         * 
1096         * @author Antoine Dutot
1097         */
1098        public class EventSet {
1099                public ArrayList<String> eventSet = new ArrayList<String>();
1100
1101                public String events[] = new String[0];
1102
1103                /**
1104                 * Add an event to the set.
1105                 * 
1106                 * @param event
1107                 *            The event to add.
1108                 */
1109                public void pushEvent(String event) {
1110                        eventSet.add(event);
1111                        events = eventSet.toArray(events);
1112                }
1113
1114                /**
1115                 * Remove an event from the set.
1116                 * 
1117                 * @param event
1118                 *            The event to remove.
1119                 */
1120                public void popEvent(String event) {
1121                        int index = eventSet.lastIndexOf(event);
1122
1123                        if (index >= 0)
1124                                eventSet.remove(index);
1125
1126                        events = eventSet.toArray(events);
1127                }
1128
1129                /**
1130                 * The set of events in order, the most important at the end.
1131                 * 
1132                 * @return The event set.
1133                 */
1134                public String[] getEvents() {
1135                        return events;
1136                }
1137        }
1138
1139        /**
1140         * All the style groups sorted by their Z index.
1141         * 
1142         * <p>
1143         * This structure is maintained by each time a group is added or removed, or
1144         * when the style of a group changed.
1145         * </p>
1146         * 
1147         * @author Antoine Dutot
1148         */
1149        public class ZIndex implements Iterable<HashSet<StyleGroup>> {
1150                /**
1151                 * Ordered set of groups.
1152                 */
1153                public ArrayList<HashSet<StyleGroup>> zIndex = new ArrayList<HashSet<StyleGroup>>();
1154
1155                /**
1156                 * Knowing a group, tell if its Z index.
1157                 */
1158                public HashMap<String, Integer> reverseZIndex = new HashMap<String, Integer>();
1159
1160                /**
1161                 * New empty Z index.
1162                 */
1163                public ZIndex() {
1164                        initZIndex();
1165                }
1166
1167                protected void initZIndex() {
1168                        zIndex.ensureCapacity(256);
1169
1170                        for (int i = 0; i < 256; i++)
1171                                zIndex.add(null);
1172                }
1173
1174                /**
1175                 * Iterator on the set of Z index cells. Each item is a set of style
1176                 * groups that pertain to the same Z index.
1177                 * 
1178                 * @return Iterator on the Z index.
1179                 */
1180                protected Iterator<HashSet<StyleGroup>> getIterator() {
1181                        return new ZIndexIterator();
1182                }
1183
1184                public Iterator<HashSet<StyleGroup>> iterator() {
1185                        return getIterator();
1186                }
1187
1188                /**
1189                 * A new group appeared, put it in the z index.
1190                 * 
1191                 * @param group
1192                 *            The group to add.
1193                 */
1194                protected void groupAdded(StyleGroup group) {
1195                        int z = convertZ(group.getZIndex());
1196
1197                        if (zIndex.get(z) == null)
1198                                zIndex.set(z, new HashSet<StyleGroup>());
1199
1200                        zIndex.get(z).add(group);
1201                        reverseZIndex.put(group.getId(), z);
1202                }
1203
1204                /**
1205                 * A group eventually changed, check its location.
1206                 * 
1207                 * @param group
1208                 *            The group to check.
1209                 */
1210                protected void groupChanged(StyleGroup group) {
1211                        int oldZ = reverseZIndex.get(group.getId());
1212                        int newZ = convertZ(group.getZIndex());
1213
1214                        if (oldZ != newZ) {
1215                                HashSet<StyleGroup> map = zIndex.get(oldZ);
1216
1217                                if (map != null) {
1218                                        map.remove(group);
1219                                        reverseZIndex.remove(group.getId());
1220
1221                                        if (map.isEmpty())
1222                                                zIndex.set(oldZ, null);
1223                                }
1224
1225                                groupAdded(group);
1226                        }
1227                }
1228
1229                /**
1230                 * A group was removed, remove it from the Z index.
1231                 * 
1232                 * @param group
1233                 *            The group to remove.
1234                 */
1235                protected void groupRemoved(StyleGroup group) {
1236                        int z = convertZ(group.getZIndex());
1237
1238                        HashSet<StyleGroup> map = zIndex.get(z);
1239
1240                        if (map != null) {
1241                                map.remove(group);
1242                                reverseZIndex.remove(group.getId());
1243
1244                                if (map.isEmpty())
1245                                        zIndex.set(z, null);
1246                        } else {
1247                                throw new RuntimeException("Inconsistency in Z-index");
1248                        }
1249                }
1250
1251                public void clear() {
1252                        zIndex.clear();
1253                        reverseZIndex.clear();
1254                        initZIndex();
1255                }
1256
1257                /**
1258                 * Convert a [-127,127] value into a [0,255] value and check bounds.
1259                 * 
1260                 * @param z
1261                 *            The Z value to convert.
1262                 * @return The Z value converted and bounded to [0,255].
1263                 */
1264                protected int convertZ(int z) {
1265                        z += 127;
1266
1267                        if (z < 0)
1268                                z = 0;
1269                        else if (z > 255)
1270                                z = 255;
1271
1272                        return z;
1273                }
1274
1275                @Override
1276                public String toString() {
1277                        StringBuilder sb = new StringBuilder();
1278
1279                        sb.append(String.format("Z index :%n"));
1280
1281                        for (int i = 0; i < 256; i++) {
1282                                if (zIndex.get(i) != null) {
1283                                        sb.append(String.format("    * %d -> ", i - 127));
1284
1285                                        HashSet<StyleGroup> map = zIndex.get(i);
1286
1287                                        for (StyleGroup g : map)
1288                                                sb.append(String.format("%s ", g.getId()));
1289
1290                                        sb.append(String.format("%n"));
1291                                }
1292                        }
1293
1294                        return sb.toString();
1295                }
1296
1297                public class ZIndexIterator implements Iterator<HashSet<StyleGroup>> {
1298                        public int index = 0;
1299
1300                        public ZIndexIterator() {
1301                                zapUntilACell();
1302                        }
1303
1304                        protected void zapUntilACell() {
1305                                while (index < 256 && zIndex.get(index) == null)
1306                                        index++;
1307                        }
1308
1309                        public boolean hasNext() {
1310                                return (index < 256);
1311                        }
1312
1313                        public HashSet<StyleGroup> next() {
1314                                if (hasNext()) {
1315                                        HashSet<StyleGroup> cell = zIndex.get(index);
1316                                        index++;
1317                                        zapUntilACell();
1318                                        return cell;
1319                                }
1320
1321                                return null;
1322                        }
1323
1324                        public void remove() {
1325                                throw new RuntimeException(
1326                                                "This iterator does not support removal.");
1327                        }
1328                }
1329        }
1330
1331        /**
1332         * Set of groups that cast a shadow.
1333         * 
1334         * @author Antoine Dutot
1335         */
1336        public class ShadowSet implements Iterable<StyleGroup> {
1337                /**
1338                 * The set of groups casting shadow.
1339                 */
1340                protected HashSet<StyleGroup> shadowSet = new HashSet<StyleGroup>();
1341
1342                /**
1343                 * Iterator on the set of groups that cast a shadow.
1344                 * 
1345                 * @return An iterator on the shadow style group set.
1346                 */
1347                protected Iterator<StyleGroup> getIterator() {
1348                        return shadowSet.iterator();
1349                }
1350
1351                public Iterator<StyleGroup> iterator() {
1352                        return getIterator();
1353                }
1354
1355                /**
1356                 * A group appeared, check its shadow status.
1357                 * 
1358                 * @param group
1359                 *            The group added.
1360                 */
1361                protected void groupAdded(StyleGroup group) {
1362                        if (group.getShadowMode() != ShadowMode.NONE)
1363                                shadowSet.add(group);
1364                }
1365
1366                /**
1367                 * A group eventually changed, check its shadow status.
1368                 * 
1369                 * @param group
1370                 *            The group that changed.
1371                 */
1372                protected void groupChanged(StyleGroup group) {
1373                        if (group.getShadowMode() == ShadowMode.NONE)
1374                                shadowSet.remove(group);
1375                        else
1376                                shadowSet.add(group);
1377                }
1378
1379                /**
1380                 * A group was removed, remove it from the shadow if needed.
1381                 * 
1382                 * @param group
1383                 *            The group removed.
1384                 */
1385                protected void groupRemoved(StyleGroup group) {
1386                        // Faster than to first test its existence or shadow status :
1387
1388                        shadowSet.remove(group);
1389                }
1390
1391                protected void clear() {
1392                        shadowSet.clear();
1393                }
1394        }
1395
1396        /**
1397         * Iterator that allows to browse all graph elements of a given kind (nodes,
1398         * edges, sprites, graphs) as if they where in a single set, whereas they
1399         * are in style groups.
1400         * 
1401         * @author Antoine Dutot
1402         * @param <E>
1403         *            The kind of graph element.
1404         */
1405        protected class ElementIterator<E extends Element> implements Iterator<E> {
1406                protected HashMap<String, String> elt2grp;
1407
1408                protected Iterator<String> elts;
1409
1410                public ElementIterator(HashMap<String, String> elements2groups) {
1411                        elt2grp = elements2groups;
1412                        elts = elements2groups.keySet().iterator();
1413                }
1414
1415                public boolean hasNext() {
1416                        return elts.hasNext();
1417                }
1418
1419                @SuppressWarnings("unchecked")
1420                public E next() {
1421                        String eid = elts.next();
1422                        String gid = elt2grp.get(eid);
1423                        StyleGroup grp = groups.get(gid);
1424
1425                        return (E) grp.getElement(eid);
1426                }
1427
1428                public void remove() {
1429                        throw new RuntimeException(
1430                                        "remove not implemented in this iterator");
1431                }
1432        }
1433
1434        /**
1435         * Dummy set of nodes.
1436         */
1437        protected class NodeSet implements Iterable<Node> {
1438                @SuppressWarnings("unchecked")
1439                public Iterator<Node> iterator() {
1440                        return (Iterator<Node>) getNodeIterator();
1441                }
1442        }
1443
1444        /**
1445         * Dummy set of edges.
1446         */
1447        protected class EdgeSet implements Iterable<Edge> {
1448                @SuppressWarnings("unchecked")
1449                public Iterator<Edge> iterator() {
1450                        return (Iterator<Edge>) getEdgeIterator();
1451                }
1452        }
1453
1454        /**
1455         * Dummy set of sprites.
1456         */
1457        protected class SpriteSet implements Iterable<GraphicSprite> {
1458                @SuppressWarnings("unchecked")
1459                public Iterator<GraphicSprite> iterator() {
1460                        return (Iterator<GraphicSprite>) getSpriteIterator();
1461                }
1462        }
1463
1464        protected class GraphSet implements Iterable<GraphicGraph> {
1465                @SuppressWarnings("unchecked")
1466                public Iterator<GraphicGraph> iterator() {
1467                        return (Iterator<GraphicGraph>) getGraphIterator();
1468                }
1469        }
1470
1471}