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.Collection;
036import java.util.HashMap;
037import java.util.HashSet;
038import java.util.Iterator;
039
040import org.graphstream.graph.Element;
041import org.graphstream.ui.graphicGraph.GraphicElement.SwingElementRenderer;
042import org.graphstream.ui.graphicGraph.stylesheet.Rule;
043import org.graphstream.ui.graphicGraph.stylesheet.Selector;
044import org.graphstream.ui.graphicGraph.stylesheet.Style;
045
046/**
047 * A group of graph elements that share the same style.
048 * 
049 * <p>
050 * The purpose of a style group is to allow retrieving all elements with the
051 * same style easily. Most of the time, with graphic engines, pushing the
052 * graphic state (the style, colors, line width, textures, gradients) is a
053 * costly operation. Doing it once for several elements can speed up things a
054 * lot. This is the purpose of the style group.
055 * </p>
056 * 
057 * <p>
058 * The action of drawing elements in group (first push style, then draw all
059 * elements) are called bulk drawing. All elements that can be drawn at once
060 * this way are called bulk elements.
061 * </p>
062 * 
063 * <p>
064 * In a style group it is not always possible do draw elements in a such a
065 * "bulk" operation. If the style contains "dynamic values" for example, that is
066 * value that depend on the value of an attribute stored on the element, or if
067 * the element is modified by an event (clicked, selected), the element will not
068 * be drawn the same as others.
069 * </p>
070 * 
071 * <p>
072 * The style group provides iterators on each of these categories of elements :
073 * <ul>
074 * <li>{@link #elements()} allows to browse all elements contained in the group
075 * without exception.</li>
076 * <li>{@link #dynamicElements()} allows to browse the subset of elements having
077 * a attribute that modify their style.</li>
078 * <li>{@link #elementsEvents()} allows to browse the subset of elements
079 * modified by an event.</li>
080 * <li>{@link #bulkElements()} allows to browse all remaining elements that have
081 * no dynamic attribute or event.</li>
082 * </ul>
083 * The calling the three last iterators would yield the same elements as calling
084 * the first one. When drawing you can optimise the drawing by first pushing the
085 * graphic state and then drawing at once all bulk elements. If the dynamic and
086 * event subsets are not empty you then must draw such elements modifying the
087 * graphic state for each one.
088 * </p>
089 */
090public class StyleGroup extends Style implements Iterable<Element> {
091        // Attribute
092
093        /**
094         * The group unique identifier.
095         */
096        protected String id;
097
098        /**
099         * The set of style rules.
100         */
101        protected ArrayList<Rule> rules = new ArrayList<Rule>();
102
103        /**
104         * Graph elements of this group.
105         */
106        protected HashMap<String, Element> elements = new HashMap<String, Element>();
107
108        /**
109         * The global events actually occurring.
110         */
111        protected StyleGroupSet.EventSet eventSet;
112
113        /**
114         * Set of elements whose style is actually modified individually by an
115         * event. Such elements must be rendered one by one, not in groups like
116         * others.
117         */
118        protected HashMap<Element, ElementEvents> eventsFor;
119
120        /**
121         * Set of elements that have some dynamic style values. Such elements must
122         * be rendered one by one, not in groups, like others.
123         */
124        protected HashSet<Element> dynamicOnes;
125
126        /**
127         * A set of events actually pushed only for this group.
128         */
129        protected String[] curEvents;
130
131        /**
132         * The set of bulk elements.
133         */
134        protected BulkElements bulkElements = new BulkElements();
135
136        /**
137         * Associated renderers.
138         */
139        public HashMap<String, SwingElementRenderer> renderers;
140
141        // Construction
142
143        /**
144         * New style group for a first graph element and the set of style rules that
145         * matches it. More graph elements can be added later.
146         * 
147         * @param identifier
148         *            The unique group identifier (see
149         *            {@link org.graphstream.ui.graphicGraph.stylesheet.StyleSheet#getStyleGroupIdFor(Element, ArrayList)}
150         *            ).
151         * @param rules
152         *            The set of style rules for the style group (see
153         *            {@link org.graphstream.ui.graphicGraph.stylesheet.StyleSheet#getRulesFor(Element)}
154         *            ).
155         * @param firstElement
156         *            The first element to construct the group.
157         */
158        public StyleGroup(String identifier, Collection<Rule> rules,
159                        Element firstElement, StyleGroupSet.EventSet eventSet) {
160                this.id = identifier;
161                this.rules.addAll(rules);
162                this.elements.put(firstElement.getId(), firstElement);
163                this.values = null; // To avoid consume memory since this style will not
164                                                        // store anything.
165                this.eventSet = eventSet;
166
167                for (Rule rule : rules)
168                        rule.addGroup(identifier);
169        }
170
171        // Access
172
173        /**
174         * The group unique identifier.
175         * 
176         * @return A style group identifier.
177         */
178        public String getId() {
179                return id;
180        }
181
182        /**
183         * Type of graph element concerned by this style (node, edge, sprite,
184         * graph).
185         * 
186         * @return The type of the style group elements.
187         */
188        public Selector.Type getType() {
189                return rules.get(0).selector.type;
190        }
191
192        /**
193         * True if at least one of the style properties is dynamic (set according to
194         * an attribute of the element to draw). Such elements cannot therefore be
195         * drawn in a group operation, but one by one.
196         * 
197         * @return True if one property is dynamic.
198         */
199        public boolean hasDynamicElements() {
200                return (dynamicOnes != null && dynamicOnes.size() > 0);
201        }
202
203        /**
204         * If true this group contains some elements that are actually changed by an
205         * event. Such elements cannot therefore be drawn in a group operation, but
206         * one by one.
207         * 
208         * @return True if the group contains some elements changed by an event.
209         */
210        public boolean hasEventElements() {
211                return (eventsFor != null && eventsFor.size() > 0);
212        }
213
214        /**
215         * True if the given element actually has active events.
216         * 
217         * @param element
218         *            The element to test.
219         * @return True if the element has actually active events.
220         */
221        public boolean elementHasEvents(Element element) {
222                return (eventsFor != null && eventsFor.containsKey(element));
223        }
224
225        /**
226         * True if the given element has dynamic style values provided by specific
227         * attributes.
228         * 
229         * @param element
230         *            The element to test.
231         * @return True if the element has actually specific style attributes.
232         */
233        public boolean elementIsDynamic(Element element) {
234                return (dynamicOnes != null && dynamicOnes.contains(element));
235        }
236
237        /**
238         * Get the value of a given property.
239         * 
240         * This is a redefinition of the method in {@link Style} to consider the
241         * fact a style group aggregates several style rules.
242         * 
243         * @param property
244         *            The style property the value is searched for.
245         */
246        @Override
247        public Object getValue(String property, String... events) {
248                int n = rules.size();
249
250                if (events == null || events.length == 0) {
251                        if (curEvents != null && curEvents.length > 0) {
252                                events = curEvents;
253                        } else if (eventSet.events != null && eventSet.events.length > 0) {
254                                events = eventSet.events;
255                        }
256                }
257
258                for (int i = 1; i < n; i++) {
259                        Style style = rules.get(i).getStyle();
260
261                        if (style.hasValue(property, events))
262                                return style.getValue(property, events);
263                }
264
265                return rules.get(0).getStyle().getValue(property, events);
266        }
267
268        /**
269         * True if there are no elements in the group.
270         * 
271         * @return True if the group is empty of elements.
272         */
273        public boolean isEmpty() {
274                return elements.isEmpty();
275        }
276
277        /**
278         * True if the group contains the element whose identifier is given.
279         * 
280         * @param elementId
281         *            The element to search.
282         * @return true if the element is in the group.
283         */
284        public boolean contains(String elementId) {
285                return elements.containsKey(elementId);
286        }
287
288        /**
289         * True if the group contains the element given.
290         * 
291         * @param element
292         *            The element to search.
293         * @return true if the element is in the group.
294         */
295        public boolean contains(Element element) {
296                return elements.containsKey(element.getId());
297        }
298
299        /**
300         * Return an element of the group, knowing its identifier.
301         * 
302         * @param id
303         *            The searched element identifier.
304         * @return The element corresponding to the identifier or null if not found.
305         */
306        public Element getElement(String id) {
307                return elements.get(id);
308        }
309
310        /**
311         * The number of elements of the group.
312         * 
313         * @return The element count.
314         */
315        public int getElementCount() {
316                return elements.size();
317        }
318
319        /**
320         * Iterator on the set of graph elements of this group.
321         * 
322         * @return The elements iterator.
323         */
324        public Iterator<? extends Element> getElementIterator() {
325                return elements.values().iterator();
326        }
327
328        /**
329         * Iterable set of elements. This the complete set of elements contained in
330         * this group without regard to the fact they are modified by an event or
331         * are dynamic. If you plan to respect events or dynamic elements, you must
332         * check the elements are not modified by events using
333         * {@link #elementHasEvents(Element)} and are not dynamic by using
334         * {@link #elementIsDynamic(Element)} and then draw modified elements using
335         * {@link #elementsEvents()} and {@link #dynamicElements()}. But the easiest
336         * way of drawing is to use first {@link #bulkElements()} for all non
337         * dynamic non event elements, then the {@link #dynamicElements()} and
338         * {@link #elementsEvents()} to draw all dynamic and event elements.
339         * 
340         * @return All the elements in no particular order.
341         */
342        public Iterable<? extends Element> elements() {
343                return elements.values();
344        }
345
346        /**
347         * Iterable set of elements that can be drawn in a bulk operation, that is
348         * the subset of all elements that are not dynamic or modified by an event.
349         * 
350         * @return The iterable set of bulk elements.
351         */
352        public Iterable<? extends Element> bulkElements() {
353                return bulkElements;
354        }
355
356        /**
357         * Subset of elements that are actually modified by one or more events. The
358         * {@link ElementEvents} class contains the element and an array of events
359         * that can be pushed on the style group set.
360         * 
361         * @return The subset of elements modified by one or more events.
362         */
363        public Iterable<ElementEvents> elementsEvents() {
364                return eventsFor.values();
365        }
366
367        /**
368         * Subset of elements that have dynamic style values and therefore must be
369         * rendered one by one, not in groups like others. Even though elements
370         * style can specify some dynamics, the elements must individually have
371         * attributes that specify the dynamic value. If the elements do not have
372         * these attributes they can be rendered in bulk operations.
373         * 
374         * @return The subset of dynamic elements of the group.
375         */
376        public Iterable<Element> dynamicElements() {
377                return dynamicOnes;
378        }
379
380        public Iterator<Element> iterator() {
381                return elements.values().iterator();
382        }
383
384        /**
385         * The associated renderers.
386         * 
387         * @return A renderer or null if not found.
388         */
389        public SwingElementRenderer getRenderer(String id) {
390                if (renderers != null)
391                        return renderers.get(id);
392
393                return null;
394        }
395
396        /**
397         * Set of events for a given element or null if the element has not
398         * currently occurring events.
399         * 
400         * @return A set of events or null if none occurring at that time.
401         */
402        public ElementEvents getEventsFor(Element element) {
403                if (eventsFor != null)
404                        return eventsFor.get(element);
405
406                return null;
407        }
408
409        /**
410         * Test if an element is pushed as dynamic.
411         */
412        public boolean isElementDynamic(Element element) {
413                if (dynamicOnes != null)
414                        return dynamicOnes.contains(element);
415
416                return false;
417        }
418
419        // Command
420
421        /**
422         * Add a new graph element to the group.
423         * 
424         * @param element
425         *            The new graph element to add.
426         */
427        public void addElement(Element element) {
428                elements.put(element.getId(), element);
429        }
430
431        /**
432         * Remove a graph element from the group.
433         * 
434         * @param element
435         *            The element to remove.
436         * @return The removed element, or null if the element was not found.
437         */
438        public Element removeElement(Element element) {
439                if (eventsFor != null && eventsFor.containsKey(element))
440                        eventsFor.remove(element); // Remove an eventual remaining event.
441
442                if (dynamicOnes != null && dynamicOnes.contains(element))
443                        dynamicOnes.remove(element); // Remove an eventual remaining dynamic
444                                                                                        // information.
445
446                return elements.remove(element.getId());
447        }
448
449        /**
450         * Push an event specifically for the given element. Events are stacked in
451         * order. Called by the GraphicElement.
452         * 
453         * @param element
454         *            The element to modify with an event.
455         * @param event
456         *            The event to push.
457         */
458        protected void pushEventFor(Element element, String event) {
459                if (elements.containsKey(element.getId())) {
460                        if (eventsFor == null)
461                                eventsFor = new HashMap<Element, ElementEvents>();
462
463                        ElementEvents evs = eventsFor.get(element);
464
465                        if (evs == null) {
466                                evs = new ElementEvents(element, this, event);
467                                eventsFor.put(element, evs);
468                        } else {
469                                evs.pushEvent(event);
470                        }
471                }
472        }
473
474        /**
475         * Pop an event for the given element. Called by the GraphicElement.
476         * 
477         * @param element
478         *            The element.
479         * @param event
480         *            The event.
481         */
482        protected void popEventFor(Element element, String event) {
483                if (elements.containsKey(element.getId())) {
484                        ElementEvents evs = eventsFor.get(element);
485
486                        if (evs != null) {
487                                evs.popEvent(event);
488
489                                if (evs.eventCount() == 0)
490                                        eventsFor.remove(element);
491                        }
492
493                        if (eventsFor.isEmpty())
494                                eventsFor = null;
495                }
496        }
497
498        /**
499         * Before drawing an element that has events, use this method to activate
500         * the events, the style values will be modified accordingly. Events for
501         * this element must have been registered via
502         * {@link #pushEventFor(Element, String)}. After rendering the
503         * {@link #deactivateEvents()} MUST be called.
504         * 
505         * @param element
506         *            The element to push events for.
507         */
508        public void activateEventsFor(Element element) {
509                ElementEvents evs = eventsFor.get(element);
510
511                if (evs != null && curEvents == null)
512                        curEvents = evs.events();
513        }
514
515        /**
516         * De-activate any events activated for an element. This method MUST be
517         * called if {@link #activateEventsFor(Element)} has been called.
518         */
519        public void deactivateEvents() {
520                curEvents = null;
521        }
522
523        /**
524         * Indicate the element has dynamic values and thus cannot be drawn in bulk
525         * operations. Called by the GraphicElement.
526         * 
527         * @param element
528         *            The element.
529         */
530        protected void pushElementAsDynamic(Element element) {
531                if (dynamicOnes == null)
532                        dynamicOnes = new HashSet<Element>();
533
534                dynamicOnes.add(element);
535        }
536
537        /**
538         * Indicate the element has no more dynamic values and can be drawn in bulk
539         * operations. Called by the GraphicElement.
540         * 
541         * @param element
542         *            The element.
543         */
544        protected void popElementAsDynamic(Element element) {
545                dynamicOnes.remove(element);
546
547                if (dynamicOnes.isEmpty())
548                        dynamicOnes = null;
549        }
550
551        /**
552         * Remove all graph elements of this group, and remove this group from the
553         * group list of each style rule.
554         */
555        public void release() {
556                for (Rule rule : rules)
557                        rule.removeGroup(id);
558
559                elements.clear();
560        }
561
562        /**
563         * Redefinition of the {@link Style} to forbid changing the values.
564         */
565        @Override
566        public void setValue(String property, Object value) {
567                throw new RuntimeException(
568                                "you cannot change the values of a style group.");
569        }
570
571        /**
572         * Add a renderer to this group.
573         * 
574         * @param id
575         *            The renderer identifier.
576         * @param renderer
577         *            The renderer.
578         */
579        public void addRenderer(String id, SwingElementRenderer renderer) {
580                if (renderers == null)
581                        renderers = new HashMap<String, SwingElementRenderer>();
582
583                renderers.put(id, renderer);
584        }
585
586        /**
587         * Remove a renderer.
588         * 
589         * @param id
590         *            The renderer identifier.
591         * @return The removed renderer or null if not found.
592         */
593        public SwingElementRenderer removeRenderer(String id) {
594                return renderers.remove(id);
595        }
596
597        @Override
598        public String toString() {
599                return toString(-1);
600        }
601
602        @Override
603        public String toString(int level) {
604                StringBuilder builder = new StringBuilder();
605                String prefix = "";
606                String sprefix = "    ";
607
608                for (int i = 0; i < level; i++)
609                        prefix += sprefix;
610
611                builder.append(String.format("%s%s%n", prefix, id));
612                builder.append(String.format("%s%sContains : ", prefix, sprefix));
613
614                for (Element element : elements.values()) {
615                        builder.append(String.format("%s ", element.getId()));
616                }
617
618                builder.append(String.format("%n%s%sStyle : ", prefix, sprefix));
619
620                for (Rule rule : rules) {
621                        builder.append(String.format("%s ", rule.selector.toString()));
622                }
623
624                builder.append(String.format("%n"));
625
626                return builder.toString();
627        }
628
629        // Nested classes
630
631        /**
632         * Description of an element that is actually modified by one or more events
633         * occurring on it.
634         */
635        public static class ElementEvents {
636                // Attribute
637
638                /**
639                 * Set of events on the element.
640                 */
641                protected String events[];
642
643                /**
644                 * The element.
645                 */
646                protected Element element;
647
648                /**
649                 * The group the element pertains to.
650                 */
651                protected StyleGroup group;
652
653                // Construction
654
655                protected ElementEvents(Element element, StyleGroup group, String event) {
656                        this.element = element;
657                        this.group = group;
658                        this.events = new String[1];
659
660                        events[0] = event;
661                }
662
663                // Access
664
665                /**
666                 * The element on which the events are occurring.
667                 * 
668                 * @return an element.
669                 */
670                public Element getElement() {
671                        return element;
672                }
673
674                /**
675                 * Number of events actually affecting the element.
676                 * 
677                 * @return The number of events affecting the element.
678                 */
679                public int eventCount() {
680                        if (events == null)
681                                return 0;
682
683                        return events.length;
684                }
685
686                /**
687                 * The set of events actually occurring on the element.
688                 * 
689                 * @return A set of strings.
690                 */
691                public String[] events() {
692                        return events;
693                }
694
695                // Command
696
697                public void activate() {
698                        group.activateEventsFor(element);
699                }
700
701                public void deactivate() {
702                        group.deactivateEvents();
703                }
704
705                protected void pushEvent(String event) {
706                        int n = events.length + 1;
707                        String e[] = new String[n];
708                        boolean found = false;
709
710                        for (int i = 0; i < events.length; i++) {
711                                if (!events[i].equals(event))
712                                        e[i] = events[i];
713                                else
714                                        found = true;
715                        }
716
717                        e[events.length] = event;
718
719                        if (!found)
720                                events = e;
721                }
722
723                protected void popEvent(String event) {
724                        if (events.length > 1) {
725                                String e[] = new String[events.length - 1];
726                                boolean found = false;
727
728                                for (int i = 0, j = 0; i < events.length; i++) {
729                                        if (!events[i].equals(event)) {
730                                                if (j < e.length) {
731                                                        e[j++] = events[i];
732                                                }
733                                        } else {
734                                                found = true;
735                                        }
736                                }
737
738                                if (found)
739                                        events = e;
740                        } else {
741                                if (events[0].equals(event)) {
742                                        events = null;
743                                }
744                        }
745                }
746
747                @Override
748                public String toString() {
749                        StringBuilder builder = new StringBuilder();
750
751                        builder.append(String.format("%s events {", element.getId()));
752                        for (String event : events)
753                                builder.append(String.format(" %s", event));
754                        builder.append(" }");
755
756                        return builder.toString();
757                }
758        }
759
760        /**
761         * Virtual set on the elements that have not dynamic style value or event.
762         */
763        protected class BulkElements implements Iterable<Element> {
764                public Iterator<Element> iterator() {
765                        return new BulkIterator(elements.values().iterator());
766                }
767        }
768
769        /**
770         * Iterator on the set of elements that have no event or dynamic style
771         * values.
772         */
773        protected class BulkIterator implements Iterator<Element> {
774                /**
775                 * Iterator on the set of all elements.
776                 */
777                protected Iterator<Element> iterator;
778
779                /**
780                 * The next element without event or dynamic style.value.
781                 */
782                Element next;
783
784                /**
785                 * New bulk iterator positioned on the first element with no event or
786                 * dynamic style attribute.
787                 * 
788                 * @param iterator
789                 *            Iterator on the set of all elements.
790                 */
791                public BulkIterator(Iterator<Element> iterator) {
792                        this.iterator = iterator;
793                        boolean loop = true;
794
795                        while (loop && iterator.hasNext()) {
796                                next = iterator.next();
797
798                                if (!elementHasEvents(next) && !elementIsDynamic(next))
799                                        loop = false;
800                                else
801                                        next = null;
802                        }
803                }
804
805                public boolean hasNext() {
806                        return (next != null);
807                }
808
809                public Element next() {
810                        Element e = next;
811                        boolean loop = true;
812
813                        next = null;
814
815                        while (loop && iterator.hasNext()) {
816                                next = iterator.next();
817
818                                if (!elementIsDynamic(next) && !elementHasEvents(next))
819                                        loop = false;
820                                else
821                                        next = null;
822                        }
823
824                        return e;
825                }
826
827                public void remove() {
828                        throw new UnsupportedOperationException(
829                                        "this iterator does not allows removing elements");
830                }
831        }
832}